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
361
362 notify(new NetChanges(changes, netChanges));
363 }
364
365
366
367
368
369
370 protected abstract void notify( NetChanges netChanges );
371
372
373
374
375
376 @Immutable
377 public static final class NetChanges extends Changes {
378 private static final long serialVersionUID = 1L;
379
380 private final List<NetChange> netChanges;
381
382 public NetChanges( Changes changes,
383 List<NetChange> netChanges ) {
384 super(changes);
385 assert netChanges != null;
386 assert !netChanges.isEmpty();
387 this.netChanges = Collections.unmodifiableList(netChanges);
388 }
389
390
391
392
393
394
395 public List<NetChange> getNetChanges() {
396 return this.netChanges;
397 }
398
399
400
401
402
403
404 @Override
405 public String toString() {
406 if (processId.length() != 0) {
407 return getTimestamp() + " @" + getUserName() + " [" + getSourceName() + "] - " + netChanges.size() + " changes";
408 }
409 return getTimestamp() + " @" + getUserName() + " #" + getProcessId() + " [" + getSourceName() + "] - "
410 + netChanges.size() + " changes";
411 }
412 }
413
414
415
416
417 @Immutable
418 public static final class NetChange {
419
420 private final String workspaceName;
421 private final Location location;
422 private final EnumSet<ChangeType> eventTypes;
423 private final Set<Property> addedProperties;
424 private final Set<Property> modifiedProperties;
425 private final Set<Property> addedOrModifiedProperties;
426 private final Set<Name> removedProperties;
427 private final int hc;
428 private final Location movedFrom;
429 private final Location movedBefore;
430 private final boolean isReorder;
431
432 public NetChange( String workspaceName,
433 Location location,
434 EnumSet<ChangeType> eventTypes,
435 Set<Property> addedProperties,
436 Set<Property> modifiedProperties,
437 Set<Name> removedProperties,
438 Location movedFrom,
439 Location movedBefore,
440 boolean isReorder ) {
441 assert workspaceName != null;
442 assert location != null;
443 this.workspaceName = workspaceName;
444 this.location = location;
445 this.hc = HashCode.compute(this.workspaceName, this.location);
446 this.eventTypes = eventTypes;
447 Set<Property> addedOrModified = null;
448 if (addedProperties == null) {
449 addedProperties = Collections.emptySet();
450 addedOrModified = modifiedProperties;
451 } else {
452 addedOrModified = addedProperties;
453 }
454 if (modifiedProperties == null) {
455 if (addedOrModified == null) addedOrModified = Collections.emptySet();
456 modifiedProperties = Collections.emptySet();
457 } else {
458 if (addedOrModified == null) {
459 addedOrModified = modifiedProperties;
460 } else {
461 addedOrModified = new HashSet<Property>(modifiedProperties);
462 addedOrModified.addAll(addedProperties);
463 }
464 }
465 if (removedProperties == null) removedProperties = Collections.emptySet();
466 this.addedProperties = Collections.unmodifiableSet(addedProperties);
467 this.modifiedProperties = Collections.unmodifiableSet(modifiedProperties);
468 this.removedProperties = Collections.unmodifiableSet(removedProperties);
469 this.addedOrModifiedProperties = Collections.unmodifiableSet(addedOrModified);
470 this.movedFrom = movedFrom;
471 this.movedBefore = movedBefore;
472 this.isReorder = isReorder;
473 }
474
475
476
477
478 public Location getLocation() {
479 return this.location;
480 }
481
482
483
484
485
486
487 public Location getOriginalLocation() {
488 return this.movedFrom;
489 }
490
491
492
493
494
495
496
497 public Location getMovedBefore() {
498 return movedBefore;
499 }
500
501
502
503
504
505
506 public boolean isReorder() {
507 return isReorder;
508 }
509
510
511
512
513 public Path getPath() {
514 return this.location.getPath();
515 }
516
517
518
519
520 public String getRepositoryWorkspaceName() {
521 return this.workspaceName;
522 }
523
524
525
526
527 public Set<Property> getAddedProperties() {
528 return this.addedProperties;
529 }
530
531
532
533
534 public Set<Property> getModifiedProperties() {
535 return this.modifiedProperties;
536 }
537
538
539
540
541
542
543 public Set<Property> getAddedOrModifiedProperties() {
544 return this.addedOrModifiedProperties;
545 }
546
547
548
549
550 public Set<Name> getRemovedProperties() {
551 return this.removedProperties;
552 }
553
554
555
556
557 @Override
558 public int hashCode() {
559 return this.hc;
560 }
561
562
563
564
565
566
567
568 public boolean includesAllOf( ChangeType... jcrEventTypes ) {
569 for (ChangeType jcrEventType : jcrEventTypes) {
570 if (!this.eventTypes.contains(jcrEventType)) return false;
571 }
572 return true;
573 }
574
575
576
577
578
579
580
581 public boolean includes( ChangeType... jcrEventTypes ) {
582 for (ChangeType jcrEventType : jcrEventTypes) {
583 if (this.eventTypes.contains(jcrEventType)) return true;
584 }
585 return false;
586 }
587
588 public boolean isSameNode( NetChange that ) {
589 if (that == this) return true;
590 if (this.hc != that.hc) return false;
591 if (!this.workspaceName.equals(that.workspaceName)) return false;
592 if (!this.location.isSame(that.location)) return false;
593 return true;
594 }
595
596
597
598
599
600
601
602 public boolean isPropertyModified( String property ) {
603 return this.modifiedProperties.contains(property);
604 }
605
606
607
608
609
610
611
612 public boolean isPropertyRemoved( String property ) {
613 return this.removedProperties.contains(property);
614 }
615
616
617
618
619 @Override
620 public boolean equals( Object obj ) {
621 if (obj == this) return true;
622 if (obj instanceof NetChange) {
623 NetChange that = (NetChange)obj;
624 if (!this.isSameNode(that)) return false;
625 if (this.eventTypes != that.eventTypes) return false;
626 return true;
627 }
628 return false;
629 }
630
631
632
633
634 @Override
635 public String toString() {
636 return this.workspaceName + "=>" + this.location;
637 }
638 }
639
640 private enum LockAction {
641 LOCKED,
642 UNLOCKED;
643 }
644
645
646
647
648 @NotThreadSafe
649 private static class NetChangeDetails {
650
651 private final Set<Property> modifiedProperties = new HashSet<Property>();
652 private final Set<Property> addedProperties = new HashSet<Property>();
653 private final Set<Name> removedProperties = new HashSet<Name>();
654 private EnumSet<ChangeType> eventTypes = EnumSet.noneOf(ChangeType.class);
655 private Location movedFrom;
656 private Location movedBefore;
657 private boolean reorder;
658 private NetChangeDetails nextChanges;
659
660 protected NetChangeDetails() {
661 }
662
663 public void setLockAction( LockAction lockAction ) {
664 switch (lockAction) {
665 case LOCKED:
666
667 eventTypes.add(ChangeType.NODE_LOCKED);
668 eventTypes.remove(ChangeType.NODE_UNLOCKED);
669 break;
670 case UNLOCKED:
671 if (!eventTypes.remove(ChangeType.NODE_LOCKED)) {
672
673 eventTypes.add(ChangeType.NODE_UNLOCKED);
674 }
675 break;
676 }
677 }
678
679 public void setNextChanges( NetChangeDetails details ) {
680 this.nextChanges = details;
681 }
682
683 public NetChangeDetails next() {
684 return nextChanges;
685 }
686
687 public NetChangeDetails latest() {
688 return nextChanges != null ? nextChanges.latest() : this;
689 }
690
691 public void movedFrom( Location originalLocation ) {
692 this.eventTypes.add(ChangeType.NODE_MOVED);
693 this.movedFrom = originalLocation;
694 }
695
696 public void movedBefore( Location before,
697 boolean isReorder ) {
698 this.eventTypes.add(ChangeType.NODE_MOVED);
699 this.movedBefore = before;
700 this.reorder = isReorder;
701 }
702
703 public void addEventType( ChangeType eventType ) {
704 this.eventTypes.add(eventType);
705 }
706
707 public void addProperty( Property property ) {
708 this.addedProperties.add(property);
709 this.eventTypes.add(ChangeType.PROPERTY_ADDED);
710 }
711
712 public void changeProperty( Property property ) {
713
714 if (!this.addedProperties.contains(property)) {
715 this.modifiedProperties.add(property);
716 this.eventTypes.add(ChangeType.PROPERTY_CHANGED);
717 }
718 }
719
720 public void removeProperty( Name propertyName ) {
721
722 boolean handled = false;
723
724 for (Property property : this.addedProperties) {
725 if (property.getName().equals(propertyName)) {
726 handled = true;
727 this.addedProperties.remove(property);
728
729
730 if (this.addedProperties.isEmpty()) {
731 this.eventTypes.remove(ChangeType.PROPERTY_ADDED);
732 }
733
734 break;
735 }
736 }
737
738 if (!handled) {
739
740 for (Property property : this.modifiedProperties) {
741 if (property.getName().equals(propertyName)) {
742 this.modifiedProperties.remove(property);
743
744
745 if (this.modifiedProperties.isEmpty()) {
746 this.eventTypes.remove(ChangeType.PROPERTY_CHANGED);
747 }
748
749 break;
750 }
751 }
752
753
754 this.removedProperties.add(propertyName);
755 this.eventTypes.add(ChangeType.PROPERTY_REMOVED);
756 }
757 }
758
759
760
761
762 public EnumSet<ChangeType> getEventTypes() {
763 return this.eventTypes;
764 }
765
766
767
768
769 public Set<Property> getAddedProperties() {
770 return this.addedProperties;
771 }
772
773
774
775
776 public Set<Property> getModifiedProperties() {
777 return this.modifiedProperties;
778 }
779
780
781
782
783 public Set<Name> getRemovedProperties() {
784 return this.removedProperties;
785 }
786
787
788
789
790 public Location getMovedFrom() {
791 return movedFrom;
792 }
793
794
795
796
797 public Location getMovedBefore() {
798 return movedBefore;
799 }
800
801
802
803
804 public boolean isReorder() {
805 return reorder;
806 }
807 }
808 }