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.graph.session;
25
26 import java.security.AccessControlException;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.Iterator;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.UUID;
39 import net.jcip.annotations.Immutable;
40 import net.jcip.annotations.NotThreadSafe;
41 import net.jcip.annotations.ThreadSafe;
42 import org.modeshape.common.collection.ReadOnlyIterator;
43 import org.modeshape.common.i18n.I18n;
44 import org.modeshape.common.util.CheckArg;
45 import org.modeshape.common.util.StringUtil;
46 import org.modeshape.graph.ExecutionContext;
47 import org.modeshape.graph.Graph;
48 import org.modeshape.graph.GraphI18n;
49 import org.modeshape.graph.JcrLexicon;
50 import org.modeshape.graph.Location;
51 import org.modeshape.graph.Results;
52 import org.modeshape.graph.Subgraph;
53 import org.modeshape.graph.connector.RepositorySourceException;
54 import org.modeshape.graph.connector.UuidAlreadyExistsException;
55 import org.modeshape.graph.property.DateTime;
56 import org.modeshape.graph.property.Name;
57 import org.modeshape.graph.property.NamespaceRegistry;
58 import org.modeshape.graph.property.Path;
59 import org.modeshape.graph.property.PathFactory;
60 import org.modeshape.graph.property.PathNotFoundException;
61 import org.modeshape.graph.property.Property;
62 import org.modeshape.graph.property.Path.Segment;
63 import org.modeshape.graph.request.BatchRequestBuilder;
64 import org.modeshape.graph.request.ChangeRequest;
65 import org.modeshape.graph.request.CloneBranchRequest;
66 import org.modeshape.graph.request.CopyBranchRequest;
67 import org.modeshape.graph.request.InvalidWorkspaceException;
68 import org.modeshape.graph.request.MoveBranchRequest;
69 import org.modeshape.graph.request.Request;
70 import org.modeshape.graph.request.RequestException;
71 import org.modeshape.graph.session.GraphSession.Authorizer.Action;
72 import com.google.common.collect.LinkedListMultimap;
73 import com.google.common.collect.ListMultimap;
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 @NotThreadSafe
91 public class GraphSession<Payload, PropertyPayload> {
92
93 protected final ListMultimap<Name, Node<Payload, PropertyPayload>> NO_CHILDREN = LinkedListMultimap.create();
94 protected final Map<Name, PropertyInfo<PropertyPayload>> NO_PROPERTIES = Collections.emptyMap();
95
96 protected final Authorizer authorizer;
97 protected final ExecutionContext context;
98 protected final Graph store;
99 protected final Node<Payload, PropertyPayload> root;
100 protected final Operations<Payload, PropertyPayload> nodeOperations;
101 protected final PathFactory pathFactory;
102 protected final NodeIdFactory idFactory;
103 protected final String workspaceName;
104 protected int loadDepth = 1;
105
106
107
108
109 protected final Map<NodeId, Node<Payload, PropertyPayload>> nodes = new HashMap<NodeId, Node<Payload, PropertyPayload>>();
110
111
112
113 protected final Map<NodeId, Dependencies> changeDependencies = new HashMap<NodeId, Dependencies>();
114
115 private LinkedList<Request> requests;
116 private BatchRequestBuilder requestBuilder;
117 protected Graph.Batch operations;
118
119
120
121
122
123
124
125
126
127 public GraphSession( Graph graph,
128 String workspaceName,
129 Operations<Payload, PropertyPayload> nodeOperations ) {
130 this(graph, workspaceName, nodeOperations, null);
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144 public GraphSession( Graph graph,
145 String workspaceName,
146 Operations<Payload, PropertyPayload> nodeOperations,
147 Authorizer authorizer ) {
148 assert graph != null;
149 this.store = graph;
150 this.context = store.getContext();
151 if (workspaceName != null) {
152 this.workspaceName = this.store.useWorkspace(workspaceName).getName();
153 } else {
154 this.workspaceName = this.store.getCurrentWorkspaceName();
155 }
156 this.nodeOperations = nodeOperations != null ? nodeOperations : new NodeOperations<Payload, PropertyPayload>();
157 this.pathFactory = context.getValueFactories().getPathFactory();
158 this.authorizer = authorizer != null ? authorizer : new NoOpAuthorizer();
159
160 this.idFactory = new NodeIdFactory() {
161 private long nextId = 0L;
162
163 public NodeId create() {
164 return new NodeId(++nextId);
165 }
166 };
167
168 Location rootLocation = Location.create(pathFactory.createRootPath());
169 NodeId rootId = idFactory.create();
170 this.root = createNode(null, rootId, rootLocation);
171 this.nodes.put(rootId, root);
172
173
174 this.requests = new LinkedList<Request>();
175 this.requestBuilder = new BatchRequestBuilder(this.requests);
176 this.operations = this.store.batch(this.requestBuilder);
177 }
178
179 ExecutionContext context() {
180 return context;
181 }
182
183 final String readable( Name name ) {
184 return name.getString(context.getNamespaceRegistry());
185 }
186
187 final String readable( Path.Segment segment ) {
188 return segment.getString(context.getNamespaceRegistry());
189 }
190
191 final String readable( Path path ) {
192 return path.getString(context.getNamespaceRegistry());
193 }
194
195 final String readable( Location location ) {
196 return location.getString(context.getNamespaceRegistry());
197 }
198
199
200
201
202
203
204 public int getDepthForLoadingNodes() {
205 return loadDepth;
206 }
207
208
209
210
211
212
213
214
215 public void setDepthForLoadingNodes( int depth ) {
216 CheckArg.isPositive(depth, "depth");
217 this.loadDepth = depth;
218 }
219
220
221
222
223
224
225 public Node<Payload, PropertyPayload> getRoot() {
226 return root;
227 }
228
229
230
231
232
233
234 public PathFactory getPathFactory() {
235 return pathFactory;
236 }
237
238
239
240
241
242
243
244
245
246
247
248
249 public Node<Payload, PropertyPayload> findNodeWith( Location location ) throws PathNotFoundException, AccessControlException {
250 if (!location.hasPath()) {
251 UUID uuid = location.getUuid();
252 if (uuid != null) {
253
254
255 for (Node<Payload, PropertyPayload> node : nodes.values()) {
256 UUID nodeUuid = uuidFor(node.getLocation());
257
258 if (uuid.equals(nodeUuid)) {
259 return node;
260 }
261 }
262 }
263
264
265 location = store.getNodeAt(location).getLocation();
266 }
267 assert location.hasPath();
268 return findNodeWith(null, location.getPath());
269 }
270
271 private UUID uuidFor( Location location ) {
272 UUID uuid = location.getUuid();
273 if (uuid != null) return uuid;
274
275 Property idProp = location.getIdProperty(JcrLexicon.UUID);
276 if (idProp == null) return null;
277
278 return (UUID)idProp.getFirstValue();
279 }
280
281
282
283
284
285
286
287
288 public Node<Payload, PropertyPayload> findNodeWith( NodeId id ) {
289 CheckArg.isNotNull(id, "id");
290 return nodes.get(id);
291 }
292
293
294
295
296
297
298
299
300
301
302
303
304 public Node<Payload, PropertyPayload> findNodeWith( NodeId id,
305 Path path ) throws PathNotFoundException, AccessControlException {
306 if (id == null && path == null) {
307 CheckArg.isNotNull(id, "id");
308 CheckArg.isNotNull(path, "path");
309 }
310 Node<Payload, PropertyPayload> result = id != null ? nodes.get(id) : null;
311
312
313 if (result == null || result.isStale()) {
314 assert path != null;
315 result = findNodeWith(path);
316 }
317 return result;
318 }
319
320
321
322
323
324
325
326
327
328
329 public Node<Payload, PropertyPayload> findNodeWith( Path path ) throws PathNotFoundException, AccessControlException {
330 if (path.isRoot()) return getRoot();
331 if (path.isIdentifier()) return findNodeWith(Location.create(path));
332 return findNodeRelativeTo(root, path.relativeTo(root.getPath()), true);
333 }
334
335
336
337
338
339
340
341
342
343
344
345
346
347 protected Node<Payload, PropertyPayload> findNodeWith( Path path,
348 boolean loadIfRequired )
349 throws PathNotFoundException, AccessControlException {
350 if (path.isRoot()) return getRoot();
351 if (path.isIdentifier()) return findNodeWith(Location.create(path));
352 return findNodeRelativeTo(root, path.relativeTo(root.getPath()), loadIfRequired);
353 }
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368 public Node<Payload, PropertyPayload> findNodeRelativeTo( Node<Payload, PropertyPayload> startingPoint,
369 Path relativePath )
370 throws PathNotFoundException, AccessControlException {
371 return findNodeRelativeTo(startingPoint, relativePath, true);
372 }
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389 @SuppressWarnings( "synthetic-access" )
390 protected Node<Payload, PropertyPayload> findNodeRelativeTo( Node<Payload, PropertyPayload> startingPoint,
391 Path relativePath,
392 boolean loadIfRequired )
393 throws PathNotFoundException, AccessControlException {
394 Node<Payload, PropertyPayload> node = startingPoint;
395 if (!relativePath.isRoot()) {
396
397 Path absolutePath = relativePath.resolveAgainst(startingPoint.getPath());
398
399
400 authorizer.checkPermissions(absolutePath, Action.READ);
401
402
403 Iterator<Path.Segment> iter = relativePath.iterator();
404 while (iter.hasNext()) {
405 Path.Segment segment = iter.next();
406 try {
407 if (segment.isSelfReference()) continue;
408 if (segment.isParentReference()) {
409 node = node.getParent();
410 assert node != null;
411 continue;
412 }
413
414 if (node.isLoaded()) {
415
416 node = node.getChild(segment);
417 } else {
418 if (!loadIfRequired) return null;
419
420
421
422 Graph.Batch batch = store.batch();
423
424
425 Path firstPath = node.getPath();
426 batch.read(firstPath);
427
428 Path nextPath = pathFactory.create(firstPath, segment);
429 if (!iter.hasNext() && loadDepth > 1) {
430 batch.readSubgraphOfDepth(loadDepth).at(nextPath);
431 } else {
432 batch.read(nextPath);
433 }
434
435 while (iter.hasNext()) {
436 nextPath = pathFactory.create(nextPath, iter.next());
437 if (!iter.hasNext() && loadDepth > 1) {
438 batch.readSubgraphOfDepth(loadDepth).at(nextPath);
439 } else {
440 batch.read(nextPath);
441 }
442 }
443
444
445 Results batchResults = batch.execute();
446
447
448 Path previousPath = null;
449 Node<Payload, PropertyPayload> topNode = node;
450 Node<Payload, PropertyPayload> previousNode = node;
451 for (org.modeshape.graph.Node persistentNode : batchResults) {
452 Location location = persistentNode.getLocation();
453 Path path = location.getPath();
454 if (path.isRoot()) {
455 previousNode = root;
456 root.location = location;
457 } else {
458 if (path.getParent().equals(previousPath)) {
459 previousNode = previousNode.getChild(path.getLastSegment());
460 } else {
461 Path subgraphPath = path.relativeTo(topNode.getPath());
462 previousNode = findNodeRelativeTo(topNode, subgraphPath);
463 }
464
465 if (path.getLastSegment().equals(relativePath.getLastSegment()) && path.equals(absolutePath)) {
466 node = previousNode;
467 }
468 }
469 nodeOperations.materialize(persistentNode, previousNode);
470 previousPath = path;
471 }
472 }
473 } catch (RequestException re) {
474
475
476 try {
477
478 Iterator<Path.Segment> redoIter = relativePath.iterator();
479 Node<Payload, PropertyPayload> redoNode = startingPoint;
480 while (redoIter.hasNext()) {
481 Path.Segment redoSegment = redoIter.next();
482 if (redoSegment.isSelfReference()) continue;
483 if (redoSegment.isParentReference()) {
484 redoNode = redoNode.getParent();
485 assert redoNode != null;
486 continue;
487 }
488 Path firstPath = redoNode.getPath();
489
490 if (redoNode.isLoaded()) {
491
492 redoNode = redoNode.getChild(redoSegment);
493 } else {
494 Path nextPath = firstPath;
495
496 while (redoIter.hasNext()) {
497 nextPath = pathFactory.create(nextPath, redoSegment);
498 store.getNodeAt(nextPath);
499 }
500 }
501 }
502 } catch (PathNotFoundException e) {
503
504 throw new PathNotFoundException(Location.create(relativePath), e.getLowestAncestorThatDoesExist());
505 }
506
507 } catch (PathNotFoundException e) {
508
509 throw new PathNotFoundException(Location.create(relativePath), e.getLowestAncestorThatDoesExist());
510 }
511 }
512 }
513 return node;
514 }
515
516
517
518
519
520
521 public boolean hasPendingChanges() {
522 return root.isChanged(true);
523 }
524
525
526
527
528 public void clearAllChangedNodes() {
529 root.clearChanges();
530 changeDependencies.clear();
531 requests.clear();
532 }
533
534
535
536
537
538
539
540
541
542
543
544
545 public void immediateMove( Path nodeToMove,
546 Path destination ) throws AccessControlException, RepositorySourceException {
547 CheckArg.isNotNull(nodeToMove, "nodeToMove");
548 CheckArg.isNotNull(destination, "destination");
549
550 Path newParentPath = destination.getParent();
551 Name newName = destination.getLastSegment().getName();
552
553
554 authorizer.checkPermissions(newParentPath, Action.ADD_NODE);
555 authorizer.checkPermissions(nodeToMove.getParent(), Action.REMOVE);
556
557
558 Results results = store.batch().move(nodeToMove).as(newName).into(newParentPath).execute();
559 MoveBranchRequest moveRequest = (MoveBranchRequest)results.getRequests().get(0);
560 Location locationAfter = moveRequest.getActualLocationAfter();
561
562
563 Node<Payload, PropertyPayload> parent = this.findNodeWith(locationAfter.getPath().getParent(), false);
564 if (parent != null && parent.isLoaded()) {
565
566 parent.synchronizeWithNewlyPersistedNode(locationAfter);
567 }
568 }
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585 public void immediateCopy( Path source,
586 Path destination ) throws AccessControlException, RepositorySourceException {
587 immediateCopy(source, workspaceName, destination);
588 }
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609 public void immediateCopy( Path source,
610 String sourceWorkspace,
611 Path destination )
612 throws InvalidWorkspaceException, AccessControlException, PathNotFoundException, RepositorySourceException {
613 CheckArg.isNotNull(source, "source");
614 CheckArg.isNotNull(destination, "destination");
615 if (sourceWorkspace == null) sourceWorkspace = workspaceName;
616
617
618 authorizer.checkPermissions(destination, Action.ADD_NODE);
619 authorizer.checkPermissions(source, Action.READ);
620
621
622
623 Results results = store.batch().copy(source).fromWorkspace(sourceWorkspace).to(destination).execute();
624
625
626 CopyBranchRequest request = (CopyBranchRequest)results.getRequests().get(0);
627 Location locationOfCopy = request.getActualLocationAfter();
628
629
630 Node<Payload, PropertyPayload> parent = this.findNodeWith(locationOfCopy.getPath().getParent(), false);
631 if (parent != null && parent.isLoaded()) {
632
633 parent.synchronizeWithNewlyPersistedNode(locationOfCopy);
634 }
635 }
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656 public void immediateClone( Path source,
657 String sourceWorkspace,
658 Path destination,
659 boolean removeExisting,
660 boolean destPathIncludesSegment )
661 throws InvalidWorkspaceException, AccessControlException, UuidAlreadyExistsException, PathNotFoundException,
662 RepositorySourceException {
663 CheckArg.isNotNull(source, "source");
664 CheckArg.isNotNull(destination, "destination");
665 if (sourceWorkspace == null) sourceWorkspace = workspaceName;
666
667
668 authorizer.checkPermissions(destination.getParent(), Action.ADD_NODE);
669 authorizer.checkPermissions(source, Action.READ);
670
671
672
673 Graph.Batch batch = store.batch();
674 if (removeExisting) {
675
676 if (destPathIncludesSegment) {
677 batch.clone(source)
678 .fromWorkspace(sourceWorkspace)
679 .as(destination.getLastSegment())
680 .into(destination.getParent())
681 .replacingExistingNodesWithSameUuids();
682 } else {
683 Name newNodeName = destination.getLastSegment().getName();
684 batch.clone(source)
685 .fromWorkspace(sourceWorkspace)
686 .as(newNodeName)
687 .into(destination.getParent())
688 .replacingExistingNodesWithSameUuids();
689 }
690 } else {
691
692 if (destPathIncludesSegment) {
693 batch.clone(source)
694 .fromWorkspace(sourceWorkspace)
695 .as(destination.getLastSegment())
696 .into(destination.getParent())
697 .failingIfAnyUuidsMatch();
698 } else {
699 Name newNodeName = destination.getLastSegment().getName();
700 batch.clone(source)
701 .fromWorkspace(sourceWorkspace)
702 .as(newNodeName)
703 .into(destination.getParent())
704 .failingIfAnyUuidsMatch();
705 }
706 }
707
708 Results results = batch.execute();
709
710
711 CloneBranchRequest request = (CloneBranchRequest)results.getRequests().get(0);
712 Location locationOfCopy = request.getActualLocationAfter();
713
714
715 Set<Path> removedAlready = new HashSet<Path>();
716 for (Location removed : request.getRemovedNodes()) {
717 Path path = removed.getPath();
718 if (isBelow(path, removedAlready)) {
719
720 continue;
721 }
722 Node<Payload, PropertyPayload> removedNode = findNodeWith(path, false);
723 removedNode.remove(false);
724 removedAlready.add(path);
725 }
726
727
728 Node<Payload, PropertyPayload> parent = this.findNodeWith(locationOfCopy.getPath().getParent(), false);
729 if (parent != null && parent.isLoaded()) {
730
731 parent.synchronizeWithNewlyPersistedNode(locationOfCopy);
732 }
733 }
734
735 private static final boolean isBelow( Path path,
736 Collection<Path> paths ) {
737 for (Path aPath : paths) {
738 if (aPath.isAncestorOf(path)) return true;
739 }
740 return false;
741 }
742
743
744
745
746
747
748
749
750
751
752
753
754 @SuppressWarnings( "synthetic-access" )
755 public void refresh( boolean keepChanges ) throws InvalidStateException, RepositorySourceException {
756 if (keepChanges) {
757 refresh(root, keepChanges);
758 } else {
759
760 nodes.clear();
761 nodes.put(root.getNodeId(), root);
762
763 requests.clear();
764 changeDependencies.clear();
765
766 root.status = Status.UNCHANGED;
767 root.childrenByName = null;
768 root.expirationTime = Long.MAX_VALUE;
769 root.changedBelow = false;
770 root.payload = null;
771 }
772 }
773
774
775
776
777
778
779
780
781
782
783
784
785
786 public void refresh( Node<Payload, PropertyPayload> node,
787 boolean keepChanges ) throws InvalidStateException, RepositorySourceException {
788 if (!node.isRoot() && node.isChanged(true)) {
789
790 if (node.containsChangesWithExternalDependencies()) {
791 I18n msg = GraphI18n.unableToRefreshBranchBecauseChangesDependOnChangesToNodesOutsideOfBranch;
792 String path = readable(node.getPath());
793 throw new InvalidStateException(msg.text(path, workspaceName));
794 }
795 }
796
797 if (keepChanges && node.isChanged(true)) {
798
799
800 RefreshState<Payload, PropertyPayload> refreshState = new RefreshState<Payload, PropertyPayload>();
801 node.refreshPhase1(refreshState);
802
803 Results readResults = null;
804 if (!refreshState.getNodesToBeRefreshed().isEmpty()) {
805 Graph.Batch batch = store.batch();
806 for (Node<Payload, PropertyPayload> nodeToBeRefreshed : refreshState.getNodesToBeRefreshed()) {
807 batch.read(nodeToBeRefreshed.getLocation());
808 }
809
810
811 try {
812 readResults = batch.execute();
813 } catch (org.modeshape.graph.property.PathNotFoundException e) {
814 throw new InvalidStateException(e.getLocalizedMessage(), e);
815 }
816 }
817
818
819 node.refreshPhase2(refreshState, readResults);
820 } else {
821
822 node.clearChanges();
823
824 node.unload();
825
826
827 if (operations.isExecuteRequired()) {
828
829 this.requestBuilder.finishPendingRequest();
830
831
832 for (Iterator<Request> iter = this.requests.iterator(); iter.hasNext();) {
833 Request request = iter.next();
834 assert request instanceof ChangeRequest;
835 ChangeRequest change = (ChangeRequest)request;
836 if (change.changes(workspaceName, node.getPath())) {
837 iter.remove();
838 }
839 }
840 }
841 }
842 }
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859 public void refreshProperties( Node<Payload, PropertyPayload> node ) throws InvalidStateException, RepositorySourceException {
860 assert node != null;
861
862 if (node.isNew()) {
863 I18n msg = GraphI18n.unableToRefreshPropertiesBecauseNodeIsModified;
864 String path = readable(node.getPath());
865 throw new InvalidStateException(msg.text(path, workspaceName));
866 }
867
868 org.modeshape.graph.Node persistentNode = store.getNodeAt(node.getLocation());
869 nodeOperations.materializeProperties(persistentNode, node);
870 }
871
872
873
874
875
876
877
878
879 public void save() throws PathNotFoundException, ValidationException, InvalidStateException {
880 if (!operations.isExecuteRequired()) {
881
882 this.root.clearChanges();
883 this.root.unload();
884 return;
885 }
886
887 if (!root.isChanged(true)) {
888
889 root.recomputeChangedBelow();
890 if (!root.isChanged(true)) {
891
892 this.root.clearChanges();
893 this.root.unload();
894 return;
895 }
896 }
897
898
899
900 final DateTime saveTime = context.getValueFactories().getDateFactory().create();
901 root.onChangedNodes(new LoadAllChildrenVisitor() {
902 @Override
903 protected void finishParentAfterLoading( Node<Payload, PropertyPayload> node ) {
904 nodeOperations.preSave(node, saveTime);
905 }
906 });
907
908 root.onChangedNodes(new LoadAllChildrenVisitor() {
909 @Override
910 protected void finishParentAfterLoading( Node<Payload, PropertyPayload> node ) {
911 nodeOperations.compute(operations, node);
912 }
913 });
914
915
916 try {
917 operations.execute();
918 } catch (org.modeshape.graph.property.PathNotFoundException e) {
919 throw new InvalidStateException(e.getLocalizedMessage(), e);
920 } catch (RuntimeException e) {
921 throw new RepositorySourceException(e.getLocalizedMessage(), e);
922 }
923
924
925
926 this.requests = new LinkedList<Request>();
927 this.requestBuilder = new BatchRequestBuilder(this.requests);
928 this.operations = store.batch(this.requestBuilder);
929
930
931 this.root.clearChanges();
932 this.root.unload();
933 }
934
935
936
937
938
939
940
941
942
943
944 public void save( Node<Payload, PropertyPayload> node )
945 throws PathNotFoundException, ValidationException, InvalidStateException {
946 assert node != null;
947 if (node.isRoot()) {
948
949 save();
950 return;
951 }
952 if (node.isStale()) {
953
954 String readableLocation = readable(node.getLocation());
955 I18n msg = GraphI18n.nodeHasAlreadyBeenRemovedFromThisSession;
956 throw new InvalidStateException(msg.text(readableLocation, workspaceName));
957 }
958 if (node.isNew()) {
959 String path = readable(node.getPath());
960 throw new RepositorySourceException(GraphI18n.unableToSaveNodeThatWasCreatedSincePreviousSave.text(path,
961 workspaceName));
962 }
963 if (!node.isChanged(true)) {
964
965 return;
966 }
967
968
969 if (node.containsChangesWithExternalDependencies()) {
970 I18n msg = GraphI18n.unableToSaveBranchBecauseChangesDependOnChangesToNodesOutsideOfBranch;
971 String path = readable(node.getPath());
972 throw new ValidationException(msg.text(path, workspaceName));
973 }
974
975
976
977 final DateTime saveTime = context.getValueFactories().getDateFactory().create();
978 root.onChangedNodes(new LoadAllChildrenVisitor() {
979 @Override
980 protected void finishParentAfterLoading( Node<Payload, PropertyPayload> node ) {
981 nodeOperations.preSave(node, saveTime);
982 }
983 });
984
985
986 this.requestBuilder.finishPendingRequest();
987
988
989 Path path = node.getPath();
990 LinkedList<Request> branchRequests = new LinkedList<Request>();
991 LinkedList<Request> nonBranchRequests = new LinkedList<Request>();
992 for (Request request : this.requests) {
993 assert request instanceof ChangeRequest;
994 ChangeRequest change = (ChangeRequest)request;
995 if (change.changes(workspaceName, path)) {
996 branchRequests.add(request);
997 } else {
998 nonBranchRequests.add(request);
999 }
1000 }
1001 if (branchRequests.isEmpty()) return;
1002
1003
1004 final Graph.Batch branchBatch = store.batch(new BatchRequestBuilder(branchRequests));
1005
1006 node.onChangedNodes(new LoadAllChildrenVisitor() {
1007 @Override
1008 protected void finishParentAfterLoading( Node<Payload, PropertyPayload> node ) {
1009 nodeOperations.compute(branchBatch, node);
1010 }
1011 });
1012
1013 try {
1014 branchBatch.execute();
1015 } catch (org.modeshape.graph.property.PathNotFoundException e) {
1016 throw new InvalidStateException(e.getLocalizedMessage(), e);
1017 } catch (RuntimeException e) {
1018 throw new RepositorySourceException(e.getLocalizedMessage(), e);
1019 }
1020
1021
1022 this.requests = nonBranchRequests;
1023 this.requestBuilder = new BatchRequestBuilder(this.requests);
1024 this.operations = store.batch(this.requestBuilder);
1025
1026
1027 node.clearChanges();
1028 node.unload();
1029 }
1030
1031 protected Node<Payload, PropertyPayload> createNode( Node<Payload, PropertyPayload> parent,
1032 NodeId nodeId,
1033 Location location ) {
1034 return new Node<Payload, PropertyPayload>(this, parent, nodeId, location);
1035 }
1036
1037 protected long getCurrentTime() {
1038 return System.currentTimeMillis();
1039 }
1040
1041 protected void recordMove( Node<Payload, PropertyPayload> nodeBeingMoved,
1042 Node<Payload, PropertyPayload> oldParent,
1043 Node<Payload, PropertyPayload> newParent ) {
1044
1045 NodeId id = nodeBeingMoved.getNodeId();
1046 Dependencies dependencies = changeDependencies.get(id);
1047 if (dependencies == null) {
1048 dependencies = new Dependencies();
1049 dependencies.setMovedFrom(oldParent.getNodeId());
1050 changeDependencies.put(id, dependencies);
1051 } else {
1052 dependencies.setMovedFrom(newParent.getNodeId());
1053 }
1054 }
1055
1056
1057
1058
1059
1060
1061
1062 protected void recordDelete( Node<Payload, PropertyPayload> node ) {
1063
1064 operations.delete(node.getLocation());
1065
1066 nodes.remove(node.getNodeId());
1067 changeDependencies.remove(node.getNodeId());
1068 recordUnloaded(node);
1069 }
1070
1071
1072
1073
1074
1075
1076
1077 protected void recordUnloaded( final Node<Payload, PropertyPayload> node ) {
1078 if (node.isLoaded() && node.getChildrenCount() > 0) {
1079
1080 node.onCachedNodes(new NodeVisitor<Payload, PropertyPayload>() {
1081 @SuppressWarnings( "synthetic-access" )
1082 @Override
1083 public boolean visit( Node<Payload, PropertyPayload> unloaded ) {
1084 if (unloaded != node) {
1085 nodes.remove(unloaded.getNodeId());
1086 changeDependencies.remove(unloaded.getNodeId());
1087 unloaded.parent = null;
1088 }
1089 return true;
1090 }
1091 });
1092 }
1093 }
1094
1095 @ThreadSafe
1096 public static interface Operations<NodePayload, PropertyPayload> {
1097
1098
1099
1100
1101
1102
1103
1104 void materialize( org.modeshape.graph.Node persistentNode,
1105 Node<NodePayload, PropertyPayload> node );
1106
1107
1108
1109
1110
1111
1112
1113 void materializeProperties( org.modeshape.graph.Node persistentNode,
1114 Node<NodePayload, PropertyPayload> node );
1115
1116
1117
1118
1119
1120
1121
1122 void postUpdateLocation( Node<NodePayload, PropertyPayload> node,
1123 Location oldLocation );
1124
1125 void preSetProperty( Node<NodePayload, PropertyPayload> node,
1126 Name propertyName,
1127 PropertyInfo<PropertyPayload> newProperty ) throws ValidationException;
1128
1129 void postSetProperty( Node<NodePayload, PropertyPayload> node,
1130 Name propertyName,
1131 PropertyInfo<PropertyPayload> oldProperty );
1132
1133 void preRemoveProperty( Node<NodePayload, PropertyPayload> node,
1134 Name propertyName ) throws ValidationException;
1135
1136 void postRemoveProperty( Node<NodePayload, PropertyPayload> node,
1137 Name propertyName,
1138 PropertyInfo<PropertyPayload> oldProperty );
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150 void preCreateChild( Node<NodePayload, PropertyPayload> parentNode,
1151 Path.Segment newChild,
1152 Map<Name, PropertyInfo<PropertyPayload>> properties ) throws ValidationException;
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164 void postCreateChild( Node<NodePayload, PropertyPayload> parentNode,
1165 Node<NodePayload, PropertyPayload> newChild,
1166 Map<Name, PropertyInfo<PropertyPayload>> properties ) throws ValidationException;
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176 void preMove( Node<NodePayload, PropertyPayload> nodeToBeMoved,
1177 Node<NodePayload, PropertyPayload> newParentNode ) throws ValidationException;
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187 void postMove( Node<NodePayload, PropertyPayload> movedNode,
1188 Node<NodePayload, PropertyPayload> oldParentNode );
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198 void preCopy( Node<NodePayload, PropertyPayload> original,
1199 Node<NodePayload, PropertyPayload> newParentNode ) throws ValidationException;
1200
1201
1202
1203
1204
1205
1206
1207
1208 void postCopy( Node<NodePayload, PropertyPayload> original,
1209 Node<NodePayload, PropertyPayload> copy );
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219 void preRemoveChild( Node<NodePayload, PropertyPayload> parentNode,
1220 Node<NodePayload, PropertyPayload> child ) throws ValidationException;
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230 void postRemoveChild( Node<NodePayload, PropertyPayload> parentNode,
1231 Node<NodePayload, PropertyPayload> removedChild );
1232
1233
1234
1235
1236
1237
1238
1239
1240 void preSave( Node<NodePayload, PropertyPayload> node,
1241 DateTime saveTime ) throws ValidationException;
1242
1243
1244
1245
1246
1247
1248
1249 void compute( Graph.Batch batch,
1250 Node<NodePayload, PropertyPayload> node );
1251 }
1252
1253 @ThreadSafe
1254 public static interface NodeIdFactory {
1255 NodeId create();
1256 }
1257
1258 @ThreadSafe
1259 public static interface Authorizer {
1260
1261 public enum Action {
1262 READ,
1263 REMOVE,
1264 ADD_NODE,
1265 SET_PROPERTY;
1266 }
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276 void checkPermissions( Path path,
1277 Action action ) throws AccessControlException;
1278 }
1279
1280
1281
1282
1283 @ThreadSafe
1284 protected static class NoOpAuthorizer implements Authorizer {
1285
1286
1287
1288
1289
1290
1291 public void checkPermissions( Path path,
1292 Action action ) throws AccessControlException {
1293 }
1294 }
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304 @ThreadSafe
1305 public static class NodeOperations<Payload, PropertyPayload> implements Operations<Payload, PropertyPayload> {
1306
1307
1308
1309
1310
1311 public void materialize( org.modeshape.graph.Node persistentNode,
1312 Node<Payload, PropertyPayload> node ) {
1313
1314 Map<Name, PropertyInfo<PropertyPayload>> properties = new HashMap<Name, PropertyInfo<PropertyPayload>>();
1315 for (Property property : persistentNode.getProperties()) {
1316 Name propertyName = property.getName();
1317 PropertyInfo<PropertyPayload> info = new PropertyInfo<PropertyPayload>(property, property.isMultiple(),
1318 Status.UNCHANGED, null);
1319 properties.put(propertyName, info);
1320 }
1321
1322 node.loadedWith(persistentNode.getChildren(), properties, persistentNode.getExpirationTime());
1323 }
1324
1325
1326
1327
1328
1329
1330 public void materializeProperties( org.modeshape.graph.Node persistentNode,
1331 Node<Payload, PropertyPayload> node ) {
1332
1333 Map<Name, PropertyInfo<PropertyPayload>> properties = new HashMap<Name, PropertyInfo<PropertyPayload>>();
1334 for (Property property : persistentNode.getProperties()) {
1335 Name propertyName = property.getName();
1336 PropertyInfo<PropertyPayload> info = new PropertyInfo<PropertyPayload>(property, property.isMultiple(),
1337 Status.UNCHANGED, null);
1338 properties.put(propertyName, info);
1339 }
1340
1341 node.loadedWith(properties);
1342 }
1343
1344
1345
1346
1347
1348
1349 public void postUpdateLocation( Node<Payload, PropertyPayload> node,
1350 Location oldLocation ) {
1351
1352 }
1353
1354
1355
1356
1357
1358
1359 public void preSave( Node<Payload, PropertyPayload> node,
1360 DateTime saveTime ) throws ValidationException {
1361
1362 }
1363
1364
1365
1366
1367
1368
1369 public void compute( Graph.Batch batch,
1370 Node<Payload, PropertyPayload> node ) {
1371
1372 }
1373
1374
1375
1376
1377
1378
1379 public void preSetProperty( Node<Payload, PropertyPayload> node,
1380 Name propertyName,
1381 PropertyInfo<PropertyPayload> newProperty ) throws ValidationException {
1382
1383 }
1384
1385
1386
1387
1388
1389
1390 public void postSetProperty( Node<Payload, PropertyPayload> node,
1391 Name propertyName,
1392 PropertyInfo<PropertyPayload> oldProperty ) {
1393
1394 }
1395
1396
1397
1398
1399
1400
1401 public void preRemoveProperty( Node<Payload, PropertyPayload> node,
1402 Name propertyName ) throws ValidationException {
1403
1404 }
1405
1406
1407
1408
1409
1410
1411 public void postRemoveProperty( Node<Payload, PropertyPayload> node,
1412 Name propertyName,
1413 PropertyInfo<PropertyPayload> oldProperty ) {
1414
1415 }
1416
1417
1418
1419
1420
1421
1422
1423 public void preCreateChild( Node<Payload, PropertyPayload> parent,
1424 Segment newChild,
1425 Map<Name, PropertyInfo<PropertyPayload>> properties ) throws ValidationException {
1426
1427 }
1428
1429
1430
1431
1432
1433
1434
1435 public void postCreateChild( Node<Payload, PropertyPayload> parent,
1436 Node<Payload, PropertyPayload> childChild,
1437 Map<Name, PropertyInfo<PropertyPayload>> properties ) throws ValidationException {
1438
1439 }
1440
1441
1442
1443
1444
1445
1446
1447 public void preCopy( Node<Payload, PropertyPayload> original,
1448 Node<Payload, PropertyPayload> newParent ) throws ValidationException {
1449 }
1450
1451
1452
1453
1454
1455
1456
1457 public void postCopy( Node<Payload, PropertyPayload> original,
1458 Node<Payload, PropertyPayload> copy ) throws ValidationException {
1459 }
1460
1461
1462
1463
1464
1465
1466
1467 public void preMove( Node<Payload, PropertyPayload> nodeToBeMoved,
1468 Node<Payload, PropertyPayload> newParent ) throws ValidationException {
1469 }
1470
1471
1472
1473
1474
1475
1476
1477 public void postMove( Node<Payload, PropertyPayload> movedNode,
1478 Node<Payload, PropertyPayload> oldParent ) {
1479 }
1480
1481
1482
1483
1484
1485
1486
1487 public void preRemoveChild( Node<Payload, PropertyPayload> parent,
1488 Node<Payload, PropertyPayload> newChild ) throws ValidationException {
1489
1490 }
1491
1492
1493
1494
1495
1496
1497
1498 public void postRemoveChild( Node<Payload, PropertyPayload> parent,
1499 Node<Payload, PropertyPayload> oldChild ) {
1500
1501 }
1502 }
1503
1504 @NotThreadSafe
1505 public static class Node<Payload, PropertyPayload> {
1506 private final GraphSession<Payload, PropertyPayload> cache;
1507 private final NodeId nodeId;
1508 private Node<Payload, PropertyPayload> parent;
1509 private long expirationTime = Long.MAX_VALUE;
1510 private Location location;
1511 private Status status = Status.UNCHANGED;
1512 private boolean changedBelow;
1513 private Map<Name, PropertyInfo<PropertyPayload>> properties;
1514 private ListMultimap<Name, Node<Payload, PropertyPayload>> childrenByName;
1515 private Payload payload;
1516
1517 public Node( GraphSession<Payload, PropertyPayload> cache,
1518 Node<Payload, PropertyPayload> parent,
1519 NodeId nodeId,
1520 Location location ) {
1521 this.cache = cache;
1522 this.parent = parent;
1523 this.nodeId = nodeId;
1524 this.location = location;
1525 assert this.cache != null;
1526 assert this.nodeId != null;
1527 assert this.location != null;
1528 assert this.location.hasPath();
1529 }
1530
1531
1532
1533
1534
1535
1536 public GraphSession<Payload, PropertyPayload> getSession() {
1537 return cache;
1538 }
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548 public final long getExpirationTimeInMillis() {
1549 return expirationTime;
1550 }
1551
1552
1553
1554
1555
1556
1557
1558 public final boolean isExpired() {
1559 return expirationTime != Long.MAX_VALUE && expirationTime < cache.getCurrentTime();
1560 }
1561
1562
1563
1564
1565
1566
1567
1568
1569 public final boolean isLoaded() {
1570 if (childrenByName == null) return false;
1571
1572 if (isExpired()) {
1573
1574 if (isChanged(true)) return true;
1575
1576 unload();
1577 return false;
1578 }
1579
1580 return true;
1581 }
1582
1583
1584
1585
1586
1587
1588
1589 protected final void load() throws RepositorySourceException {
1590 if (isLoaded()) return;
1591 assert !isStale();
1592
1593 if (status == Status.NEW) {
1594 this.childrenByName = cache.NO_CHILDREN;
1595 this.properties = cache.NO_PROPERTIES;
1596 return;
1597 }
1598
1599
1600 Path path = getPath();
1601 cache.authorizer.checkPermissions(path, Action.READ);
1602 int depth = cache.getDepthForLoadingNodes();
1603 if (depth == 1) {
1604
1605 org.modeshape.graph.Node persistentNode = cache.store.getNodeAt(getLocation());
1606
1607 Location actualLocation = persistentNode.getLocation();
1608 if (!this.location.isSame(actualLocation)) {
1609
1610 this.location = actualLocation;
1611 }
1612
1613 cache.nodeOperations.materialize(persistentNode, this);
1614 } else {
1615
1616 Subgraph subgraph = cache.store.getSubgraphOfDepth(depth).at(getLocation());
1617 Location actualLocation = subgraph.getLocation();
1618 if (!this.location.isSame(actualLocation)) {
1619
1620 this.location = actualLocation;
1621 }
1622
1623 cache.nodeOperations.materialize(subgraph.getRoot(), this);
1624
1625 for (org.modeshape.graph.Node persistentNode : subgraph) {
1626
1627 Path relativePath = persistentNode.getLocation().getPath().relativeTo(path);
1628 Node<Payload, PropertyPayload> node = cache.findNodeRelativeTo(this, relativePath);
1629 if (!node.isLoaded()) {
1630
1631 cache.nodeOperations.materialize(persistentNode, node);
1632 }
1633 }
1634 }
1635 }
1636
1637
1638
1639
1640 protected final void unload() {
1641 assert !isStale();
1642 assert status == Status.UNCHANGED;
1643 assert !changedBelow;
1644 if (!isLoaded()) return;
1645 cache.recordUnloaded(this);
1646 childrenByName = null;
1647 expirationTime = Long.MAX_VALUE;
1648 }
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663 protected final boolean refreshPhase1( RefreshState<Payload, PropertyPayload> refreshState ) {
1664 assert !isStale();
1665 if (childrenByName == null) {
1666
1667 return true;
1668 }
1669
1670 boolean canUnloadChildren = true;
1671 for (Node<Payload, PropertyPayload> child : childrenByName.values()) {
1672 if (child.refreshPhase1(refreshState)) {
1673
1674 canUnloadChildren = false;
1675 }
1676 }
1677
1678
1679 if (isChanged(false)) return false;
1680
1681
1682 if (canUnloadChildren) {
1683
1684 return true;
1685 }
1686
1687 refreshState.markAsRequiringRefresh(this);
1688 return false;
1689 }
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700 protected final void refreshPhase2( RefreshState<Payload, PropertyPayload> refreshState,
1701 Results persistentInfoForRefreshedNodes ) {
1702 assert !isStale();
1703 if (this.status != Status.UNCHANGED) {
1704
1705 return;
1706 }
1707 if (refreshState.requiresRefresh(this)) {
1708
1709
1710 assert childrenByName != null;
1711 org.modeshape.graph.Node persistentNode = persistentInfoForRefreshedNodes.getNode(location);
1712 assert !persistentNode.getChildren().isEmpty();
1713
1714
1715
1716 Map<Location, Node<Payload, PropertyPayload>> childrenToKeep = new HashMap<Location, Node<Payload, PropertyPayload>>();
1717 for (Node<Payload, PropertyPayload> existing : childrenByName.values()) {
1718 if (existing.isChanged(true)) {
1719 childrenToKeep.put(existing.getLocation(), existing);
1720 } else {
1721
1722 cache.nodes.remove(existing.getNodeId());
1723 assert !cache.changeDependencies.containsKey(existing.getNodeId());
1724 existing.parent = null;
1725 }
1726 }
1727
1728
1729 childrenByName.clear();
1730
1731
1732 for (Location location : persistentNode.getChildren()) {
1733 Name childName = location.getPath().getLastSegment().getName();
1734 List<Node<Payload, PropertyPayload>> currentChildren = childrenByName.get(childName);
1735
1736 Node<Payload, PropertyPayload> existingChild = childrenToKeep.get(location);
1737 if (existingChild != null) {
1738
1739 currentChildren.add(existingChild);
1740 if (currentChildren.size() != existingChild.getPath().getLastSegment().getIndex()) {
1741
1742 Path.Segment segment = cache.pathFactory.createSegment(childName, currentChildren.size());
1743 existingChild.updateLocation(segment);
1744
1745
1746 }
1747 } else {
1748
1749 NodeId nodeId = cache.idFactory.create();
1750 Node<Payload, PropertyPayload> replacementChild = cache.createNode(this, nodeId, location);
1751 cache.nodes.put(replacementChild.getNodeId(), replacementChild);
1752 assert replacementChild.getName().equals(childName);
1753 assert replacementChild.parent == this;
1754
1755 currentChildren.add(replacementChild);
1756
1757 Path.Segment segment = cache.pathFactory.createSegment(childName, currentChildren.size());
1758 replacementChild.updateLocation(segment);
1759 }
1760 }
1761 return;
1762 }
1763
1764 if (!this.changedBelow) unload();
1765 }
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779 public void loadedWith( List<Location> children,
1780 Map<Name, PropertyInfo<PropertyPayload>> properties,
1781 DateTime expirationTime ) {
1782 assert !isStale();
1783
1784 if (children.isEmpty()) {
1785 childrenByName = cache.NO_CHILDREN;
1786 } else {
1787 childrenByName = LinkedListMultimap.create();
1788 for (Location location : children) {
1789 NodeId id = cache.idFactory.create();
1790 Name childName = location.getPath().getLastSegment().getName();
1791 Node<Payload, PropertyPayload> child = cache.createNode(this, id, location);
1792 cache.nodes.put(child.getNodeId(), child);
1793 List<Node<Payload, PropertyPayload>> currentChildren = childrenByName.get(childName);
1794 currentChildren.add(child);
1795 child.parent = this;
1796
1797 Path.Segment segment = cache.pathFactory.createSegment(childName, currentChildren.size());
1798 child.updateLocation(segment);
1799 }
1800 }
1801
1802 loadedWith(properties);
1803
1804
1805 this.expirationTime = expirationTime != null ? expirationTime.getMilliseconds() : Long.MAX_VALUE;
1806 }
1807
1808
1809
1810
1811
1812
1813
1814 public void loadedWith( Map<Name, PropertyInfo<PropertyPayload>> properties ) {
1815
1816 if (properties.isEmpty()) {
1817 this.properties = cache.NO_PROPERTIES;
1818 } else {
1819 this.properties = new HashMap<Name, PropertyInfo<PropertyPayload>>(properties);
1820 }
1821 }
1822
1823
1824
1825
1826
1827
1828 protected void updateLocation( Path.Segment segment ) {
1829 assert !isStale();
1830 Path newPath = null;
1831 Path currentPath = getPath();
1832 if (segment != null) {
1833 if (segment.equals(currentPath.getLastSegment())) return;
1834
1835 Path parentPath = getParent().getPath();
1836 newPath = cache.pathFactory.create(parentPath, segment);
1837 } else {
1838 if (this.isRoot()) return;
1839
1840 newPath = cache.pathFactory.createRootPath();
1841 assert this.isRoot();
1842 }
1843 Location newLocation = this.location.with(newPath);
1844 if (newLocation != this.location) {
1845 Location oldLocation = this.location;
1846 this.location = newLocation;
1847 cache.nodeOperations.postUpdateLocation(this, oldLocation);
1848 }
1849
1850 if (isLoaded() && childrenByName != cache.NO_CHILDREN) {
1851
1852 for (Map.Entry<Name, Collection<Node<Payload, PropertyPayload>>> entry : childrenByName.asMap().entrySet()) {
1853 Name childName = entry.getKey();
1854 int sns = 1;
1855 for (Node<Payload, PropertyPayload> child : entry.getValue()) {
1856 Path.Segment childSegment = cache.pathFactory.createSegment(childName, sns++);
1857 child.updateLocation(childSegment);
1858 }
1859 }
1860 }
1861 }
1862
1863
1864
1865
1866
1867
1868
1869
1870 protected void synchronizeWithNewlyPersistedNode( Location newChild ) {
1871 if (!this.isLoaded()) return;
1872 Path childPath = newChild.getPath();
1873 Name childName = childPath.getLastSegment().getName();
1874 if (this.childrenByName.isEmpty()) {
1875
1876 this.childrenByName = LinkedListMultimap.create();
1877 if (childPath.getLastSegment().hasIndex()) {
1878
1879 newChild = newChild.with(cache.pathFactory.create(childPath.getParent(), childName));
1880 }
1881 Node<Payload, PropertyPayload> child = cache.createNode(this, cache.idFactory.create(), newChild);
1882 this.childrenByName.put(childName, child);
1883 return;
1884 }
1885
1886
1887 ListMultimap<Name, Node<Payload, PropertyPayload>> children = LinkedListMultimap.create();
1888 boolean added = false;
1889 for (Node<Payload, PropertyPayload> child : this.childrenByName.values()) {
1890 if (!added && child.isNew()) {
1891
1892 Node<Payload, PropertyPayload> newChildNode = cache.createNode(this, cache.idFactory.create(), newChild);
1893 children.put(childName, newChildNode);
1894 added = true;
1895 }
1896 children.put(child.getName(), child);
1897 }
1898 if (!added) {
1899 Node<Payload, PropertyPayload> newChildNode = cache.createNode(this, cache.idFactory.create(), newChild);
1900 children.put(childName, newChildNode);
1901 }
1902
1903
1904 this.childrenByName = children;
1905
1906
1907 List<Node<Payload, PropertyPayload>> childrenWithName = childrenByName.get(childName);
1908 int snsIndex = 1;
1909 for (Node<Payload, PropertyPayload> sns : childrenWithName) {
1910 if (sns.getSegment().getIndex() != snsIndex) {
1911
1912 Path.Segment newSegment = cache.pathFactory.createSegment(childName, snsIndex);
1913 sns.updateLocation(newSegment);
1914 sns.markAsChanged();
1915 }
1916 ++snsIndex;
1917 }
1918 }
1919
1920
1921
1922
1923
1924
1925
1926 public final boolean isChanged( boolean recursive ) {
1927 if (this.status == Status.UNCHANGED) return recursive && this.changedBelow;
1928 return true;
1929 }
1930
1931
1932
1933
1934
1935
1936
1937 public final boolean isNew() {
1938 return this.status == Status.NEW;
1939 }
1940
1941
1942
1943
1944
1945
1946
1947 public boolean containsChangesWithExternalDependencies() {
1948 assert !isStale();
1949 if (!isChanged(true)) {
1950
1951 return false;
1952 }
1953
1954
1955 for (Map.Entry<NodeId, Dependencies> entry : cache.changeDependencies.entrySet()) {
1956 Dependencies dependency = entry.getValue();
1957 NodeId nodeId = entry.getKey();
1958 Node<Payload, PropertyPayload> changedNode = cache.nodes.get(nodeId);
1959
1960
1961 if (!changedNode.isAtOrBelow(this)) {
1962
1963 if (cache.nodes.get(dependency.getMovedFrom()).isAtOrBelow(this)) {
1964
1965 return true;
1966 }
1967
1968 for (NodeId dependentId : dependency.getRequireChangesTo()) {
1969
1970 if (cache.nodes.get(dependentId).isAtOrBelow(this)) {
1971
1972 return true;
1973 }
1974 }
1975
1976 continue;
1977 }
1978
1979
1980
1981 if (dependency.getMovedFrom() != null) {
1982 Node<Payload, PropertyPayload> originalParent = cache.nodes.get(dependency.getMovedFrom());
1983
1984 if (originalParent == null) {
1985 continue;
1986 }
1987
1988 if (!originalParent.isAtOrBelow(this)) {
1989
1990 return true;
1991 }
1992
1993 for (NodeId dependentId : dependency.getRequireChangesTo()) {
1994
1995 if (!cache.nodes.get(dependentId).isAtOrBelow(this)) {
1996
1997 return true;
1998 }
1999 }
2000 }
2001 }
2002 return false;
2003 }
2004
2005
2006
2007
2008
2009
2010 public void clearChanges() {
2011 assert !isStale();
2012 if (this.status != Status.UNCHANGED) {
2013 this.status = Status.UNCHANGED;
2014 this.changedBelow = false;
2015 unload();
2016 } else {
2017 if (!this.changedBelow) return;
2018
2019 if (childrenByName != null && childrenByName != cache.NO_CHILDREN) {
2020 for (Node<Payload, PropertyPayload> child : childrenByName.values()) {
2021 child.clearChanges();
2022 }
2023 }
2024 this.changedBelow = false;
2025 }
2026
2027 if (this.parent != null) this.parent.recomputeChangedBelow();
2028 }
2029
2030
2031
2032
2033
2034
2035
2036 public final void markAsChanged() {
2037 assert !isStale();
2038 if (this.status == Status.NEW) return;
2039 this.status = Status.CHANGED;
2040 if (this.parent != null) this.parent.markAsChangedBelow();
2041 }
2042
2043 public final void markAsCopied() {
2044 assert !isStale();
2045 this.status = Status.COPIED;
2046 if (this.parent != null) this.parent.markAsChangedBelow();
2047 }
2048
2049
2050
2051
2052
2053
2054
2055 public final void markAsNew() {
2056 assert !isStale();
2057 this.status = Status.NEW;
2058 if (this.parent != null) this.parent.markAsChanged();
2059 }
2060
2061 protected final void markAsChangedBelow() {
2062 if (!this.changedBelow) {
2063 this.changedBelow = true;
2064 if (this.parent != null) this.parent.markAsChangedBelow();
2065 }
2066 }
2067
2068 protected final void recomputeChangedBelow() {
2069 if (!this.changedBelow) return;
2070
2071 assert childrenByName != null;
2072 for (Node<Payload, PropertyPayload> child : childrenByName.values()) {
2073 if (child.isChanged(true)) {
2074 this.markAsChangedBelow();
2075 return;
2076 }
2077 }
2078
2079 this.changedBelow = false;
2080 if (this.parent != null) this.parent.recomputeChangedBelow();
2081 }
2082
2083
2084
2085
2086
2087
2088
2089
2090 public void moveTo( Node<Payload, PropertyPayload> parent ) {
2091 moveTo(parent, null, true);
2092 }
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103 public void moveTo( Node<Payload, PropertyPayload> parent,
2104 Name newNodeName ) {
2105 moveTo(parent, newNodeName, true);
2106 }
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120 protected void moveTo( Node<Payload, PropertyPayload> parent,
2121 Name newNodeName,
2122 boolean useBatch ) {
2123 final Node<Payload, PropertyPayload> child = this;
2124 assert !parent.isStale();
2125
2126 if (parent.isAtOrBelow(child)) {
2127 String path = cache.readable(getPath());
2128 String parentPath = cache.readable(parent.getPath());
2129 String workspaceName = cache.workspaceName;
2130 String msg = GraphI18n.unableToMoveNodeToBeChildOfDecendent.text(path, parentPath, workspaceName);
2131 throw new ValidationException(msg);
2132 }
2133
2134 assert !child.isRoot();
2135 if (newNodeName == null) newNodeName = getName();
2136
2137
2138 cache.authorizer.checkPermissions(parent.getPath(), Action.ADD_NODE);
2139 cache.authorizer.checkPermissions(child.getPath().getParent(), Action.REMOVE);
2140
2141 parent.load();
2142
2143 cache.nodeOperations.preMove(child, parent);
2144
2145
2146 final Node<Payload, PropertyPayload> oldParent = child.parent;
2147
2148 if (useBatch) {
2149 if (newNodeName.equals(getName())) {
2150 cache.operations.move(child.getLocation()).into(parent.getLocation());
2151 } else {
2152 cache.operations.move(child.getLocation()).as(newNodeName).into(parent.getLocation());
2153 }
2154 } else {
2155 if (newNodeName.equals(getName())) {
2156 cache.store.move(child.getLocation()).into(parent.getLocation());
2157 } else {
2158 cache.store.move(child.getLocation()).as(newNodeName).into(parent.getLocation());
2159 }
2160 }
2161
2162 child.remove();
2163
2164 if (parent.childrenByName == cache.NO_CHILDREN) {
2165 parent.childrenByName = LinkedListMultimap.create();
2166 }
2167 parent.childrenByName.put(newNodeName, child);
2168 child.parent = parent;
2169 parent.markAsChanged();
2170
2171 int snsIndex = parent.childrenByName.get(newNodeName).size();
2172 Path.Segment segment = cache.pathFactory.createSegment(newNodeName, snsIndex);
2173 child.updateLocation(segment);
2174 cache.recordMove(child, oldParent, parent);
2175
2176 cache.nodeOperations.postMove(child, oldParent);
2177 }
2178
2179
2180
2181
2182
2183
2184 public void rename( Name newNodeName ) {
2185 moveTo(this.parent, newNodeName, true);
2186 }
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198 public void copyTo( Node<Payload, PropertyPayload> parent ) {
2199 CheckArg.isNotNull(parent, "parent");
2200 CheckArg.isEquals(this.isRoot(), "this.isRoot()", false, "false");
2201 final Node<Payload, PropertyPayload> child = this;
2202 assert !parent.isStale();
2203 assert child.parent != this;
2204 assert !child.isRoot();
2205
2206
2207 cache.authorizer.checkPermissions(parent.getPath(), Action.ADD_NODE);
2208 cache.authorizer.checkPermissions(child.getPath(), Action.READ);
2209
2210 parent.load();
2211 if (parent.childrenByName == cache.NO_CHILDREN) {
2212 parent.childrenByName = LinkedListMultimap.create();
2213 }
2214
2215 cache.nodeOperations.preCopy(this, parent);
2216
2217 Name childName = child.getName();
2218
2219 List<Node<Payload, PropertyPayload>> currentChildren = parent.childrenByName.get(childName);
2220 Location copyLocation = Location.create(cache.pathFactory.create(parent.getPath(),
2221 childName,
2222 currentChildren.size() + 1));
2223
2224
2225 cache.operations.copy(child.getLocation()).to(copyLocation);
2226
2227
2228 Node<Payload, PropertyPayload> copy = cache.createNode(parent, cache.idFactory.create(), copyLocation);
2229 copy.markAsCopied();
2230
2231 cache.nodeOperations.postCopy(this, copy);
2232 }
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243 public void cloneNode() {
2244 copyTo(getParent());
2245 }
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257 public void orderChildBefore( Path.Segment childToBeMoved,
2258 Path.Segment before ) throws PathNotFoundException {
2259 CheckArg.isNotNull(childToBeMoved, "childToBeMoved");
2260
2261
2262 cache.authorizer.checkPermissions(getPath(), Action.REMOVE);
2263 cache.authorizer.checkPermissions(getPath(), Action.ADD_NODE);
2264
2265
2266 Node<Payload, PropertyPayload> nodeToBeMoved = getChild(childToBeMoved);
2267 Node<Payload, PropertyPayload> beforeNode = before != null ? getChild(before) : null;
2268
2269 if (beforeNode == null) {
2270
2271 cache.operations.move(nodeToBeMoved.getLocation()).into(this.location);
2272 } else {
2273
2274 cache.operations.move(nodeToBeMoved.getLocation()).before(beforeNode.getLocation());
2275 }
2276
2277
2278 ListMultimap<Name, Node<Payload, PropertyPayload>> children = LinkedListMultimap.create();
2279 for (Node<Payload, PropertyPayload> child : childrenByName.values()) {
2280 if (child == nodeToBeMoved) continue;
2281 if (before != null && child.getSegment().equals(before)) {
2282 children.put(nodeToBeMoved.getName(), nodeToBeMoved);
2283 }
2284 children.put(child.getName(), child);
2285 }
2286 if (before == null) {
2287 children.put(nodeToBeMoved.getName(), nodeToBeMoved);
2288 }
2289
2290
2291 this.childrenByName = children;
2292 this.markAsChanged();
2293
2294
2295 Name movedName = nodeToBeMoved.getName();
2296 List<Node<Payload, PropertyPayload>> childrenWithName = childrenByName.get(movedName);
2297 int snsIndex = 1;
2298 for (Node<Payload, PropertyPayload> sns : childrenWithName) {
2299 if (sns.getSegment().getIndex() != snsIndex) {
2300
2301 Path.Segment newSegment = cache.pathFactory.createSegment(movedName, snsIndex);
2302 sns.updateLocation(newSegment);
2303 sns.markAsChanged();
2304 }
2305 ++snsIndex;
2306 }
2307 }
2308
2309
2310
2311
2312
2313 protected void remove() {
2314 remove(true);
2315 }
2316
2317
2318
2319
2320
2321
2322
2323
2324 protected void remove( boolean markParentAsChanged ) {
2325 assert !isStale();
2326 assert this.parent != null;
2327 assert this.parent.isLoaded();
2328 assert this.parent.childrenByName != null;
2329 assert this.parent.childrenByName != cache.NO_CHILDREN;
2330 if (markParentAsChanged) {
2331 this.parent.markAsChanged();
2332 this.markAsChanged();
2333 }
2334 Name name = getName();
2335 List<Node<Payload, PropertyPayload>> childrenWithSameName = this.parent.childrenByName.get(name);
2336 this.parent = null;
2337 if (childrenWithSameName.size() == 1) {
2338
2339 childrenWithSameName.clear();
2340 } else {
2341
2342 int lastIndex = childrenWithSameName.size() - 1;
2343 assert lastIndex > 0;
2344 int index = childrenWithSameName.indexOf(this);
2345
2346 childrenWithSameName.remove(index);
2347 if (index != lastIndex) {
2348
2349 for (int i = index; i != lastIndex; ++i) {
2350 Node<Payload, PropertyPayload> sibling = childrenWithSameName.get(i);
2351 Path.Segment segment = cache.pathFactory.createSegment(name, i + 1);
2352 sibling.updateLocation(segment);
2353 }
2354 }
2355 }
2356 }
2357
2358
2359
2360
2361
2362
2363
2364 public void destroy() {
2365 assert !isStale();
2366
2367 cache.authorizer.checkPermissions(getPath(), Action.REMOVE);
2368
2369 final Node<Payload, PropertyPayload> parent = this.parent;
2370 cache.nodeOperations.preRemoveChild(parent, this);
2371
2372 remove();
2373
2374 cache.recordDelete(this);
2375 cache.nodeOperations.postRemoveChild(parent, this);
2376 }
2377
2378 public final boolean isRoot() {
2379 return this.parent == null;
2380 }
2381
2382
2383
2384
2385
2386
2387 public boolean isStale() {
2388
2389 Node<?, ?> node = this;
2390 while (node.parent != null) {
2391 node = node.parent;
2392 }
2393
2394 return node != cache.root;
2395 }
2396
2397
2398
2399
2400
2401
2402 public Node<Payload, PropertyPayload> getParent() {
2403 assert !isStale();
2404 return parent;
2405 }
2406
2407
2408
2409
2410 public final NodeId getNodeId() {
2411 return nodeId;
2412 }
2413
2414
2415
2416
2417
2418
2419 public Name getName() {
2420 return location.getPath().getLastSegment().getName();
2421 }
2422
2423
2424
2425
2426
2427
2428 public final Path.Segment getSegment() {
2429 return location.getPath().getLastSegment();
2430 }
2431
2432
2433
2434
2435
2436
2437 public final Path getPath() {
2438 return location.getPath();
2439 }
2440
2441
2442
2443
2444
2445
2446 public final Location getLocation() {
2447 return location;
2448 }
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459 public Node<Payload, PropertyPayload> createChild( Name name ) {
2460 CheckArg.isNotNull(name, "name");
2461 return doCreateChild(name, null, null);
2462 }
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475 public Node<Payload, PropertyPayload> createChild( Name name,
2476 Property... properties ) {
2477 CheckArg.isNotNull(name, "name");
2478 CheckArg.isNotNull(properties, "properties");
2479 return doCreateChild(name, null, properties);
2480 }
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493 public Node<Payload, PropertyPayload> createChild( Name name,
2494 Collection<Property> idProperties ) {
2495 CheckArg.isNotNull(name, "name");
2496 CheckArg.isNotEmpty(idProperties, "idProperties");
2497 return doCreateChild(name, idProperties, null);
2498 }
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512 public Node<Payload, PropertyPayload> createChild( Name name,
2513 Collection<Property> idProperties,
2514 Property... remainingProperties ) {
2515 CheckArg.isNotNull(name, "name");
2516 CheckArg.isNotEmpty(idProperties, "idProperties");
2517 return doCreateChild(name, idProperties, remainingProperties);
2518 }
2519
2520 private Node<Payload, PropertyPayload> doCreateChild( Name name,
2521 Collection<Property> idProperties,
2522 Property[] remainingProperties ) throws ValidationException {
2523 assert !isStale();
2524
2525
2526 Path path = getPath();
2527 cache.authorizer.checkPermissions(path, Action.ADD_NODE);
2528
2529
2530 load();
2531
2532
2533 List<Node<Payload, PropertyPayload>> currentChildren = childrenByName.get(name);
2534 Path newPath = cache.pathFactory.create(path, name, currentChildren.size() + 1);
2535 Location newChild = idProperties != null && !idProperties.isEmpty() ? Location.create(newPath, idProperties) : Location.create(newPath);
2536
2537
2538 Map<Name, PropertyInfo<PropertyPayload>> newProperties = new HashMap<Name, PropertyInfo<PropertyPayload>>();
2539 if (idProperties != null) {
2540 for (Property idProp : idProperties) {
2541 PropertyInfo<PropertyPayload> info = new PropertyInfo<PropertyPayload>(idProp, idProp.isMultiple(),
2542 Status.NEW, null);
2543 newProperties.put(info.getName(), info);
2544 }
2545 }
2546 if (remainingProperties != null) {
2547 for (Property property : remainingProperties) {
2548 PropertyInfo<PropertyPayload> info2 = new PropertyInfo<PropertyPayload>(property, property.isMultiple(),
2549 Status.NEW, null);
2550 newProperties.put(info2.getName(), info2);
2551 }
2552 }
2553
2554
2555 cache.nodeOperations.preCreateChild(this, newPath.getLastSegment(), newProperties);
2556
2557
2558 Status statusBefore = this.status;
2559 boolean changedBelowBefore = this.changedBelow;
2560
2561
2562 Node<Payload, PropertyPayload> child = cache.createNode(this, cache.idFactory.create(), newChild);
2563 child.markAsNew();
2564 if (childrenByName == cache.NO_CHILDREN) {
2565 childrenByName = LinkedListMultimap.create();
2566 }
2567 childrenByName.put(name, child);
2568
2569
2570 assert child.properties == null;
2571 child.properties = newProperties;
2572 child.childrenByName = cache.NO_CHILDREN;
2573
2574 try {
2575
2576 cache.nodeOperations.postCreateChild(this, child, child.properties);
2577
2578
2579 Graph.Create<Graph.Batch> create = cache.operations.create(newChild.getPath());
2580 if (!child.properties.isEmpty()) {
2581
2582 for (PropertyInfo<PropertyPayload> property : child.properties.values()) {
2583 create.with(property.getProperty());
2584 }
2585 }
2586 create.and();
2587 } catch (ValidationException e) {
2588
2589 if (childrenByName.size() == 1) {
2590 childrenByName = cache.NO_CHILDREN;
2591 } else {
2592 childrenByName.remove(child.getName(), child);
2593 }
2594 this.status = statusBefore;
2595 this.changedBelow = changedBelowBefore;
2596 throw e;
2597 }
2598
2599 cache.nodes.put(child.getNodeId(), child);
2600
2601 return child;
2602 }
2603
2604
2605
2606
2607
2608
2609
2610
2611 public boolean hasChild( Path.Segment segment ) {
2612 return hasChild(segment.getName(), segment.getIndex());
2613 }
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623 public boolean hasChild( Name name,
2624 int sns ) {
2625 load();
2626 List<Node<Payload, PropertyPayload>> children = childrenByName.get(name);
2627 return children.size() >= sns;
2628 }
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638 public Node<Payload, PropertyPayload> getChild( Path.Segment segment ) {
2639 return getChild(segment.getName(), segment.getIndex());
2640 }
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650 public Node<Payload, PropertyPayload> getFirstChild( Name name ) {
2651 return getChild(name, 1);
2652 }
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663 public Node<Payload, PropertyPayload> getChild( Name name,
2664 int sns ) {
2665 load();
2666 List<Node<Payload, PropertyPayload>> children = childrenByName.get(name);
2667 try {
2668 return children.get(sns - 1);
2669 } catch (IndexOutOfBoundsException e) {
2670 Path missingPath = cache.pathFactory.create(getPath(), name, sns);
2671 throw new PathNotFoundException(Location.create(missingPath), getPath());
2672 }
2673 }
2674
2675
2676
2677
2678
2679
2680
2681
2682 public Iterable<Node<Payload, PropertyPayload>> getChildren( Name name ) {
2683 load();
2684 final Collection<Node<Payload, PropertyPayload>> children = childrenByName.get(name);
2685 return new Iterable<Node<Payload, PropertyPayload>>() {
2686 public Iterator<Node<Payload, PropertyPayload>> iterator() {
2687 return new ReadOnlyIterator<Node<Payload, PropertyPayload>>(children.iterator());
2688 }
2689 };
2690 }
2691
2692
2693
2694
2695
2696
2697
2698 public Iterable<Node<Payload, PropertyPayload>> getChildren() {
2699 load();
2700 final Collection<Node<Payload, PropertyPayload>> children = childrenByName.values();
2701 return new Iterable<Node<Payload, PropertyPayload>>() {
2702 public Iterator<Node<Payload, PropertyPayload>> iterator() {
2703 return new ReadOnlyIterator<Node<Payload, PropertyPayload>>(children.iterator());
2704 }
2705 };
2706 }
2707
2708
2709
2710
2711
2712
2713
2714 public int getChildrenCount() {
2715 load();
2716 return childrenByName.size();
2717 }
2718
2719
2720
2721
2722
2723
2724
2725
2726 public int getChildrenCount( Name name ) {
2727 load();
2728 return childrenByName.get(name).size();
2729 }
2730
2731
2732
2733
2734
2735
2736
2737 public boolean isLeaf() {
2738 load();
2739 return childrenByName.isEmpty();
2740 }
2741
2742
2743
2744
2745
2746
2747
2748 public PropertyInfo<PropertyPayload> getProperty( Name name ) {
2749 load();
2750 return properties.get(name);
2751 }
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762 public PropertyInfo<PropertyPayload> setProperty( Property property,
2763 boolean isMultiValued,
2764 PropertyPayload payload ) {
2765 assert !isStale();
2766 cache.authorizer.checkPermissions(getPath(), Action.SET_PROPERTY);
2767
2768 load();
2769 if (properties == cache.NO_PROPERTIES) {
2770 properties = new HashMap<Name, PropertyInfo<PropertyPayload>>();
2771 }
2772
2773 Name name = property.getName();
2774 PropertyInfo<PropertyPayload> previous = properties.get(name);
2775 Status status = null;
2776 if (previous != null) {
2777 status = previous.getStatus();
2778 if (status == Status.UNCHANGED) status = Status.CHANGED;
2779 } else {
2780 status = Status.NEW;
2781 }
2782 PropertyInfo<PropertyPayload> info = new PropertyInfo<PropertyPayload>(property, isMultiValued, status, payload);
2783 cache.nodeOperations.preSetProperty(this, property.getName(), info);
2784 properties.put(name, info);
2785 cache.operations.set(property).on(location);
2786 markAsChanged();
2787 cache.nodeOperations.postSetProperty(this, property.getName(), previous);
2788 return previous;
2789 }
2790
2791
2792
2793
2794
2795
2796
2797 public PropertyInfo<PropertyPayload> removeProperty( Name name ) {
2798 assert !isStale();
2799 cache.authorizer.checkPermissions(getPath(), Action.REMOVE);
2800
2801 load();
2802 if (!properties.containsKey(name)) return null;
2803 cache.nodeOperations.preRemoveProperty(this, name);
2804 PropertyInfo<PropertyPayload> results = properties.remove(name);
2805 markAsChanged();
2806 cache.operations.remove(name).on(location);
2807 cache.nodeOperations.postRemoveProperty(this, name, results);
2808 return results;
2809 }
2810
2811
2812
2813
2814
2815
2816 public Set<Name> getPropertyNames() {
2817 load();
2818 return properties.keySet();
2819 }
2820
2821
2822
2823
2824
2825
2826 public Collection<PropertyInfo<PropertyPayload>> getProperties() {
2827 load();
2828 return properties.values();
2829 }
2830
2831
2832
2833
2834
2835
2836 public int getPropertyCount() {
2837 load();
2838 return properties.size();
2839 }
2840
2841 public boolean isAtOrBelow( Node<Payload, PropertyPayload> other ) {
2842 Node<Payload, PropertyPayload> node = this;
2843 while (node != null) {
2844 if (node == other) return true;
2845 node = node.getParent();
2846 }
2847 return false;
2848 }
2849
2850
2851
2852
2853 public Payload getPayload() {
2854 load();
2855 return payload;
2856 }
2857
2858
2859
2860
2861 public void setPayload( Payload payload ) {
2862 this.payload = payload;
2863 }
2864
2865
2866
2867
2868
2869
2870 @Override
2871 public final int hashCode() {
2872 return nodeId.hashCode();
2873 }
2874
2875
2876
2877
2878
2879
2880 @SuppressWarnings( "unchecked" )
2881 @Override
2882 public boolean equals( Object obj ) {
2883 if (obj == this) return true;
2884 if (obj instanceof Node) {
2885 Node<Payload, PropertyPayload> that = (Node<Payload, PropertyPayload>)obj;
2886 if (this.isStale() || that.isStale()) return false;
2887 if (!this.nodeId.equals(that.nodeId)) return false;
2888 return this.location.isSame(that.location);
2889 }
2890 return false;
2891 }
2892
2893
2894
2895
2896
2897
2898
2899 public String getString( NamespaceRegistry registry ) {
2900 return "Cached node <" + nodeId + "> at " + location.getString(registry);
2901 }
2902
2903
2904
2905
2906
2907
2908 @Override
2909 public String toString() {
2910 return getString(null);
2911 }
2912
2913
2914
2915
2916
2917
2918 public void onLoadedNodes( NodeVisitor<Payload, PropertyPayload> visitor ) {
2919 if (this.isLoaded()) {
2920
2921
2922 LinkedList<Node<Payload, PropertyPayload>> queue = new LinkedList<Node<Payload, PropertyPayload>>();
2923 queue.add(this);
2924 while (!queue.isEmpty()) {
2925 Node<Payload, PropertyPayload> node = queue.poll();
2926
2927 Iterator<Node<Payload, PropertyPayload>> iter = node.getChildren().iterator();
2928
2929 if (visitor.visit(node)) {
2930
2931 int index = -1;
2932 while (iter.hasNext()) {
2933 Node<Payload, PropertyPayload> child = iter.next();
2934 if (child.isLoaded()) {
2935 queue.add(++index, child);
2936 }
2937 }
2938 }
2939 }
2940 }
2941 visitor.finish();
2942 }
2943
2944
2945
2946
2947
2948
2949 public void onCachedNodes( NodeVisitor<Payload, PropertyPayload> visitor ) {
2950
2951
2952 LinkedList<Node<Payload, PropertyPayload>> queue = new LinkedList<Node<Payload, PropertyPayload>>();
2953 queue.add(this);
2954 while (!queue.isEmpty()) {
2955 Node<Payload, PropertyPayload> node = queue.poll();
2956 if (!node.isLoaded()) {
2957 visitor.visit(node);
2958 continue;
2959 }
2960
2961 Iterator<Node<Payload, PropertyPayload>> iter = node.getChildren().iterator();
2962
2963 if (visitor.visit(node)) {
2964
2965 int index = -1;
2966 while (iter.hasNext()) {
2967 Node<Payload, PropertyPayload> child = iter.next();
2968 queue.add(++index, child);
2969 }
2970 }
2971 }
2972 visitor.finish();
2973 }
2974
2975
2976
2977
2978
2979
2980 public void onChangedNodes( NodeVisitor<Payload, PropertyPayload> visitor ) {
2981 if (this.isChanged(true)) {
2982
2983 LinkedList<Node<Payload, PropertyPayload>> changedNodes = new LinkedList<Node<Payload, PropertyPayload>>();
2984 changedNodes.add(this);
2985 while (!changedNodes.isEmpty()) {
2986 Node<Payload, PropertyPayload> node = changedNodes.poll();
2987
2988 boolean visitChildren = true;
2989 if (node.isChanged(false)) {
2990 visitChildren = visitor.visit(node);
2991 }
2992 if (visitChildren && node.isChanged(true)) {
2993
2994 int index = -1;
2995 Iterator<Node<Payload, PropertyPayload>> iter = node.getChildren().iterator();
2996 while (iter.hasNext()) {
2997 Node<Payload, PropertyPayload> child = iter.next();
2998 if (node.isChanged(true)) {
2999 changedNodes.add(++index, child);
3000 }
3001 }
3002 }
3003 }
3004 }
3005 visitor.finish();
3006 }
3007
3008
3009
3010
3011
3012
3013
3014 public StructureSnapshot<PropertyPayload> getSnapshot( final boolean pathsOnly ) {
3015 final List<Snapshot<PropertyPayload>> snapshots = new ArrayList<Snapshot<PropertyPayload>>();
3016 onCachedNodes(new NodeVisitor<Payload, PropertyPayload>() {
3017 @Override
3018 public boolean visit( Node<Payload, PropertyPayload> node ) {
3019 snapshots.add(new Snapshot<PropertyPayload>(node, pathsOnly, true));
3020 return node.isLoaded();
3021 }
3022 });
3023 return new StructureSnapshot<PropertyPayload>(cache.context().getNamespaceRegistry(),
3024 Collections.unmodifiableList(snapshots));
3025 }
3026 }
3027
3028 public static enum Status {
3029 NEW,
3030 CHANGED,
3031 UNCHANGED,
3032 COPIED;
3033 }
3034
3035 public static final class PropertyInfo<PropertyPayload> {
3036 private final Property property;
3037 private final Status status;
3038 private final boolean multiValued;
3039 private final PropertyPayload payload;
3040
3041 public PropertyInfo( Property property,
3042 boolean multiValued,
3043 Status status,
3044 PropertyPayload payload ) {
3045 assert property != null;
3046 assert status != null;
3047 this.property = property;
3048 this.status = status;
3049 this.multiValued = multiValued;
3050 this.payload = payload;
3051 }
3052
3053
3054
3055
3056
3057
3058 public Status getStatus() {
3059 return status;
3060 }
3061
3062
3063
3064
3065
3066
3067 public boolean isModified() {
3068 return status != Status.UNCHANGED && status != Status.NEW;
3069 }
3070
3071
3072
3073
3074
3075
3076 public boolean isNew() {
3077 return status == Status.NEW;
3078 }
3079
3080
3081
3082
3083
3084
3085 public Name getName() {
3086 return property.getName();
3087 }
3088
3089
3090
3091
3092
3093
3094 public Property getProperty() {
3095 return property;
3096 }
3097
3098
3099
3100
3101
3102
3103 public PropertyPayload getPayload() {
3104 return payload;
3105 }
3106
3107
3108
3109
3110
3111
3112 public boolean isMultiValued() {
3113 return multiValued;
3114 }
3115
3116
3117
3118
3119
3120
3121 @Override
3122 public int hashCode() {
3123 return getName().hashCode();
3124 }
3125
3126
3127
3128
3129
3130
3131 @Override
3132 public boolean equals( Object obj ) {
3133 if (obj == this) return true;
3134 if (obj instanceof PropertyInfo<?>) {
3135 PropertyInfo<?> that = (PropertyInfo<?>)obj;
3136 return getName().equals(that.getName());
3137 }
3138 return false;
3139 }
3140
3141
3142
3143
3144
3145
3146 @Override
3147 public String toString() {
3148 StringBuilder sb = new StringBuilder();
3149 sb.append(getName());
3150
3151 if (property.isSingle()) {
3152 sb.append(" with value ");
3153 } else {
3154 sb.append(" with values ");
3155 }
3156 sb.append(Arrays.asList(property.getValuesAsArray()));
3157 return sb.toString();
3158 }
3159 }
3160
3161
3162
3163
3164
3165
3166
3167 @NotThreadSafe
3168 public static abstract class NodeVisitor<NodePayload, PropertyPayloadType> {
3169
3170
3171
3172
3173
3174
3175 public abstract boolean visit( Node<NodePayload, PropertyPayloadType> node );
3176
3177
3178
3179
3180
3181 public void finish() {
3182 }
3183 }
3184
3185
3186
3187
3188
3189
3190
3191
3192 @NotThreadSafe
3193 protected abstract class LoadNodesVisitor extends NodeVisitor<Payload, PropertyPayload> {
3194 private Graph.Batch batch = GraphSession.this.store.batch();
3195 private List<Node<Payload, PropertyPayload>> nodesToLoad = new LinkedList<Node<Payload, PropertyPayload>>();
3196
3197
3198
3199
3200
3201
3202
3203 protected void load( Node<Payload, PropertyPayload> node ) {
3204 if (node != null && !node.isLoaded() && !node.isNew()) {
3205 nodesToLoad.add(node);
3206 batch.read(node.getLocation());
3207 }
3208 }
3209
3210
3211
3212
3213
3214
3215 @Override
3216 public void finish() {
3217 super.finish();
3218 if (!nodesToLoad.isEmpty()) {
3219
3220 Results results = batch.execute();
3221
3222 for (Node<Payload, PropertyPayload> childToBeRead : nodesToLoad) {
3223 org.modeshape.graph.Node persistentNode = results.getNode(childToBeRead.getLocation());
3224 nodeOperations.materialize(persistentNode, childToBeRead);
3225 finishNodeAfterLoading(childToBeRead);
3226 }
3227 }
3228 }
3229
3230
3231
3232
3233
3234
3235 protected void finishNodeAfterLoading( Node<Payload, PropertyPayload> node ) {
3236
3237 }
3238 }
3239
3240
3241
3242
3243
3244 @NotThreadSafe
3245 protected class LoadAllChildrenVisitor extends LoadNodesVisitor {
3246 private List<Node<Payload, PropertyPayload>> parentsVisited = new LinkedList<Node<Payload, PropertyPayload>>();
3247
3248
3249
3250
3251
3252
3253 @Override
3254 public boolean visit( Node<Payload, PropertyPayload> node ) {
3255 parentsVisited.add(node);
3256 Iterator<Node<Payload, PropertyPayload>> iter = node.getChildren().iterator();
3257 while (iter.hasNext()) {
3258 load(iter.next());
3259 }
3260 return true;
3261 }
3262
3263
3264
3265
3266
3267
3268 @Override
3269 public void finish() {
3270 super.finish();
3271 for (Node<Payload, PropertyPayload> parent : parentsVisited) {
3272 finishParentAfterLoading(parent);
3273 }
3274 }
3275
3276
3277
3278
3279
3280
3281 protected void finishParentAfterLoading( Node<Payload, PropertyPayload> parentNode ) {
3282
3283 }
3284 }
3285
3286 protected static final class Snapshot<PropertyPayload> {
3287 private final Location location;
3288 private final boolean isLoaded;
3289 private final boolean isChanged;
3290 private final Collection<PropertyInfo<PropertyPayload>> properties;
3291 private final NodeId id;
3292
3293 protected Snapshot( Node<?, PropertyPayload> node,
3294 boolean pathsOnly,
3295 boolean includeProperties ) {
3296 this.location = pathsOnly && node.getLocation().hasIdProperties() ? Location.create(node.getLocation().getPath()) : node.getLocation();
3297 this.isLoaded = node.isLoaded();
3298 this.isChanged = node.isChanged(false);
3299 this.id = node.getNodeId();
3300 this.properties = includeProperties ? node.getProperties() : null;
3301 }
3302
3303
3304
3305
3306 public Location getLocation() {
3307 return location;
3308 }
3309
3310
3311
3312
3313 public boolean isChanged() {
3314 return isChanged;
3315 }
3316
3317
3318
3319
3320 public boolean isLoaded() {
3321 return isLoaded;
3322 }
3323
3324
3325
3326
3327 public NodeId getId() {
3328 return id;
3329 }
3330
3331
3332
3333
3334 public Collection<PropertyInfo<PropertyPayload>> getProperties() {
3335 return properties;
3336 }
3337 }
3338
3339
3340
3341
3342
3343
3344
3345 @Immutable
3346 public static final class StructureSnapshot<PropertyPayload> implements Iterable<Snapshot<PropertyPayload>> {
3347 private final List<Snapshot<PropertyPayload>> snapshotsInPreOrder;
3348 private final NamespaceRegistry registry;
3349
3350 protected StructureSnapshot( NamespaceRegistry registry,
3351 List<Snapshot<PropertyPayload>> snapshotsInPreOrder ) {
3352 assert snapshotsInPreOrder != null;
3353 this.snapshotsInPreOrder = snapshotsInPreOrder;
3354 this.registry = registry;
3355 }
3356
3357
3358
3359
3360
3361
3362 public Iterator<Snapshot<PropertyPayload>> iterator() {
3363 return snapshotsInPreOrder.iterator();
3364 }
3365
3366
3367
3368
3369
3370
3371 public List<Snapshot<PropertyPayload>> getSnapshotsInPreOrder() {
3372 return snapshotsInPreOrder;
3373 }
3374
3375
3376
3377
3378
3379
3380 @Override
3381 public String toString() {
3382 int maxLength = 0;
3383 for (Snapshot<PropertyPayload> snapshot : this) {
3384 String path = snapshot.getLocation().getPath().getString(registry);
3385 maxLength = Math.max(maxLength, path.length());
3386 }
3387 StringBuilder sb = new StringBuilder();
3388 for (Snapshot<PropertyPayload> snapshot : this) {
3389 Location location = snapshot.getLocation();
3390 sb.append(StringUtil.justifyLeft(location.getPath().getString(registry), maxLength, ' '));
3391
3392 sb.append(StringUtil.justifyRight(snapshot.getId().toString(), 10, ' '));
3393
3394 if (snapshot.isChanged()) sb.append(" (*)");
3395 else if (!snapshot.isLoaded()) sb.append(" (-)");
3396 else sb.append(" ");
3397
3398 if (location.hasIdProperties()) {
3399 sb.append(" ");
3400 if (location.getIdProperties().size() == 1 && location.getUuid() != null) {
3401 sb.append(location.getUuid());
3402 } else {
3403 boolean first = true;
3404 sb.append('[');
3405 for (Property property : location) {
3406 sb.append(property.getString(registry));
3407 if (first) first = false;
3408 else sb.append(", ");
3409 }
3410 sb.append(']');
3411 }
3412 }
3413
3414 if (snapshot.getProperties() != null) {
3415 boolean first = true;
3416 sb.append(" {");
3417 for (PropertyInfo<?> info : snapshot.getProperties()) {
3418 if (first) first = false;
3419 else sb.append("} {");
3420 sb.append(info.getProperty().getString(registry));
3421 }
3422 sb.append("}");
3423 }
3424 sb.append("\n");
3425 }
3426 return sb.toString();
3427 }
3428 }
3429
3430 @NotThreadSafe
3431 protected static final class RefreshState<Payload, PropertyPayload> {
3432 private final Set<Node<Payload, PropertyPayload>> refresh = new HashSet<Node<Payload, PropertyPayload>>();
3433
3434 public void markAsRequiringRefresh( Node<Payload, PropertyPayload> node ) {
3435 refresh.add(node);
3436 }
3437
3438 public boolean requiresRefresh( Node<Payload, PropertyPayload> node ) {
3439 return refresh.contains(node);
3440 }
3441
3442 public Set<Node<Payload, PropertyPayload>> getNodesToBeRefreshed() {
3443 return refresh;
3444 }
3445 }
3446
3447 @NotThreadSafe
3448 protected final static class Dependencies {
3449 private Set<NodeId> requireChangesTo;
3450 private NodeId movedFrom;
3451
3452 public Dependencies() {
3453 }
3454
3455
3456
3457
3458 public NodeId getMovedFrom() {
3459 return movedFrom;
3460 }
3461
3462
3463
3464
3465
3466
3467
3468 public void setMovedFrom( NodeId movedFrom ) {
3469 if (this.movedFrom == null) this.movedFrom = movedFrom;
3470 }
3471
3472
3473
3474
3475 public Set<NodeId> getRequireChangesTo() {
3476 return requireChangesTo != null ? requireChangesTo : Collections.<NodeId>emptySet();
3477 }
3478
3479
3480
3481
3482 public void addRequireChangesTo( NodeId other ) {
3483 if (other == null) return;
3484 if (requireChangesTo == null) {
3485 requireChangesTo = new HashSet<NodeId>();
3486 }
3487 requireChangesTo.add(other);
3488 }
3489 }
3490
3491
3492
3493
3494 @Immutable
3495 public final static class NodeId {
3496
3497 private final long nodeId;
3498
3499
3500
3501
3502
3503
3504 public NodeId( long nodeId ) {
3505 this.nodeId = nodeId;
3506 }
3507
3508
3509
3510
3511
3512
3513 @Override
3514 public int hashCode() {
3515 return (int)nodeId;
3516 }
3517
3518
3519
3520
3521
3522
3523 @Override
3524 public boolean equals( Object obj ) {
3525 if (obj == this) return true;
3526 if (obj instanceof NodeId) {
3527 NodeId that = (NodeId)obj;
3528 return this.nodeId == that.nodeId;
3529 }
3530 return false;
3531 }
3532
3533
3534
3535
3536
3537
3538 @Override
3539 public String toString() {
3540 return Long.toString(nodeId);
3541 }
3542 }
3543
3544 }