1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.chemistry.opencmis.client.runtime;
19
20 import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.isNotEmpty;
21 import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.isNullOrEmpty;
22
23 import java.net.URI;
24 import java.net.URL;
25 import java.text.SimpleDateFormat;
26 import java.util.Calendar;
27 import java.util.Collection;
28 import java.util.Date;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map;
33
34 import org.apache.chemistry.opencmis.client.api.ItemIterable;
35 import org.apache.chemistry.opencmis.client.api.ObjectId;
36 import org.apache.chemistry.opencmis.client.api.ObjectType;
37 import org.apache.chemistry.opencmis.client.api.OperationContext;
38 import org.apache.chemistry.opencmis.client.api.QueryResult;
39 import org.apache.chemistry.opencmis.client.api.QueryStatement;
40 import org.apache.chemistry.opencmis.client.api.Session;
41 import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
42 import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
43 import org.apache.chemistry.opencmis.commons.impl.DateTimeHelper;
44 import org.apache.chemistry.opencmis.commons.impl.StringListBuilder;
45
46
47
48
49 public class QueryStatementImpl implements QueryStatement, Cloneable {
50
51 private final Session session;
52 private final String statement;
53 private final Map<Integer, String> parametersMap = new HashMap<Integer, String>();
54
55
56
57
58
59
60
61
62
63
64 public QueryStatementImpl(Session session, String statement) {
65 if (session == null) {
66 throw new IllegalArgumentException("Session must be set!");
67 }
68
69 if (statement == null) {
70 throw new IllegalArgumentException("Statement must be set!");
71 }
72
73 this.session = session;
74 this.statement = statement.trim();
75 }
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96 public QueryStatementImpl(Session session, Collection<String> selectPropertyIds, Map<String, String> fromTypes,
97 String whereClause, List<String> orderByPropertyIds) {
98 if (session == null) {
99 throw new IllegalArgumentException("Session must be set!");
100 }
101
102 if (isNullOrEmpty(fromTypes)) {
103 throw new IllegalArgumentException("Types must be set!");
104 }
105
106 this.session = session;
107
108 StringBuilder stmt = new StringBuilder(1024);
109
110
111 ObjectType primaryType = null;
112 String primaryAlias = null;
113
114 Map<String, ObjectType> types = new HashMap<String, ObjectType>();
115 for (Map.Entry<String, String> fte : fromTypes.entrySet()) {
116 ObjectType type = session.getTypeDefinition(fte.getValue());
117
118 if (Boolean.FALSE.equals(type.isQueryable())) {
119 throw new IllegalArgumentException("Type '" + fte.getValue() + "' is not queryable!");
120 }
121
122 String alias = fte.getKey().trim();
123 if (alias.length() < 1) {
124 throw new IllegalArgumentException("Invalid alias for type '" + fte.getValue() + "'!");
125 }
126
127 if (type.getBaseTypeId() != BaseTypeId.CMIS_SECONDARY) {
128 if (primaryType == null) {
129 primaryType = type;
130 primaryAlias = alias;
131 } else {
132 throw new IllegalArgumentException(
133 "Two primary types found: " + primaryType.getId() + " and " + type.getId());
134 }
135 }
136
137
138 if (isNotEmpty(type.getPropertyDefinitions())) {
139 types.put(alias, type);
140 }
141 }
142
143 if (primaryType == null) {
144 throw new IllegalArgumentException("No primary type found!");
145 }
146
147
148 stmt.append("SELECT ");
149
150 StringListBuilder selectList = new StringListBuilder(",", stmt);
151
152 if (isNullOrEmpty(selectPropertyIds)) {
153
154 for (String alias : types.keySet()) {
155 selectList.add(alias + ".*");
156 }
157 } else {
158
159 for (String propertyId : selectPropertyIds) {
160
161 propertyId = propertyId.trim();
162
163 if (propertyId.equals("*")) {
164
165 for (String alias : types.keySet()) {
166 selectList.add(alias + ".*");
167 }
168 continue;
169 }
170
171 if (propertyId.endsWith(".*")) {
172
173
174 String starAlias = propertyId.substring(0, propertyId.length() - 2);
175 if (types.containsKey(starAlias)) {
176 selectList.add(starAlias + ".*");
177 continue;
178 } else {
179 throw new IllegalArgumentException("Alias '" + starAlias + "' is not defined!");
180 }
181 }
182
183 PropertyDefinition<?> propertyDef = null;
184 String alias = null;
185
186 for (Map.Entry<String, ObjectType> te : types.entrySet()) {
187 propertyDef = te.getValue().getPropertyDefinitions().get(propertyId);
188 if (propertyDef != null) {
189 alias = te.getKey();
190 break;
191 }
192 }
193
194 if (propertyDef == null) {
195 throw new IllegalArgumentException(
196 "Property '" + propertyId + "' is not defined in the provided object types!");
197 }
198
199 if (propertyDef.getQueryName() == null) {
200 throw new IllegalArgumentException("Property '" + propertyId + "' has no query name!");
201 }
202
203 selectList.add(alias + "." + propertyDef.getQueryName());
204 }
205 }
206
207
208 stmt.append(" FROM ");
209
210 stmt.append(primaryType.getQueryName());
211 stmt.append(" AS ");
212 stmt.append(primaryAlias);
213
214 for (Map.Entry<String, ObjectType> te : types.entrySet()) {
215 if (te.getKey().equals(primaryAlias)) {
216 continue;
217 }
218
219 stmt.append(" JOIN ");
220 stmt.append(te.getValue().getQueryName());
221 stmt.append(" AS ");
222 stmt.append(te.getKey());
223 stmt.append(" ON ");
224 stmt.append(primaryAlias);
225 stmt.append(".cmis:objectId=");
226 stmt.append(te.getKey());
227 stmt.append(".cmis:objectId");
228 }
229
230
231 if (whereClause != null && whereClause.trim().length() > 0) {
232 stmt.append(" WHERE ");
233 stmt.append(whereClause.trim());
234 }
235
236
237 if (isNotEmpty(orderByPropertyIds)) {
238 stmt.append(" ORDER BY ");
239
240 StringListBuilder orderByList = new StringListBuilder(",", stmt);
241
242 for (String propertyId : orderByPropertyIds) {
243 String realPropertyId = propertyId.trim();
244 String realPropertyIdLower = realPropertyId.toLowerCase(Locale.ENGLISH);
245 boolean desc = false;
246
247 if (realPropertyIdLower.endsWith(" asc")) {
248
249 realPropertyId = realPropertyId.substring(0, realPropertyId.length() - 4);
250 }
251
252 if (realPropertyIdLower.endsWith(" desc")) {
253
254
255 realPropertyId = realPropertyId.substring(0, realPropertyId.length() - 5);
256 desc = true;
257 }
258
259 PropertyDefinition<?> propertyDef = null;
260 String alias = null;
261
262 for (Map.Entry<String, ObjectType> te : types.entrySet()) {
263 propertyDef = te.getValue().getPropertyDefinitions().get(realPropertyId);
264 if (propertyDef != null) {
265 alias = te.getKey();
266 break;
267 }
268 }
269
270 if (propertyDef == null) {
271 throw new IllegalArgumentException(
272 "Property '" + realPropertyId + "' is not defined in the provided object types!");
273 }
274
275 if (propertyDef.getQueryName() == null) {
276 throw new IllegalArgumentException("Property '" + realPropertyId + "' has no query name!");
277 }
278
279 if (Boolean.FALSE.equals(propertyDef.isOrderable())) {
280 throw new IllegalArgumentException("Property '" + realPropertyId + "' is not orderable!");
281 }
282
283 orderByList.add(alias + "." + propertyDef.getQueryName() + (desc ? " DESC" : ""));
284 }
285 }
286
287 this.statement = stmt.toString();
288 }
289
290 @Override
291 public void setType(int parameterIndex, String typeId) {
292 setType(parameterIndex, session.getTypeDefinition(typeId));
293 }
294
295 @Override
296 public void setType(int parameterIndex, ObjectType type) {
297 if (type == null) {
298 throw new IllegalArgumentException("Type must be set!");
299 }
300
301 String queryName = type.getQueryName();
302 if (queryName == null) {
303 throw new IllegalArgumentException("Type has no query name!");
304 }
305
306 parametersMap.put(parameterIndex, queryName);
307 }
308
309 @Override
310 public void setProperty(int parameterIndex, String typeId, String propertyId) {
311 ObjectType type = session.getTypeDefinition(typeId);
312
313 PropertyDefinition<?> propertyDefinition = type.getPropertyDefinitions().get(propertyId);
314 if (propertyDefinition == null) {
315 throw new IllegalArgumentException("Property does not exist!");
316 }
317
318 setProperty(parameterIndex, propertyDefinition);
319 }
320
321 @Override
322 public void setProperty(int parameterIndex, PropertyDefinition<?> propertyDefinition) {
323 if (propertyDefinition == null) {
324 throw new IllegalArgumentException("Property must be set!");
325 }
326
327 String queryName = propertyDefinition.getQueryName();
328 if (queryName == null) {
329 throw new IllegalArgumentException("Property has no query name!");
330 }
331
332 parametersMap.put(parameterIndex, queryName);
333 }
334
335 @Override
336 public void setNumber(int parameterIndex, Number... num) {
337 if (num == null || num.length == 0) {
338 throw new IllegalArgumentException("Number must be set!");
339 }
340
341 StringListBuilder slb = new StringListBuilder(",");
342 for (Number n : num) {
343 if (n == null) {
344 throw new IllegalArgumentException("Number is null!");
345 }
346
347 slb.add(n.toString());
348 }
349
350 parametersMap.put(parameterIndex, slb.toString());
351 }
352
353 @Override
354 public void setString(int parameterIndex, String... str) {
355 if (str == null || str.length == 0) {
356 throw new IllegalArgumentException("String must be set!");
357 }
358
359 StringListBuilder slb = new StringListBuilder(",");
360 for (String s : str) {
361 if (s == null) {
362 throw new IllegalArgumentException("String is null!");
363 }
364
365 slb.add(escape(s));
366 }
367
368 parametersMap.put(parameterIndex, slb.toString());
369 }
370
371 @Override
372 public void setStringContains(int parameterIndex, String str) {
373 if (str == null) {
374 throw new IllegalArgumentException("String must be set!");
375 }
376
377 parametersMap.put(parameterIndex, escapeContains(str));
378 }
379
380 @Override
381 public void setStringLike(int parameterIndex, String str) {
382 if (str == null) {
383 throw new IllegalArgumentException("String must be set!");
384 }
385
386 parametersMap.put(parameterIndex, escapeLike(str));
387 }
388
389 @Override
390 public void setId(int parameterIndex, ObjectId... id) {
391 if (id == null || id.length == 0) {
392 throw new IllegalArgumentException("Id must be set!");
393 }
394
395 StringListBuilder slb = new StringListBuilder(",");
396 for (ObjectId oid : id) {
397 if (oid == null || oid.getId() == null) {
398 throw new IllegalArgumentException("Id is null!");
399 }
400
401 slb.add(escape(oid.getId()));
402 }
403
404 parametersMap.put(parameterIndex, slb.toString());
405 }
406
407 @Override
408 public void setUri(int parameterIndex, URI... uri) {
409 if (uri == null) {
410 throw new IllegalArgumentException("URI must be set!");
411 }
412
413 StringListBuilder slb = new StringListBuilder(",");
414 for (URI u : uri) {
415 if (u == null) {
416 throw new IllegalArgumentException("URI is null!");
417 }
418
419 slb.add(escape(u.toString()));
420 }
421
422 parametersMap.put(parameterIndex, slb.toString());
423 }
424
425 @Override
426 public void setUrl(int parameterIndex, URL... url) {
427 if (url == null) {
428 throw new IllegalArgumentException("URL must be set!");
429 }
430
431 StringListBuilder slb = new StringListBuilder(",");
432 for (URL u : url) {
433 if (u == null) {
434 throw new IllegalArgumentException("URI is null!");
435 }
436
437 slb.add(escape(u.toString()));
438 }
439
440 parametersMap.put(parameterIndex, slb.toString());
441 }
442
443 @Override
444 public void setBoolean(int parameterIndex, boolean... bool) {
445 if (bool == null || bool.length == 0) {
446 throw new IllegalArgumentException("Boolean must not be set!");
447 }
448
449 StringListBuilder slb = new StringListBuilder(",");
450 for (boolean b : bool) {
451 slb.add(b ? "TRUE" : "FALSE");
452 }
453
454 parametersMap.put(parameterIndex, slb.toString());
455 }
456
457 @Override
458 public void setDateTime(int parameterIndex, Calendar... cal) {
459 setDateTime(parameterIndex, false, cal);
460 }
461
462 @Override
463 public void setDateTimeTimestamp(int parameterIndex, Calendar... cal) {
464 setDateTime(parameterIndex, true, cal);
465 }
466
467 protected void setDateTime(int parameterIndex, boolean prefix, Calendar... cal) {
468 if (cal == null || cal.length == 0) {
469 throw new IllegalArgumentException("Calendar must be set!");
470 }
471
472 StringBuilder sb = new StringBuilder(64);
473 for (Calendar c : cal) {
474 if (c == null) {
475 throw new IllegalArgumentException("DateTime is null!");
476 }
477
478 if (sb.length() > 0) {
479 sb.append(',');
480 }
481
482 if (prefix) {
483 sb.append("TIMESTAMP ");
484 }
485
486 sb.append(convert(c.getTime()));
487 }
488
489 parametersMap.put(parameterIndex, sb.toString());
490 }
491
492 @Override
493 public void setDateTime(int parameterIndex, Date... date) {
494 setDateTime(parameterIndex, false, date);
495 }
496
497 @Override
498 public void setDateTimeTimestamp(int parameterIndex, Date... date) {
499 setDateTime(parameterIndex, true, date);
500 }
501
502 protected void setDateTime(int parameterIndex, boolean prefix, Date... date) {
503 if (date == null || date.length == 0) {
504 throw new IllegalArgumentException("Date must be set!");
505 }
506
507 StringListBuilder slb = new StringListBuilder(",");
508 for (Date d : date) {
509 if (d == null) {
510 throw new IllegalArgumentException("DateTime is null!");
511 }
512
513 slb.add((prefix ? "TIMESTAMP " : "") + convert(d));
514 }
515
516 parametersMap.put(parameterIndex, slb.toString());
517 }
518
519 @Override
520 public void setDateTime(int parameterIndex, long... ms) {
521 setDateTime(parameterIndex, false, ms);
522 }
523
524 @Override
525 public void setDateTimeTimestamp(int parameterIndex, long... ms) {
526 setDateTime(parameterIndex, true, ms);
527 }
528
529 protected void setDateTime(int parameterIndex, boolean prefix, long... ms) {
530 if (ms == null || ms.length == 0) {
531 throw new IllegalArgumentException("Timestamp must be set!");
532 }
533
534 StringListBuilder slb = new StringListBuilder(",");
535 for (long l : ms) {
536 slb.add((prefix ? "TIMESTAMP " : "") + convert(new Date(l)));
537 }
538
539 parametersMap.put(parameterIndex, slb.toString());
540 }
541
542 @Override
543 public String toQueryString() {
544 boolean inStr = false;
545 int parameterIndex = 0;
546
547 StringBuilder sb = new StringBuilder(1024);
548 for (int i = 0; i < statement.length(); i++) {
549 char c = statement.charAt(i);
550
551 if (c == '\'') {
552 if (inStr && statement.charAt(i - 1) == '\\') {
553 inStr = true;
554 } else {
555 inStr = !inStr;
556 }
557 sb.append(c);
558 } else if (c == '?' && !inStr) {
559 parameterIndex++;
560 String s = parametersMap.get(parameterIndex);
561 if (s == null) {
562 sb.append(c);
563 } else {
564 sb.append(s);
565 }
566 } else {
567 sb.append(c);
568 }
569 }
570
571 return sb.toString();
572 }
573
574 @Override
575 public ItemIterable<QueryResult> query() {
576 return session.query(toQueryString(), false);
577 }
578
579 @Override
580 public ItemIterable<QueryResult> query(boolean searchAllVersions) {
581 return session.query(toQueryString(), searchAllVersions);
582 }
583
584 @Override
585 public ItemIterable<QueryResult> query(boolean searchAllVersions, OperationContext context) {
586 return session.query(toQueryString(), searchAllVersions, context);
587 }
588
589 @Override
590 protected QueryStatementImpl clone() throws CloneNotSupportedException {
591 QueryStatementImpl qs = new QueryStatementImpl(session, statement);
592 qs.parametersMap.putAll(parametersMap);
593
594 return qs;
595 }
596
597 @Override
598 public String toString() {
599 return toQueryString();
600 }
601
602
603
604 private static String escape(String str) {
605 StringBuilder sb = new StringBuilder(str.length() + 16);
606
607 sb.append('\'');
608
609 for (int i = 0; i < str.length(); i++) {
610 char c = str.charAt(i);
611
612 if (c == '\'' || c == '\\') {
613 sb.append('\\');
614 }
615
616 sb.append(c);
617 }
618
619 sb.append('\'');
620
621 return sb.toString();
622 }
623
624 private static String escapeLike(String str) {
625 StringBuilder sb = new StringBuilder(str.length() + 16);
626
627 sb.append('\'');
628
629 for (int i = 0; i < str.length(); i++) {
630 char c = str.charAt(i);
631
632 if (c == '\'') {
633 sb.append('\\');
634 } else if (c == '\\') {
635 if (i + 1 < str.length() && (str.charAt(i + 1) == '%' || str.charAt(i + 1) == '_')) {
636
637 } else {
638 sb.append('\\');
639 }
640 }
641
642 sb.append(c);
643 }
644
645 sb.append('\'');
646
647 return sb.toString();
648 }
649
650 private static String escapeContains(String str) {
651 StringBuilder sb = new StringBuilder(str.length() + 64);
652
653 for (int i = 0; i < str.length(); i++) {
654 char c = str.charAt(i);
655
656 if (c == '\\') {
657 sb.append('\\');
658 } else if (c == '\'' || c == '\"') {
659 sb.append('\\');
660 } else if (c == '-') {
661 if (i > 0) {
662 char cb = str.charAt(i - 1);
663 if (cb == '\\') {
664 sb.deleteCharAt(sb.length() - 1);
665 } else if (cb != ' ') {
666 sb.append('\\');
667 }
668 }
669 }
670
671 sb.append(c);
672 }
673
674 return escape(sb.toString());
675 }
676
677 private static String convert(Date date) {
678 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
679 sdf.setTimeZone(DateTimeHelper.GMT);
680
681 return "'" + sdf.format(date) + "'";
682 }
683 }