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.observe;
25
26 import java.util.Collections;
27 import java.util.EnumSet;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.LinkedList;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.TreeMap;
35 import net.jcip.annotations.Immutable;
36 import net.jcip.annotations.NotThreadSafe;
37 import net.jcip.annotations.ThreadSafe;
38 import org.modeshape.common.util.HashCode;
39 import org.modeshape.graph.Location;
40 import org.modeshape.graph.property.Name;
41 import org.modeshape.graph.property.Path;
42 import org.modeshape.graph.property.Property;
43 import org.modeshape.graph.request.ChangeRequest;
44 import org.modeshape.graph.request.CloneBranchRequest;
45 import org.modeshape.graph.request.CreateNodeRequest;
46 import org.modeshape.graph.request.DeleteChildrenRequest;
47 import org.modeshape.graph.request.MoveBranchRequest;
48 import org.modeshape.graph.request.RemovePropertyRequest;
49 import org.modeshape.graph.request.RenameNodeRequest;
50 import org.modeshape.graph.request.SetPropertyRequest;
51 import org.modeshape.graph.request.UpdatePropertiesRequest;
52 import org.modeshape.graph.request.UpdateValuesRequest;
53
54
55
56
57
58
59 @ThreadSafe
60 public abstract class NetChangeObserver extends ChangeObserver {
61
62
63
64
65 protected class ChangeContext {
66 private final Map<String, Map<Location, NetChangeDetails>> detailsByLocationByWorkspace = new HashMap<String, Map<Location, NetChangeDetails>>();
67
68 protected ChangeContext() {
69 }
70
71
72
73
74
75
76
77 public void delete( String workspace,
78 Location location ) {
79 Map<Location, NetChangeDetails> detailsByLocation = detailsByLocationByWorkspace.get(workspace);
80 assert detailsByLocation != null;
81 detailsByLocation.remove(location);
82 }
83
84
85
86
87
88
89
90
91 public NetChangeDetails detailsFor( String workspace,
92 Location location ) {
93 Map<Location, NetChangeDetails> detailsByLocation = detailsByLocationByWorkspace.get(workspace);
94 NetChangeDetails details = null;
95
96 if (detailsByLocation == null) {
97 detailsByLocation = new TreeMap<Location, NetChangeDetails>();
98 detailsByLocationByWorkspace.put(workspace, detailsByLocation);
99 details = new NetChangeDetails();
100 detailsByLocation.put(location, details);
101 } else {
102 details = detailsByLocation.get(location);
103
104 if (details == null) {
105 details = new NetChangeDetails();
106 detailsByLocation.put(location, details);
107 } else {
108 details = details.latest();
109 }
110 }
111 return details;
112 }
113
114
115
116
117
118
119
120
121
122
123 public void move( String workspace,
124 Location fromLocation,
125 Location toLocation,
126 Location before,
127 boolean isReorder ) {
128 if (fromLocation.equals(toLocation) && before == null) {
129
130 return;
131 }
132
133
134 Map<Location, NetChangeDetails> detailsByLocation = detailsByLocationByWorkspace.get(workspace);
135 if (detailsByLocation == null) {
136 detailsByLocation = new TreeMap<Location, NetChangeDetails>();
137 detailsByLocationByWorkspace.put(workspace, detailsByLocation);
138 }
139
140
141
142 NetChangeDetails existingBefore = detailsByLocation.get(toLocation);
143
144
145 NetChangeDetails from = detailsByLocation.remove(fromLocation);
146
147
148 NetChangeDetails after = from != null ? from : new NetChangeDetails();
149 after.movedFrom(fromLocation);
150 if (isReorder) after.movedBefore(before, isReorder);
151
152 if (existingBefore != null) {
153 existingBefore.setNextChanges(after);
154 } else {
155 detailsByLocation.put(toLocation, after);
156 }
157 }
158
159
160
161
162
163
164 public List<NetChange> getNetChanges() {
165
166 List<NetChange> netChanges = new LinkedList<NetChange>();
167 for (Map.Entry<String, Map<Location, NetChangeDetails>> byWorkspaceEntry : detailsByLocationByWorkspace.entrySet()) {
168 String workspaceName = byWorkspaceEntry.getKey();
169
170 for (Map.Entry<Location, NetChangeDetails> entry : byWorkspaceEntry.getValue().entrySet()) {
171 Location location = entry.getKey();
172 NetChangeDetails details = entry.getValue();
173 while (details != null) {
174 netChanges.add(new NetChange(workspaceName, location, details.getEventTypes(),
175 details.getAddedProperties(), details.getModifiedProperties(),
176 details.getRemovedProperties(), details.getMovedFrom(),
177 details.getMovedBefore(), details.isReorder()));
178 details = details.next();
179 }
180 }
181 }
182 return netChanges;
183 }
184 }
185
186 public enum ChangeType {
187 NODE_ADDED,
188 NODE_MOVED,
189 NODE_REMOVED,
190 PROPERTY_ADDED,
191 PROPERTY_REMOVED,
192 PROPERTY_CHANGED,
193 NODE_LOCKED,
194 NODE_UNLOCKED;
195 }
196
197 protected NetChangeObserver() {
198 }
199
200
201
202
203
204
205 @Override
206 public void notify( Changes changes ) {
207 ChangeContext changeContext = new ChangeContext();
208
209 for (ChangeRequest change : changes.getChangeRequests()) {
210 Location location = change.changedLocation();
211 assert (location.getPath() != null);
212
213
214 String workspace = change.changedWorkspace();
215 NetChangeDetails details = null;
216 Location original;
217
218
219 switch (change.getType()) {
220 case CREATE_NODE:
221 CreateNodeRequest create = (CreateNodeRequest)change;
222 details = changeContext.detailsFor(workspace, location);
223 details.addEventType(ChangeType.NODE_ADDED);
224 for (Property property : create) {
225 details.addProperty(property);
226 }
227 break;
228 case UPDATE_PROPERTIES:
229 UpdatePropertiesRequest update = (UpdatePropertiesRequest)change;
230 details = changeContext.detailsFor(workspace, location);
231 for (Map.Entry<Name, Property> entry : update.properties().entrySet()) {
232 Name propName = entry.getKey();
233 Property property = entry.getValue();
234
235 if (property != null) {
236 if (update.isNewProperty(propName)) {
237 details.addProperty(property);
238 } else {
239 details.changeProperty(property);
240 }
241 } else {
242 details.removeProperty(propName);
243 }
244 }
245 break;
246 case SET_PROPERTY:
247 SetPropertyRequest set = (SetPropertyRequest)change;
248 details = changeContext.detailsFor(workspace, location);
249 if (set.isNewProperty()) {
250 details.addProperty(set.property());
251 } else {
252 details.changeProperty(set.property());
253 }
254 break;
255 case REMOVE_PROPERTY:
256 RemovePropertyRequest remove = (RemovePropertyRequest)change;
257 details = changeContext.detailsFor(workspace, location);
258 details.removeProperty(remove.propertyName());
259 break;
260 case DELETE_BRANCH:
261
262 details = changeContext.detailsFor(workspace, location);
263 if (details.getEventTypes().contains(ChangeType.NODE_ADDED)) {
264 changeContext.delete(workspace, location);
265 } else {
266 details.addEventType(ChangeType.NODE_REMOVED);
267 }
268 break;
269 case DELETE_CHILDREN:
270 DeleteChildrenRequest delete = (DeleteChildrenRequest)change;
271 for (Location deletedChild : delete.getActualChildrenDeleted()) {
272 NetChangeDetails childDetails = changeContext.detailsFor(workspace, deletedChild);
273
274 if (childDetails.getEventTypes().contains(ChangeType.NODE_ADDED)) {
275 changeContext.delete(workspace, deletedChild);
276 } else {
277 childDetails.addEventType(ChangeType.NODE_REMOVED);
278 }
279 }
280 break;
281 case LOCK_BRANCH:
282 details = changeContext.detailsFor(workspace, location);
283 details.setLockAction(LockAction.LOCKED);
284 break;
285 case UNLOCK_BRANCH:
286 details = changeContext.detailsFor(workspace, location);
287 details.setLockAction(LockAction.UNLOCKED);
288 break;
289 case COPY_BRANCH:
290 details = changeContext.detailsFor(workspace, location);
291 details.addEventType(ChangeType.NODE_ADDED);
292 break;
293 case CLONE_BRANCH:
294 CloneBranchRequest cloneRequest = (CloneBranchRequest)change;
295
296
297 for (Location removed : cloneRequest.getRemovedNodes()) {
298 NetChangeDetails removedDetails = changeContext.detailsFor(workspace, removed);
299 removedDetails.addEventType(ChangeType.NODE_REMOVED);
300 }
301
302
303 details = changeContext.detailsFor(workspace, location);
304 details.addEventType(ChangeType.NODE_ADDED);
305 break;
306
307 case MOVE_BRANCH:
308
309
310 MoveBranchRequest move = (MoveBranchRequest)change;
311 original = move.getActualLocationBefore();
312 Location before = move.before();
313 boolean isReorder = move.desiredName() == null;
314 changeContext.move(workspace, original, location, before, isReorder);
315 break;
316 case RENAME_NODE:
317
318 original = ((RenameNodeRequest)change).getActualLocationBefore();
319 changeContext.move(workspace, original, location, null, false);
320 break;
321
322 case UPDATE_VALUES:
323 UpdateValuesRequest updateValuesRequest = (UpdateValuesRequest)change;
324 details = changeContext.detailsFor(workspace, location);
325 if (!updateValuesRequest.addedValues().isEmpty() || !updateValuesRequest.removedValues().isEmpty()) {
326 assert (updateValuesRequest.getActualProperty() != null);
327
328 if (updateValuesRequest.isNewProperty()) {
329 details.addEventType(ChangeType.PROPERTY_ADDED);
330 details.addProperty(updateValuesRequest.getActualProperty());
331 } else {
332 details.addEventType(ChangeType.PROPERTY_CHANGED);
333 details.changeProperty(updateValuesRequest.getActualProperty());
334 }
335 } else if (details.getEventTypes().isEmpty()) {
336
337 changeContext.delete(workspace, location);
338 }
339 break;
340 case CREATE_WORKSPACE:
341 details = changeContext.detailsFor(workspace, location);
342 details.addEventType(ChangeType.NODE_ADDED);
343 break;
344 case DESTROY_WORKSPACE:
345 details = changeContext.detailsFor(workspace, location);
346 details.addEventType(ChangeType.NODE_REMOVED);
347 break;
348 case CLONE_WORKSPACE:
349 details = changeContext.detailsFor(workspace, location);
350 details.addEventType(ChangeType.NODE_ADDED);
351 break;
352 default:
353
354 break;
355 }
356 }
357
358
359 List<NetChange> netChanges = changeContext.getNetChanges();
360 if (!netChanges.isEmpty()) {
361
362 notify(new NetChanges(changes, netChanges));
363 }
364 }
365
366
367
368
369
370
371 protected abstract void notify( NetChanges netChanges );
372
373
374
375
376
377 @Immutable
378 public static final class NetChanges extends Changes {
379 private static final long serialVersionUID = 1L;
380
381 private final List<NetChange> netChanges;
382
383 public NetChanges( Changes changes,
384 List<NetChange> netChanges ) {
385 super(changes);
386 assert netChanges != null;
387 assert !netChanges.isEmpty();
388 this.netChanges = Collections.unmodifiableList(netChanges);
389 }
390
391
392
393
394
395
396 public List<NetChange> getNetChanges() {
397 return this.netChanges;
398 }
399
400
401
402
403
404
405 @Override
406 public String toString() {
407 if (processId.length() != 0) {
408 return getTimestamp() + " @" + getUserName() + " [" + getSourceName() + "] - " + netChanges.size() + " changes";
409 }
410 return getTimestamp() + " @" + getUserName() + " #" + getProcessId() + " [" + getSourceName() + "] - "
411 + netChanges.size() + " changes";
412 }
413 }
414
415
416
417
418 @Immutable
419 public static final class NetChange {
420
421 private final String workspaceName;
422 private final Location location;
423 private final EnumSet<ChangeType> eventTypes;
424 private final Set<Property> addedProperties;
425 private final Set<Property> modifiedProperties;
426 private final Set<Property> addedOrModifiedProperties;
427 private final Set<Name> removedProperties;
428 private final int hc;
429 private final Location movedFrom;
430 private final Location movedBefore;
431 private final boolean isReorder;
432
433 public NetChange( String workspaceName,
434 Location location,
435 EnumSet<ChangeType> eventTypes,
436 Set<Property> addedProperties,
437 Set<Property> modifiedProperties,
438 Set<Name> removedProperties,
439 Location movedFrom,
440 Location movedBefore,
441 boolean isReorder ) {
442 assert workspaceName != null;
443 assert location != null;
444 this.workspaceName = workspaceName;
445 this.location = location;
446 this.hc = HashCode.compute(this.workspaceName, this.location);
447 this.eventTypes = eventTypes;
448 Set<Property> addedOrModified = null;
449 if (addedProperties == null) {
450 addedProperties = Collections.emptySet();
451 addedOrModified = modifiedProperties;
452 } else {
453 addedOrModified = addedProperties;
454 }
455 if (modifiedProperties == null) {
456 if (addedOrModified == null) addedOrModified = Collections.emptySet();
457 modifiedProperties = Collections.emptySet();
458 } else {
459 if (addedOrModified == null) {
460 addedOrModified = modifiedProperties;
461 } else {
462 addedOrModified = new HashSet<Property>(modifiedProperties);
463 addedOrModified.addAll(addedProperties);
464 }
465 }
466 if (removedProperties == null) removedProperties = Collections.emptySet();
467 this.addedProperties = Collections.unmodifiableSet(addedProperties);
468 this.modifiedProperties = Collections.unmodifiableSet(modifiedProperties);
469 this.removedProperties = Collections.unmodifiableSet(removedProperties);
470 this.addedOrModifiedProperties = Collections.unmodifiableSet(addedOrModified);
471 this.movedFrom = movedFrom;
472 this.movedBefore = movedBefore;
473 this.isReorder = isReorder;
474 }
475
476
477
478
479 public Location getLocation() {
480 return this.location;
481 }
482
483
484
485
486
487
488 public Location getOriginalLocation() {
489 return this.movedFrom;
490 }
491
492
493
494
495
496
497
498 public Location getMovedBefore() {
499 return movedBefore;
500 }
501
502
503
504
505
506
507 public boolean isReorder() {
508 return isReorder;
509 }
510
511
512
513
514 public Path getPath() {
515 return this.location.getPath();
516 }
517
518
519
520
521 public String getRepositoryWorkspaceName() {
522 return this.workspaceName;
523 }
524
525
526
527
528 public Set<Property> getAddedProperties() {
529 return this.addedProperties;
530 }
531
532
533
534
535 public Set<Property> getModifiedProperties() {
536 return this.modifiedProperties;
537 }
538
539
540
541
542
543
544 public Set<Property> getAddedOrModifiedProperties() {
545 return this.addedOrModifiedProperties;
546 }
547
548
549
550
551 public Set<Name> getRemovedProperties() {
552 return this.removedProperties;
553 }
554
555
556
557
558 @Override
559 public int hashCode() {
560 return this.hc;
561 }
562
563
564
565
566
567
568
569 public boolean includesAllOf( ChangeType... jcrEventTypes ) {
570 for (ChangeType jcrEventType : jcrEventTypes) {
571 if (!this.eventTypes.contains(jcrEventType)) return false;
572 }
573 return true;
574 }
575
576
577
578
579
580
581
582 public boolean includes( ChangeType... jcrEventTypes ) {
583 for (ChangeType jcrEventType : jcrEventTypes) {
584 if (this.eventTypes.contains(jcrEventType)) return true;
585 }
586 return false;
587 }
588
589 public boolean isSameNode( NetChange that ) {
590 if (that == this) return true;
591 if (this.hc != that.hc) return false;
592 if (!this.workspaceName.equals(that.workspaceName)) return false;
593 if (!this.location.isSame(that.location)) return false;
594 return true;
595 }
596
597
598
599
600
601
602
603 public boolean isPropertyModified( String property ) {
604 return this.modifiedProperties.contains(property);
605 }
606
607
608
609
610
611
612
613 public boolean isPropertyRemoved( String property ) {
614 return this.removedProperties.contains(property);
615 }
616
617
618
619
620 @Override
621 public boolean equals( Object obj ) {
622 if (obj == this) return true;
623 if (obj instanceof NetChange) {
624 NetChange that = (NetChange)obj;
625 if (!this.isSameNode(that)) return false;
626 if (this.eventTypes != that.eventTypes) return false;
627 return true;
628 }
629 return false;
630 }
631
632
633
634
635 @Override
636 public String toString() {
637 return this.workspaceName + "=>" + this.location;
638 }
639 }
640
641 private enum LockAction {
642 LOCKED,
643 UNLOCKED;
644 }
645
646
647
648
649 @NotThreadSafe
650 private static class NetChangeDetails {
651
652 private final Set<Property> modifiedProperties = new HashSet<Property>();
653 private final Set<Property> addedProperties = new HashSet<Property>();
654 private final Set<Name> removedProperties = new HashSet<Name>();
655 private EnumSet<ChangeType> eventTypes = EnumSet.noneOf(ChangeType.class);
656 private Location movedFrom;
657 private Location movedBefore;
658 private boolean reorder;
659 private NetChangeDetails nextChanges;
660
661 protected NetChangeDetails() {
662 }
663
664 public void setLockAction( LockAction lockAction ) {
665 switch (lockAction) {
666 case LOCKED:
667
668 eventTypes.add(ChangeType.NODE_LOCKED);
669 eventTypes.remove(ChangeType.NODE_UNLOCKED);
670 break;
671 case UNLOCKED:
672 if (!eventTypes.remove(ChangeType.NODE_LOCKED)) {
673
674 eventTypes.add(ChangeType.NODE_UNLOCKED);
675 }
676 break;
677 }
678 }
679
680 public void setNextChanges( NetChangeDetails details ) {
681 this.nextChanges = details;
682 }
683
684 public NetChangeDetails next() {
685 return nextChanges;
686 }
687
688 public NetChangeDetails latest() {
689 return nextChanges != null ? nextChanges.latest() : this;
690 }
691
692 public void movedFrom( Location originalLocation ) {
693 this.eventTypes.add(ChangeType.NODE_MOVED);
694 this.movedFrom = originalLocation;
695 }
696
697 public void movedBefore( Location before,
698 boolean isReorder ) {
699 this.eventTypes.add(ChangeType.NODE_MOVED);
700 this.movedBefore = before;
701 this.reorder = isReorder;
702 }
703
704 public void addEventType( ChangeType eventType ) {
705 this.eventTypes.add(eventType);
706 }
707
708 public void addProperty( Property property ) {
709 this.addedProperties.add(property);
710 this.eventTypes.add(ChangeType.PROPERTY_ADDED);
711 }
712
713 public void changeProperty( Property property ) {
714
715 if (!this.addedProperties.contains(property)) {
716 this.modifiedProperties.add(property);
717 this.eventTypes.add(ChangeType.PROPERTY_CHANGED);
718 }
719 }
720
721 public void removeProperty( Name propertyName ) {
722
723 boolean handled = false;
724
725 for (Property property : this.addedProperties) {
726 if (property.getName().equals(propertyName)) {
727 handled = true;
728 this.addedProperties.remove(property);
729
730
731 if (this.addedProperties.isEmpty()) {
732 this.eventTypes.remove(ChangeType.PROPERTY_ADDED);
733 }
734
735 break;
736 }
737 }
738
739 if (!handled) {
740
741 for (Property property : this.modifiedProperties) {
742 if (property.getName().equals(propertyName)) {
743 this.modifiedProperties.remove(property);
744
745
746 if (this.modifiedProperties.isEmpty()) {
747 this.eventTypes.remove(ChangeType.PROPERTY_CHANGED);
748 }
749
750 break;
751 }
752 }
753
754
755 this.removedProperties.add(propertyName);
756 this.eventTypes.add(ChangeType.PROPERTY_REMOVED);
757 }
758 }
759
760
761
762
763 public EnumSet<ChangeType> getEventTypes() {
764 return this.eventTypes;
765 }
766
767
768
769
770 public Set<Property> getAddedProperties() {
771 return this.addedProperties;
772 }
773
774
775
776
777 public Set<Property> getModifiedProperties() {
778 return this.modifiedProperties;
779 }
780
781
782
783
784 public Set<Name> getRemovedProperties() {
785 return this.removedProperties;
786 }
787
788
789
790
791 public Location getMovedFrom() {
792 return movedFrom;
793 }
794
795
796
797
798 public Location getMovedBefore() {
799 return movedBefore;
800 }
801
802
803
804
805 public boolean isReorder() {
806 return reorder;
807 }
808 }
809 }