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.io.IOException;
27 import java.io.InputStream;
28 import java.io.OutputStream;
29 import java.security.AccessControlException;
30 import java.util.ArrayList;
31 import java.util.Calendar;
32 import java.util.Collection;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.UUID;
40 import javax.jcr.Credentials;
41 import javax.jcr.InvalidSerializedDataException;
42 import javax.jcr.Item;
43 import javax.jcr.ItemExistsException;
44 import javax.jcr.ItemNotFoundException;
45 import javax.jcr.NamespaceException;
46 import javax.jcr.Node;
47 import javax.jcr.NodeIterator;
48 import javax.jcr.PathNotFoundException;
49 import javax.jcr.PropertyType;
50 import javax.jcr.ReferentialIntegrityException;
51 import javax.jcr.Repository;
52 import javax.jcr.RepositoryException;
53 import javax.jcr.Session;
54 import javax.jcr.SimpleCredentials;
55 import javax.jcr.Value;
56 import javax.jcr.ValueFactory;
57 import javax.jcr.ValueFormatException;
58 import javax.jcr.Workspace;
59 import javax.jcr.lock.LockException;
60 import javax.jcr.nodetype.ConstraintViolationException;
61 import javax.jcr.query.Query;
62 import javax.jcr.query.QueryResult;
63 import javax.jcr.query.Row;
64 import javax.jcr.query.RowIterator;
65 import javax.jcr.version.VersionException;
66 import net.jcip.annotations.Immutable;
67 import net.jcip.annotations.NotThreadSafe;
68 import org.modeshape.common.util.CheckArg;
69 import org.modeshape.graph.ExecutionContext;
70 import org.modeshape.graph.Graph;
71 import org.modeshape.graph.Location;
72 import org.modeshape.graph.SecurityContext;
73 import org.modeshape.graph.property.Binary;
74 import org.modeshape.graph.property.DateTime;
75 import org.modeshape.graph.property.NamespaceRegistry;
76 import org.modeshape.graph.property.Path;
77 import org.modeshape.graph.property.PathFactory;
78 import org.modeshape.graph.property.ValueFactories;
79 import org.modeshape.graph.query.QueryBuilder;
80 import org.modeshape.graph.query.model.QueryCommand;
81 import org.modeshape.graph.query.model.TypeSystem;
82 import org.modeshape.graph.request.InvalidWorkspaceException;
83 import org.modeshape.graph.session.GraphSession;
84 import org.modeshape.jcr.JcrContentHandler.EnclosingSAXException;
85 import org.modeshape.jcr.JcrContentHandler.SaveMode;
86 import org.modeshape.jcr.JcrNamespaceRegistry.Behavior;
87 import org.modeshape.jcr.JcrRepository.Option;
88 import org.modeshape.jcr.SessionCache.JcrPropertyPayload;
89 import org.modeshape.jcr.WorkspaceLockManager.ModeShapeLock;
90 import org.xml.sax.ContentHandler;
91 import org.xml.sax.InputSource;
92 import org.xml.sax.SAXException;
93 import org.xml.sax.SAXParseException;
94 import org.xml.sax.XMLReader;
95 import org.xml.sax.helpers.XMLReaderFactory;
96
97
98
99
100 @NotThreadSafe
101 class JcrSession implements Session {
102
103 private static final String[] NO_ATTRIBUTES_NAMES = new String[] {};
104
105
106
107
108 private final JcrRepository repository;
109
110
111
112
113 private final JcrWorkspace workspace;
114
115
116
117
118
119 private final JcrNamespaceRegistry sessionRegistry;
120
121
122
123
124 protected final ExecutionContext executionContext;
125
126
127
128
129 private final Map<String, Object> sessionAttributes;
130
131
132
133
134 private final Graph graph;
135
136 private final SessionCache cache;
137
138 private final Set<String> lockTokens;
139
140
141
142
143 private final Path rootPath;
144
145 private boolean isLive;
146
147 private final boolean performReferentialIntegrityChecks;
148
149
150
151 private Set<Location> removedNodes = null;
152
153
154
155 private Set<String> removedReferenceableNodeUuids = null;
156
157 JcrSession( JcrRepository repository,
158 JcrWorkspace workspace,
159 ExecutionContext sessionContext,
160 NamespaceRegistry globalNamespaceRegistry,
161 Map<String, Object> sessionAttributes ) {
162 assert repository != null;
163 assert workspace != null;
164 assert sessionAttributes != null;
165 assert sessionContext != null;
166 this.repository = repository;
167 this.sessionAttributes = sessionAttributes;
168 this.workspace = workspace;
169
170
171 this.executionContext = sessionContext;
172 NamespaceRegistry local = sessionContext.getNamespaceRegistry();
173 this.sessionRegistry = new JcrNamespaceRegistry(Behavior.JSR170_SESSION, local, globalNamespaceRegistry, this);
174 this.rootPath = this.executionContext.getValueFactories().getPathFactory().createRootPath();
175
176
177 this.graph = workspace.graph();
178
179 this.cache = new SessionCache(this);
180 this.isLive = true;
181 this.lockTokens = new HashSet<String>();
182
183 this.performReferentialIntegrityChecks = Boolean.valueOf(repository.getOptions()
184 .get(Option.PERFORM_REFERENTIAL_INTEGRITY_CHECKS))
185 .booleanValue();
186
187 assert this.sessionAttributes != null;
188 assert this.workspace != null;
189 assert this.repository != null;
190 assert this.executionContext != null;
191 assert this.sessionRegistry != null;
192 assert this.graph != null;
193 assert this.executionContext.getSecurityContext() != null;
194 }
195
196
197 final SessionCache cache() {
198 return this.cache;
199 }
200
201 ExecutionContext getExecutionContext() {
202 return this.executionContext;
203 }
204
205 String sessionId() {
206 return this.executionContext.getId();
207 }
208
209 JcrNodeTypeManager nodeTypeManager() {
210 return this.workspace.nodeTypeManager();
211 }
212
213 NamespaceRegistry namespaces() {
214 return this.executionContext.getNamespaceRegistry();
215 }
216
217 void signalNamespaceChanges( boolean global ) {
218 nodeTypeManager().signalNamespaceChanges();
219 if (global) repository.getRepositoryTypeManager().signalNamespaceChanges();
220 }
221
222 JcrWorkspace workspace() {
223 return this.workspace;
224 }
225
226 JcrRepository repository() {
227 return this.repository;
228 }
229
230 final Collection<String> lockTokens() {
231 return lockTokens;
232 }
233
234 Graph.Batch createBatch() {
235 return graph.batch();
236 }
237
238 Graph graph() {
239 return graph;
240 }
241
242 String sourceName() {
243 return this.repository.getRepositorySourceName();
244 }
245
246
247
248
249
250
251 public Workspace getWorkspace() {
252 return this.workspace;
253 }
254
255
256
257
258
259
260 public Repository getRepository() {
261 return this.repository;
262 }
263
264
265
266
267
268
269
270 public Object getAttribute( String name ) {
271 return sessionAttributes.get(name);
272 }
273
274
275
276
277
278
279
280 public String[] getAttributeNames() {
281 Set<String> names = sessionAttributes.keySet();
282 if (names.isEmpty()) return NO_ATTRIBUTES_NAMES;
283 return names.toArray(new String[names.size()]);
284 }
285
286
287
288
289 Map<String, Object> sessionAttributes() {
290 return new HashMap<String, Object>(sessionAttributes);
291 }
292
293
294
295
296
297
298 public String getNamespacePrefix( String uri ) throws RepositoryException {
299 return sessionRegistry.getPrefix(uri);
300 }
301
302
303
304
305
306
307 public String[] getNamespacePrefixes() {
308 return sessionRegistry.getPrefixes();
309 }
310
311
312
313
314
315
316 public String getNamespaceURI( String prefix ) throws RepositoryException {
317 return sessionRegistry.getURI(prefix);
318 }
319
320
321
322
323
324
325 public void setNamespacePrefix( String newPrefix,
326 String existingUri ) throws NamespaceException, RepositoryException {
327 sessionRegistry.registerNamespace(newPrefix, existingUri);
328 }
329
330
331
332
333
334
335 public void addLockToken( String lt ) throws LockException {
336 CheckArg.isNotNull(lt, "lock token");
337
338
339 if (lockTokens.contains(lt)) {
340 return;
341 }
342
343 if (workspace().lockManager().isHeldBySession(this, lt)) {
344 throw new LockException(JcrI18n.lockTokenAlreadyHeld.text(lt));
345 }
346
347 workspace().lockManager().setHeldBySession(this, lt, true);
348 lockTokens.add(lt);
349 }
350
351
352
353
354
355
356
357
358
359 final boolean hasRole( String roleName,
360 String workspaceName ) {
361 SecurityContext context = getExecutionContext().getSecurityContext();
362
363 return context.hasRole(roleName) || context.hasRole(roleName + "." + this.repository.getRepositorySourceName())
364 || context.hasRole(roleName + "." + this.repository.getRepositorySourceName() + "." + workspaceName);
365 }
366
367
368
369
370
371
372
373 public void checkPermission( String path,
374 String actions ) {
375 CheckArg.isNotEmpty(path, "path");
376
377 this.checkPermission(executionContext.getValueFactories().getPathFactory().create(path), actions);
378 }
379
380
381
382
383
384
385
386
387
388
389
390 void checkPermission( Path path,
391 String actions ) {
392 checkPermission(this.workspace().getName(), path, actions);
393 }
394
395
396
397
398
399
400
401
402
403
404
405
406 void checkPermission( String workspaceName,
407 Path path,
408 String actions ) {
409
410 CheckArg.isNotEmpty(actions, "actions");
411
412 boolean hasPermission = true;
413 for (String action : actions.split(",")) {
414 if (ModeShapePermissions.READ.equals(action)) {
415 hasPermission &= hasRole(ModeShapeRoles.READONLY, workspaceName)
416 || hasRole(ModeShapeRoles.READWRITE, workspaceName)
417 || hasRole(ModeShapeRoles.ADMIN, workspaceName);
418 } else if (ModeShapePermissions.REGISTER_NAMESPACE.equals(action)
419 || ModeShapePermissions.REGISTER_TYPE.equals(action) || ModeShapePermissions.UNLOCK_ANY.equals(action)) {
420 hasPermission &= hasRole(ModeShapeRoles.ADMIN, workspaceName);
421 } else {
422 hasPermission &= hasRole(ModeShapeRoles.ADMIN, workspaceName) || hasRole(ModeShapeRoles.READWRITE, workspaceName);
423 }
424 }
425
426 if (hasPermission) return;
427
428 String pathAsString = path != null ? path.getString(this.namespaces()) : "<unknown>";
429 throw new AccessControlException(JcrI18n.permissionDenied.text(pathAsString, actions));
430
431 }
432
433
434
435
436
437
438 public void exportDocumentView( String absPath,
439 ContentHandler contentHandler,
440 boolean skipBinary,
441 boolean noRecurse ) throws RepositoryException, SAXException {
442 CheckArg.isNotNull(absPath, "absPath");
443 CheckArg.isNotNull(contentHandler, "contentHandler");
444
445 Path exportRootPath = executionContext.getValueFactories().getPathFactory().create(absPath);
446 Node exportRootNode = getNode(exportRootPath);
447
448 AbstractJcrExporter exporter = new JcrDocumentViewExporter(this);
449
450 exporter.exportView(exportRootNode, contentHandler, skipBinary, noRecurse);
451 }
452
453
454
455
456
457
458 public void exportDocumentView( String absPath,
459 OutputStream out,
460 boolean skipBinary,
461 boolean noRecurse ) throws RepositoryException {
462 CheckArg.isNotNull(absPath, "absPath");
463 CheckArg.isNotNull(out, "out");
464
465 Path exportRootPath = executionContext.getValueFactories().getPathFactory().create(absPath);
466 Node exportRootNode = getNode(exportRootPath);
467
468 AbstractJcrExporter exporter = new JcrDocumentViewExporter(this);
469
470 exporter.exportView(exportRootNode, out, skipBinary, noRecurse);
471 }
472
473
474
475
476
477
478 public void exportSystemView( String absPath,
479 ContentHandler contentHandler,
480 boolean skipBinary,
481 boolean noRecurse ) throws RepositoryException, SAXException {
482 CheckArg.isNotNull(absPath, "absPath");
483 CheckArg.isNotNull(contentHandler, "contentHandler");
484
485 Path exportRootPath = executionContext.getValueFactories().getPathFactory().create(absPath);
486 Node exportRootNode = getNode(exportRootPath);
487
488 AbstractJcrExporter exporter = new JcrSystemViewExporter(this);
489
490 exporter.exportView(exportRootNode, contentHandler, skipBinary, noRecurse);
491 }
492
493
494
495
496
497
498 public void exportSystemView( String absPath,
499 OutputStream out,
500 boolean skipBinary,
501 boolean noRecurse ) throws RepositoryException {
502 CheckArg.isNotNull(absPath, "absPath");
503 CheckArg.isNotNull(out, "out");
504
505 Path exportRootPath = executionContext.getValueFactories().getPathFactory().create(absPath);
506 Node exportRootNode = getNode(exportRootPath);
507
508 AbstractJcrExporter exporter = new JcrSystemViewExporter(this);
509
510 exporter.exportView(exportRootNode, out, skipBinary, noRecurse);
511 }
512
513
514
515
516
517
518 public ContentHandler getImportContentHandler( String parentAbsPath,
519 int uuidBehavior ) throws PathNotFoundException, RepositoryException {
520 Path parentPath = this.executionContext.getValueFactories().getPathFactory().create(parentAbsPath);
521
522 return new JcrContentHandler(this, parentPath, uuidBehavior, SaveMode.SESSION);
523 }
524
525
526
527
528
529
530
531 public Item getItem( String absolutePath ) throws RepositoryException {
532 CheckArg.isNotEmpty(absolutePath, "absolutePath");
533
534 Path path = executionContext.getValueFactories().getPathFactory().create(absolutePath);
535 if (path.isRoot()) {
536 return getRootNode();
537 }
538
539 if (path.getLastSegment().hasIndex()) {
540 return getNode(path);
541 }
542
543 try {
544 return cache.findJcrItem(null, rootPath, path.relativeTo(rootPath));
545 } catch (ItemNotFoundException e) {
546 throw new PathNotFoundException(e.getMessage(), e);
547 }
548 }
549
550
551
552
553
554
555 public String[] getLockTokens() {
556 return lockTokens.toArray(new String[lockTokens.size()]);
557 }
558
559
560
561
562
563
564
565
566
567 AbstractJcrNode getNode( Path path ) throws RepositoryException, PathNotFoundException {
568 if (path.isRoot()) return cache.findJcrRootNode();
569 try {
570 return cache.findJcrNode(null, path);
571 } catch (ItemNotFoundException e) {
572 throw new PathNotFoundException(e.getMessage());
573 }
574 }
575
576
577
578
579
580
581 public AbstractJcrNode getNodeByUUID( String uuid ) throws ItemNotFoundException, RepositoryException {
582 AbstractJcrNode node = cache.findJcrNode(Location.create(UUID.fromString(uuid)));
583
584 return node;
585 }
586
587
588
589
590
591
592 public Node getRootNode() throws RepositoryException {
593 return cache.findJcrRootNode();
594 }
595
596
597
598
599
600
601
602 public String getUserID() {
603 return executionContext.getSecurityContext().getUserName();
604 }
605
606
607
608
609
610
611 public ValueFactory getValueFactory() {
612 final ValueFactories valueFactories = executionContext.getValueFactories();
613 final SessionCache sessionCache = this.cache;
614
615 return new ValueFactory() {
616
617 public Value createValue( String value,
618 int propertyType ) throws ValueFormatException {
619 return new JcrValue(valueFactories, sessionCache, propertyType, convertValueToType(value, propertyType));
620 }
621
622 public Value createValue( Node value ) throws RepositoryException {
623 if (!value.isNodeType(JcrMixLexicon.REFERENCEABLE.getString(JcrSession.this.namespaces()))) {
624 throw new RepositoryException(JcrI18n.nodeNotReferenceable.text());
625 }
626 String uuid = valueFactories.getStringFactory().create(value.getUUID());
627 return new JcrValue(valueFactories, sessionCache, PropertyType.REFERENCE, uuid);
628 }
629
630 public Value createValue( InputStream value ) {
631 Binary binary = valueFactories.getBinaryFactory().create(value);
632 return new JcrValue(valueFactories, sessionCache, PropertyType.BINARY, binary);
633 }
634
635 public Value createValue( Calendar value ) {
636 DateTime dateTime = valueFactories.getDateFactory().create(value);
637 return new JcrValue(valueFactories, sessionCache, PropertyType.DATE, dateTime);
638 }
639
640 public Value createValue( boolean value ) {
641 return new JcrValue(valueFactories, sessionCache, PropertyType.BOOLEAN, value);
642 }
643
644 public Value createValue( double value ) {
645 return new JcrValue(valueFactories, sessionCache, PropertyType.DOUBLE, value);
646 }
647
648 public Value createValue( long value ) {
649 return new JcrValue(valueFactories, sessionCache, PropertyType.LONG, value);
650 }
651
652 public Value createValue( String value ) {
653 return new JcrValue(valueFactories, sessionCache, PropertyType.STRING, value);
654 }
655
656 Object convertValueToType( Object value,
657 int toType ) throws ValueFormatException {
658 switch (toType) {
659 case PropertyType.BOOLEAN:
660 try {
661 return valueFactories.getBooleanFactory().create(value);
662 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
663 throw new ValueFormatException(vfe);
664 }
665
666 case PropertyType.DATE:
667 try {
668 return valueFactories.getDateFactory().create(value);
669 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
670 throw new ValueFormatException(vfe);
671 }
672
673 case PropertyType.NAME:
674 try {
675 return valueFactories.getNameFactory().create(value);
676 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
677 throw new ValueFormatException(vfe);
678 }
679
680 case PropertyType.PATH:
681 try {
682 return valueFactories.getPathFactory().create(value);
683 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
684 throw new ValueFormatException(vfe);
685 }
686
687 case PropertyType.REFERENCE:
688 try {
689 return valueFactories.getReferenceFactory().create(value);
690 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
691 throw new ValueFormatException(vfe);
692 }
693 case PropertyType.DOUBLE:
694 try {
695 return valueFactories.getDoubleFactory().create(value);
696 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
697 throw new ValueFormatException(vfe);
698 }
699 case PropertyType.LONG:
700 try {
701 return valueFactories.getLongFactory().create(value);
702 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
703 throw new ValueFormatException(vfe);
704 }
705
706
707 case PropertyType.BINARY:
708 try {
709 return valueFactories.getBinaryFactory().create(value);
710 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
711 throw new ValueFormatException(vfe);
712 }
713 case PropertyType.STRING:
714 try {
715 return valueFactories.getStringFactory().create(value);
716 } catch (org.modeshape.graph.property.ValueFormatException vfe) {
717 throw new ValueFormatException(vfe);
718 }
719 case PropertyType.UNDEFINED:
720 return value;
721
722 default:
723 assert false : "Unexpected JCR property type " + toType;
724
725 throw new IllegalStateException("Invalid property type " + toType);
726 }
727 }
728
729 };
730 }
731
732
733
734
735
736
737 public boolean hasPendingChanges() {
738 return cache.hasPendingChanges();
739 }
740
741
742
743
744
745
746 public Session impersonate( Credentials credentials ) throws RepositoryException {
747 return repository.login(credentials, this.workspace.getName());
748 }
749
750
751
752
753
754
755
756
757
758 JcrSession with( String workspaceName ) throws RepositoryException {
759 return repository.sessionForContext(executionContext, workspaceName, sessionAttributes);
760 }
761
762
763
764
765
766
767 public void importXML( String parentAbsPath,
768 InputStream in,
769 int uuidBehavior ) throws IOException, InvalidSerializedDataException, RepositoryException {
770
771 try {
772 XMLReader parser = XMLReaderFactory.createXMLReader();
773
774 parser.setContentHandler(getImportContentHandler(parentAbsPath, uuidBehavior));
775 parser.parse(new InputSource(in));
776 } catch (EnclosingSAXException ese) {
777 Exception cause = ese.getException();
778 if (cause instanceof ItemExistsException) {
779 throw (ItemExistsException)cause;
780 } else if (cause instanceof ConstraintViolationException) {
781 throw (ConstraintViolationException)cause;
782 } else if (cause instanceof VersionException) {
783 throw (VersionException)cause;
784 }
785 throw new RepositoryException(cause);
786 } catch (SAXParseException se) {
787 throw new InvalidSerializedDataException(se);
788 } catch (SAXException se) {
789 throw new RepositoryException(se);
790 }
791 }
792
793
794
795
796
797
798 public boolean isLive() {
799 return isLive;
800 }
801
802
803
804
805
806
807
808 public boolean itemExists( String absolutePath ) throws RepositoryException {
809 try {
810 return (getItem(absolutePath) != null);
811 } catch (PathNotFoundException error) {
812 return false;
813 }
814 }
815
816
817
818
819
820
821 public void logout() {
822 if (!isLive()) {
823 return;
824 }
825
826 isLive = false;
827 this.workspace().observationManager().removeAllEventListeners();
828 this.workspace().lockManager().cleanLocks(this);
829 this.repository.sessionLoggedOut(this);
830 this.executionContext.getSecurityContext().logout();
831 }
832
833
834
835
836
837
838 public void move( String srcAbsPath,
839 String destAbsPath ) throws ItemExistsException, RepositoryException {
840 CheckArg.isNotNull(srcAbsPath, "srcAbsPath");
841 CheckArg.isNotNull(destAbsPath, "destAbsPath");
842
843 PathFactory pathFactory = executionContext.getValueFactories().getPathFactory();
844 Path destPath = pathFactory.create(destAbsPath);
845
846 Path.Segment newNodeName = destPath.getSegment(destPath.size() - 1);
847
848 if (destAbsPath.endsWith("]")) {
849 throw new RepositoryException(JcrI18n.pathCannotHaveSameNameSiblingIndex.text(destAbsPath));
850 }
851
852 AbstractJcrNode sourceNode = getNode(pathFactory.create(srcAbsPath));
853 AbstractJcrNode newParentNode = getNode(destPath.getParent());
854
855 if (sourceNode.isLocked()) {
856 javax.jcr.lock.Lock sourceLock = sourceNode.getLock();
857 if (sourceLock != null && sourceLock.getLockToken() == null) {
858 throw new LockException(JcrI18n.lockTokenNotHeld.text(srcAbsPath));
859 }
860 }
861
862 if (newParentNode.isLocked()) {
863 javax.jcr.lock.Lock newParentLock = newParentNode.getLock();
864 if (newParentLock != null && newParentLock.getLockToken() == null) {
865 throw new LockException(JcrI18n.lockTokenNotHeld.text(destAbsPath));
866 }
867 }
868
869 if (!sourceNode.isCheckedOut()) {
870 throw new VersionException(JcrI18n.nodeIsCheckedIn.text(sourceNode.getPath()));
871 }
872
873 if (!newParentNode.isCheckedOut()) {
874 throw new VersionException(JcrI18n.nodeIsCheckedIn.text(newParentNode.getPath()));
875 }
876
877 newParentNode.editor().moveToBeChild(sourceNode, newNodeName.getName());
878 }
879
880
881
882
883
884
885 public void refresh( boolean keepChanges ) {
886 this.cache.refresh(keepChanges);
887 }
888
889
890
891
892
893
894 public void removeLockToken( String lt ) {
895 CheckArg.isNotNull(lt, "lock token");
896
897
898
899
900
901
902 ModeShapeLock lock = workspace().lockManager().lockFor(lt);
903 if (lock == null) {
904
905 lockTokens.remove(lt);
906 return;
907 }
908
909 if (lock.isSessionScoped()) {
910 throw new IllegalStateException(JcrI18n.cannotRemoveLockToken.text(lt));
911 }
912
913 workspace().lockManager().setHeldBySession(this, lt, false);
914 lockTokens.remove(lt);
915 }
916
917 void recordRemoval( Location location ) throws RepositoryException {
918 if (!performReferentialIntegrityChecks) {
919 return;
920 }
921 if (removedNodes == null) {
922 removedNodes = new HashSet<Location>();
923 removedReferenceableNodeUuids = new HashSet<String>();
924 }
925
926
927 Path path = location.getPath();
928 org.modeshape.graph.property.ValueFactory<String> stringFactory = executionContext.getValueFactories().getStringFactory();
929 String pathStr = stringFactory.create(path);
930 int sns = path.getLastSegment().getIndex();
931 if (sns == Path.DEFAULT_INDEX) pathStr = pathStr + "[1]";
932
933 TypeSystem typeSystem = executionContext.getValueFactories().getTypeSystem();
934 QueryBuilder builder = new QueryBuilder(typeSystem);
935 QueryCommand query = builder.select("jcr:uuid")
936 .from("mix:referenceable AS referenceable")
937 .where()
938 .path("referenceable")
939 .isLike(pathStr + "%")
940 .end()
941 .query();
942 JcrQueryManager queryManager = workspace().queryManager();
943 Query jcrQuery = queryManager.createQuery(query);
944 QueryResult result = jcrQuery.execute();
945 RowIterator rows = result.getRows();
946 while (rows.hasNext()) {
947 Row row = rows.nextRow();
948 String uuid = row.getValue("jcr:uuid").getString();
949 if (uuid != null) removedReferenceableNodeUuids.add(uuid);
950 }
951
952
953 Set<Location> extras = null;
954 for (Location alreadyDeleted : removedNodes) {
955 Path alreadyDeletedPath = alreadyDeleted.getPath();
956 if (alreadyDeletedPath.isAtOrAbove(path)) {
957
958 return;
959 }
960 if (alreadyDeletedPath.isDecendantOf(path)) {
961
962 if (extras == null) {
963 extras = new HashSet<Location>();
964 }
965 extras.add(alreadyDeleted);
966 }
967 }
968
969 removedNodes.add(location);
970 if (extras != null) {
971
972 removedNodes.removeAll(extras);
973 }
974 }
975
976 boolean wasRemovedInSession( Location location ) {
977 if (removedNodes == null) return false;
978 if (removedNodes.contains(location)) return true;
979 Path path = location.getPath();
980 for (Location removed : removedNodes) {
981 if (removed.getPath().isAtOrAbove(path)) return true;
982 }
983 return false;
984 }
985
986 boolean wasRemovedInSession( UUID uuid ) {
987 if (removedReferenceableNodeUuids == null) return false;
988 return removedReferenceableNodeUuids.contains(uuid);
989
990 }
991
992
993
994
995
996
997
998
999
1000
1001 void checkReferentialIntegrityOfChanges( AbstractJcrNode subgraphRoot )
1002 throws ReferentialIntegrityException, RepositoryException {
1003 if (removedNodes == null) return;
1004 if (removedReferenceableNodeUuids.isEmpty()) return;
1005
1006 if (removedNodes.size() == 1 && removedNodes.iterator().next().getPath().isRoot()) {
1007
1008 return;
1009 }
1010
1011 String subgraphPath = null;
1012 if (subgraphRoot != null) {
1013 subgraphPath = subgraphRoot.getPath();
1014 if (subgraphRoot.getIndex() == Path.DEFAULT_INDEX) subgraphPath = subgraphPath + "[1]";
1015 }
1016
1017
1018
1019 int maxBatchSize = 100;
1020 List<Object> someUuidsInBranch = new ArrayList<Object>(maxBatchSize);
1021 Iterator<String> uuidIter = removedReferenceableNodeUuids.iterator();
1022 while (uuidIter.hasNext()) {
1023
1024 while (uuidIter.hasNext() && someUuidsInBranch.size() <= maxBatchSize) {
1025 String uuid = uuidIter.next();
1026 someUuidsInBranch.add(uuid);
1027 }
1028 assert !someUuidsInBranch.isEmpty();
1029
1030 TypeSystem typeSystem = executionContext.getValueFactories().getTypeSystem();
1031 QueryBuilder builder = new QueryBuilder(typeSystem);
1032 QueryCommand query = null;
1033 if (subgraphPath != null) {
1034 query = builder.select("jcr:primaryType")
1035 .fromAllNodesAs("allNodes")
1036 .where()
1037 .referenceValue("allNodes")
1038 .isIn(someUuidsInBranch)
1039 .and()
1040 .path("allNodes")
1041 .isLike(subgraphPath + "%")
1042 .end()
1043 .query();
1044 } else {
1045 query = builder.select("jcr:primaryType")
1046 .fromAllNodesAs("allNodes")
1047 .where()
1048 .referenceValue("allNodes")
1049 .isIn(someUuidsInBranch)
1050 .end()
1051 .query();
1052 }
1053 Query jcrQuery = workspace().queryManager().createQuery(query);
1054
1055 QueryResult result = jcrQuery.execute();
1056 NodeIterator referencingNodes = result.getNodes();
1057 while (referencingNodes.hasNext()) {
1058
1059 throw new ReferentialIntegrityException();
1060 }
1061 someUuidsInBranch.clear();
1062 }
1063 }
1064
1065
1066
1067
1068
1069
1070 public void save() throws RepositoryException {
1071 checkReferentialIntegrityOfChanges(null);
1072 removedNodes = null;
1073 cache.save();
1074 }
1075
1076
1077
1078
1079
1080
1081
1082 public void reindexContent() {
1083 repository().queryManager().reindexContent(workspace());
1084 }
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094 public void reindexContent( String path,
1095 int depth ) {
1096 repository().queryManager().reindexContent(workspace(), path, depth);
1097 }
1098
1099
1100
1101
1102
1103
1104
1105 public Snapshot getSnapshot() {
1106 return new Snapshot(cache.graphSession().getRoot().getSnapshot(false));
1107 }
1108
1109
1110
1111
1112
1113
1114 @Override
1115 public String toString() {
1116 return getSnapshot().toString();
1117 }
1118
1119 @Immutable
1120 public class Snapshot {
1121 private final GraphSession.StructureSnapshot<JcrPropertyPayload> rootSnapshot;
1122
1123 protected Snapshot( GraphSession.StructureSnapshot<JcrPropertyPayload> snapshot ) {
1124 this.rootSnapshot = snapshot;
1125 }
1126
1127
1128
1129
1130
1131
1132 @Override
1133 public String toString() {
1134 return rootSnapshot.toString();
1135 }
1136 }
1137 }