1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package org.modeshape.jcr;
25
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.concurrent.locks.ReadWriteLock;
36 import java.util.concurrent.locks.ReentrantReadWriteLock;
37 import javax.jcr.PropertyType;
38 import javax.jcr.RepositoryException;
39 import javax.jcr.UnsupportedRepositoryOperationException;
40 import javax.jcr.Value;
41 import javax.jcr.ValueFormatException;
42 import javax.jcr.nodetype.NoSuchNodeTypeException;
43 import javax.jcr.nodetype.NodeDefinition;
44 import javax.jcr.nodetype.NodeType;
45 import javax.jcr.nodetype.NodeTypeDefinition;
46 import javax.jcr.nodetype.PropertyDefinition;
47 import javax.jcr.query.InvalidQueryException;
48 import javax.jcr.version.OnParentVersionAction;
49 import net.jcip.annotations.GuardedBy;
50 import net.jcip.annotations.ThreadSafe;
51 import org.modeshape.common.collection.Problems;
52 import org.modeshape.common.i18n.I18n;
53 import org.modeshape.common.text.TextEncoder;
54 import org.modeshape.common.text.XmlNameEncoder;
55 import org.modeshape.common.util.CheckArg;
56 import org.modeshape.common.util.Logger;
57 import org.modeshape.graph.ExecutionContext;
58 import org.modeshape.graph.Graph;
59 import org.modeshape.graph.Location;
60 import org.modeshape.graph.Subgraph;
61 import org.modeshape.graph.observe.Changes;
62 import org.modeshape.graph.property.Name;
63 import org.modeshape.graph.property.NameFactory;
64 import org.modeshape.graph.property.Path;
65 import org.modeshape.graph.property.PathFactory;
66 import org.modeshape.graph.property.PathNotFoundException;
67 import org.modeshape.graph.property.Property;
68 import org.modeshape.graph.property.PropertyFactory;
69 import org.modeshape.graph.query.QueryResults;
70 import org.modeshape.graph.query.model.QueryCommand;
71 import org.modeshape.graph.query.model.TypeSystem;
72 import org.modeshape.graph.query.parse.QueryParser;
73 import org.modeshape.graph.query.parse.SqlQueryParser;
74 import org.modeshape.graph.query.validate.Schemata;
75 import org.modeshape.graph.request.ChangeRequest;
76 import org.modeshape.jcr.nodetype.InvalidNodeTypeDefinitionException;
77 import org.modeshape.jcr.nodetype.NodeTypeExistsException;
78
79
80
81
82
83
84
85
86
87
88
89
90
91 @ThreadSafe
92 class RepositoryNodeTypeManager implements JcrSystemObserver {
93 private static final Logger LOGGER = Logger.getLogger(RepositoryNodeTypeManager.class);
94
95 private static final TextEncoder NAME_ENCODER = new XmlNameEncoder();
96
97 private final JcrRepository repository;
98 private final QueryParser queryParser;
99 private final ExecutionContext context;
100 private final Path nodeTypesPath;
101
102 @GuardedBy( "nodeTypeManagerLock" )
103 private final Map<Name, JcrNodeType> nodeTypes;
104 @GuardedBy( "nodeTypeManagerLock" )
105 private final Map<PropertyDefinitionId, JcrPropertyDefinition> propertyDefinitions;
106 @GuardedBy( "nodeTypeManagerLock" )
107 private final Map<NodeDefinitionId, JcrNodeDefinition> childNodeDefinitions;
108 @GuardedBy( "nodeTypeManagerLock" )
109 private NodeTypeSchemata schemata;
110 private final PropertyFactory propertyFactory;
111 private final PathFactory pathFactory;
112 private final NameFactory nameFactory;
113 private final ReadWriteLock nodeTypeManagerLock = new ReentrantReadWriteLock();
114 private final boolean includeColumnsForInheritedProperties;
115
116
117
118
119
120
121 private enum PropertyCardinality {
122 SINGLE_VALUED_ONLY,
123 MULTI_VALUED_ONLY,
124 ANY
125 }
126
127
128
129
130
131
132 private enum NodeCardinality {
133 NO_SAME_NAME_SIBLINGS,
134 SAME_NAME_SIBLINGS,
135 ANY
136 }
137
138 RepositoryNodeTypeManager( JcrRepository repository,
139 Path nodeTypesPath,
140 boolean includeColumnsForInheritedProperties ) {
141 this.repository = repository;
142 this.nodeTypesPath = nodeTypesPath;
143 this.context = repository.getExecutionContext();
144 this.includeColumnsForInheritedProperties = includeColumnsForInheritedProperties;
145 this.propertyFactory = context.getPropertyFactory();
146 this.pathFactory = context.getValueFactories().getPathFactory();
147 this.nameFactory = context.getValueFactories().getNameFactory();
148
149 propertyDefinitions = new HashMap<PropertyDefinitionId, JcrPropertyDefinition>();
150 childNodeDefinitions = new HashMap<NodeDefinitionId, JcrNodeDefinition>();
151 nodeTypes = new HashMap<Name, JcrNodeType>(50);
152 queryParser = new SqlQueryParser();
153
154 if (nodeTypesPath != null) {
155 Graph systemGraph = repository.createSystemGraph(context);
156 try {
157 systemGraph.getNodeAt(nodeTypesPath);
158 } catch (PathNotFoundException pnfe) {
159 systemGraph.create(nodeTypesPath).with(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.NODE_TYPES).and();
160 }
161 }
162 }
163
164
165
166
167
168
169 public Collection<JcrNodeType> getAllNodeTypes() {
170 try {
171 nodeTypeManagerLock.readLock().lock();
172 return Collections.unmodifiableCollection(new ArrayList<JcrNodeType>(nodeTypes.values()));
173 } finally {
174 nodeTypeManagerLock.readLock().unlock();
175 }
176 }
177
178
179
180
181
182
183
184 public Collection<JcrNodeType> getMixinNodeTypes() {
185 try {
186 nodeTypeManagerLock.readLock().lock();
187
188 List<JcrNodeType> types = new ArrayList<JcrNodeType>(nodeTypes.size());
189
190 for (JcrNodeType nodeType : nodeTypes.values()) {
191 if (nodeType.isMixin()) types.add(nodeType);
192 }
193
194 return types;
195 } finally {
196 nodeTypeManagerLock.readLock().unlock();
197 }
198 }
199
200
201
202
203
204
205
206 public Collection<JcrNodeType> getPrimaryNodeTypes() {
207 try {
208 nodeTypeManagerLock.readLock().lock();
209 List<JcrNodeType> types = new ArrayList<JcrNodeType>(nodeTypes.size());
210
211 for (JcrNodeType nodeType : nodeTypes.values()) {
212 if (!nodeType.isMixin()) types.add(nodeType);
213 }
214
215 return types;
216 } finally {
217 nodeTypeManagerLock.readLock().unlock();
218 }
219 }
220
221 public JcrPropertyDefinition getPropertyDefinition( PropertyDefinitionId id ) {
222 try {
223 nodeTypeManagerLock.readLock().lock();
224 return propertyDefinitions.get(id);
225 } finally {
226 nodeTypeManagerLock.readLock().unlock();
227 }
228 }
229
230 public JcrNodeDefinition getChildNodeDefinition( NodeDefinitionId id ) {
231 try {
232 nodeTypeManagerLock.readLock().lock();
233 return childNodeDefinitions.get(id);
234 } finally {
235 nodeTypeManagerLock.readLock().unlock();
236 }
237 }
238
239 NodeTypeSchemata getRepositorySchemata() {
240 try {
241 nodeTypeManagerLock.writeLock().lock();
242 if (schemata == null) {
243 schemata = new NodeTypeSchemata(context, nodeTypes, propertyDefinitions.values(),
244 includeColumnsForInheritedProperties);
245 }
246 return schemata;
247 } finally {
248 nodeTypeManagerLock.writeLock().unlock();
249 }
250 }
251
252 void signalNamespaceChanges() {
253 try {
254 nodeTypeManagerLock.writeLock().lock();
255 schemata = null;
256 } finally {
257 nodeTypeManagerLock.writeLock().unlock();
258 }
259 this.schemata = null;
260 }
261
262 JcrNodeType getNodeType( Name nodeTypeName ) {
263 try {
264 nodeTypeManagerLock.readLock().lock();
265 return nodeTypes.get(nodeTypeName);
266 } finally {
267 nodeTypeManagerLock.readLock().unlock();
268 }
269 }
270
271
272
273
274
275
276
277
278
279
280
281 boolean hasNodeType( Name nodeTypeName ) {
282 try {
283 nodeTypeManagerLock.readLock().lock();
284 return nodeTypes.containsKey(nodeTypeName);
285 } finally {
286 nodeTypeManagerLock.readLock().unlock();
287 }
288 }
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335 JcrPropertyDefinition findPropertyDefinition( Name primaryTypeName,
336 List<Name> mixinTypeNames,
337 Name propertyName,
338 Value value,
339 boolean checkMultiValuedDefinitions,
340 boolean skipProtected ) {
341 boolean setToEmpty = value == null;
342
343
344
345
346
347
348
349
350
351 boolean matchedOnName = false;
352
353
354 JcrNodeType primaryType = getNodeType(primaryTypeName);
355 if (primaryType != null) {
356 for (JcrPropertyDefinition definition : primaryType.allSingleValuePropertyDefinitions(propertyName)) {
357 matchedOnName = true;
358
359 if (skipProtected && definition.isProtected()) return null;
360 if (setToEmpty) {
361 if (!definition.isMandatory()) return definition;
362
363 continue;
364 }
365 assert value != null;
366
367 int type = definition.getRequiredType();
368
369 if (type == PropertyType.REFERENCE && type == value.getType()) return definition;
370 if ((type == PropertyType.UNDEFINED || type == value.getType()) && definition.satisfiesConstraints(value)) return definition;
371 }
372
373 if (matchedOnName) {
374 if (value != null) {
375 for (JcrPropertyDefinition definition : primaryType.allSingleValuePropertyDefinitions(propertyName)) {
376
377 if (skipProtected && definition.isProtected()) return null;
378
379 int type = definition.getRequiredType();
380 if (type == PropertyType.REFERENCE && definition.canCastToType(value)) {
381 return definition;
382 }
383 if (definition.canCastToTypeAndSatisfyConstraints(value)) return definition;
384 }
385 }
386
387 if (checkMultiValuedDefinitions) {
388
389 for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) {
390
391 if (skipProtected && definition.isProtected()) return null;
392 if (setToEmpty) {
393 if (!definition.isMandatory()) return definition;
394
395 continue;
396 }
397 assert value != null;
398
399 int type = definition.getRequiredType();
400
401 if (type == PropertyType.REFERENCE && type == value.getType()) return definition;
402 if ((type == PropertyType.UNDEFINED || type == value.getType()) && definition.satisfiesConstraints(value)) return definition;
403 }
404 if (value != null) {
405 for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) {
406
407 if (skipProtected && definition.isProtected()) return null;
408 assert definition.getRequiredType() != PropertyType.UNDEFINED;
409
410 int type = definition.getRequiredType();
411 if (type == PropertyType.REFERENCE && definition.canCastToType(value)) {
412 return definition;
413 }
414 if (definition.canCastToTypeAndSatisfyConstraints(value)) return definition;
415 }
416 }
417 }
418 return null;
419 }
420 }
421
422
423 List<JcrNodeType> mixinTypes = null;
424 if (mixinTypeNames != null && !mixinTypeNames.isEmpty()) {
425 mixinTypes = new LinkedList<JcrNodeType>();
426 for (Name mixinTypeName : mixinTypeNames) {
427 JcrNodeType mixinType = getNodeType(mixinTypeName);
428 if (mixinType == null) continue;
429 mixinTypes.add(mixinType);
430 for (JcrPropertyDefinition definition : mixinType.allSingleValuePropertyDefinitions(propertyName)) {
431 matchedOnName = true;
432
433 if (skipProtected && definition.isProtected()) return null;
434 if (setToEmpty) {
435 if (!definition.isMandatory()) return definition;
436
437 continue;
438 }
439 assert value != null;
440
441 int type = definition.getRequiredType();
442
443 if (type == PropertyType.REFERENCE && type == value.getType()) return definition;
444 if ((type == PropertyType.UNDEFINED || type == value.getType()) && definition.satisfiesConstraints(value)) return definition;
445 }
446 if (matchedOnName) {
447 if (value != null) {
448 for (JcrPropertyDefinition definition : mixinType.allSingleValuePropertyDefinitions(propertyName)) {
449
450 if (skipProtected && definition.isProtected()) return null;
451 assert definition.getRequiredType() != PropertyType.UNDEFINED;
452
453 int type = definition.getRequiredType();
454 if (type == PropertyType.REFERENCE && definition.canCastToType(value)) {
455 return definition;
456 }
457 if (definition.canCastToTypeAndSatisfyConstraints(value)) return definition;
458 }
459 }
460
461 if (checkMultiValuedDefinitions) {
462 for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) {
463
464 if (skipProtected && definition.isProtected()) return null;
465 if (setToEmpty) {
466 if (!definition.isMandatory()) return definition;
467
468 continue;
469 }
470 assert value != null;
471
472 int type = definition.getRequiredType();
473
474 if (type == PropertyType.REFERENCE && type == value.getType()) return definition;
475 if ((type == PropertyType.UNDEFINED || type == value.getType())
476 && definition.satisfiesConstraints(value)) return definition;
477 }
478 if (value != null) {
479 for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) {
480 matchedOnName = true;
481
482 if (skipProtected && definition.isProtected()) return null;
483 assert definition.getRequiredType() != PropertyType.UNDEFINED;
484
485 int type = definition.getRequiredType();
486 if (type == PropertyType.REFERENCE && definition.canCastToType(value)) {
487 return definition;
488 }
489 if (definition.canCastToTypeAndSatisfyConstraints(value)) return definition;
490
491 }
492 }
493 }
494
495 return null;
496 }
497 }
498 }
499
500 if (checkMultiValuedDefinitions) {
501 if (primaryType != null) {
502
503 for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) {
504 matchedOnName = true;
505
506 if (skipProtected && definition.isProtected()) return null;
507 if (setToEmpty) {
508 if (!definition.isMandatory()) return definition;
509
510 continue;
511 }
512 assert value != null;
513
514 int type = definition.getRequiredType();
515
516 if (type == PropertyType.REFERENCE && type == value.getType()) return definition;
517 if ((type == PropertyType.UNDEFINED || type == value.getType()) && definition.satisfiesConstraints(value)) return definition;
518 }
519 if (value != null) {
520 for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) {
521 matchedOnName = true;
522
523 if (skipProtected && definition.isProtected()) return null;
524 assert definition.getRequiredType() != PropertyType.UNDEFINED;
525
526 int type = definition.getRequiredType();
527 if (type == PropertyType.REFERENCE && definition.canCastToType(value)) {
528 return definition;
529 }
530 if (definition.canCastToTypeAndSatisfyConstraints(value)) return definition;
531 }
532 }
533 }
534
535 if (matchedOnName) return null;
536
537 if (mixinTypeNames != null && !mixinTypeNames.isEmpty()) {
538 mixinTypes = new LinkedList<JcrNodeType>();
539 for (Name mixinTypeName : mixinTypeNames) {
540 JcrNodeType mixinType = getNodeType(mixinTypeName);
541 if (mixinType == null) continue;
542 mixinTypes.add(mixinType);
543 for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) {
544 matchedOnName = true;
545
546 if (skipProtected && definition.isProtected()) return null;
547 if (setToEmpty) {
548 if (!definition.isMandatory()) return definition;
549
550 continue;
551 }
552 assert value != null;
553
554 int type = definition.getRequiredType();
555
556 if (type == PropertyType.REFERENCE && type == value.getType()) return definition;
557 if ((type == PropertyType.UNDEFINED || type == value.getType()) && definition.satisfiesConstraints(value)) return definition;
558 }
559 if (value != null) {
560 for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) {
561 matchedOnName = true;
562
563 if (skipProtected && definition.isProtected()) return null;
564 assert definition.getRequiredType() != PropertyType.UNDEFINED;
565
566 int type = definition.getRequiredType();
567 if (type == PropertyType.REFERENCE && definition.canCastToType(value)) {
568 return definition;
569 }
570 if (definition.canCastToTypeAndSatisfyConstraints(value)) return definition;
571 }
572 }
573 }
574 }
575 if (matchedOnName) return null;
576
577 }
578
579
580 if (!propertyName.equals(JcrNodeType.RESIDUAL_NAME)) return findPropertyDefinition(primaryTypeName,
581 mixinTypeNames,
582 JcrNodeType.RESIDUAL_NAME,
583 value,
584 checkMultiValuedDefinitions,
585 skipProtected);
586 return null;
587 }
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632 JcrPropertyDefinition findPropertyDefinition( Name primaryTypeName,
633 List<Name> mixinTypeNames,
634 Name propertyName,
635 Value[] values,
636 boolean skipProtected ) {
637 boolean setToEmpty = values == null;
638 int propertyType = values == null || values.length == 0 ? PropertyType.STRING : values[0].getType();
639
640
641
642
643
644
645
646
647
648 boolean matchedOnName = false;
649
650
651 JcrNodeType primaryType = getNodeType(primaryTypeName);
652 if (primaryType != null) {
653 for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) {
654 matchedOnName = true;
655
656 if (skipProtected && definition.isProtected()) return null;
657 if (setToEmpty) {
658 if (!definition.isMandatory()) return definition;
659
660 continue;
661 }
662 assert values != null;
663
664 int type = definition.getRequiredType();
665 boolean typeMatches = values.length == 0 || type == PropertyType.UNDEFINED || type == propertyType;
666
667 if (typeMatches && type == PropertyType.REFERENCE) return definition;
668 if (typeMatches && definition.satisfiesConstraints(values)) return definition;
669 }
670
671 if (matchedOnName) {
672 if (values != null && values.length != 0) {
673
674
675
676
677 for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) {
678
679 if (skipProtected && definition.isProtected()) return null;
680 assert definition.getRequiredType() != PropertyType.UNDEFINED;
681
682 if (definition.getRequiredType() == PropertyType.REFERENCE && definition.canCastToType(values)) return definition;
683 if (definition.canCastToTypeAndSatisfyConstraints(values)) return definition;
684 }
685 }
686
687 return null;
688 }
689 }
690
691
692 List<JcrNodeType> mixinTypes = null;
693 if (mixinTypeNames != null && !mixinTypeNames.isEmpty()) {
694 mixinTypes = new LinkedList<JcrNodeType>();
695 for (Name mixinTypeName : mixinTypeNames) {
696 JcrNodeType mixinType = getNodeType(mixinTypeName);
697 if (mixinType == null) continue;
698 mixinTypes.add(mixinType);
699 for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) {
700 matchedOnName = true;
701
702 if (skipProtected && definition.isProtected()) return null;
703 if (setToEmpty) {
704 if (!definition.isMandatory()) return definition;
705
706 continue;
707 }
708 assert values != null;
709
710 int type = definition.getRequiredType();
711 boolean typeMatches = values.length == 0 || type == PropertyType.UNDEFINED || type == propertyType;
712
713 if (typeMatches && type == PropertyType.REFERENCE) return definition;
714 if (typeMatches && definition.satisfiesConstraints(values)) return definition;
715 }
716 if (matchedOnName) {
717 if (values != null && values.length != 0) {
718
719
720
721
722 for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) {
723
724 if (skipProtected && definition.isProtected()) return null;
725 assert definition.getRequiredType() != PropertyType.UNDEFINED;
726
727 if (definition.getRequiredType() == PropertyType.REFERENCE && definition.canCastToType(values)) return definition;
728 if (definition.canCastToTypeAndSatisfyConstraints(values)) return definition;
729 }
730 }
731
732 return null;
733 }
734
735 }
736 }
737
738
739 if (!propertyName.equals(JcrNodeType.RESIDUAL_NAME)) return findPropertyDefinition(primaryTypeName,
740 mixinTypeNames,
741 JcrNodeType.RESIDUAL_NAME,
742 values,
743 skipProtected);
744 return null;
745 }
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760 private List<JcrPropertyDefinition> findPropertyDefinitions( List<Name> typeNamesToCheck,
761 Name propertyName,
762 PropertyCardinality typeToCheck,
763 List<JcrNodeType> pendingTypes ) {
764 assert typeNamesToCheck != null;
765
766 Collection<JcrPropertyDefinition> propDefs = null;
767 List<JcrPropertyDefinition> matchingDefs = new ArrayList<JcrPropertyDefinition>();
768
769
770 for (Name typeNameToCheck : typeNamesToCheck) {
771 JcrNodeType typeName = findTypeInMapOrList(typeNameToCheck, pendingTypes);
772 if (typeName == null) continue;
773
774 switch (typeToCheck) {
775 case SINGLE_VALUED_ONLY:
776 propDefs = typeName.allSingleValuePropertyDefinitions(propertyName);
777 break;
778 case MULTI_VALUED_ONLY:
779 propDefs = typeName.allMultiValuePropertyDefinitions(propertyName);
780 break;
781 case ANY:
782 propDefs = typeName.allPropertyDefinitions(propertyName);
783 break;
784 default:
785 throw new IllegalStateException("Should be unreachable: " + typeToCheck);
786 }
787
788 matchingDefs.addAll(propDefs);
789 }
790
791 return matchingDefs;
792 }
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807 boolean canRemoveProperty( Name primaryTypeNameOfParent,
808 List<Name> mixinTypeNamesOfParent,
809 Name propertyName,
810 boolean skipProtected ) {
811
812 JcrNodeType primaryType = getNodeType(primaryTypeNameOfParent);
813 if (primaryType != null) {
814 for (JcrPropertyDefinition definition : primaryType.allPropertyDefinitions(propertyName)) {
815
816 if (skipProtected && definition.isProtected()) continue;
817
818 return !definition.isMandatory();
819 }
820 }
821
822
823 if (mixinTypeNamesOfParent != null && !mixinTypeNamesOfParent.isEmpty()) {
824 for (Name mixinTypeName : mixinTypeNamesOfParent) {
825 JcrNodeType mixinType = getNodeType(mixinTypeName);
826 if (mixinType == null) continue;
827 for (JcrPropertyDefinition definition : mixinType.allPropertyDefinitions(propertyName)) {
828
829 if (skipProtected && definition.isProtected()) continue;
830
831 return !definition.isMandatory();
832 }
833 }
834 }
835
836
837 if (!propertyName.equals(JcrNodeType.RESIDUAL_NAME)) return canRemoveProperty(primaryTypeNameOfParent,
838 mixinTypeNamesOfParent,
839 JcrNodeType.RESIDUAL_NAME,
840 skipProtected);
841 return false;
842 }
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857 boolean canRemoveItem( Name primaryTypeNameOfParent,
858 List<Name> mixinTypeNamesOfParent,
859 Name itemName,
860 boolean skipProtected ) {
861
862 JcrNodeType primaryType = getNodeType(primaryTypeNameOfParent);
863 if (primaryType != null) {
864 for (JcrPropertyDefinition definition : primaryType.allPropertyDefinitions(itemName)) {
865
866 if (skipProtected && definition.isProtected()) continue;
867
868 return !definition.isMandatory();
869 }
870 }
871
872
873 if (primaryType != null) {
874 for (JcrNodeDefinition definition : primaryType.allChildNodeDefinitions(itemName)) {
875
876 if (skipProtected && definition.isProtected()) continue;
877
878 return !definition.isMandatory();
879 }
880 }
881
882
883 if (mixinTypeNamesOfParent != null && !mixinTypeNamesOfParent.isEmpty()) {
884 for (Name mixinTypeName : mixinTypeNamesOfParent) {
885 JcrNodeType mixinType = getNodeType(mixinTypeName);
886 if (mixinType == null) continue;
887 for (JcrPropertyDefinition definition : mixinType.allPropertyDefinitions(itemName)) {
888
889 if (skipProtected && definition.isProtected()) continue;
890
891 return !definition.isMandatory();
892 }
893 }
894 }
895
896
897 if (mixinTypeNamesOfParent != null && !mixinTypeNamesOfParent.isEmpty()) {
898 for (Name mixinTypeName : mixinTypeNamesOfParent) {
899 JcrNodeType mixinType = getNodeType(mixinTypeName);
900 if (mixinType == null) continue;
901 for (JcrNodeDefinition definition : mixinType.allChildNodeDefinitions(itemName)) {
902
903 if (skipProtected && definition.isProtected()) continue;
904
905 return !definition.isMandatory();
906 }
907 }
908 }
909
910
911 if (!itemName.equals(JcrNodeType.RESIDUAL_NAME)) return canRemoveItem(primaryTypeNameOfParent,
912 mixinTypeNamesOfParent,
913 JcrNodeType.RESIDUAL_NAME,
914 skipProtected);
915 return false;
916 }
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936 JcrNodeDefinition findChildNodeDefinition( Name primaryTypeNameOfParent,
937 List<Name> mixinTypeNamesOfParent,
938 Name childName,
939 Name childPrimaryNodeType,
940 int numberOfExistingChildrenWithSameName,
941 boolean skipProtected ) {
942 JcrNodeType childType = childPrimaryNodeType != null ? getNodeType(childPrimaryNodeType) : null;
943 boolean requireSns = numberOfExistingChildrenWithSameName > 1;
944
945
946 JcrNodeType primaryType = getNodeType(primaryTypeNameOfParent);
947 if (primaryType != null) {
948 for (JcrNodeDefinition definition : primaryType.allChildNodeDefinitions(childName, requireSns)) {
949
950 if (skipProtected && definition.isProtected()) return null;
951
952 if (definition.allowsChildWithType(childType)) return definition;
953 }
954 }
955
956
957 if (mixinTypeNamesOfParent != null && !mixinTypeNamesOfParent.isEmpty()) {
958 for (Name mixinTypeName : mixinTypeNamesOfParent) {
959 JcrNodeType mixinType = getNodeType(mixinTypeName);
960 if (mixinType == null) continue;
961 for (JcrNodeDefinition definition : mixinType.allChildNodeDefinitions(childName, requireSns)) {
962
963 if (skipProtected && definition.isProtected()) return null;
964
965 if (definition.allowsChildWithType(childType)) return definition;
966 }
967 }
968 }
969
970
971 if (!childName.equals(JcrNodeType.RESIDUAL_NAME)) return findChildNodeDefinition(primaryTypeNameOfParent,
972 mixinTypeNamesOfParent,
973 JcrNodeType.RESIDUAL_NAME,
974 childPrimaryNodeType,
975 numberOfExistingChildrenWithSameName,
976 skipProtected);
977 return null;
978 }
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993 private List<JcrNodeDefinition> findChildNodeDefinitions( List<Name> typeNamesToCheck,
994 Name childNodeName,
995 NodeCardinality typesToCheck,
996 List<JcrNodeType> pendingTypes ) {
997 assert typeNamesToCheck != null;
998 Collection<JcrNodeDefinition> nodeDefs = null;
999 List<JcrNodeDefinition> matchingDefs = new ArrayList<JcrNodeDefinition>();
1000
1001 for (Name typeNameToCheck : typeNamesToCheck) {
1002 JcrNodeType typeName = findTypeInMapOrList(typeNameToCheck, pendingTypes);
1003 if (typeName == null) continue;
1004
1005 switch (typesToCheck) {
1006 case NO_SAME_NAME_SIBLINGS:
1007 nodeDefs = typeName.allChildNodeDefinitions(childNodeName, false);
1008 break;
1009 case SAME_NAME_SIBLINGS:
1010 nodeDefs = typeName.allChildNodeDefinitions(childNodeName, true);
1011 break;
1012 case ANY:
1013 nodeDefs = typeName.allChildNodeDefinitions(childNodeName);
1014 break;
1015 }
1016
1017 assert nodeDefs != null;
1018 for (JcrNodeDefinition definition : nodeDefs) {
1019 if (NodeCardinality.NO_SAME_NAME_SIBLINGS == typesToCheck && definition.allowsSameNameSiblings()) continue;
1020 matchingDefs.add(definition);
1021 }
1022 }
1023
1024 return matchingDefs;
1025 }
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040 boolean canRemoveAllChildren( Name primaryTypeNameOfParent,
1041 List<Name> mixinTypeNamesOfParent,
1042 Name childName,
1043 boolean skipProtected ) {
1044
1045 JcrNodeType primaryType = getNodeType(primaryTypeNameOfParent);
1046 if (primaryType != null) {
1047 for (JcrNodeDefinition definition : primaryType.allChildNodeDefinitions(childName)) {
1048
1049 if (skipProtected && definition.isProtected()) continue;
1050
1051 return !definition.isMandatory();
1052 }
1053 }
1054
1055
1056 if (mixinTypeNamesOfParent != null && !mixinTypeNamesOfParent.isEmpty()) {
1057 for (Name mixinTypeName : mixinTypeNamesOfParent) {
1058 JcrNodeType mixinType = getNodeType(mixinTypeName);
1059 if (mixinType == null) continue;
1060 for (JcrNodeDefinition definition : mixinType.allChildNodeDefinitions(childName)) {
1061
1062 if (skipProtected && definition.isProtected()) continue;
1063
1064 return !definition.isMandatory();
1065 }
1066 }
1067 }
1068
1069
1070 if (!childName.equals(JcrNodeType.RESIDUAL_NAME)) return canRemoveAllChildren(primaryTypeNameOfParent,
1071 mixinTypeNamesOfParent,
1072 JcrNodeType.RESIDUAL_NAME,
1073 skipProtected);
1074 return false;
1075 }
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096 void projectOnto( Graph graph,
1097 Path parentOfTypeNodes ) {
1098 assert graph != null;
1099 assert parentOfTypeNodes != null;
1100
1101
1102 try {
1103 graph.getNodeAt(parentOfTypeNodes);
1104 } catch (PathNotFoundException pnfe) {
1105 PropertyFactory propertyFactory = context.getPropertyFactory();
1106 graph.create(parentOfTypeNodes,
1107 propertyFactory.create(JcrLexicon.PRIMARY_TYPE,
1108 ModeShapeLexicon.NODE_TYPES.getString(context.getNamespaceRegistry()))).and();
1109 }
1110
1111 Graph.Batch batch = graph.batch();
1112
1113 for (JcrNodeType nodeType : getAllNodeTypes()) {
1114 projectNodeTypeOnto(nodeType, parentOfTypeNodes, batch);
1115 }
1116
1117 batch.execute();
1118 }
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129 private void projectNodeTypeOnto( JcrNodeType nodeType,
1130 Path parentOfTypeNodes,
1131 Graph.Batch batch ) {
1132 assert nodeType != null;
1133 assert parentOfTypeNodes != null;
1134 assert batch != null;
1135
1136 Path nodeTypePath = pathFactory.create(parentOfTypeNodes, nodeType.getInternalName());
1137
1138 NodeType[] supertypes = nodeType.getDeclaredSupertypes();
1139 List<Name> supertypeNames = new ArrayList<Name>(supertypes.length);
1140 for (int i = 0; i < supertypes.length; i++) {
1141 supertypeNames.add(((JcrNodeType)supertypes[i]).getInternalName());
1142 }
1143
1144 List<Property> propsList = new ArrayList<Property>();
1145 propsList.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.NODE_TYPE));
1146 propsList.add(propertyFactory.create(JcrLexicon.IS_MIXIN, nodeType.isMixin()));
1147 propsList.add(propertyFactory.create(JcrLexicon.IS_ABSTRACT, nodeType.isAbstract()));
1148 propsList.add(propertyFactory.create(JcrLexicon.IS_QUERYABLE, nodeType.isQueryable()));
1149
1150 if (nodeType.getPrimaryItemName() != null) {
1151 propsList.add(propertyFactory.create(JcrLexicon.PRIMARY_ITEM_NAME, nodeType.getPrimaryItemName()));
1152 }
1153
1154 propsList.add(propertyFactory.create(JcrLexicon.NODE_TYPE_NAME, nodeType.getName()));
1155 propsList.add(propertyFactory.create(JcrLexicon.HAS_ORDERABLE_CHILD_NODES, nodeType.hasOrderableChildNodes()));
1156 propsList.add(propertyFactory.create(JcrLexicon.SUPERTYPES, supertypeNames));
1157
1158 batch.create(nodeTypePath).with(propsList).orReplace().and();
1159
1160 PropertyDefinition[] propertyDefs = nodeType.getDeclaredPropertyDefinitions();
1161 for (int i = 0; i < propertyDefs.length; i++) {
1162 projectPropertyDefinitionOnto(propertyDefs[i], nodeTypePath, batch);
1163 }
1164
1165 NodeDefinition[] childNodeDefs = nodeType.getDeclaredChildNodeDefinitions();
1166 for (int i = 0; i < childNodeDefs.length; i++) {
1167 projectChildNodeDefinitionOnto(childNodeDefs[i], nodeTypePath, batch);
1168 }
1169
1170 }
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188 private void projectPropertyDefinitionOnto( PropertyDefinition propertyDef,
1189 Path nodeTypePath,
1190 Graph.Batch batch ) {
1191 assert propertyDef != null;
1192 assert nodeTypePath != null;
1193 assert batch != null;
1194
1195 JcrPropertyDefinition jcrPropDef = (JcrPropertyDefinition)propertyDef;
1196 String propName = jcrPropDef.getInternalName().getString(context.getNamespaceRegistry(), NAME_ENCODER);
1197 Path propDefPath = pathFactory.create(nodeTypePath, JcrLexicon.PROPERTY_DEFINITION);
1198
1199 List<Property> propsList = new ArrayList<Property>();
1200 propsList.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.PROPERTY_DEFINITION));
1201
1202 if (!JcrNodeType.RESIDUAL_ITEM_NAME.equals(jcrPropDef.getName())) {
1203 propsList.add(propertyFactory.create(JcrLexicon.NAME, propName));
1204 }
1205 propsList.add(propertyFactory.create(JcrLexicon.AUTO_CREATED, jcrPropDef.isAutoCreated()));
1206 propsList.add(propertyFactory.create(JcrLexicon.MANDATORY, jcrPropDef.isMandatory()));
1207 propsList.add(propertyFactory.create(JcrLexicon.MULTIPLE, jcrPropDef.isMultiple()));
1208 propsList.add(propertyFactory.create(JcrLexicon.PROTECTED, jcrPropDef.isProtected()));
1209 propsList.add(propertyFactory.create(JcrLexicon.ON_PARENT_VERSION,
1210 OnParentVersionAction.nameFromValue(jcrPropDef.getOnParentVersion())));
1211 propsList.add(propertyFactory.create(JcrLexicon.REQUIRED_TYPE,
1212 PropertyType.nameFromValue(jcrPropDef.getRequiredType()).toUpperCase()));
1213
1214 Value[] defaultValues = jcrPropDef.getDefaultValues();
1215 if (defaultValues.length > 0) {
1216 String[] defaultsAsString = new String[defaultValues.length];
1217
1218 for (int i = 0; i < defaultValues.length; i++) {
1219 try {
1220 defaultsAsString[i] = defaultValues[i].getString();
1221 } catch (RepositoryException re) {
1222
1223 throw new IllegalStateException(re);
1224 }
1225 }
1226 propsList.add(propertyFactory.create(JcrLexicon.DEFAULT_VALUES, (Object[])defaultsAsString));
1227 }
1228
1229 String[] valueConstraints = jcrPropDef.getValueConstraints();
1230 if (valueConstraints.length > 0) {
1231 propsList.add(propertyFactory.create(JcrLexicon.VALUE_CONSTRAINTS, (Object[])valueConstraints));
1232 }
1233 batch.create(propDefPath).with(propsList).and();
1234 }
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252 private void projectChildNodeDefinitionOnto( NodeDefinition childNodeDef,
1253 Path nodeTypePath,
1254 Graph.Batch batch ) {
1255 assert childNodeDef != null;
1256 assert nodeTypePath != null;
1257 assert batch != null;
1258
1259 JcrNodeDefinition jcrNodeDef = (JcrNodeDefinition)childNodeDef;
1260 String nodeName = jcrNodeDef.getInternalName().getString(context.getNamespaceRegistry(), NAME_ENCODER);
1261 Path nodeDefPath = pathFactory.create(nodeTypePath, JcrLexicon.CHILD_NODE_DEFINITION);
1262
1263 List<Property> propsList = new ArrayList<Property>();
1264 propsList.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.CHILD_NODE_DEFINITION));
1265
1266 if (!JcrNodeType.RESIDUAL_ITEM_NAME.equals(jcrNodeDef.getName())) {
1267 propsList.add(propertyFactory.create(JcrLexicon.NAME, nodeName));
1268 }
1269
1270 if (jcrNodeDef.getDefaultPrimaryType() != null) {
1271 propsList.add(propertyFactory.create(JcrLexicon.DEFAULT_PRIMARY_TYPE, jcrNodeDef.getDefaultPrimaryType().getName()));
1272 }
1273
1274 propsList.add(propertyFactory.create(JcrLexicon.REQUIRED_PRIMARY_TYPES, (Object[])jcrNodeDef.requiredPrimaryTypeNames()));
1275 propsList.add(propertyFactory.create(JcrLexicon.SAME_NAME_SIBLINGS, jcrNodeDef.allowsSameNameSiblings()));
1276 propsList.add(propertyFactory.create(JcrLexicon.ON_PARENT_VERSION,
1277 OnParentVersionAction.nameFromValue(jcrNodeDef.getOnParentVersion())));
1278 propsList.add(propertyFactory.create(JcrLexicon.AUTO_CREATED, jcrNodeDef.isAutoCreated()));
1279 propsList.add(propertyFactory.create(JcrLexicon.MANDATORY, jcrNodeDef.isMandatory()));
1280 propsList.add(propertyFactory.create(JcrLexicon.PROTECTED, jcrNodeDef.isProtected()));
1281
1282 batch.create(nodeDefPath).with(propsList).and();
1283 }
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300 void unregisterNodeType( Collection<Name> nodeTypeNames )
1301 throws NoSuchNodeTypeException, InvalidNodeTypeDefinitionException, RepositoryException {
1302 CheckArg.isNotNull(nodeTypeNames, "nodeTypeNames");
1303 try {
1304
1305
1306
1307 nodeTypeManagerLock.writeLock().lock();
1308
1309 for (Name nodeTypeName : nodeTypeNames) {
1310
1311
1312
1313 if (nodeTypeName == null) {
1314 throw new NoSuchNodeTypeException(JcrI18n.invalidNodeTypeName.text());
1315 }
1316 String name = nodeTypeName.getString(context.getNamespaceRegistry());
1317
1318 if (!this.nodeTypes.containsKey(nodeTypeName)) {
1319 throw new NoSuchNodeTypeException(JcrI18n.noSuchNodeType.text(name));
1320 }
1321
1322
1323
1324
1325 for (JcrNodeType nodeType : nodeTypes.values()) {
1326
1327 if (nodeTypeNames.contains(nodeType.getInternalName())) {
1328 continue;
1329 }
1330
1331 for (JcrNodeType supertype : nodeType.supertypes()) {
1332 if (nodeTypeName.equals(supertype.getInternalName())) {
1333 throw new InvalidNodeTypeDefinitionException(
1334 JcrI18n.cannotUnregisterSupertype.text(name,
1335 supertype.getName()));
1336 }
1337 }
1338
1339 for (JcrNodeDefinition childNode : nodeType.childNodeDefinitions()) {
1340 NodeType defaultPrimaryType = childNode.getDefaultPrimaryType();
1341 if (defaultPrimaryType != null && name.equals(defaultPrimaryType.getName())) {
1342 throw new InvalidNodeTypeDefinitionException(
1343 JcrI18n.cannotUnregisterDefaultPrimaryType.text(name,
1344 nodeType.getName(),
1345 childNode.getName()));
1346 }
1347 if (childNode.requiredPrimaryTypeNameSet().contains(nodeTypeName)) {
1348 throw new InvalidNodeTypeDefinitionException(
1349 JcrI18n.cannotUnregisterRequiredPrimaryType.text(name,
1350 nodeType.getName(),
1351 childNode.getName()));
1352 }
1353 }
1354
1355
1356
1357
1358 if (isNodeTypeInUse(nodeTypeName)) {
1359 throw new InvalidNodeTypeDefinitionException(JcrI18n.cannotUnregisterInUseType.text(name));
1360
1361 }
1362
1363 }
1364 }
1365
1366 this.nodeTypes.keySet().removeAll(nodeTypeNames);
1367
1368 if (nodeTypesPath != null) {
1369 Graph.Batch batch = repository.createSystemGraph(context).batch();
1370
1371 for (Name nodeTypeName : nodeTypeNames) {
1372 Path nodeTypePath = pathFactory.create(nodeTypesPath, nodeTypeName);
1373 batch.delete(nodeTypePath).and();
1374 }
1375
1376 batch.execute();
1377 }
1378 this.schemata = null;
1379
1380 } finally {
1381 nodeTypeManagerLock.writeLock().unlock();
1382 }
1383 }
1384
1385
1386
1387
1388
1389
1390
1391
1392 boolean isNodeTypeInUse( Name nodeTypeName ) throws InvalidQueryException {
1393 String nodeTypeString = nodeTypeName.getString(context.getNamespaceRegistry());
1394 String expression = "SELECT * from [" + nodeTypeString + "] LIMIT 1";
1395
1396 TypeSystem typeSystem = context.getValueFactories().getTypeSystem();
1397
1398 QueryCommand command = queryParser.parseQuery(expression, typeSystem);
1399 assert command != null : "Could not parse " + expression;
1400
1401 Schemata schemata = getRepositorySchemata();
1402
1403 Set<String> workspaceNames = repository.workspaceNames();
1404 for (String workspaceName : workspaceNames) {
1405 QueryResults result = repository.queryManager().query(workspaceName, command, schemata, null, null);
1406
1407 if (result.getRowCount() > 0) {
1408 return true;
1409 }
1410 }
1411
1412 return false;
1413 }
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429 JcrNodeType registerNodeType( NodeTypeDefinition ntd )
1430 throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, RepositoryException {
1431 assert ntd != null;
1432 List<JcrNodeType> result = registerNodeTypes(Collections.singletonList(ntd));
1433 return result.isEmpty() ? null : result.get(0);
1434 }
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448 List<JcrNodeType> registerNodeTypes( NodeTypeDefinition[] ntds )
1449 throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, RepositoryException {
1450 assert ntds != null;
1451 return registerNodeTypes(Arrays.asList(ntds));
1452 }
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527 List<JcrNodeType> registerNodeTypes( Iterable<NodeTypeDefinition> nodeTypeDefns )
1528 throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, RepositoryException {
1529
1530 if (nodeTypeDefns == null) {
1531 return Collections.emptyList();
1532 }
1533
1534 List<JcrNodeType> typesPendingRegistration = new ArrayList<JcrNodeType>();
1535
1536 try {
1537 nodeTypeManagerLock.writeLock().lock();
1538 for (NodeTypeDefinition nodeTypeDefn : nodeTypeDefns) {
1539 if (nodeTypeDefn instanceof JcrNodeTypeTemplate) {
1540
1541 nodeTypeDefn = ((JcrNodeTypeTemplate)nodeTypeDefn).with(context);
1542 }
1543 Name internalName = nameFactory.create(nodeTypeDefn.getName());
1544 if (internalName == null || internalName.getLocalName().length() == 0) {
1545 throw new InvalidNodeTypeDefinitionException(JcrI18n.invalidNodeTypeName.text());
1546 }
1547
1548 if (nodeTypes.containsKey(internalName)) {
1549 String name = nodeTypeDefn.getName();
1550 throw new NodeTypeExistsException(internalName, JcrI18n.nodeTypeAlreadyExists.text(name));
1551 }
1552
1553 List<JcrNodeType> supertypes = supertypesFor(nodeTypeDefn, typesPendingRegistration);
1554 JcrNodeType nodeType = nodeTypeFrom(nodeTypeDefn, supertypes);
1555 validate(nodeType, supertypes, typesPendingRegistration);
1556
1557 typesPendingRegistration.add(nodeType);
1558 }
1559
1560
1561 for (JcrNodeType nodeType : typesPendingRegistration) {
1562 for (JcrNodeDefinition nodeDef : nodeType.getDeclaredChildNodeDefinitions()) {
1563 JcrNodeType[] requiredPrimaryTypes = new JcrNodeType[nodeDef.requiredPrimaryTypeNames().length];
1564 int i = 0;
1565 for (Name primaryTypeName : nodeDef.requiredPrimaryTypeNames()) {
1566 requiredPrimaryTypes[i] = findTypeInMapOrList(primaryTypeName, typesPendingRegistration);
1567
1568 if (requiredPrimaryTypes[i] == null) {
1569 throw new RepositoryException(
1570 JcrI18n.invalidPrimaryTypeName.text(primaryTypeName, nodeType.getName()));
1571 }
1572 i++;
1573 }
1574 }
1575 }
1576
1577 Graph.Batch batch = null;
1578 if (nodeTypesPath != null) batch = repository.createSystemGraph(context).batch();
1579
1580 for (JcrNodeType nodeType : typesPendingRegistration) {
1581
1582
1583
1584
1585
1586 Name name = nodeType.getInternalName();
1587 nodeTypes.put(name, nodeType);
1588 for (JcrNodeDefinition childDefinition : nodeType.childNodeDefinitions()) {
1589 childNodeDefinitions.put(childDefinition.getId(), childDefinition);
1590 }
1591 for (JcrPropertyDefinition propertyDefinition : nodeType.propertyDefinitions()) {
1592 propertyDefinitions.put(propertyDefinition.getId(), propertyDefinition);
1593 }
1594
1595 if (nodeTypesPath != null) projectNodeTypeOnto(nodeType, nodeTypesPath, batch);
1596 }
1597
1598
1599 this.schemata = null;
1600 if (nodeTypesPath != null) {
1601 assert batch != null;
1602 batch.execute();
1603 }
1604 } finally {
1605 nodeTypeManagerLock.writeLock().unlock();
1606 }
1607
1608 return typesPendingRegistration;
1609 }
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680 List<JcrNodeType> registerNodeTypes( Subgraph nodeTypeSubgraph,
1681 Location locationOfParentOfNodeTypes )
1682 throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, RepositoryException {
1683 assert nodeTypeSubgraph != null;
1684 assert locationOfParentOfNodeTypes != null;
1685 CndNodeTypeReader factory = new CndNodeTypeReader(this.context);
1686 factory.read(nodeTypeSubgraph, locationOfParentOfNodeTypes, nodeTypeSubgraph.getGraph().getSourceName());
1687 return registerNodeTypes(factory);
1688 }
1689
1690 private JcrNodeType nodeTypeFrom( NodeTypeDefinition nodeType,
1691 List<JcrNodeType> supertypes ) throws RepositoryException {
1692 PropertyDefinition[] propDefns = nodeType.getDeclaredPropertyDefinitions();
1693 NodeDefinition[] childDefns = nodeType.getDeclaredChildNodeDefinitions();
1694 List<JcrPropertyDefinition> properties = new ArrayList<JcrPropertyDefinition>();
1695 List<JcrNodeDefinition> childNodes = new ArrayList<JcrNodeDefinition>();
1696
1697 if (propDefns != null) {
1698 for (PropertyDefinition propDefn : propDefns) {
1699 properties.add(propertyDefinitionFrom(propDefn));
1700 }
1701 }
1702 if (childDefns != null) {
1703 for (NodeDefinition childNodeDefn : childDefns) {
1704 childNodes.add(childNodeDefinitionFrom(childNodeDefn));
1705 }
1706 }
1707
1708 Name name = nameFactory.create(nodeType.getName());
1709 Name primaryItemName = nameFactory.create(nodeType.getPrimaryItemName());
1710 boolean mixin = nodeType.isMixin();
1711 boolean isAbstract = nodeType.isAbstract();
1712 boolean queryable = nodeType.isQueryable();
1713 boolean orderableChildNodes = nodeType.hasOrderableChildNodes();
1714
1715 return new JcrNodeType(this.context, this, name, supertypes, primaryItemName, childNodes, properties, mixin, isAbstract,
1716 queryable, orderableChildNodes);
1717 }
1718
1719 private JcrPropertyDefinition propertyDefinitionFrom( PropertyDefinition propDefn ) throws RepositoryException {
1720 Name propertyName = nameFactory.create(propDefn.getName());
1721 int onParentVersionBehavior = propDefn.getOnParentVersion();
1722 int requiredType = propDefn.getRequiredType();
1723 boolean mandatory = propDefn.isMandatory();
1724 boolean multiple = propDefn.isMultiple();
1725 boolean autoCreated = propDefn.isAutoCreated();
1726 boolean isProtected = propDefn.isProtected();
1727 boolean fullTextSearchable = propDefn.isFullTextSearchable();
1728 boolean queryOrderable = propDefn.isQueryOrderable();
1729
1730 Value[] defaultValues = propDefn.getDefaultValues();
1731 if (defaultValues != null) {
1732 for (int i = 0; i != defaultValues.length; ++i) {
1733 Value value = defaultValues[i];
1734 defaultValues[i] = new JcrValue(this.context.getValueFactories(), null, value);
1735 }
1736 } else {
1737 defaultValues = new Value[0];
1738 }
1739
1740 String[] valueConstraints = propDefn.getValueConstraints();
1741 String[] queryOperators = propDefn.getAvailableQueryOperators();
1742 if (valueConstraints == null) valueConstraints = new String[0];
1743 if (queryOperators == null) queryOperators = new String[0];
1744 return new JcrPropertyDefinition(this.context, null, propertyName, onParentVersionBehavior, autoCreated, mandatory,
1745 isProtected, defaultValues, requiredType, valueConstraints, multiple,
1746 fullTextSearchable, queryOrderable, queryOperators);
1747 }
1748
1749 private JcrNodeDefinition childNodeDefinitionFrom( NodeDefinition childNodeDefn ) {
1750 Name childNodeName = nameFactory.create(childNodeDefn.getName());
1751 Name defaultPrimaryTypeName = nameFactory.create(childNodeDefn.getDefaultPrimaryTypeName());
1752 int onParentVersion = childNodeDefn.getOnParentVersion();
1753
1754 boolean mandatory = childNodeDefn.isMandatory();
1755 boolean allowsSns = childNodeDefn.allowsSameNameSiblings();
1756 boolean autoCreated = childNodeDefn.isAutoCreated();
1757 boolean isProtected = childNodeDefn.isProtected();
1758
1759 Name[] requiredTypes;
1760 String[] requiredTypeNames = childNodeDefn.getRequiredPrimaryTypeNames();
1761 if (requiredTypeNames != null) {
1762 List<Name> names = new ArrayList<Name>(requiredTypeNames.length);
1763 for (String typeName : requiredTypeNames) {
1764 names.add(nameFactory.create(typeName));
1765 }
1766 requiredTypes = names.toArray(new Name[names.size()]);
1767 } else {
1768 requiredTypes = new Name[0];
1769 }
1770
1771 return new JcrNodeDefinition(this.context, null, childNodeName, onParentVersion, autoCreated, mandatory, isProtected,
1772 allowsSns, defaultPrimaryTypeName, requiredTypes);
1773 }
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784 private JcrNodeType findTypeInMapOrList( Name typeName,
1785 Collection<JcrNodeType> pendingList ) {
1786 for (JcrNodeType pendingNodeType : pendingList) {
1787 if (pendingNodeType.getInternalName().equals(typeName)) {
1788 return pendingNodeType;
1789 }
1790 }
1791
1792 return nodeTypes.get(typeName);
1793 }
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806 private List<JcrNodeType> supertypesFor( NodeTypeDefinition nodeType,
1807 Collection<JcrNodeType> pendingTypes ) throws RepositoryException {
1808 assert nodeType != null;
1809
1810 List<JcrNodeType> supertypes = new LinkedList<JcrNodeType>();
1811
1812 boolean isMixin = nodeType.isMixin();
1813 boolean needsPrimaryAncestor = !isMixin;
1814 String nodeTypeName = nodeType.getName();
1815
1816 for (String supertypeNameStr : nodeType.getDeclaredSupertypeNames()) {
1817 Name supertypeName = nameFactory.create(supertypeNameStr);
1818 JcrNodeType supertype = findTypeInMapOrList(supertypeName, pendingTypes);
1819 if (supertype == null) {
1820 throw new InvalidNodeTypeDefinitionException(JcrI18n.invalidSupertypeName.text(supertypeNameStr, nodeTypeName));
1821 }
1822 needsPrimaryAncestor &= supertype.isMixin();
1823 supertypes.add(supertype);
1824 }
1825
1826
1827 if (needsPrimaryAncestor) {
1828 Name nodeName = nameFactory.create(nodeTypeName);
1829 if (!JcrNtLexicon.BASE.equals(nodeName)) {
1830 JcrNodeType ntBase = findTypeInMapOrList(JcrNtLexicon.BASE, pendingTypes);
1831 assert ntBase != null;
1832 supertypes.add(0, ntBase);
1833 }
1834 }
1835 return supertypes;
1836 }
1837
1838
1839
1840
1841
1842
1843
1844 final Collection<JcrNodeType> subtypesFor( JcrNodeType nodeType ) {
1845 CheckArg.isNotNull(nodeType, "nodeType");
1846
1847 try {
1848 nodeTypeManagerLock.readLock().lock();
1849
1850 List<JcrNodeType> subtypes = new LinkedList<JcrNodeType>();
1851 for (JcrNodeType type : this.nodeTypes.values()) {
1852 if (type.supertypes().contains(nodeType)) {
1853 subtypes.add(type);
1854 }
1855 }
1856
1857 return subtypes;
1858 } finally {
1859 nodeTypeManagerLock.readLock().unlock();
1860 }
1861 }
1862
1863
1864
1865
1866
1867
1868
1869 final Collection<JcrNodeType> declaredSubtypesFor( JcrNodeType nodeType ) {
1870 CheckArg.isNotNull(nodeType, "nodeType");
1871
1872 try {
1873 nodeTypeManagerLock.readLock().lock();
1874
1875 String nodeTypeName = nodeType.getName();
1876 List<JcrNodeType> subtypes = new LinkedList<JcrNodeType>();
1877 for (JcrNodeType type : this.nodeTypes.values()) {
1878 if (Arrays.asList(type.getDeclaredSupertypeNames()).contains(nodeTypeName)) {
1879 subtypes.add(type);
1880 }
1881 }
1882
1883 return subtypes;
1884 } finally {
1885 nodeTypeManagerLock.readLock().unlock();
1886 }
1887 }
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911 private void validate( List<JcrNodeType> supertypes,
1912 String nodeName ) throws RepositoryException {
1913 assert supertypes != null;
1914
1915 Map<PropertyDefinitionId, JcrPropertyDefinition> props = new HashMap<PropertyDefinitionId, JcrPropertyDefinition>();
1916
1917 for (JcrNodeType supertype : supertypes) {
1918 for (JcrPropertyDefinition property : supertype.propertyDefinitions()) {
1919 JcrPropertyDefinition oldProp = props.put(new PropertyDefinitionId(property.getInternalName(),
1920 property.getInternalName(),
1921 PropertyType.UNDEFINED, property.isMultiple()),
1922 property);
1923 if (oldProp != null) {
1924 String oldPropTypeName = oldProp.getDeclaringNodeType().getName();
1925 String propTypeName = property.getDeclaringNodeType().getName();
1926 if (!oldPropTypeName.equals(propTypeName)) {
1927 throw new InvalidNodeTypeDefinitionException(JcrI18n.supertypesConflict.text(oldPropTypeName,
1928 propTypeName,
1929 "property",
1930 property.getName()));
1931 }
1932 }
1933 }
1934 }
1935
1936 Map<NodeDefinitionId, JcrNodeDefinition> childNodes = new HashMap<NodeDefinitionId, JcrNodeDefinition>();
1937
1938 for (JcrNodeType supertype : supertypes) {
1939 for (JcrNodeDefinition childNode : supertype.childNodeDefinitions()) {
1940 JcrNodeDefinition oldNode = childNodes.put(new NodeDefinitionId(childNode.getInternalName(),
1941 childNode.getInternalName(), new Name[0]),
1942 childNode);
1943 if (oldNode != null) {
1944 String oldNodeTypeName = oldNode.getDeclaringNodeType().getName();
1945 String childNodeTypeName = childNode.getDeclaringNodeType().getName();
1946 if (!oldNodeTypeName.equals(childNodeTypeName)) {
1947 throw new InvalidNodeTypeDefinitionException(JcrI18n.supertypesConflict.text(oldNodeTypeName,
1948 childNodeTypeName,
1949 "child node",
1950 childNode.getName()));
1951 }
1952 }
1953 }
1954 }
1955 }
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968 private void validate( JcrNodeType nodeType,
1969 List<JcrNodeType> supertypes,
1970 List<JcrNodeType> pendingTypes ) throws RepositoryException {
1971 Name nodeTypeName = nodeType.getInternalName();
1972 validate(supertypes, nodeTypeName.getString(this.context.getNamespaceRegistry()));
1973
1974 List<Name> supertypeNames = new ArrayList<Name>(supertypes.size());
1975 for (JcrNodeType supertype : supertypes) {
1976 supertypeNames.add(supertype.getInternalName());
1977 }
1978
1979 boolean foundExact = false;
1980 boolean foundResidual = false;
1981 Name primaryItemName = nodeType.getInternalPrimaryItemName();
1982
1983 for (JcrNodeDefinition node : nodeType.getDeclaredChildNodeDefinitions()) {
1984 validate(node, supertypeNames, pendingTypes);
1985 if (node.isResidual()) foundResidual = true;
1986
1987 if (primaryItemName != null && primaryItemName.equals(node.getInternalName())) {
1988 foundExact = true;
1989 }
1990 }
1991
1992 for (JcrPropertyDefinition prop : nodeType.getDeclaredPropertyDefinitions()) {
1993 validate(prop, supertypeNames, pendingTypes);
1994 if (prop.isResidual()) foundResidual = true;
1995 if (primaryItemName != null && primaryItemName.equals(prop.getInternalName())) {
1996 if (foundExact) {
1997 throw new RepositoryException(JcrI18n.ambiguousPrimaryItemName.text(primaryItemName));
1998 }
1999 foundExact = true;
2000 }
2001 }
2002
2003 if (primaryItemName != null && !foundExact && !foundResidual) {
2004 throw new RepositoryException(JcrI18n.invalidPrimaryItemName.text(primaryItemName));
2005 }
2006 }
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032 private void validate( JcrNodeDefinition node,
2033 List<Name> supertypes,
2034 List<JcrNodeType> pendingTypes ) throws RepositoryException {
2035 if (node.isAutoCreated() && !node.isProtected() && node.defaultPrimaryTypeName() == null) {
2036 throw new InvalidNodeTypeDefinitionException(JcrI18n.autocreatedNodesNeedDefaults.text(node.getName()));
2037 }
2038 if (node.isMandatory() && JcrNodeType.RESIDUAL_ITEM_NAME.equals(node.getName())) {
2039 throw new InvalidNodeTypeDefinitionException(JcrI18n.residualDefinitionsCannotBeMandatory.text("child nodes"));
2040 }
2041
2042 Name nodeName = context.getValueFactories().getNameFactory().create(node.getName());
2043 nodeName = nodeName == null ? JcrNodeType.RESIDUAL_NAME : nodeName;
2044
2045 List<JcrNodeDefinition> ancestors = findChildNodeDefinitions(supertypes, nodeName, NodeCardinality.ANY, pendingTypes);
2046
2047 for (JcrNodeDefinition ancestor : ancestors) {
2048 if (ancestor.isProtected()) {
2049 throw new InvalidNodeTypeDefinitionException(
2050 JcrI18n.cannotOverrideProtectedDefinition.text(ancestor.getDeclaringNodeType()
2051 .getName(),
2052 "child node"));
2053 }
2054
2055 if (ancestor.isMandatory() && !node.isMandatory()) {
2056 throw new InvalidNodeTypeDefinitionException(
2057 JcrI18n.cannotMakeMandatoryDefinitionOptional.text(ancestor.getDeclaringNodeType()
2058 .getName(),
2059 "child node"));
2060
2061 }
2062
2063 Name[] requiredPrimaryTypeNames = ancestor.requiredPrimaryTypeNames();
2064 for (int i = 0; i < requiredPrimaryTypeNames.length; i++) {
2065 NodeType apt = findTypeInMapOrList(requiredPrimaryTypeNames[i], pendingTypes);
2066
2067 if (apt == null) {
2068 I18n msg = JcrI18n.couldNotFindDefinitionOfRequiredPrimaryType;
2069 throw new InvalidNodeTypeDefinitionException(msg.text(requiredPrimaryTypeNames[i],
2070 node.getName(),
2071 node.getDeclaringNodeType()));
2072
2073 }
2074
2075 boolean found = false;
2076
2077 for (Name name : node.requiredPrimaryTypeNames()) {
2078 JcrNodeType npt = findTypeInMapOrList(name, pendingTypes);
2079
2080 if (npt.isNodeType(apt.getName())) {
2081 found = true;
2082 break;
2083 }
2084 }
2085
2086
2087 if (!found && !JcrNodeType.RESIDUAL_NAME.equals(node.name)) {
2088 I18n msg = JcrI18n.cannotRedefineChildNodeWithIncompatibleDefinition;
2089 throw new InvalidNodeTypeDefinitionException(msg.text(nodeName, apt.getName(), node.getDeclaringNodeType()));
2090
2091 }
2092 }
2093 }
2094 }
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119 private void validate( JcrPropertyDefinition prop,
2120 List<Name> supertypes,
2121 List<JcrNodeType> pendingTypes ) throws RepositoryException {
2122 assert prop != null;
2123 assert supertypes != null;
2124 assert pendingTypes != null;
2125
2126 if (prop.isMandatory() && !prop.isProtected() && JcrNodeType.RESIDUAL_ITEM_NAME.equals(prop.getName())) {
2127 throw new InvalidNodeTypeDefinitionException(JcrI18n.residualDefinitionsCannotBeMandatory.text("properties"));
2128 }
2129
2130 Value[] defaultValues = prop.getDefaultValues();
2131 if (prop.isAutoCreated() && !prop.isProtected() && (defaultValues == null || defaultValues.length == 0)) {
2132 throw new InvalidNodeTypeDefinitionException(JcrI18n.autocreatedPropertyNeedsDefault.text(prop.getName(),
2133 prop.getDeclaringNodeType()
2134 .getName()));
2135 }
2136
2137 if (!prop.isMultiple() && (defaultValues != null && defaultValues.length > 1)) {
2138 throw new InvalidNodeTypeDefinitionException(
2139 JcrI18n.singleValuedPropertyNeedsSingleValuedDefault.text(prop.getName(),
2140 prop.getDeclaringNodeType()
2141 .getName()));
2142 }
2143
2144 Name propName = context.getValueFactories().getNameFactory().create(prop.getName());
2145 propName = propName == null ? JcrNodeType.RESIDUAL_NAME : propName;
2146
2147 List<JcrPropertyDefinition> ancestors = findPropertyDefinitions(supertypes,
2148 propName,
2149 prop.isMultiple() ? PropertyCardinality.MULTI_VALUED_ONLY : PropertyCardinality.SINGLE_VALUED_ONLY,
2150 pendingTypes);
2151
2152 for (JcrPropertyDefinition ancestor : ancestors) {
2153 if (ancestor.isProtected()) {
2154 throw new InvalidNodeTypeDefinitionException(
2155 JcrI18n.cannotOverrideProtectedDefinition.text(ancestor.getDeclaringNodeType()
2156 .getName(),
2157 "property"));
2158 }
2159
2160 if (ancestor.isMandatory() && !prop.isMandatory()) {
2161 throw new InvalidNodeTypeDefinitionException(
2162 JcrI18n.cannotMakeMandatoryDefinitionOptional.text(ancestor.getDeclaringNodeType()
2163 .getName(),
2164 "property"));
2165
2166 }
2167
2168
2169
2170 if (ancestor.getValueConstraints() != null
2171 && !Arrays.equals(ancestor.getValueConstraints(), prop.getValueConstraints())) {
2172 throw new InvalidNodeTypeDefinitionException(
2173 JcrI18n.constraintsChangedInSubtype.text(propName,
2174 ancestor.getDeclaringNodeType()
2175 .getName()));
2176 }
2177
2178 if (!isAlwaysSafeConversion(prop.getRequiredType(), ancestor.getRequiredType())) {
2179 throw new InvalidNodeTypeDefinitionException(
2180 JcrI18n.cannotRedefineProperty.text(propName,
2181 PropertyType.nameFromValue(prop.getRequiredType()),
2182 ancestor.getDeclaringNodeType()
2183 .getName(),
2184 PropertyType.nameFromValue(ancestor.getRequiredType())));
2185
2186 }
2187 }
2188 }
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203 private boolean isAlwaysSafeConversion( int fromType,
2204 int toType ) {
2205
2206 if (fromType == toType) return true;
2207
2208 switch (toType) {
2209 case PropertyType.BOOLEAN:
2210 return fromType == PropertyType.BINARY || fromType == PropertyType.STRING;
2211
2212 case PropertyType.DATE:
2213 return fromType == PropertyType.DOUBLE || fromType == PropertyType.LONG;
2214
2215 case PropertyType.DOUBLE:
2216
2217 return fromType == PropertyType.LONG;
2218 case PropertyType.LONG:
2219
2220 return fromType == PropertyType.DOUBLE;
2221
2222 case PropertyType.PATH:
2223 return fromType == PropertyType.NAME;
2224
2225
2226 case PropertyType.NAME:
2227 case PropertyType.REFERENCE:
2228 return false;
2229
2230
2231 case PropertyType.BINARY:
2232 case PropertyType.STRING:
2233 case PropertyType.UNDEFINED:
2234 return true;
2235
2236 default:
2237 throw new IllegalStateException("Unexpected state: " + toType);
2238 }
2239 }
2240
2241 @Override
2242 public Path getObservedPath() {
2243 return this.nodeTypesPath;
2244 }
2245
2246 @Override
2247 public void notify( Changes changes ) {
2248 boolean needsReload = false;
2249
2250 for (ChangeRequest change : changes.getChangeRequests()) {
2251 assert change.changedLocation().hasPath();
2252
2253 Path changedPath = change.changedLocation().getPath();
2254 if (changedPath.equals(nodeTypesPath)) {
2255
2256 continue;
2257 }
2258 assert nodeTypesPath.isAncestorOf(changedPath);
2259
2260 switch (change.getType()) {
2261 case CREATE_NODE:
2262 case DELETE_BRANCH:
2263 needsReload = true;
2264
2265 break;
2266 default:
2267 assert false : "Unexpected change request: " + change;
2268 }
2269 }
2270
2271 if (!needsReload) return;
2272
2273 this.nodeTypeManagerLock.writeLock().lock();
2274 try {
2275 GraphNodeTypeReader reader = new GraphNodeTypeReader(this.context);
2276 Graph systemGraph = repository.createSystemGraph(this.context);
2277
2278 reader.read(systemGraph, nodeTypesPath, null);
2279
2280 Problems readerProblems = reader.getProblems();
2281 if (readerProblems.hasProblems()) {
2282 if (readerProblems.hasErrors()) {
2283 LOGGER.error(JcrI18n.errorReadingNodeTypesFromRemote, reader.getProblems());
2284 return;
2285 }
2286
2287 LOGGER.warn(JcrI18n.problemReadingNodeTypesFromRemote, reader.getProblems());
2288 }
2289
2290 Map<Name, JcrNodeType> newNodeTypeMap = new HashMap<Name, JcrNodeType>();
2291 try {
2292 for (NodeTypeDefinition nodeTypeDefn : reader.getNodeTypeDefinitions()) {
2293 List<JcrNodeType> supertypes = supertypesFor(nodeTypeDefn, newNodeTypeMap.values());
2294 JcrNodeType nodeType = nodeTypeFrom(nodeTypeDefn, supertypes);
2295
2296 newNodeTypeMap.put(nodeType.getInternalName(), nodeType);
2297 }
2298 } catch (Throwable re) {
2299 LOGGER.error(JcrI18n.errorSynchronizingNodeTypes, re);
2300 }
2301
2302 this.nodeTypes.clear();
2303 this.nodeTypes.putAll(newNodeTypeMap);
2304
2305 assert this.nodeTypes.get(ModeShapeLexicon.ROOT) != null;
2306
2307 for (JcrSession activeSession : repository.activeSessions()) {
2308 JcrWorkspace workspace = activeSession.workspace();
2309 if (workspace == null) continue;
2310
2311 JcrNodeTypeManager nodeTypeManager = workspace.nodeTypeManager();
2312 if (nodeTypeManager == null) continue;
2313
2314 nodeTypeManager.signalExternalNodeTypeChanges();
2315 }
2316
2317 } finally {
2318 this.schemata = null;
2319 this.nodeTypeManagerLock.writeLock().unlock();
2320 }
2321
2322 }
2323 }