/
AbstractJcrNode.java
3418 lines (3089 loc) · 162 KB
/
AbstractJcrNode.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
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
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
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
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* ModeShape (http://www.modeshape.org)
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
* See the AUTHORS.txt file in the distribution for a full listing of
* individual contributors.
*
* ModeShape is free software. Unless otherwise indicated, all code in ModeShape
* is licensed to you under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* ModeShape is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.modeshape.jcr;
import java.io.InputStream;
import java.math.BigDecimal;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import javax.jcr.AccessDeniedException;
import javax.jcr.Binary;
import javax.jcr.InvalidItemStateException;
import javax.jcr.Item;
import javax.jcr.ItemExistsException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.ItemVisitor;
import javax.jcr.MergeException;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.PropertyIterator;
import javax.jcr.PropertyType;
import javax.jcr.ReferentialIntegrityException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.Lock;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeType;
import javax.jcr.version.ActivityViolationException;
import javax.jcr.version.OnParentVersionAction;
import javax.jcr.version.Version;
import javax.jcr.version.VersionException;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.common.annotation.ThreadSafe;
import org.modeshape.common.i18n.I18n;
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.JcrSharedNodeCache.SharedSet;
import org.modeshape.jcr.RepositoryNodeTypeManager.NodeTypes;
import org.modeshape.jcr.api.value.DateTime;
import org.modeshape.jcr.cache.CachedNode;
import org.modeshape.jcr.cache.CachedNode.ReferenceType;
import org.modeshape.jcr.cache.ChildReference;
import org.modeshape.jcr.cache.ChildReferences;
import org.modeshape.jcr.cache.MutableCachedNode;
import org.modeshape.jcr.cache.NodeCache;
import org.modeshape.jcr.cache.NodeKey;
import org.modeshape.jcr.cache.NodeNotFoundInParentException;
import org.modeshape.jcr.cache.PropertyTypeUtil;
import org.modeshape.jcr.cache.SessionCache;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.NamespaceRegistry;
import org.modeshape.jcr.value.Path;
import org.modeshape.jcr.value.Path.Segment;
import org.modeshape.jcr.value.PathFactory;
import org.modeshape.jcr.value.Property;
import org.modeshape.jcr.value.PropertyFactory;
import org.modeshape.jcr.value.Reference;
import org.modeshape.jcr.value.ValueFactories;
import org.modeshape.jcr.value.ValueFactory;
import org.modeshape.jcr.value.basic.NodeKeyReference;
/**
* The abstract base class for all {@link Node} implementations.
*/
@ThreadSafe
abstract class AbstractJcrNode extends AbstractJcrItem implements Node {
enum Type {
ROOT,
NODE,
SYSTEM,
VERSION,
VERSION_HISTORY;
private static final Map<Name, Type> DEFAULT_TYPE_BY_NAME;
static {
Map<Name, Type> byName = new HashMap<Name, Type>();
// Root node ...
byName.put(ModeShapeLexicon.ROOT, Type.ROOT);
// System content ...
byName.put(ModeShapeLexicon.SYSTEM, Type.SYSTEM);
// Versioning
byName.put(JcrNtLexicon.VERSION_HISTORY, Type.VERSION_HISTORY);
byName.put(JcrNtLexicon.VERSION_LABELS, Type.SYSTEM);
byName.put(JcrNtLexicon.VERSION, Type.VERSION);
// Node types ...
byName.put(ModeShapeLexicon.NODE_TYPES, Type.SYSTEM);
byName.put(JcrNtLexicon.NODE_TYPE, Type.SYSTEM);
byName.put(JcrNtLexicon.PROPERTY_DEFINITION, Type.SYSTEM);
byName.put(JcrNtLexicon.CHILD_NODE_DEFINITION, Type.SYSTEM);
// Namespaces ...
byName.put(ModeShapeLexicon.NAMESPACES, Type.SYSTEM);
byName.put(ModeShapeLexicon.NAMESPACE, Type.SYSTEM);
// Locks ...
byName.put(ModeShapeLexicon.LOCKS, Type.SYSTEM);
byName.put(ModeShapeLexicon.LOCK, Type.SYSTEM);
DEFAULT_TYPE_BY_NAME = Collections.unmodifiableMap(byName);
}
/**
* Determine the type given the supplied primary type.
*
* @param primaryType the primary type
* @return the type, or null if the node type could not be determined by the supplied primary type
*/
public static Type typeForPrimaryType( Name primaryType ) {
return DEFAULT_TYPE_BY_NAME.get(primaryType);
}
}
@Immutable
private final static class CachedDefinition {
protected final NodeDefinitionId nodeDefnId;
protected final int nodeTypesVersion;
protected CachedDefinition( NodeDefinitionId nodeDefnId,
int nodeTypesVersion ) {
this.nodeDefnId = nodeDefnId;
this.nodeTypesVersion = nodeTypesVersion;
}
}
protected static final Pattern WILDCARD_PATTERN = Pattern.compile(".*");
private static final Set<Name> INTERNAL_NODE_TYPE_NAMES = Collections.singleton(ModeShapeLexicon.SHARE);
protected final NodeKey key;
private final ConcurrentMap<Name, AbstractJcrProperty> jcrProperties = new ConcurrentHashMap<Name, AbstractJcrProperty>();
private volatile CachedDefinition cachedDefn;
protected AbstractJcrNode( JcrSession session,
NodeKey key ) {
super(session);
this.key = key;
}
abstract boolean isRoot();
abstract Type type();
/**
* Check that this type of node can be modified
*
* @throws RepositoryException
*/
protected void checkNodeTypeCanBeModified() throws RepositoryException {
}
protected SessionCache sessionCache() {
return session.cache();
}
protected final NodeKey key() {
return this.key;
}
/**
* Get the cached node.
*
* @return the cached node
* @throws InvalidItemStateException if the node has been removed in this session's transient state
* @throws ItemNotFoundException if the node does not exist
*/
protected final CachedNode node() throws ItemNotFoundException, InvalidItemStateException {
CachedNode node = sessionCache().getNode(key);
if (node == null) {
if (sessionCache().isDestroyed(key)) {
throw new InvalidItemStateException("The node with key " + key + " has been removed in this session.");
}
throw new ItemNotFoundException("The node with key " + key + " no longer exists.");
}
return node;
}
protected final MutableCachedNode mutable() {
return sessionCache().mutable(key);
}
protected NodeKey parentKey() throws RepositoryException {
return node().getParentKey(sessionCache());
}
protected final MutableCachedNode mutableParent() throws RepositoryException {
SessionCache cache = sessionCache();
return cache.mutable(parentKey());
}
@Override
Path path() throws ItemNotFoundException, InvalidItemStateException {
return node().getPath(sessionCache());
}
/**
* Obtain a string identifying this node, usually for error or logging purposes. This method never throws an exception.
*
* @return the location string; never null
*/
protected final String location() {
try {
return getPath();
} catch (Throwable t) {
return key.toString();
}
}
protected Name name() throws RepositoryException {
return node().getName(sessionCache());
}
protected Segment segment() throws RepositoryException {
return node().getSegment(sessionCache());
}
/**
* Checks if this node is foreign for its current owning session
*
* @return boolean if this node is considered "foreign" to this session, or false otherwise
* @see JcrSession#isForeignKey(org.modeshape.jcr.cache.NodeKey)
*/
protected final boolean isForeign() {
return session().isForeignKey(key());
}
protected final boolean isInTheSameProcessAs( String otherProcessId ) {
return session().context().getProcessId().equalsIgnoreCase(otherProcessId);
}
@Override
public final String getIdentifier() {
return session().nodeIdentifier(key());
}
/**
* Get the absolute and normalized identifier path for this node, regardless of whether this node is referenceable.
*
* @return the node's identifier path; never null
* @throws RepositoryException if there is an error accessing the identifier of this node
*/
final String identifierPath() throws RepositoryException {
return "[" + getIdentifier() + "]";
}
@Override
public final JcrSession getSession() {
return session();
}
@Override
public AbstractJcrProperty getProperty( String relativePath ) throws PathNotFoundException, RepositoryException {
CheckArg.isNotEmpty(relativePath, "relativePath");
checkSession();
int indexOfFirstSlash = relativePath.indexOf('/');
if (indexOfFirstSlash == 0 || relativePath.startsWith("[")) {
// Not a relative path ...
throw new IllegalArgumentException(JcrI18n.invalidPathParameter.text(relativePath, "relativePath"));
}
Name propertyName = null;
if (indexOfFirstSlash != -1) {
// We know it's a relative path with more than one segment ...
Path path = pathFrom(relativePath).getNormalizedPath();
assert !path.isIdentifier();
if (path.size() > 1) {
try {
AbstractJcrItem item = session.findItem(key, path);
if (item instanceof AbstractJcrProperty) {
return (AbstractJcrProperty)item;
}
} catch (ItemNotFoundException e) {
I18n msg = JcrI18n.propertyNotFoundAtPathRelativeToReferenceNode;
throw new PathNotFoundException(msg.text(relativePath, location(), workspaceName()));
}
I18n msg = JcrI18n.propertyNotFoundAtPathRelativeToReferenceNode;
throw new PathNotFoundException(msg.text(relativePath, location(), workspaceName()));
}
propertyName = path.getLastSegment().getName();
} else {
propertyName = nameFrom(relativePath);
}
// It's just a name, so look for it directly ...
AbstractJcrProperty result = getProperty(propertyName);
if (result != null) return result;
I18n msg = JcrI18n.pathNotFoundRelativeTo;
throw new PathNotFoundException(msg.text(relativePath, location(), workspaceName()));
}
/**
* Get the {@link AbstractJcrProperty JCR Property} object for the existing property with the supplied name.
*
* @param propertyName the property name; may not be null
* @return the JCR Property object, or null if there is no property with the specified name
* @throws RepositoryException if there is a problem accessing the repository
*/
final AbstractJcrProperty getProperty( Name propertyName ) throws RepositoryException {
AbstractJcrProperty prop = jcrProperties.get(propertyName);
if (prop == null) {
// See if there's a property on the node ...
CachedNode node = node();
SessionCache cache = sessionCache();
org.modeshape.jcr.value.Property p = node.getProperty(propertyName, cache);
if (p != null) {
Name primaryType = node.getPrimaryType(cache);
Set<Name> mixinTypes = node.getMixinTypes(cache);
prop = createJcrProperty(p, primaryType, mixinTypes);
if (prop != null) {
AbstractJcrProperty newJcrProperty = jcrProperties.putIfAbsent(propertyName, prop);
if (newJcrProperty != null) {
// Some other thread snuck in and created it, so use that one ...
prop = newJcrProperty;
}
}
}
} else {
// Make sure the property hasn't been removed by another session ...
CachedNode node = node();
SessionCache cache = sessionCache();
if (!node.hasProperty(propertyName, cache)) {
jcrProperties.remove(propertyName);
prop = null;
}
}
return prop;
}
/**
* Create a new JCR Property instance given the supplied information. Note that this does not alter the node in any way, since
* it does not store a reference to this property (the caller must do that if needed).
*
* @param property the cached node property; may not be null
* @param primaryTypeName the name of the node's primary type; may not be null
* @param mixinTypeNames the names of the node's mixin types; may be null or empty
* @return the JCR Property instance, or null if the property could not be represented with a valid property definition given
* the primary type and mixin types
* @throws ConstraintViolationException if the property has no valid property definition
*/
private final AbstractJcrProperty createJcrProperty( Property property,
Name primaryTypeName,
Set<Name> mixinTypeNames ) throws ConstraintViolationException {
NodeTypes nodeTypes = session.nodeTypes();
JcrPropertyDefinition defn = propertyDefinitionFor(property, primaryTypeName, mixinTypeNames, nodeTypes);
int jcrPropertyType = defn.getRequiredType();
jcrPropertyType = determineBestPropertyTypeIfUndefined(jcrPropertyType, property);
AbstractJcrProperty prop = null;
if (defn.isMultiple()) {
prop = new JcrMultiValueProperty(this, property.getName(), jcrPropertyType);
} else {
prop = new JcrSingleValueProperty(this, property.getName(), jcrPropertyType);
}
prop.setPropertyDefinitionId(defn.getId(), nodeTypes.getVersion());
return prop;
}
private final int determineBestPropertyTypeIfUndefined( int actualPropertyType,
Property property ) {
if (actualPropertyType == PropertyType.UNDEFINED) {
return PropertyTypeUtil.jcrPropertyTypeFor(property);
}
return actualPropertyType;
}
final ValueFactories factories() {
return context().getValueFactories();
}
final String readable( Object obj ) {
return session.stringFactory().create(obj);
}
final String readable( Collection<?> obj ) {
ValueFactory<String> stringFactory = session.stringFactory();
StringBuilder sb = new StringBuilder();
sb.append('[');
Iterator<?> iter = obj.iterator();
if (iter.hasNext()) {
sb.append(stringFactory.create(iter.next()));
while (iter.hasNext()) {
sb.append(',');
sb.append(stringFactory.create(iter.next()));
}
}
sb.append(']');
return sb.toString();
}
/**
* Find the property definition for the property, given this node's primary type and mixin types.
*
* @param property the property owned by this node; may not be null
* @param primaryType the name of the node's primary type; may not be null
* @param mixinTypes the names of the node's mixin types; may be null or empty
* @param nodeTypes the node types cache to use; may not be null
* @return the property definition; never null
* @throws ConstraintViolationException if the property has no valid property definition
*/
final JcrPropertyDefinition propertyDefinitionFor( org.modeshape.jcr.value.Property property,
Name primaryType,
Set<Name> mixinTypes,
NodeTypes nodeTypes ) throws ConstraintViolationException {
// Figure out the JCR property type ...
boolean single = property.isSingle();
boolean skipProtected = false;
JcrPropertyDefinition defn = findBestPropertyDefinition(primaryType,
mixinTypes,
property,
single,
skipProtected,
false,
nodeTypes);
if (defn != null) return defn;
// See if there is a definition that has constraints that were violated ...
defn = findBestPropertyDefinition(primaryType, mixinTypes, property, single, skipProtected, true, nodeTypes);
String pName = readable(property.getName());
String loc = location();
if (defn != null) {
I18n msg = JcrI18n.propertyNoLongerSatisfiesConstraints;
throw new ConstraintViolationException(msg.text(pName, loc, defn.getName(), defn.getDeclaringNodeType().getName()));
}
CachedNode node = sessionCache().getNode(key);
String ptype = readable(node.getPrimaryType(sessionCache()));
String mixins = readable(node.getMixinTypes(sessionCache()));
String pstr = property.getString(session.namespaces());
throw new ConstraintViolationException(JcrI18n.propertyNoLongerHasValidDefinition.text(pstr, loc, ptype, mixins));
}
/**
* Find the best property definition in this node's primary type and mixin types.
*
* @param primaryTypeNameOfParent the name of the primary type for the parent node; may not be null
* @param mixinTypeNamesOfParent the names of the mixin types for the parent node; may be null or empty if there are no mixins
* to include in the search
* @param property the property
* @param isSingle true if the property definition should be single-valued, or false if the property definition should allow
* multiple values
* @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if
* this operation is being done from within internal implementations
* @param skipConstraints true if any constraints on the potential property definitions should be skipped; usually this is
* true for the first attempt but then 'false' for a subsequent attempt when figuring out an appropriate error message
* @param nodeTypes the node types cache to use; may not be null
* @return the property definition that allows setting this property, or null if there is no such definition
*/
final JcrPropertyDefinition findBestPropertyDefinition( Name primaryTypeNameOfParent,
Collection<Name> mixinTypeNamesOfParent,
org.modeshape.jcr.value.Property property,
boolean isSingle,
boolean skipProtected,
boolean skipConstraints,
NodeTypes nodeTypes ) {
JcrPropertyDefinition definition = null;
int propertyType = PropertyTypeUtil.jcrPropertyTypeFor(property);
// If single-valued ...
ValueFactories factories = context().getValueFactories();
if (isSingle) {
// Create a value for the ModeShape property value ...
Object value = property.getFirstValue();
Value jcrValue = new JcrValue(factories, propertyType, value);
definition = nodeTypes.findPropertyDefinition(session,
primaryTypeNameOfParent,
mixinTypeNamesOfParent,
property.getName(),
jcrValue,
true,
skipProtected);
} else {
// Create values for the ModeShape property value ...
Value[] jcrValues = new Value[property.size()];
int index = 0;
for (Object value : property) {
jcrValues[index++] = new JcrValue(factories, propertyType, value);
}
definition = nodeTypes.findPropertyDefinition(session,
primaryTypeNameOfParent,
mixinTypeNamesOfParent,
property.getName(),
jcrValues,
skipProtected);
}
if (definition != null) return definition;
// No definition that allowed the values ...
return null;
}
final boolean hasProperty( Name name ) throws RepositoryException {
if (jcrProperties.containsKey(name)) return true;
return node().hasProperty(name, sessionCache());
}
boolean removeProperty( AbstractJcrProperty property ) {
if (jcrProperties.remove(property.name(), property)) {
mutable().removeProperty(sessionCache(), property.name());
return true;
}
return false;
}
boolean isReferenceable() throws RepositoryException {
return isNodeType(JcrMixLexicon.REFERENCEABLE);
}
boolean isLockable() throws RepositoryException {
return isNodeType(JcrMixLexicon.LOCKABLE);
}
boolean isShareable() throws RepositoryException {
return isNodeType(JcrMixLexicon.SHAREABLE);
}
boolean isShared() {
return false;
}
final JcrValue valueFrom( int propertyType,
Object value ) {
return new JcrValue(context().getValueFactories(), propertyType, value);
}
final JcrValue valueFrom( String value ) {
return session.valueFactory().createValue(value);
}
final JcrValue valueFrom( UUID value ) {
Reference ref = context().getValueFactories().getReferenceFactory().create(value);
return valueFrom(PropertyType.REFERENCE, ref);
}
final JcrValue valueFrom( Calendar value ) {
DateTime dateTime = context().getValueFactories().getDateFactory().create(value);
return valueFrom(PropertyType.DATE, dateTime);
}
final JcrValue valueFrom( InputStream value ) {
org.modeshape.jcr.value.BinaryValue binary = context().getValueFactories().getBinaryFactory().create(value);
return valueFrom(PropertyType.BINARY, binary);
}
final JcrValue valueFrom( Binary value ) {
return valueFrom(PropertyType.BINARY, value);
}
final JcrValue valueFrom( javax.jcr.Node value ) throws RepositoryException {
if (!(value instanceof AbstractJcrNode)) {
throw new IllegalArgumentException("Invalid node type (expected a ModeShape node): " + value.getClass().toString());
}
AbstractJcrNode node = (AbstractJcrNode)value;
if (!this.isInTheSameProcessAs(node.session().context().getProcessId())) {
throw new RepositoryException(JcrI18n.nodeNotInTheSameSession.text(node.path()));
}
NodeKey key = ((AbstractJcrNode)value).key();
Reference ref = session.context()
.getValueFactories()
.getReferenceFactory()
.create(key, ((AbstractJcrNode)value).isForeign());
return valueFrom(PropertyType.REFERENCE, ref);
}
final JcrValue[] valuesFrom( int propertyType,
Object[] values ) {
/*
* Null values in the array are "compacted" (read: ignored) as per section 7.1.6 in the JCR 1.0.1 specification.
*/
int len = values.length;
ValueFactories factories = context().getValueFactories();
List<JcrValue> results = new ArrayList<JcrValue>(len);
for (int i = 0; i != len; ++i) {
if (values[i] != null) results.add(new JcrValue(factories, propertyType, values[i]));
}
return results.toArray(new JcrValue[results.size()]);
}
final JcrVersionManager versionManager() {
return session.workspace().versionManager();
}
/**
* Checks that this node is not already locked by another session. If the node is not locked or the node is locked but the
* lock is owned by this {@code Session}, this method completes silently. If the node is locked (either directly or as part of
* a deep lock from an ancestor), this method throws a {@code LockException}.
*
* @throws LockException if this node is locked (that is, if {@code isLocked() == true && getLock().getLockToken() == null}).
* @throws RepositoryException if any other error occurs
* @see Node#isLocked()
* @see Lock#getLockToken()
*/
protected final void checkForLock() throws LockException, RepositoryException {
Lock lock = getLockIfExists();
if (lock != null && !lock.isLockOwningSession() && lock.getLockToken() == null) {
throw new LockException(JcrI18n.lockTokenNotHeld.text(location()));
}
}
/**
* Verifies that this node is either not versionable or that it is versionable but checked out.
*
* @throws VersionException if the node is versionable but is checked in and cannot be modified
* @throws RepositoryException if there is an error accessing the repository
*/
protected final void checkForCheckedOut() throws VersionException, RepositoryException {
if (!isCheckedOut()) {
throw new VersionException(JcrI18n.nodeIsCheckedIn.text(location()));
}
}
/**
* Get the total number of children.
*
* @return the total number of children
* @throws RepositoryException
*/
protected final long childCount() throws RepositoryException {
return node().getChildReferences(sessionCache()).size();
}
/**
* Get the number of children that have the supplied name.
*
* @param name the child name
* @return the number of children with names that match the supplied name
* @throws RepositoryException
*/
protected final long childCount( Name name ) throws RepositoryException {
return node().getChildReferences(sessionCache()).getChildCount(name);
}
/**
* Get the JCR node for the named child.
*
* @param name the child name; may not be null
* @param expectedType the expected implementation type for the node, or null if it is not known
* @return the JCR node; never null
* @throws PathNotFoundException if there is no child with the supplied name
* @throws ItemNotFoundException if this node or the referenced child no longer exist or cannot be found
* @throws InvalidItemStateException if this node has been removed in this session's transient state
*/
protected final AbstractJcrNode childNode( Name name,
Type expectedType )
throws PathNotFoundException, ItemNotFoundException, InvalidItemStateException {
ChildReference ref = node().getChildReferences(sessionCache()).getChild(name);
if (ref == null) {
String msg = JcrI18n.childNotFoundUnderNode.text(readable(name), location(), session.workspaceName());
throw new PathNotFoundException(msg);
}
return session().node(ref.getKey(), expectedType, key());
}
/**
* Get the JCR node for the named child.
*
* @param segment the child name and SNS index; may not be null
* @param expectedType the expected implementation type for the node, or null if it is not known
* @return the JCR node; never null
* @throws PathNotFoundException if there is no child with the supplied name
* @throws ItemNotFoundException if this node or the referenced child cannot be found
* @throws InvalidItemStateException if this node has been removed in this session's transient state
*/
protected final AbstractJcrNode childNode( Segment segment,
Type expectedType )
throws PathNotFoundException, ItemNotFoundException, InvalidItemStateException {
ChildReference ref = node().getChildReferences(sessionCache()).getChild(segment);
if (ref == null) {
String msg = JcrI18n.childNotFoundUnderNode.text(readable(segment), location(), session.workspaceName());
throw new PathNotFoundException(msg);
}
return session().node(ref.getKey(), expectedType, key());
}
@Override
public boolean hasNode( String relativePath ) throws RepositoryException {
CheckArg.isNotEmpty(relativePath, "relativePath");
checkSession();
if (relativePath.equals(".")) return true;
if (relativePath.equals("..")) return isRoot() ? false : true;
int indexOfFirstSlash = relativePath.indexOf('/');
if (indexOfFirstSlash == 0 || relativePath.startsWith("[")) {
// Not a relative path ...
throw new IllegalArgumentException(JcrI18n.invalidPathParameter.text(relativePath, "relativePath"));
}
Path.Segment segment = null;
if (indexOfFirstSlash != -1) {
// We know it's a relative path with more than one segment ...
Path path = pathFrom(relativePath).getNormalizedPath();
if (path.size() == 1) {
if (path.getLastSegment().isSelfReference()) return true;
if (path.getLastSegment().isParentReference()) return isRoot() ? false : true;
}
// We know it's a resolved relative path with more than one segment ...
if (path.size() > 1) {
try {
return session().node(node(), path) != null;
} catch (PathNotFoundException e) {
return false;
}
}
segment = path.getLastSegment();
} else {
segment = segmentFrom(relativePath);
}
assert !segment.isIdentifier();
// It's just a name, so look for a child ...
ChildReference ref = node().getChildReferences(sessionCache()).getChild(segment);
return ref != null;
}
@Override
public AbstractJcrNode getNode( String relativePath ) throws PathNotFoundException, RepositoryException {
CheckArg.isNotEmpty(relativePath, "relativePath");
checkSession();
if (relativePath.equals(".")) return this;
if (relativePath.equals("..")) return this.getParent();
int indexOfFirstSlash = relativePath.indexOf('/');
if (indexOfFirstSlash == 0 || relativePath.startsWith("[")) {
// Not a relative path ...
throw new IllegalArgumentException(JcrI18n.invalidPathParameter.text(relativePath, "relativePath"));
}
Path.Segment segment = null;
if (indexOfFirstSlash != -1) {
// We know it's a relative path with more than one segment ...
Path path = pathFrom(relativePath).getNormalizedPath();
if (path.size() == 1) {
if (path.getLastSegment().isSelfReference()) return this;
if (path.getLastSegment().isParentReference()) return this.getParent();
}
// We know it's a resolved relative path with more than one segment ...
if (path.size() > 1) {
return session().node(node(), path);
}
segment = path.getLastSegment();
} else {
segment = segmentFrom(relativePath);
}
assert !segment.isIdentifier();
// It's just a name, so look for a child ...
ChildReference ref = node().getChildReferences(sessionCache()).getChild(segment);
if (ref == null) {
String msg = JcrI18n.childNotFoundUnderNode.text(readable(segment), location(), session.workspaceName());
throw new PathNotFoundException(msg);
}
try {
return session().node(ref.getKey(), null, key());
} catch (ItemNotFoundException e) {
// expected by TCK
String msg = JcrI18n.pathNotFoundRelativeTo.text(relativePath, location(), workspaceName());
throw new PathNotFoundException(msg);
}
}
AbstractJcrNode getNode( Name childName ) throws PathNotFoundException, RepositoryException {
// It's just a name, so look for a child ...
ChildReference ref = node().getChildReferences(sessionCache()).getChild(childName);
if (ref == null) {
String msg = JcrI18n.childNotFoundUnderNode.text(readable(childName), location(), session.workspaceName());
throw new PathNotFoundException(msg);
}
return session().node(ref.getKey(), null, key());
}
AbstractJcrNode getNodeIfExists( Name childName ) throws RepositoryException {
// It's just a name, so look for a child ...
ChildReference ref = node().getChildReferences(sessionCache()).getChild(childName);
return ref != null ? session().node(ref.getKey(), null, key()) : null;
}
@Override
public NodeIterator getNodes() throws RepositoryException {
ChildReferences childReferences = node().getChildReferences(sessionCache());
if (childReferences.isEmpty()) return JcrEmptyNodeIterator.INSTANCE;
return new JcrChildNodeIterator(new ChildNodeResolver(session, key()), childReferences);
}
@Override
public NodeIterator getNodes( String namePattern ) throws RepositoryException {
CheckArg.isNotNull(namePattern, "namePattern");
checkSession();
namePattern = namePattern.trim();
if (namePattern.length() == 0) return JcrEmptyNodeIterator.INSTANCE;
if ("*".equals(namePattern)) return getNodes();
return getNodes(namePattern.split("[|]"));
}
@Override
public NodeIterator getNodes( String[] nameGlobs ) throws RepositoryException {
CheckArg.isNotNull(nameGlobs, "nameGlobs");
if (nameGlobs.length == 0) return JcrEmptyNodeIterator.INSTANCE;
List<?> patterns = createPatternsFor(nameGlobs);
Iterator<ChildReference> iter = null;
if (patterns.size() == 1 && patterns.get(0) instanceof String) {
// This is a literal, so just look up by name ...
Name literal = nameFrom((String)patterns.get(0));
iter = node().getChildReferences(sessionCache()).iterator(literal);
} else {
NamespaceRegistry registry = session.namespaces();
iter = node().getChildReferences(sessionCache()).iterator(patterns, registry);
}
return new JcrChildNodeIterator(new ChildNodeResolver(session, key()), iter);
}
protected static List<?> createPatternsFor( String[] namePatterns ) throws RepositoryException {
List<Object> patterns = new LinkedList<Object>();
for (String stringPattern : namePatterns) {
stringPattern = stringPattern.trim();
int length = stringPattern.length();
if (length == 0) continue;
if (stringPattern.indexOf("*") == -1) {
// Doesn't use wildcard, so use String not Pattern
patterns.add(stringPattern);
} else {
// We need to escape the regular expression characters ...
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i != length; i++) {
char c = stringPattern.charAt(i);
switch (c) {
// Per the spec, the the following characters are not allowed in patterns:
case '/':
case '[':
case ']':
case '\'':
case '"':
case '|':
case '\t':
case '\n':
case '\r':
String msg = JcrI18n.invalidNamePattern.text(c, stringPattern);
throw new RepositoryException(msg);
// The following characters must be escaped when used in regular expressions ...
case '?':
case '(':
case ')':
case '$':
case '^':
case '.':
case '{':
case '}':
case '\\':
sb.append("\\");
sb.append(c);
break;
case '*':
// replace with the regular expression wildcard
sb.append(".*");
break;
default:
sb.append(c);
break;
}
}
String escapedString = sb.toString();
Pattern pattern = Pattern.compile(escapedString);
patterns.add(pattern);
}
}
return patterns;
}
@Override
public AbstractJcrNode addNode( String relPath )
throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException,
RepositoryException {
checkSession();
return addNode(relPath, null, null);
}
@Override
public AbstractJcrNode addNode( String relPath,
String primaryNodeTypeName )
throws ItemExistsException, PathNotFoundException, NoSuchNodeTypeException, LockException, VersionException,
ConstraintViolationException, RepositoryException {
checkSession();
return addNode(relPath, primaryNodeTypeName, null);
}
/**
* Adds the a new node with the given primary type (if specified) at the given relative path with the given UUID (if
* specified).
*
* @param relPath the at which the new node should be created
* @param primaryNodeTypeName the desired primary type for the new node; null value indicates that the default primary type
* from the appropriate definition for this node should be used
* @param desiredKey the key for the new node; may be null if the key is to be generated
* @return the newly created node
* @throws ItemExistsException if an item at the specified path already exists and same-name siblings are not allowed.
* @throws PathNotFoundException if the specified path implies intermediary nodes that do not exist.
* @throws VersionException not thrown at this time, but included for compatibility with the specification
* @throws ConstraintViolationException if the change would violate a node type or implementation-specific constraint.
* @throws LockException not thrown at this time, but included for compatibility with the specification
* @throws RepositoryException if another error occurs
* @see #addNode(String, String)
*/
final AbstractJcrNode addNode( String relPath,
String primaryNodeTypeName,
NodeKey desiredKey )
throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException,
RepositoryException {
// Parse the primary type name ...
Name childPrimaryTypeName = null;
try {
childPrimaryTypeName = session.nameFactory().create(primaryNodeTypeName);
} catch (org.modeshape.jcr.value.ValueFormatException e) {
throw new RepositoryException(JcrI18n.invalidNodeTypeNameParameter.text(primaryNodeTypeName, "primaryNodeTypeName"));
}
// Resolve the relative path ...
Path path = null;
try {
path = session.pathFactory().create(relPath);
} catch (org.modeshape.jcr.value.ValueFormatException e) {
throw new RepositoryException(JcrI18n.invalidPathParameter.text(relPath, "relPath"));
}
if (path.size() == 0 || path.isIdentifier() || path.getLastSegment().getIndex() > 1 || relPath.endsWith("]")) {
throw new RepositoryException(JcrI18n.invalidPathParameter.text(relPath, "relPath"));
}
if (path.size() > 1) {
// The relative path points to another node, so look for it ...
Path parentPath = path.getParent();
try {
// Find the parent node ...
AbstractJcrItem parent = session.findItem(this, parentPath);
if (parent instanceof AbstractJcrNode) {
// delegate to the parent node ...
Name childName = path.getLastSegment().getName();
session.checkPermission(path, ModeShapePermissions.ADD_NODE);
return ((AbstractJcrNode)parent).addChildNode(childName, childPrimaryTypeName, desiredKey, false);
} else if (parent instanceof AbstractJcrProperty) {
// Per the TCK, if relPath references a property, then we have to throw a ConstraintViolationException.
throw new ConstraintViolationException(JcrI18n.invalidPathParameter.text(relPath, "relPath"));
}
} catch (ItemNotFoundException e) {
// We have to convert to a path not found ...
throw new PathNotFoundException(e.getMessage(), e.getCause());
} catch (RepositoryException e) {
throw e;
}
}
// Otherwise, the path has size == 1 and it specifies the child ...
session.checkPermission(path, ModeShapePermissions.ADD_NODE);
Name childName = path.getLastSegment().getName();
return addChildNode(childName, childPrimaryTypeName, desiredKey, false);
}
/**
* Adds the a new node with the given primary type (if specified) at the given relative path with the given UUID (if
* specified).
*
*
* @param childName the name for the new node; may not be null
* @param childPrimaryNodeTypeName the desired primary type for the new node; null value indicates that the default primary
* type from the appropriate definition for this node should be used
* @param desiredKey the key for the new node; may be null if the key is to be generated
* @param skipVersioningValidation true if the operation can be performed on a checked-in node.
* @return the newly created node