1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.tentackle.persist.wurblet;
20
21 import org.wurbelizer.wurbel.WurbelException;
22
23 import org.tentackle.common.StringHelper;
24 import org.tentackle.model.Attribute;
25 import org.tentackle.model.AttributeSorting;
26 import org.tentackle.model.Entity;
27 import org.tentackle.model.InheritanceType;
28 import org.tentackle.model.MethodArgument;
29 import org.tentackle.model.ModelException;
30 import org.tentackle.model.Relation;
31 import org.tentackle.model.RelationType;
32 import org.tentackle.model.SelectionType;
33 import org.tentackle.model.SortType;
34 import org.tentackle.model.TrackType;
35 import org.tentackle.sql.Backend;
36 import org.tentackle.sql.DataType;
37 import org.tentackle.wurblet.Join;
38 import org.tentackle.wurblet.JoinPath;
39 import org.tentackle.wurblet.ModelWurblet;
40 import org.tentackle.wurblet.WurbletArgument;
41 import org.tentackle.wurblet.WurbletArgumentExpression;
42 import org.tentackle.wurblet.WurbletArgumentParser;
43 import org.tentackle.wurblet.WurbletRelation;
44
45 import java.util.ArrayList;
46 import java.util.HashSet;
47 import java.util.LinkedHashSet;
48 import java.util.List;
49 import java.util.Set;
50 import java.util.StringTokenizer;
51 import java.util.regex.Pattern;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 public class DbModelWurblet extends ModelWurblet {
70
71
72
73
74 private static final Pattern CONSTANT_PATTERN = Pattern.compile("(?:[A-Za-z_$][A-Za-z0-9_$]*\\.)*[A-Z][A-Z0-9_]*");
75
76 private boolean argumentGroupingEnabled;
77 private boolean pathAllowed;
78 private boolean tracked;
79 private boolean attracked;
80 private boolean fullTracked;
81 private Attribute contextAttribute;
82 private List<JoinPath> joinPaths;
83 private WurbletArgumentParser parser;
84 private List<WurbletArgument> methodArguments;
85
86
87
88
89
90
91
92
93
94
95 public boolean isArgumentGroupingEnabled() {
96 return argumentGroupingEnabled;
97 }
98
99
100
101
102
103
104 public boolean isPathAllowed() {
105 return pathAllowed;
106 }
107
108
109 @Override
110 public void run() throws WurbelException {
111
112 String wurbletOptions = getConfiguration();
113 if (wurbletOptions != null) {
114 if (wurbletOptions.contains("groupArgs")) {
115 argumentGroupingEnabled = true;
116 }
117 if (wurbletOptions.contains("pathAllowed")) {
118 pathAllowed = true;
119 }
120 }
121
122 super.run();
123
124 Entity entity = getEntity();
125 if (entity != null) {
126
127 applyTrackingAndContextOptions(entity);
128
129
130 parser = new WurbletArgumentParser(entity, argumentGroupingEnabled, getWurbletArgs());
131
132 if (!pathAllowed) {
133 for (WurbletArgument arg: parser.getAllArguments()) {
134 if (arg.isPath()) {
135 throw new WurbelException("relation paths not allowed in wurblet " + this);
136 }
137 }
138 }
139
140
141 for (WurbletArgument arg: getMethodArguments()) {
142 if (arg.isOptional() && !parser.getExpressionArguments().contains(arg)) {
143 throw new WurbelException("optional condition '" + arg + "' not allowed in a join filter");
144 }
145 }
146
147 if (isRemote()) {
148
149 for (WurbletArgument joinArgument: parser.getJoinArguments()) {
150 List<WurbletRelation> wurbletRelations = joinArgument.getWurbletRelations();
151 if (wurbletRelations != null) {
152 for (WurbletRelation wurbletRelation : wurbletRelations) {
153 Relation relation = wurbletRelation.getRelation();
154 if (!relation.isComposite() && !relation.isSerialized() && relation.getSelectionType() != SelectionType.EAGER) {
155 throw new WurbelException("joined non-composite relation '" + relation.getName() +
156 "' must be serialized for remote access");
157 }
158 Relation opRel = relation.getForeignRelation();
159 if (opRel != null && !opRel.isSerialized() && !opRel.isComposite()) {
160 throw new WurbelException("joined opposite relation '" + opRel.getEntity() + "." + opRel.getName() +
161 "' must be serialized for remote access");
162 }
163 }
164 }
165 else {
166 throw new WurbelException("join argument '" + joinArgument + "' does not provide relations?");
167 }
168 }
169 }
170 }
171
172 }
173
174
175
176
177
178
179
180
181
182
183 protected void applyTrackingAndContextOptions(Entity entity) throws WurbelException {
184 tracked = entity.getOptions().getTrackType().isTracked();
185 attracked = entity.getOptions().getTrackType().isAttracked();
186 fullTracked = entity.getOptions().getTrackType() == TrackType.FULLTRACKED;
187 contextAttribute = entity.getContextIdAttribute();
188
189 for (String arg: getOptionArgs()) {
190 if ("tracked".equals(arg)) {
191 tracked = true;
192 attracked = false;
193 fullTracked = false;
194 }
195 else if ("attracked".equals(arg)) {
196 tracked = true;
197 attracked = true;
198 fullTracked = false;
199 }
200 else if ("fulltracked".equals(arg)) {
201 tracked = true;
202 attracked = true;
203 fullTracked = true;
204 }
205 else if ("untracked".equals(arg)) {
206 tracked = false;
207 attracked = false;
208 fullTracked = false;
209 }
210 else if (arg.startsWith("context=")) {
211 String ctx = arg.substring(8);
212 if (ctx.isEmpty()) {
213 contextAttribute = null;
214 }
215 else {
216
217 contextAttribute = entity.getAttributeByJavaName(ctx, true);
218 if (contextAttribute == null) {
219 throw new WurbelException("no such context attribute '" + ctx + "' in " + entity);
220 }
221 }
222 }
223 }
224 }
225
226
227
228
229
230
231 public boolean isTracked() {
232 return tracked;
233 }
234
235
236
237
238
239
240 public boolean isAttracked() {
241 return attracked;
242 }
243
244
245
246
247
248
249 public boolean isFullTracked() {
250 return fullTracked;
251 }
252
253
254
255
256
257
258 public Attribute getContextAttribute() {
259 return contextAttribute;
260 }
261
262
263
264
265
266
267
268 public boolean isEntityPersistable() {
269 return !getEntity().isAbstract() ||
270 !getEntity().getHierarchyInheritanceType().isMappingToNoTable();
271 }
272
273
274
275
276
277
278 public void assertEntityIsPersistable() throws WurbelException {
279 if (!isEntityPersistable()) {
280 throw new WurbelException(getEntity() + " does not map to any table and is not persistable");
281 }
282 }
283
284
285
286
287
288
289 public void assertEntityNotEmbedded() throws WurbelException {
290 if (getEntity().isEmbedded()) {
291 StringBuilder buf = new StringBuilder();
292 for (Entity embeddingEntity : getEntity().getEmbeddingEntities()) {
293 if (!buf.isEmpty()) {
294 buf.append(", ");
295 }
296 buf.append(embeddingEntity);
297 }
298 throw new WurbelException(getEntity() + " is embedded in " + buf);
299 }
300 }
301
302
303
304
305
306
307
308 public List<WurbletArgument> getMethodArguments() {
309 if (methodArguments == null) {
310 List<WurbletArgument> wurbletArguments = new ArrayList<>();
311 for (JoinPath joinPath : getJoinPaths()) {
312 for (Join element : joinPath.getElements()) {
313 WurbletArgumentParser elementParser = element.getWurbletRelation().getParser();
314 if (elementParser != null) {
315 wurbletArguments.addAll(elementParser.getMethodArguments());
316 }
317 }
318 }
319 wurbletArguments.addAll(parser.getMethodArguments());
320 methodArguments = wurbletArguments;
321 }
322 return methodArguments;
323 }
324
325
326
327
328
329
330
331 public List<WurbletArgument> getExpressionArguments() {
332 return parser.getExpressionArguments();
333 }
334
335
336
337
338
339
340
341
342
343
344 public boolean isWithOptionalArguments() {
345 return parser.isWithOptionalArguments();
346 }
347
348
349
350
351
352
353
354
355
356 public void assertNoOptionalArguments() throws WurbelException {
357 for (WurbletArgument arg: getMethodArguments()) {
358 if (arg.isOptional()) {
359 throw new WurbelException("optional condition '" + arg + "' not supported by wurblet " + this);
360 }
361 }
362 }
363
364
365
366
367
368
369
370
371
372
373
374 public String createOptionalCondition(List<WurbletArgument> args) {
375 StringBuilder buf = new StringBuilder();
376 Set<String> names = new LinkedHashSet<>();
377 for (WurbletArgument arg: args) {
378 if (!arg.isOptional()) {
379 return null;
380 }
381 names.add(arg.getOptionalName());
382 }
383 for (String name: names) {
384 if (!buf.isEmpty()) {
385 buf.append(" || ");
386 }
387 buf.append(name);
388 }
389 return buf.isEmpty() ? null : buf.toString();
390 }
391
392
393
394
395
396
397
398
399 public List<WurbletArgument> getExtraArguments() {
400 return parser.getExtraArguments();
401 }
402
403
404
405
406
407
408 public WurbletArgumentExpression getExpression() {
409 return parser.getExpression();
410 }
411
412
413
414
415
416
417 public List<WurbletArgument> getSortingArguments() {
418 return parser.getSortingArguments();
419 }
420
421
422
423
424
425
426 public boolean isWithSorting() {
427 return !getSortingArguments().isEmpty();
428 }
429
430
431
432
433
434
435
436 public List<AttributeSorting> getDefaultSorting() {
437 Entity entity = getEntity();
438 while (entity != null) {
439 List<AttributeSorting> sorting = entity.getSorting();
440 if (sorting != null && !sorting.isEmpty()) {
441 return sorting;
442 }
443 entity = entity.getSuperEntity();
444 }
445 return null;
446 }
447
448
449
450
451
452
453
454
455 public List<WurbletArgument> getDefaultSortKeys() throws WurbelException {
456 List<WurbletArgument> sortKeys = new ArrayList<>();
457 List<AttributeSorting> sorting = getDefaultSorting();
458 if (sorting != null) {
459 for (AttributeSorting as: sorting) {
460 WurbletArgument key = parser.createArgument(as.toString(), true, false);
461 sortKeys.add(key);
462 }
463 }
464 return sortKeys;
465 }
466
467
468
469
470
471
472
473 public boolean isWithDefaultSorting() {
474 return getDefaultSorting() != null;
475 }
476
477
478
479
480
481
482
483
484 public List<JoinPath> getJoinPaths() {
485 if (joinPaths == null) {
486 joinPaths = parser.getJoinPaths();
487
488 for (JoinPath path: joinPaths) {
489 path.normalize();
490 }
491 }
492 return joinPaths;
493 }
494
495
496
497
498
499
500 public boolean isWithJoins() {
501 return !getJoinPaths().isEmpty();
502 }
503
504
505
506
507
508
509
510 public boolean isWithFilteredJoins(boolean componentsOnly) {
511 for (JoinPath joinPath : getJoinPaths()) {
512 if (joinPath.isFiltered(componentsOnly)) {
513 return true;
514 }
515 }
516 return false;
517 }
518
519
520
521
522
523
524
525
526
527 public String createRelopCode(WurbletArgument arg) throws WurbelException {
528
529 if (arg.isArray()) {
530
531 String op = arg.getArrayOperator();
532 assertSupportedByBackends("array operator '" + op + "'", b -> b.isArrayOperatorSupported(op));
533 }
534
535 String value = arg.getValue();
536 if (arg.isValueLiterally() && !value.startsWith("\"")) {
537 value = StringHelper.toDoubleQuotes(value);
538 }
539
540 String str;
541
542
543 switch(arg.getRelop()) {
544 case Backend.SQL_EQUAL:
545 if (arg.isValueLiterally()) {
546 str = "Backend.SQL_EQUAL).append(" + value;
547 }
548 else {
549 str = arg.isArray() ? "Backend.SQL_EQUAL" : "Backend.SQL_EQUAL_PAR";
550 }
551 break;
552
553 case Backend.SQL_NOTEQUAL:
554 case "!=":
555 if (arg.isValueLiterally()) {
556 str = "Backend.SQL_NOTEQUAL).append(" + value;
557 }
558 else {
559 str = arg.isArray() ? "Backend.SQL_NOTEQUAL" : "Backend.SQL_NOTEQUAL_PAR";
560 }
561 break;
562
563 case Backend.SQL_LESS:
564 if (arg.isValueLiterally()) {
565 str = "Backend.SQL_LESS).append(" + value;
566 }
567 else {
568 str = arg.isArray() ? "Backend.SQL_LESS" : "Backend.SQL_LESS_PAR";
569 }
570 break;
571
572 case Backend.SQL_GREATER:
573 if (arg.isValueLiterally()) {
574 str = "Backend.SQL_GREATER).append(" + value;
575 }
576 else {
577 str = arg.isArray() ? "Backend.SQL_GREATER" : "Backend.SQL_GREATER_PAR";
578 }
579 break;
580
581 case Backend.SQL_LESSOREQUAL:
582 if (arg.isValueLiterally()) {
583 str = "Backend.SQL_LESSOREQUAL).append(" + value;
584 }
585 else {
586 str = arg.isArray() ? "Backend.SQL_LESSOREQUAL" : "Backend.SQL_LESSOREQUAL_PAR";
587 }
588 break;
589
590 case Backend.SQL_GREATEROREQUAL:
591 if (arg.isValueLiterally()) {
592 str = "Backend.SQL_GREATEROREQUAL).append(" + value;
593 }
594 else {
595 str = arg.isArray() ? "Backend.SQL_GREATEROREQUAL" : "Backend.SQL_GREATEROREQUAL_PAR";
596 }
597 break;
598
599 case Backend.SQL_LIKE:
600 if (arg.isValueLiterally()) {
601 str = "Backend.SQL_LIKE).append(" + value;
602 }
603 else {
604 str = "Backend.SQL_LIKE_PAR";
605 }
606 break;
607
608 case Backend.SQL_NOTLIKE:
609 if (arg.isValueLiterally()) {
610 str = "Backend.SQL_NOTLIKE).append(" + value;
611 }
612 else {
613 str = "Backend.SQL_NOTLIKE_PAR";
614 }
615 break;
616
617 case Backend.SQL_ISNULL:
618 str = "Backend.SQL_ISNULL";
619 break;
620
621 case Backend.SQL_ISNOTNULL:
622 str = "Backend.SQL_ISNOTNULL";
623 break;
624
625 default:
626 if (arg.isArray()) {
627 str = "";
628 }
629 else {
630 str = "\"" + arg.getRelop() + (arg.isValueLiterally() ? value : "?") + "\"";
631 }
632 }
633
634 if (!str.isEmpty()) {
635 str = ".append(" + str + ")";
636 }
637
638 if (arg.isArray()) {
639 switch (arg.getArrayOperator()) {
640 case Backend.SQL_ARRAY_ANY:
641 str += ".append(Backend.SQL_ARRAY_ANY_PAR)";
642 break;
643 case Backend.SQL_ARRAY_ALL:
644 str += ".append(Backend.SQL_ARRAY_ALL_PAR)";
645 break;
646 case Backend.SQL_ARRAY_NOT_IN:
647 str += ".append(Backend.SQL_ARRAY_NOT_IN_PAR)";
648 break;
649 default:
650 str += ".append(Backend.SQL_ARRAY_IN_PAR)";
651 }
652 }
653
654 return str;
655 }
656
657
658
659
660
661
662
663
664
665 public String createRelopSql(WurbletArgument arg) throws WurbelException {
666
667 if (arg.isArray()) {
668
669 String op = arg.getArrayOperator();
670 assertSupportedByBackends("array operator '" + op + "'", b -> b.isArrayOperatorSupported(op));
671 }
672
673 String value = arg.getValue();
674 if (arg.isValueLiterally() && !value.startsWith("\"")) {
675 value = StringHelper.toDoubleQuotes(value);
676 }
677
678 String str;
679
680
681 switch(arg.getRelop()) {
682 case Backend.SQL_EQUAL:
683 if (arg.isValueLiterally()) {
684 str = Backend.SQL_EQUAL + value;
685 }
686 else {
687 str = arg.isArray() ? Backend.SQL_EQUAL : Backend.SQL_EQUAL_PAR;
688 }
689 break;
690
691 case Backend.SQL_NOTEQUAL:
692 case "!=":
693 if (arg.isValueLiterally()) {
694 str = Backend.SQL_NOTEQUAL + value;
695 }
696 else {
697 str = arg.isArray() ? Backend.SQL_NOTEQUAL : Backend.SQL_NOTEQUAL_PAR;
698 }
699 break;
700
701 case Backend.SQL_LESS:
702 if (arg.isValueLiterally()) {
703 str = Backend.SQL_LESS + value;
704 }
705 else {
706 str = arg.isArray() ? Backend.SQL_LESS : Backend.SQL_LESS_PAR;
707 }
708 break;
709
710 case Backend.SQL_GREATER:
711 if (arg.isValueLiterally()) {
712 str = Backend.SQL_GREATER + value;
713 }
714 else {
715 str = arg.isArray() ? Backend.SQL_GREATER : Backend.SQL_GREATER_PAR;
716 }
717 break;
718
719 case Backend.SQL_LESSOREQUAL:
720 if (arg.isValueLiterally()) {
721 str = Backend.SQL_LESSOREQUAL + value;
722 }
723 else {
724 str = arg.isArray() ? Backend.SQL_LESSOREQUAL : Backend.SQL_LESSOREQUAL_PAR;
725 }
726 break;
727
728 case Backend.SQL_GREATEROREQUAL:
729 if (arg.isValueLiterally()) {
730 str = Backend.SQL_GREATEROREQUAL + value;
731 }
732 else {
733 str = arg.isArray() ? Backend.SQL_GREATEROREQUAL : Backend.SQL_GREATEROREQUAL_PAR;
734 }
735 break;
736
737 case Backend.SQL_LIKE:
738 if (arg.isValueLiterally()) {
739 str = Backend.SQL_LIKE + value;
740 }
741 else {
742 str = Backend.SQL_LIKE_PAR;
743 }
744 break;
745
746 case Backend.SQL_NOTLIKE:
747 if (arg.isValueLiterally()) {
748 str = Backend.SQL_NOTLIKE + value;
749 }
750 else {
751 str = Backend.SQL_NOTLIKE_PAR;
752 }
753 break;
754
755 case Backend.SQL_ISNULL:
756 str = Backend.SQL_ISNULL;
757 break;
758
759 case Backend.SQL_ISNOTNULL:
760 str = Backend.SQL_ISNOTNULL;
761 break;
762
763 default:
764 if (arg.isArray()) {
765 str = "";
766 }
767 else {
768 str = arg.getRelop() + (arg.isValueLiterally() ? value : "?");
769 }
770 }
771
772 if (arg.isArray()) {
773 switch (arg.getArrayOperator()) {
774 case Backend.SQL_ARRAY_ANY:
775 str += Backend.SQL_ARRAY_ANY_PAR;
776 break;
777 case Backend.SQL_ARRAY_ALL:
778 str += Backend.SQL_ARRAY_ALL_PAR;
779 break;
780 case Backend.SQL_ARRAY_NOT_IN:
781 str += Backend.SQL_ARRAY_NOT_IN_PAR;
782 break;
783 default:
784 str += Backend.SQL_ARRAY_IN_PAR;
785 }
786 }
787
788 return str;
789 }
790
791
792
793
794
795
796
797
798
799 public String createOrderBy(List<WurbletArgument> sortKeys) throws WurbelException {
800 if (!sortKeys.isEmpty()) {
801 StringBuilder buf = new StringBuilder();
802 boolean needComma = false;
803 for (WurbletArgument key: sortKeys) {
804 int[] columnIndexes;
805 Attribute attr = key.getAttribute();
806 Integer index = key.getColumnIndex();
807 DataType<?> effectiveDataType = getEffectiveDataType(attr);
808 if (index != null) {
809 columnIndexes = new int[] { index };
810 }
811 else {
812 columnIndexes = effectiveDataType.getSortableColumns();
813 }
814 if (columnIndexes == null || columnIndexes.length == 0) {
815 throw new WurbelException(effectiveDataType + " is not sortable by the database");
816 }
817 for (int columnIndex : columnIndexes) {
818 if (needComma) {
819 buf.append("\n .append(Backend.SQL_COMMA)");
820 }
821 String name = getColumnNameConstant(attr, columnIndex);
822 buf.append(".append(");
823 if (getEntity().getHierarchyInheritanceType() == InheritanceType.MULTI) {
824 buf.append(deriveClassNameForEntity(getEntity().getTopSuperEntity()))
825 .append(".CLASSVARIABLES.getColumnName(").append(name).append(')');
826 }
827 else {
828 if (!getEntity().equals(attr.getEntity())) {
829
830 List<WurbletRelation> wurbletRelations = key.getWurbletRelations();
831 List<Relation> embeddingPath = key.getEmbeddingPath();
832 if (embeddingPath != null && !embeddingPath.isEmpty() && wurbletRelations != null && wurbletRelations.size() > embeddingPath.size()) {
833 wurbletRelations = new ArrayList<>(wurbletRelations.subList(0, wurbletRelations.size() - embeddingPath.size()));
834 }
835 boolean joinFound = false;
836 for (JoinPath joinPath: getJoinPaths()) {
837 Join join = joinPath.findJoin(wurbletRelations);
838 if (join == null) {
839 join = joinPath.findJoin(key.getComponent());
840 }
841 if (join != null) {
842 buf.append('"').append(join.getName()).append('.');
843 if (attr.isEmbedded()) {
844 buf.append(key.getEmbeddingColumnPrefix()).append(attr.getColumnName());
845 }
846 else {
847 buf.append(attr.getColumnName());
848 }
849 buf.append('"');
850 joinFound = true;
851 break;
852 }
853 }
854 if (!joinFound) {
855 if ((wurbletRelations == null || wurbletRelations.isEmpty()) && embeddingPath != null && !embeddingPath.isEmpty()) {
856 buf.append('"').append(key.getEmbeddingColumnPrefix()).append(attr.getColumnName()).append('"');
857 }
858 else {
859 throw new WurbelException("missing join for sort key: " + key);
860 }
861 }
862 }
863 else {
864 if (isPdo()) {
865 buf.append("getColumnName(").append(name).append(')');
866 }
867 else {
868 buf.append(name);
869 }
870 }
871 }
872 buf.append(").append(")
873 .append(SortType.ASC == key.getSortType() ? "Backend.SQL_SORTASC" : "Backend.SQL_SORTDESC")
874 .append(')');
875 needComma = true;
876 }
877 }
878 return buf.toString();
879 }
880 return null;
881 }
882
883
884
885
886
887
888
889 public String createOrderBy() throws WurbelException {
890 return createOrderBy(getSortingArguments());
891 }
892
893
894
895
896
897
898
899
900 public boolean isAbstractJoinPath(List<JoinPath> paths) {
901 boolean isAbstract = false;
902 for (JoinPath path: paths) {
903 if (!path.getElements().isEmpty()) {
904 Join join = path.getElements().get(0);
905 Relation relation = join.getWurbletRelation().getRelation();
906 if (relation.getEntity().isAbstract() || relation.getForeignEntity().isAbstract()) {
907 isAbstract = true;
908 break;
909 }
910 }
911 if (isAbstractJoinPath(path.getPaths())) {
912 isAbstract = true;
913 break;
914 }
915 }
916 return isAbstract;
917 }
918
919
920
921
922
923
924
925
926 public String createJoins() throws WurbelException {
927 StringBuilder buf = new StringBuilder();
928 String pdoType = getPdoClassName();
929 if (isGenerified()) {
930 pdoType = "T";
931 }
932 buf.append(" JoinedSelect<").append(pdoType).append("> js = new JoinedSelect<")
933 .append(pdoType).append(">()\n");
934 createJoinsImpl(buf, getEntity(), " ", getJoinPaths(), pdoType, null);
935 buf.replace(buf.length() - 1, buf.length(), ";\n");
936 return buf.toString();
937 }
938
939 private void createJoinsImpl(StringBuilder buf, Entity lastNonEmbeddedEntity, String inset, List<JoinPath> paths, String pdoType, String parentName)
940 throws WurbelException {
941 for (JoinPath path: paths) {
942 String lastJoinName = null;
943 if (!path.getElements().isEmpty()) {
944 Join join = path.getElements().get(0);
945 Relation relation = join.getWurbletRelation().getRelation();
946 lastJoinName = join.getName();
947 String poImpl = deriveClassNameForEntity(relation.getEntity());
948 String alias = lastNonEmbeddedEntity.getTableProvidingEntity().getTableAlias();
949 String leftClass = relation.getEntity().equals(getEntity()) ? pdoType : lastNonEmbeddedEntity.getName();
950 String joinClass = relation.getForeignEntity().getName();
951 String joinAlias = relation.getForeignEntity().getTableProvidingEntity().getTableAlias();
952 buf.append(inset).append(".addJoin(\n");
953 if (relation.getRelationType() == RelationType.LIST) {
954 buf.append(inset).append(" new Join<>(JoinType.LEFT, ");
955 if (parentName == null) {
956 buf.append("getColumnName(CN_ID), \"");
957 }
958 else {
959 buf.append('"').append(parentName).append(".id\", \"");
960 }
961 buf.append(join.getName()).append('.').append(relation.getForeignAttribute().getColumnName())
962 .append("\", ").append(joinClass).append(".class, \"").append(join.getName()).append("\",\n");
963 String extraSql = createOptionalWhereForJoin(relation, parentName, join);
964 if (!extraSql.isEmpty()) {
965 buf.append(inset).append(" ").append(extraSql).append(",\n");
966 }
967 buf.append(inset).append(" (").append(leftClass).append(" ").append(alias).append(", ")
968 .append(joinClass).append(" ").append(joinAlias).append(") -> {\n");
969 if (relation.isReversed()) {
970 buf.append(inset).append(" ((").append(poImpl).append(") ").append(alias)
971 .append(".getPersistenceDelegate()).").append(relation.getSetterName());
972 if (relation.isSerialized()) {
973 buf.append("Blunt");
974 }
975 buf.append("(").append(joinAlias).append(");\n");
976 }
977 else {
978 buf.append(inset).append(" ((").append(poImpl).append(") ").append(alias)
979 .append(".getPersistenceDelegate()).").append(relation.getGetterName()).append("Blunt().addBlunt(")
980 .append(joinAlias).append(");\n");
981 if (relation.getLinkMethodName() != null) {
982 String joinImpl = deriveClassNameForEntity(relation.getForeignEntity());
983 buf.append(inset).append(" ((").append(joinImpl).append(") ")
984 .append(joinAlias).append(".getPersistenceDelegate()).")
985 .append(createRelationUpdateReferenceCode(relation, alias, true)).append(";\n");
986 }
987 }
988 }
989 else {
990 buf.append(inset).append(" new Join<>(JoinType.LEFT, ");
991 Attribute attribute = relation.getMethodArgs().getFirst().getAttribute();
992 if (parentName == null) {
993 buf.append("getColumnName(");
994 if (attribute.isEmbedded()) {
995 buf.append('"').append(join.getArgument().getEmbeddingColumnPrefix()).append(attribute.getColumnName()).append('"');
996 }
997 else {
998 buf.append(getColumnNameConstant(attribute, 0));
999 }
1000 buf.append("), \"");
1001 }
1002 else {
1003 buf.append('"').append(parentName).append('.').append(attribute.getColumnName()).append("\", \"");
1004 }
1005 buf.append(join.getName()).append(".id\", ")
1006 .append(joinClass).append(".class, \"").append(join.getName()).append("\",\n")
1007 .append(inset).append(" (").append(leftClass).append(" ").append(alias).append(", ")
1008 .append(joinClass).append(" ").append(joinAlias).append(") -> {\n")
1009 .append(inset).append(" ((").append(poImpl).append(") ")
1010 .append(alias).append('.');
1011 if (relation.getEntity().isEmbedded()) {
1012 buf.append(join.getArgument().getEmbeddingGetterPrefix());
1013 StringTokenizer stok = new StringTokenizer(relation.getPathName(), ".");
1014 while(stok.hasMoreTokens()) {
1015 buf.append("get").append(stok.nextToken()).append("().");
1016 }
1017 }
1018 buf.append("getPersistenceDelegate()).")
1019 .append(relation.getSetterName());
1020 if (relation.isSerialized()) {
1021 buf.append("Blunt");
1022 }
1023 buf.append("(").append(joinAlias).append(");\n");
1024 }
1025 buf.append(inset).append(" }\n")
1026 .append(inset).append(" )\n");
1027 }
1028
1029 List<JoinPath> subPaths = path.getPaths();
1030 if (!subPaths.isEmpty()) {
1031
1032 Relation subRelation = subPaths.get(0).getElements().getFirst().getWurbletRelation().getRelation();
1033 Entity subEntity = subRelation.getEntity();
1034 createJoinsImpl(buf, subEntity.isEmbedded() ? lastNonEmbeddedEntity : subEntity, inset + " ", subPaths, subEntity.getName(), lastJoinName);
1035 }
1036 buf.append(inset).append(")\n");
1037 }
1038 }
1039
1040
1041 private String createOptionalWhereForJoin(Relation relation, String parentName, Join join) throws WurbelException {
1042 StringBuilder buf = new StringBuilder();
1043 List<MethodArgument> methodArgs = relation.getMethodArgs();
1044 if (methodArgs.size() > 1) {
1045 for (MethodArgument methodArg: methodArgs.subList(1, methodArgs.size())) {
1046 Attribute attr = methodArg.getForeignAttribute();
1047 buf.append("\" AND ").append(join.getName()).append('.')
1048 .append(attr.getColumnName())
1049 .append('=');
1050 if (methodArg.isValue()) {
1051 buf.append('?');
1052 DataType<?> effectiveDataType = getEffectiveDataType(attr);
1053 if (effectiveDataType.isColumnCountBackendSpecific()) {
1054 throw new WurbelException("backend-specific datatype " + effectiveDataType + " cannot be used in joins");
1055 }
1056 for (int columnIndex = 1; columnIndex < effectiveDataType.getColumnCount(null); columnIndex++) {
1057 buf.append(" AND ").append(join.getName()).append('.').append(getColumnName(attr, columnIndex)).append("=?");
1058 }
1059 buf.append('"');
1060 }
1061 else {
1062 attr = methodArg.getAttribute();
1063 if (parentName == null) {
1064 buf.append("\" + getColumnName(").append(getColumnNameConstant(attr, 0)).append(")");
1065 }
1066 else {
1067 buf.append('"').append(parentName).append('.').append(attr.getColumnName()).append("\"");
1068 }
1069 }
1070 }
1071 }
1072
1073 WurbletArgumentParser joinFilterParser = join.getWurbletRelation().getParser();
1074 if (joinFilterParser != null && join.getName() != null) {
1075 if (buf.isEmpty()) {
1076 buf.append('"');
1077 }
1078 else if (buf.charAt(buf.length() - 1) == '"') {
1079 buf.deleteCharAt(buf.length() - 1);
1080 }
1081 else {
1082 buf.append(" + \"");
1083 }
1084 buf.append(new JoinClauseGenerator(this, join).generate()).append('"');
1085 }
1086
1087 return buf.toString();
1088 }
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100 public String createWhereSetPars(WurbletArgument arg) throws WurbelException, ModelException {
1101 Attribute attr = arg.getAttribute();
1102 int columnIndex = 0;
1103 if (arg.isArray()) {
1104 String javaType;
1105 if (attr.isConvertible()) {
1106 javaType = attr.getInnerTypeName();
1107 columnIndex = -1;
1108 }
1109 else {
1110 javaType = attr.getDataType().toNonPrimitive().getJavaType();
1111 if (arg.getColumnIndex() != null) {
1112 columnIndex = arg.getColumnIndex();
1113 }
1114 }
1115 return "st.setArray(ndx++, " + javaType + ".class, " + columnIndex + ", " +
1116 attr.toMethodArgument(arg.getJdbcValue()) + ", Backend.SQL_ARRAY_" +
1117 arg.getArrayOperator().replace(' ', '_') + ")";
1118 }
1119 if (arg.getColumnIndex() == null) {
1120 return createWhereSetPars(attr, attr.toMethodArgument(arg.getJdbcValue()));
1121 }
1122 columnIndex = arg.getColumnIndex();
1123 StringBuilder buf = new StringBuilder();
1124 if (arg.getDataType().isPredefined()) {
1125 String jdbcValue = arg.getJdbcValue();
1126 buf.append("st.set(SqlType.").append(arg.getDataType().getSqlType(null, columnIndex)).append(", ndx++, ");
1127 if (arg.isMethodArgument()) {
1128 buf.append(arg.getDataType().getColumnGetter(columnIndex, jdbcValue));
1129 }
1130 else {
1131 buf.append(jdbcValue);
1132 }
1133 buf.append(")");
1134 }
1135 else {
1136 buf.append("ndx += st.set(");
1137 if (!attr.getEntity().equals(getEntity())) {
1138 buf.append(deriveClassNameForEntity(attr.getEntity())).append('.');
1139 }
1140 buf.append(arg.getDataType().getDataTypeConstant())
1141 .append(", ndx, ")
1142 .append(arg.getJdbcValue())
1143 .append(", ").append(columnIndex)
1144 .append(", ").append(attr.getOptions().isMapNull())
1145 .append(", ").append(attr.getSize()).append(")");
1146 }
1147 return buf.toString();
1148 }
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159 public String createWhereSetPars(Attribute attr, String argument) throws WurbelException {
1160 StringBuilder buf = new StringBuilder();
1161 DataType<?> dataType = getEffectiveDataType(attr);
1162 if (dataType.isPredefined()) {
1163 buf.append("st.")
1164 .append(createJdbcSetterName(dataType))
1165 .append("(ndx++, ")
1166 .append(getJdbcCode(attr, argument));
1167 if (attr.getOptions().isMapNull()) {
1168 buf.append(", true)");
1169 }
1170 else {
1171 buf.append(")");
1172 }
1173
1174 if (dataType.isColumnCountBackendSpecific()) {
1175 throw new WurbelException("backend-specific datatype " + dataType + " cannot be used in where-clauses");
1176 }
1177 int diff = dataType.getColumnCount(null) - 1;
1178 if (diff == 1) {
1179 buf.append(";\n ndx++");
1180 }
1181 else if (diff > 1) {
1182 buf.append(";\n ndx += ").append(diff);
1183 }
1184 }
1185 else {
1186 buf.append("ndx += st.set(");
1187 if (!attr.getEntity().equals(getEntity())) {
1188 buf.append(deriveClassNameForEntity(attr.getEntity())).append('.');
1189 }
1190 buf.append(dataType.getDataTypeConstant())
1191 .append(", ndx, ")
1192 .append(getJdbcCode(attr, argument))
1193 .append(", ").append(attr.getOptions().isMapNull())
1194 .append(", ").append(attr.getSize()).append(")");
1195 }
1196 return buf.toString();
1197 }
1198
1199
1200
1201
1202
1203
1204
1205
1206 public String createJoinSetPars() throws WurbelException, ModelException {
1207 StringBuilder buf = new StringBuilder();
1208 for (JoinPath path: getJoinPaths()) {
1209 createJoinSetPars(buf, path);
1210 }
1211 return buf.toString();
1212 }
1213
1214 private void createJoinSetPars(StringBuilder buf, JoinPath path) throws WurbelException, ModelException {
1215 if (!path.getElements().isEmpty()) {
1216 Join join = path.getElements().get(0);
1217 Relation relation = join.getWurbletRelation().getRelation();
1218 List<MethodArgument> methodArgs = relation.getMethodArgs();
1219 if (methodArgs.size() > 1) {
1220 for (MethodArgument methodArg: methodArgs.subList(1, methodArgs.size())) {
1221 if (methodArg.isValue()) {
1222 Attribute attr = methodArg.getForeignAttribute();
1223 buf.append(" ").append(createWhereSetPars(attr, attr.toMethodArgument(methodArg.getValue()))).append(";\n");
1224 }
1225 }
1226 }
1227 for (JoinPath subPath: path.getPaths()) {
1228 createJoinSetPars(buf, subPath);
1229 }
1230 }
1231 }
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241 public String createStatementId() throws WurbelException {
1242 StringBuilder buf = new StringBuilder();
1243 String id = getGuardName();
1244 for (int i=0; i < id.length(); i++) {
1245 char c = id.charAt(i);
1246 if (Character.isUpperCase(c) && !buf.isEmpty()) {
1247 buf.append('_');
1248 }
1249 buf.append(Character.toUpperCase(c));
1250 }
1251 buf.append("_STMT");
1252 return buf.toString();
1253 }
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264 public String buildMethodParameters(boolean limit, boolean offset) throws WurbelException {
1265
1266 StringBuilder params = new StringBuilder();
1267
1268 if (limit) {
1269 appendCommaSeparated(params, "int limit");
1270 }
1271 if (offset) {
1272 appendCommaSeparated(params, "int offset");
1273 }
1274
1275 Set<String> argSet = new HashSet<>();
1276 for (WurbletArgument key: getMethodArguments()) {
1277 if (key.isMethodArgument()) {
1278 String arg = key.getMethodArgumentName();
1279 if (argSet.add(arg)) {
1280 Attribute attr = key.getAttribute();
1281 try {
1282 String javaType;
1283 if (key.isArray()) {
1284 String elementType;
1285 if (attr.isConvertible()) {
1286 elementType = attr.getApplicationTypeName();
1287 }
1288 else {
1289 elementType = attr.getDataType().toNonPrimitive().getJavaType();
1290 }
1291 javaType = "Collection<" + elementType + ">";
1292 }
1293 else {
1294 javaType = attr.getJavaType();
1295 }
1296 appendCommaSeparated(params, javaType);
1297 }
1298 catch (ModelException me) {
1299 throw new WurbelException("cannot determine java type for key " + key, me);
1300 }
1301 params.append(' ');
1302 params.append(arg);
1303 }
1304 }
1305
1306 if (key.isOptional() && argSet.add(key.getOptionalName())) {
1307 appendCommaSeparated(params, "boolean");
1308 params.append(' ');
1309 params.append(key.getOptionalName());
1310 }
1311 }
1312
1313 return params.toString();
1314 }
1315
1316
1317
1318
1319
1320
1321
1322
1323 public String buildMethodParameters() throws WurbelException {
1324 return buildMethodParameters(false, false);
1325 }
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336 public String buildInvocationParameters(boolean limit, boolean offset) throws WurbelException {
1337
1338 StringBuilder params = new StringBuilder();
1339
1340 if (limit) {
1341 appendCommaSeparated(params, "limit");
1342 }
1343 if (offset) {
1344 appendCommaSeparated(params, "offset");
1345 }
1346
1347 Set<String> argSet = new HashSet<>();
1348 for (WurbletArgument key: getMethodArguments()) {
1349 if (key.isMethodArgument()) {
1350 String arg = key.getMethodArgumentName();
1351 if (argSet.add(arg)) {
1352 appendCommaSeparated(params, arg);
1353 }
1354 }
1355 if (key.isOptional() && argSet.add(key.getOptionalName())) {
1356 appendCommaSeparated(params, key.getOptionalName());
1357 }
1358 }
1359
1360 return params.toString();
1361 }
1362
1363
1364
1365
1366
1367
1368
1369 public boolean isPdoProvidingArguments() {
1370 for (WurbletArgument key: getMethodArguments()) {
1371 if (!key.isMethodArgument() && !key.isValueLiterally()) {
1372 String arg = key.getValue();
1373 if (arg != null && arg.endsWith("()")) {
1374 return true;
1375 }
1376 }
1377 }
1378 return false;
1379 }
1380
1381
1382
1383
1384
1385
1386
1387
1388 public String buildInvocationParameters() throws WurbelException {
1389 return buildInvocationParameters(false, false);
1390 }
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400 public String acs(String str, String appendStr) {
1401 StringBuilder builder = new StringBuilder(str);
1402 appendCommaSeparated(builder, appendStr);
1403 return builder.toString();
1404 }
1405
1406
1407
1408
1409
1410
1411
1412
1413 public String pcs(String str, String prependStr) {
1414 StringBuilder builder = new StringBuilder(str);
1415 prependCommaSeparated(builder, prependStr);
1416 return builder.toString();
1417 }
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427 public String aas(String str) {
1428 if (str != null && !str.isEmpty()) {
1429 return ", " + str;
1430 }
1431 else {
1432 return "";
1433 }
1434 }
1435
1436
1437
1438
1439
1440
1441
1442
1443 public String as(String str) {
1444 return str == null ? "" : str;
1445 }
1446
1447
1448
1449
1450
1451
1452
1453
1454 public String createJdbcSetterName(DataType<?> dataType) {
1455 StringBuilder buf = new StringBuilder("set");
1456 if (dataType.isPrimitive()) {
1457 buf.append(StringHelper.firstToUpper(dataType.getJavaType()));
1458 }
1459 else {
1460 String variant = dataType.getVariant();
1461 if (variant.isEmpty()) {
1462 buf.append(dataType.getMethodTypeName());
1463 }
1464 else {
1465 buf.append(variant);
1466 }
1467 }
1468 return buf.toString();
1469 }
1470
1471
1472
1473
1474
1475
1476
1477
1478 public String createJdbcGetterName(Attribute attribute) throws WurbelException {
1479 StringBuilder buf = new StringBuilder("get");
1480 DataType<?> dataType = getEffectiveDataType(attribute);
1481 if (dataType.isPrimitive()) {
1482 buf.append(StringHelper.firstToUpper(dataType.getJavaType()));
1483 }
1484 else {
1485 switch (dataType.getJavaType()) {
1486 case "Boolean":
1487 case "Byte":
1488 case "Short":
1489 case "Long":
1490 case "Float":
1491 case "Double":
1492 buf.append('A');
1493 break;
1494 }
1495 String variant = dataType.getVariant();
1496 if (variant.isEmpty()) {
1497 buf.append(dataType.getMethodTypeName());
1498 }
1499 else {
1500 buf.append(variant);
1501 }
1502 }
1503 return buf.toString();
1504 }
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517 public String getModelCode(Attribute attribute, String jdbcCode) throws WurbelException {
1518 if (attribute.isConvertible()) {
1519 try {
1520 return attribute.getJavaType() + ".toInternal(" + jdbcCode + ")";
1521 }
1522 catch (ModelException me) {
1523 throw new WurbelException("cannot determine java model-side code for " + attribute + ": " + jdbcCode, me);
1524 }
1525 }
1526 else {
1527 return jdbcCode;
1528 }
1529 }
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541 public String getJdbcCode(Attribute attribute, String modelCode) throws WurbelException {
1542 String jdbcCode = modelCode;
1543 if (attribute.isConvertible()) {
1544 try {
1545 String applicationType = attribute.getApplicationTypeName();
1546 if (isConstant(modelCode)) {
1547
1548 jdbcCode = modelCode + ".toExternal()";
1549 }
1550 else {
1551 if (attribute.getInnerDataType().isPrimitive()) {
1552 jdbcCode = modelCode + " == null ? " + applicationType + ".getDefault().toExternal() : " +
1553 modelCode + ".toExternal()";
1554 }
1555 else {
1556
1557 jdbcCode = modelCode + " == null ? null : " + modelCode + ".toExternal()";
1558 }
1559 }
1560 }
1561 catch (ModelException me) {
1562 throw new WurbelException("cannot determine java jdbc-side code for " + attribute + ": " + modelCode, me);
1563 }
1564 }
1565 return jdbcCode;
1566 }
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578 public boolean isConstant(String code) {
1579 return code != null && CONSTANT_PATTERN.matcher(code).matches();
1580 }
1581
1582
1583
1584
1585
1586
1587
1588
1589 public boolean isRelationTransient(Relation relation) {
1590 return relation.getSelectionType() == SelectionType.LAZY && !relation.isComposite() && !relation.isSerialized();
1591 }
1592
1593
1594
1595
1596
1597
1598
1599
1600 public String createRelationArgString(Relation relation) {
1601 StringBuilder buf = new StringBuilder();
1602 buf.append('(');
1603 List<MethodArgument> args = relation.getMethodArgs();
1604 boolean first = true;
1605 for (MethodArgument arg: args) {
1606 if (first) {
1607 first = false;
1608 }
1609 else {
1610 buf.append(", ");
1611 }
1612 buf.append(arg.getMethodArgument());
1613 }
1614 buf.append(')');
1615 return buf.toString();
1616 }
1617
1618
1619
1620
1621
1622
1623
1624
1625 public String createRelationWurbletArgString(Relation relation) {
1626 StringBuilder buf = new StringBuilder();
1627 List<MethodArgument> args = relation.getMethodArgs();
1628 for (MethodArgument arg: args) {
1629 if (!buf.isEmpty()) {
1630 buf.append(' ');
1631 }
1632 buf.append(arg.getForeignAttribute().getName());
1633 }
1634 return buf.toString();
1635 }
1636
1637
1638
1639
1640
1641
1642
1643
1644 public String createRelationSelectCode(Relation relation) {
1645 StringBuilder text = new StringBuilder();
1646 if (relation.getForeignEntity().isAbstract() &&
1647 relation.getRelationType() == RelationType.OBJECT &&
1648 relation.getMethodName() == null) {
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660 text.append("(").append(relation.getClassName()).append("<?>) ");
1661 }
1662 text.append("on(").append(relation.getClassName()).append(".class).")
1663 .append(createRelationSelectMethodName(relation))
1664 .append(createRelationArgString(relation));
1665 return text.toString();
1666 }
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677 public String createRelationUpdateReferenceCode(Relation relation, String pdo, boolean blunt) {
1678 Relation foreignRel = relation.getForeignRelation();
1679 if (blunt &&
1680 (foreignRel == null ||
1681 foreignRel.getRelationType() != RelationType.OBJECT || !foreignRel.isSerialized() ||
1682 (foreignRel.getSelectionType() != SelectionType.LAZY && foreignRel.getSelectionType() != SelectionType.EAGER))) {
1683 blunt = false;
1684 }
1685
1686 if (!blunt && relation.getLinkMethodName() != null) {
1687 return relation.getLinkMethodName() + (relation.getLinkMethodIndex() != null ?
1688 ("(" + pdo + ", ndx++)") : ("(" + pdo + ")"));
1689 }
1690 else {
1691 if (foreignRel != null) {
1692 return foreignRel.getSetterName() + (blunt ? "Blunt" : "") + "(" + pdo + ")";
1693 }
1694
1695 return "set" + relation.getForeignEntity() + "(" + pdo + ")";
1696 }
1697 }
1698
1699
1700
1701
1702
1703
1704
1705
1706 public String createRelationUpdateReferenceCode(Relation relation) {
1707 return createRelationUpdateReferenceCode(relation, "me()", false);
1708 }
1709
1710
1711
1712
1713
1714
1715
1716
1717 public String createRelationSetFirstArgMethodName(Relation relation) throws ModelException {
1718 if (relation.getMethodArgs().size() > 1) {
1719 throw new ModelException("more than one method argument for relation " + relation, relation);
1720 }
1721 return "set" + StringHelper.firstToUpper(getFirstMethodAttribute(relation).getName());
1722 }
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732 public Attribute getFirstMethodAttribute(Relation relation) throws ModelException {
1733 List<MethodArgument> methodArgs = relation.getMethodArgs();
1734 if (methodArgs.isEmpty()) {
1735 throw new ModelException("no method arguments for relation " + relation, relation);
1736 }
1737 Attribute attribute = methodArgs.get(0).getAttribute();
1738 if (attribute == null) {
1739 throw new ModelException("missing attribute for relation " + relation, relation);
1740 }
1741 return attribute;
1742 }
1743
1744
1745
1746
1747
1748
1749
1750
1751 public String createRelationDeleteCode(Relation relation) {
1752 return "on(" + relation.getClassName() + ".class)." +
1753 createListRelationDeleteMethodName(relation) +
1754 createRelationArgString(relation);
1755 }
1756
1757
1758
1759
1760
1761
1762
1763
1764 public String createRelationLinkCode(Relation relation) {
1765 if (relation.getLinkMethodName() != null) {
1766 return relation.getLinkMethodName() + (relation.getLinkMethodIndex() != null ? "(me(), ndx++)" : "(me())");
1767 }
1768 String text = "set";
1769 if (relation.getMethodName() != null) {
1770 text += relation.getMethodName();
1771 }
1772 else {
1773 text += relation.getEntity().getName() + "Id";
1774 }
1775 return text + createRelationArgString(relation);
1776 }
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793 public List<EagerJoin> getEagerJoinTree() {
1794
1795 for (Relation rel: getEntity().getSubEntityRelations()) {
1796 if (rel.getSelectionType() == SelectionType.EAGER) {
1797 return new ArrayList<>();
1798 }
1799 }
1800 return createEagerJoinTree(getEntity().getRelations(), "e", 0);
1801 }
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811 private List<EagerJoin> createEagerJoinTree(List<Relation> relations, String aliasPrefix, int remainingDepth) {
1812 List<EagerJoin> eagerJoins = new ArrayList<>();
1813 int joinCount = 0;
1814 for (Relation relation: relations) {
1815 if (relation.getSelectionType() == SelectionType.EAGER) {
1816
1817 int depth = remainingDepth == 0 ? relation.getEagerDepth() : remainingDepth;
1818 String alias = aliasPrefix + "_" + ++joinCount;
1819 EagerJoin eagerJoin = new EagerJoin(relation, alias);
1820 if (depth > 1) {
1821 eagerJoin.getJoins().addAll(
1822 createEagerJoinTree(relation.getForeignEntity().getRelationsIncludingInherited(), alias, depth - 1));
1823 }
1824 eagerJoins.add(eagerJoin);
1825 }
1826 }
1827 return eagerJoins;
1828 }
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845 public String createEagerJoins() throws WurbelException, ModelException {
1846 List<EagerJoin> eagerJoins = getEagerJoinTree();
1847 if (eagerJoins.isEmpty()) {
1848 return " null);\n";
1849 }
1850 StringBuilder buf = new StringBuilder(" List.of(\n");
1851 buf.append(String.join(",\n",
1852 createEagerJoinTexts(getEntity(), getPdoClassName(), getEntity().getTableAlias(),
1853 " ", eagerJoins, true)));
1854 buf.append("\n )\n );\n");
1855 return buf.toString();
1856 }
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871 private List<String> createEagerJoinTexts(Entity entity, String pdoName, String parentName, String inset,
1872 List<EagerJoin> eagerJoins, boolean topLevel)
1873 throws WurbelException, ModelException {
1874
1875 List<String> texts = new ArrayList<>();
1876 String poImpl = deriveClassNameForEntity(entity);
1877
1878 for (EagerJoin eagerJoin: eagerJoins) {
1879 Relation relation = eagerJoin.getRelation();
1880 if (relation.getMethodArgs().size() > 1) {
1881 throw new ModelException("eager relations with 'args=...' are currently not supported", relation);
1882 }
1883 Entity foreignEntity = relation.getForeignEntity();
1884 String joinClass = foreignEntity.getName();
1885 String sqlAlias = eagerJoin.getAlias();
1886
1887
1888 String joinName = topLevel ? foreignEntity.getTopSuperEntity().getTableAlias() : sqlAlias;
1889
1890 boolean explicitTypes = foreignEntity.isAbstract() || !topLevel;
1891
1892 String columnName;
1893 String joinedColumnName;
1894 if (relation.getRelationType() == RelationType.LIST) {
1895 columnName = parentName + ".id";
1896 joinedColumnName = sqlAlias + "." + relation.getForeignAttribute().getColumnName();
1897 }
1898 else {
1899 columnName = parentName + "." + relation.getAttribute().getColumnName();
1900 joinedColumnName = sqlAlias + ".id";
1901 }
1902
1903
1904
1905
1906
1907
1908
1909 String diamond = eagerJoin.getJoins().isEmpty() ? "<>" : ("<" + pdoName + ", " + joinClass + ">");
1910
1911 StringBuilder buf = new StringBuilder();
1912 buf.append(inset).append("new Join").append(diamond).append("(JoinType.LEFT, \"").append(columnName).append("\", \"")
1913 .append(joinedColumnName).append("\", ").append(joinClass).append(".class, \"").append(sqlAlias).append("\",\n")
1914 .append(inset).append(" (");
1915 if (explicitTypes) {
1916 buf.append(pdoName).append(' ');
1917 }
1918 buf.append(parentName).append(", ");
1919 if (explicitTypes) {
1920 buf.append(joinClass).append(' ');
1921 }
1922 buf.append(joinName).append(") -> {\n");
1923
1924 String body = inset + " ";
1925 if (foreignEntity.isAbstract()) {
1926 if (relation.getRelationType() == RelationType.LIST) {
1927 buf.append(body).append(poImpl).append(" po = (").append(poImpl).append(") ")
1928 .append(parentName).append(".getPersistenceDelegate();\n")
1929 .append(body).append("po.").append(relation.getGetterName()).append("Blunt().add(")
1930 .append(joinName).append(");\n");
1931 }
1932 else {
1933 buf.append(body).append(parentName).append('.').append(relation.getSetterName())
1934 .append('(').append(joinName).append(");\n");
1935 }
1936 }
1937 else if (relation.getRelationType() == RelationType.LIST) {
1938 if (relation.isReversed()) {
1939 buf.append(body).append("((").append(poImpl).append(") ").append(parentName)
1940 .append(".getPersistenceDelegate()).").append(relation.getSetterName())
1941 .append(relation.isSerialized() ? "Blunt" : "").append('(').append(joinName).append(");\n");
1942 }
1943 else {
1944 buf.append(body).append("((").append(poImpl).append(") ").append(parentName)
1945 .append(".getPersistenceDelegate()).").append(relation.getGetterName())
1946 .append("Blunt().addBlunt(").append(joinName).append(");\n");
1947 if (relation.getLinkMethodName() != null) {
1948 buf.append(body).append("((").append(deriveClassNameForEntity(foreignEntity)).append(") ")
1949 .append(joinName).append(".getPersistenceDelegate()).")
1950 .append(createRelationUpdateReferenceCode(relation, parentName, true)).append(";\n");
1951 }
1952 }
1953 }
1954 else {
1955 buf.append(body).append(parentName).append('.').append(relation.getSetterName())
1956 .append(relation.isSerialized() ? "Blunt" : "").append('(').append(joinName).append(");\n");
1957 }
1958
1959 buf.append(inset).append(" }\n").append(inset).append(')');
1960
1961 for (String subText: createEagerJoinTexts(foreignEntity, joinClass, sqlAlias, inset + " ",
1962 eagerJoin.getJoins(), false)) {
1963 buf.append('\n').append(inset).append(" .addJoin(\n")
1964 .append(subText).append('\n')
1965 .append(inset).append(" )");
1966 }
1967
1968 texts.add(buf.toString());
1969 }
1970
1971 return texts;
1972 }
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989 public List<String> getEagerJoinPaths() {
1990 List<String> paths = new ArrayList<>();
1991 appendEagerJoinPaths(paths, "", createEagerJoinTree(getEntity().getRelationsIncludingInherited(), "e", 0));
1992 return paths;
1993 }
1994
1995
1996
1997
1998
1999
2000
2001
2002 private void appendEagerJoinPaths(List<String> paths, String prefix, List<EagerJoin> eagerJoins) {
2003 for (EagerJoin eagerJoin: eagerJoins) {
2004 String path = prefix + "*" + eagerJoin.getRelation().getName();
2005 if (eagerJoin.getJoins().isEmpty()) {
2006 paths.add(path);
2007 }
2008 else {
2009
2010 appendEagerJoinPaths(paths, path, eagerJoin.getJoins());
2011 }
2012 }
2013 }
2014
2015 }