1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package org.modeshape.jcr;
25
26 import static org.modeshape.graph.JcrLexicon.MIXIN_TYPES;
27 import static org.modeshape.graph.JcrLexicon.PRIMARY_TYPE;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.NoSuchElementException;
35 import java.util.UUID;
36 import java.util.concurrent.ConcurrentHashMap;
37 import javax.jcr.RangeIterator;
38 import javax.jcr.RepositoryException;
39 import javax.jcr.observation.Event;
40 import javax.jcr.observation.EventIterator;
41 import javax.jcr.observation.EventJournal;
42 import javax.jcr.observation.EventListener;
43 import javax.jcr.observation.EventListenerIterator;
44 import javax.jcr.observation.ObservationManager;
45 import net.jcip.annotations.Immutable;
46 import net.jcip.annotations.NotThreadSafe;
47 import org.modeshape.common.util.CheckArg;
48 import org.modeshape.common.util.Logger;
49 import org.modeshape.graph.ExecutionContext;
50 import org.modeshape.graph.Graph;
51 import org.modeshape.graph.Location;
52 import org.modeshape.graph.observe.Changes;
53 import org.modeshape.graph.observe.NetChangeObserver;
54 import org.modeshape.graph.observe.Observable;
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.Property;
61 import org.modeshape.graph.property.UuidFactory;
62 import org.modeshape.graph.property.ValueFactories;
63 import org.modeshape.graph.property.ValueFactory;
64 import org.modeshape.graph.property.ValueFormatException;
65 import org.modeshape.graph.request.ChangeRequest;
66
67
68
69
70 final class JcrObservationManager implements ObservationManager {
71
72
73
74
75
76 static final String OBSERVATION_USER_DATA_KEY = "org.modeshape.jcr.observation.userdata";
77
78 static final String MOVE_FROM_KEY = "srcAbsPath";
79 static final String MOVE_TO_KEY = "destAbsPath";
80 static final String ORDER_CHILD_KEY = "srcChildRelPath";
81 static final String ORDER_BEFORE_KEY = "destChildRelPath";
82
83
84
85
86 private final Observable repositoryObservable;
87
88
89
90
91 private final Map<EventListener, JcrListenerAdapter> listeners;
92
93
94
95
96 private final NamespaceRegistry namespaceRegistry;
97
98
99
100
101 private final JcrSession session;
102
103
104
105
106 private final ValueFactories valueFactories;
107
108 private final ValueFactory<String> stringFactory;
109
110
111
112
113 private final String workspaceName;
114
115
116
117
118 private final String systemWorkspaceName;
119
120
121
122
123 private final String systemSourceName;
124
125
126
127
128
129
130 JcrObservationManager( JcrSession session,
131 Observable repositoryObservable ) {
132 CheckArg.isNotNull(session, "session");
133 CheckArg.isNotNull(repositoryObservable, "repositoryObservable");
134
135 this.session = session;
136 this.repositoryObservable = repositoryObservable;
137 this.listeners = new ConcurrentHashMap<EventListener, JcrListenerAdapter>();
138 this.namespaceRegistry = this.session.getExecutionContext().getNamespaceRegistry();
139 this.valueFactories = this.session.getExecutionContext().getValueFactories();
140 this.stringFactory = this.valueFactories.getStringFactory();
141 this.workspaceName = this.session.getWorkspace().getName();
142 this.systemWorkspaceName = this.session.repository().getSystemWorkspaceName();
143 this.systemSourceName = this.session.repository().getSystemSourceName();
144 }
145
146
147
148
149
150
151
152
153
154 public synchronized void addEventListener( EventListener listener,
155 int eventTypes,
156 String absPath,
157 boolean isDeep,
158 String[] uuid,
159 String[] nodeTypeName,
160 boolean noLocal ) throws RepositoryException {
161 CheckArg.isNotNull(listener, "listener");
162 checkSession();
163
164
165 JcrListenerAdapter adapter = new JcrListenerAdapter(listener, eventTypes, absPath, isDeep, uuid, nodeTypeName, noLocal);
166
167 this.repositoryObservable.unregister(adapter);
168 this.repositoryObservable.register(adapter);
169 this.listeners.put(listener, adapter);
170 }
171
172
173
174
175 void checkSession() throws RepositoryException {
176 session.checkLive();
177 }
178
179
180
181
182 NamespaceRegistry namespaceRegistry() {
183 return this.namespaceRegistry;
184 }
185
186 final String stringFor( Path path ) {
187 return this.stringFactory.create(path);
188 }
189
190 final String stringFor( Path.Segment segment ) {
191 return this.stringFactory.create(segment);
192 }
193
194 final String stringFor( Name name ) {
195 return this.stringFactory.create(name);
196 }
197
198
199
200
201
202 JcrNodeTypeManager getNodeTypeManager() throws RepositoryException {
203 return (JcrNodeTypeManager)this.session.getWorkspace().getNodeTypeManager();
204 }
205
206
207
208
209
210
211 public EventListenerIterator getRegisteredEventListeners() throws RepositoryException {
212 checkSession();
213 return new JcrEventListenerIterator(this.listeners.keySet());
214 }
215
216
217
218
219 String getUserId() {
220 return this.session.getUserID();
221 }
222
223
224
225
226 ValueFactories getValueFactories() {
227 return this.valueFactories;
228 }
229
230
231
232
233 Graph getGraph() {
234 return ((JcrWorkspace)this.session.getWorkspace()).graph();
235 }
236
237
238
239
240 String getSessionId() {
241 return this.session.sessionId();
242 }
243
244
245
246
247 final String getWorkspaceName() {
248 return workspaceName;
249 }
250
251 final String getSystemWorkspaceName() {
252 return systemWorkspaceName;
253 }
254
255 final String getSystemSourceName() {
256 return systemSourceName;
257 }
258
259
260
261
262 synchronized void removeAllEventListeners() {
263 for (JcrListenerAdapter listener : this.listeners.values()) {
264 assert (listener != null);
265 this.repositoryObservable.unregister(listener);
266 }
267
268 this.listeners.clear();
269 }
270
271
272
273
274
275
276
277 public synchronized void removeEventListener( EventListener listener ) throws RepositoryException {
278 checkSession();
279 CheckArg.isNotNull(listener, "listener");
280
281 JcrListenerAdapter jcrListener = this.listeners.remove(listener);
282
283 if (jcrListener != null) {
284 this.repositoryObservable.unregister(jcrListener);
285 }
286 }
287
288
289
290
291
292
293
294
295
296
297 @Override
298 public void setUserData( String userData ) {
299
300 session.setSessionData(OBSERVATION_USER_DATA_KEY, userData);
301 }
302
303
304
305
306
307
308
309
310
311 @Override
312 public EventJournal getEventJournal() {
313 return null;
314 }
315
316
317
318
319
320
321
322
323
324
325 @Override
326 public EventJournal getEventJournal( int eventTypes,
327 String absPath,
328 boolean isDeep,
329 String[] uuid,
330 String[] nodeTypeName ) {
331 return null;
332 }
333
334
335
336
337
338
339 class JcrRangeIterator<E> implements RangeIterator {
340
341
342
343
344 private final List<? extends E> elements;
345
346
347
348
349 private int position = 0;
350
351
352
353
354
355 public JcrRangeIterator( Collection<? extends E> elements ) {
356 CheckArg.isNotNull(elements, "elements");
357 this.elements = new ArrayList<E>(elements);
358 }
359
360
361
362
363
364
365 public long getPosition() {
366 return this.position;
367 }
368
369
370
371
372
373
374 public long getSize() {
375 return this.elements.size();
376 }
377
378
379
380
381
382
383 public boolean hasNext() {
384 return (getPosition() < getSize());
385 }
386
387
388
389
390
391
392 public Object next() {
393 if (!hasNext()) {
394 throw new NoSuchElementException();
395 }
396
397 Object element = this.elements.get(this.position);
398 ++this.position;
399
400 return element;
401 }
402
403
404
405
406
407
408
409 public void remove() {
410 throw new UnsupportedOperationException();
411 }
412
413
414
415
416
417
418 public void skip( long skipNum ) {
419 this.position += skipNum;
420
421 if (!hasNext()) {
422 throw new NoSuchElementException();
423 }
424 }
425 }
426
427
428
429
430 class JcrEventListenerIterator extends JcrRangeIterator<EventListener> implements EventListenerIterator {
431
432
433
434
435
436 public JcrEventListenerIterator( Collection<EventListener> listeners ) {
437 super(listeners);
438 }
439
440
441
442
443
444
445 public EventListener nextEventListener() {
446 return (EventListener)next();
447 }
448 }
449
450
451
452
453 class JcrEventIterator extends JcrRangeIterator<Event> implements EventIterator {
454
455
456
457
458
459 public JcrEventIterator( Collection<Event> events ) {
460 super(events);
461 }
462
463
464
465
466
467
468 public Event nextEvent() {
469 return (Event)next();
470 }
471 }
472
473
474
475
476 @Immutable
477 class JcrEventBundle {
478
479
480
481
482 private final DateTime date;
483
484
485
486
487 private final String userId;
488
489 private final String userData;
490
491 public JcrEventBundle( DateTime dateTime,
492 String userId,
493 String userData ) {
494 this.userId = userId;
495 this.userData = userData;
496 this.date = dateTime;
497 }
498
499 public String getUserID() {
500 return this.userId;
501 }
502
503
504
505
506 public DateTime getDate() {
507 return date;
508 }
509
510
511
512
513 public String getUserData() {
514 return userData;
515 }
516 }
517
518
519
520
521 @Immutable
522 class JcrEvent implements Event {
523
524 private final String id;
525
526
527
528
529 private final String path;
530
531
532
533
534 private final int type;
535
536
537
538
539 private final JcrEventBundle bundle;
540
541
542
543
544
545
546
547 public JcrEvent( JcrEventBundle bundle,
548 int type,
549 String path,
550 String id ) {
551 this.type = type;
552 this.path = path;
553 this.bundle = bundle;
554 this.id = id;
555 }
556
557
558
559
560
561
562 public String getPath() {
563 return this.path;
564 }
565
566
567
568
569
570
571 public int getType() {
572 return this.type;
573 }
574
575
576
577
578
579
580 public String getUserID() {
581 return bundle.getUserID();
582 }
583
584
585
586
587
588
589 @Override
590 public long getDate() {
591 return bundle.getDate().getMilliseconds();
592 }
593
594
595
596
597
598
599 @Override
600 public String getIdentifier() {
601 return id;
602 }
603
604
605
606
607
608
609 @Override
610 public String getUserData() {
611 return bundle.getUserData();
612 }
613
614
615
616
617
618
619
620 @Override
621 public Map<String, String> getInfo() {
622 return Collections.emptyMap();
623 }
624
625
626
627
628
629
630 @Override
631 public String toString() {
632 StringBuilder sb = new StringBuilder();
633 switch (this.type) {
634 case Event.NODE_ADDED:
635 sb.append("Node added");
636 break;
637 case Event.NODE_REMOVED:
638 sb.append("Node removed");
639 break;
640 case Event.PROPERTY_ADDED:
641 sb.append("Property added");
642 break;
643 case Event.PROPERTY_CHANGED:
644 sb.append("Property changed");
645 break;
646 case Event.PROPERTY_REMOVED:
647 sb.append("Property removed");
648 break;
649 case Event.NODE_MOVED:
650 sb.append("Node moved");
651 break;
652 }
653 sb.append(" at ").append(path).append(" by ").append(getUserID());
654 return sb.toString();
655 }
656 }
657
658
659
660
661 @Immutable
662 class JcrMoveEvent extends JcrEvent {
663
664 private final Map<String, String> info;
665
666
667
668
669
670
671
672
673 public JcrMoveEvent( JcrEventBundle bundle,
674 int type,
675 String path,
676 String id,
677 Map<String, String> info ) {
678 super(bundle, type, path, id);
679 this.info = info;
680 }
681
682
683
684
685
686
687 @Override
688 public Map<String, String> getInfo() {
689 return this.info;
690 }
691
692
693
694
695
696
697 @Override
698 public String toString() {
699 StringBuilder sb = new StringBuilder();
700 sb.append("Node moved");
701 String from = info.containsKey(MOVE_FROM_KEY) ? info.get(MOVE_FROM_KEY) : info.get(ORDER_CHILD_KEY);
702 String to = info.containsKey(MOVE_TO_KEY) ? info.get(MOVE_TO_KEY) : info.get(ORDER_BEFORE_KEY);
703 sb.append(" from ").append(from).append(" to ").append(to).append(" by ").append(getUserID());
704 return sb.toString();
705 }
706 }
707
708
709
710
711
712 @NotThreadSafe
713 class JcrListenerAdapter extends NetChangeObserver {
714
715
716
717
718 private final String absPath;
719
720
721
722
723
724 private Map<Location, Map<Name, Property>> propertiesByLocation;
725
726
727
728
729 private final EventListener delegate;
730
731
732
733
734 private final int eventTypes;
735
736
737
738
739 private final boolean isDeep;
740
741
742
743
744
745
746 private final String[] nodeTypeNames;
747
748
749
750
751 private final boolean noLocal;
752
753
754
755
756
757
758 private final String[] uuids;
759
760
761
762
763
764
765
766
767
768
769 public JcrListenerAdapter( EventListener delegate,
770 int eventTypes,
771 String absPath,
772 boolean isDeep,
773 String[] uuids,
774 String[] nodeTypeNames,
775 boolean noLocal ) {
776 assert (delegate != null);
777
778 this.delegate = delegate;
779 this.eventTypes = eventTypes;
780 this.absPath = absPath;
781 this.isDeep = isDeep;
782 this.uuids = uuids;
783 this.nodeTypeNames = nodeTypeNames;
784 this.noLocal = noLocal;
785 }
786
787
788
789
790
791 private boolean acceptBasedOnEventSource( Changes changes ) {
792 if (this.noLocal) {
793
794 return !getSessionId().equals(changes.getContextId());
795 }
796 return true;
797 }
798
799
800
801
802
803 private boolean acceptBasedOnNodeTypeName( NetChange change ) {
804 boolean accept = true;
805
806 if (shouldCheckNodeType()) {
807 ValueFactory<String> stringFactory = getValueFactories().getStringFactory();
808 Location parentLocation = Location.create(change.getLocation().getPath().getParent());
809 Map<Name, Property> propMap = this.propertiesByLocation.get(parentLocation);
810 assert (propMap != null);
811
812 try {
813 String primaryTypeName = stringFactory.create(propMap.get(PRIMARY_TYPE).getFirstValue());
814 String[] mixinNames = null;
815
816 if (propMap.get(MIXIN_TYPES) != null) {
817 mixinNames = stringFactory.create(propMap.get(MIXIN_TYPES).getValuesAsArray());
818 }
819
820 return getNodeTypeManager().isDerivedFrom(this.nodeTypeNames, primaryTypeName, mixinNames);
821 } catch (RepositoryException e) {
822 accept = false;
823 Logger.getLogger(getClass()).error(e,
824 JcrI18n.cannotPerformNodeTypeCheck,
825 propMap.get(PRIMARY_TYPE),
826 propMap.get(MIXIN_TYPES),
827 this.nodeTypeNames);
828 }
829 }
830
831 return accept;
832 }
833
834
835
836
837
838 private boolean acceptBasedOnPath( NetChange change ) {
839 if ((this.absPath != null) && (this.absPath.length() != 0)) {
840 Path matchPath = getValueFactories().getPathFactory().create(this.absPath);
841 Path changePath = null;
842
843 if (change.includes(ChangeType.NODE_ADDED, ChangeType.NODE_REMOVED)) {
844 changePath = change.getPath().getParent();
845 } else {
846 changePath = change.getPath();
847 }
848
849 if (this.isDeep) {
850 return matchPath.isAtOrAbove(changePath);
851 }
852
853 return matchPath.equals(changePath);
854 }
855
856 return true;
857 }
858
859
860
861
862
863 private boolean acceptBasedOnUuid( NetChange change ) {
864 boolean accept = true;
865
866 if ((this.uuids != null) && (this.uuids.length != 0)) {
867 UUID matchUuid = change.getLocation().getUuid();
868
869 if (matchUuid != null) {
870 accept = false;
871 UuidFactory uuidFactory = getValueFactories().getUuidFactory();
872
873 for (String uuidText : this.uuids) {
874 if ((uuidText != null) && (uuidText.length() != 0)) {
875 try {
876 UUID testUuid = uuidFactory.create(uuidText);
877
878 if (matchUuid.equals(testUuid)) {
879 accept = true;
880 break;
881 }
882 } catch (ValueFormatException e) {
883 Logger.getLogger(getClass()).error(JcrI18n.cannotCreateUuid, uuidText);
884 }
885 }
886 }
887 }
888 }
889
890 return accept;
891 }
892
893
894
895
896
897
898 @Override
899 public boolean equals( Object obj ) {
900 if ((obj != null) && (obj instanceof JcrListenerAdapter)) {
901 return (this.delegate == ((JcrListenerAdapter)obj).delegate);
902 }
903
904 return false;
905 }
906
907
908
909
910
911
912 @Override
913 public int hashCode() {
914 return this.delegate.hashCode();
915 }
916
917
918
919
920
921
922 @Override
923 public void notify( Changes changes ) {
924
925
926
927 if (!acceptBasedOnEventSource(changes)) {
928 return;
929 }
930
931 try {
932 if (shouldCheckNodeType()) {
933 List<Location> changedLocations = new ArrayList<Location>();
934
935
936 for (ChangeRequest request : changes.getChangeRequests()) {
937 String changedWorkspaceName = request.changedWorkspace();
938
939 if (!getWorkspaceName().equals(changedWorkspaceName)) {
940
941 if (!getSystemWorkspaceName().equals(changedWorkspaceName)
942 && !changes.getSourceName().equals(getSystemSourceName())) {
943
944 continue;
945 }
946 }
947 Path changedPath = request.changedLocation().getPath();
948 Path parentPath = changedPath.getParent();
949 changedLocations.add(Location.create(parentPath));
950 }
951
952
953 Graph graph = getGraph();
954 this.propertiesByLocation = graph.getProperties(PRIMARY_TYPE, MIXIN_TYPES).on(changedLocations);
955 }
956
957
958 super.notify(changes);
959 } finally {
960 this.propertiesByLocation = null;
961 }
962 }
963
964
965
966
967
968
969 @Override
970 protected void notify( NetChanges netChanges ) {
971 Collection<Event> events = new ArrayList<Event>();
972
973 String userData = netChanges.getData().get(OBSERVATION_USER_DATA_KEY);
974 JcrEventBundle bundle = new JcrEventBundle(netChanges.getTimestamp(), netChanges.getUserName(), userData);
975
976 for (NetChange change : netChanges.getNetChanges()) {
977 String changedWorkspaceName = change.getRepositoryWorkspaceName();
978
979 if (!getWorkspaceName().equals(changedWorkspaceName)) {
980
981 if (!getSystemWorkspaceName().equals(changedWorkspaceName)
982 && !netChanges.getSourceName().equals(getSystemSourceName())) {
983
984 continue;
985 }
986 }
987
988
989 if (change.includes(ChangeType.NODE_LOCKED) || change.includes(ChangeType.NODE_UNLOCKED)) {
990 continue;
991 }
992
993
994 if (!acceptBasedOnNodeTypeName(change) || !acceptBasedOnPath(change) || !acceptBasedOnUuid(change)) {
995 continue;
996 }
997
998
999 Path path = change.getPath();
1000 PathFactory pathFactory = getValueFactories().getPathFactory();
1001 String id = change.getLocation().getUuid().toString();
1002
1003 if (change.includes(ChangeType.NODE_MOVED)) {
1004 Location original = change.getOriginalLocation();
1005 Path originalPath = original.getPath();
1006 if ((this.eventTypes & Event.NODE_MOVED) == Event.NODE_MOVED) {
1007 Location before = change.getMovedBefore();
1008 boolean sameParent = !originalPath.isRoot() && !path.isRoot()
1009 && originalPath.getParent().equals(path.getParent());
1010 Map<String, String> info = new HashMap<String, String>();
1011 if (sameParent && change.isReorder()) {
1012 info.put(ORDER_CHILD_KEY, stringFor(originalPath.getLastSegment()));
1013 info.put(ORDER_BEFORE_KEY, before != null ? stringFor(before.getPath().getLastSegment()) : null);
1014 } else {
1015 info.put(MOVE_FROM_KEY, stringFor(originalPath));
1016 info.put(MOVE_TO_KEY, stringFor(path));
1017 }
1018 info = Collections.unmodifiableMap(info);
1019 events.add(new JcrMoveEvent(bundle, Event.NODE_MOVED, stringFor(path), id, info));
1020 }
1021
1022 if ((this.eventTypes & Event.NODE_ADDED) == Event.NODE_ADDED) {
1023 events.add(new JcrEvent(bundle, Event.NODE_ADDED, stringFor(path), id));
1024 }
1025 if ((this.eventTypes & Event.NODE_REMOVED) == Event.NODE_REMOVED) {
1026 events.add(new JcrEvent(bundle, Event.NODE_REMOVED, stringFor(originalPath), id));
1027 }
1028 }
1029 if (change.includes(ChangeType.NODE_ADDED) && ((this.eventTypes & Event.NODE_ADDED) == Event.NODE_ADDED)) {
1030
1031 events.add(new JcrEvent(bundle, Event.NODE_ADDED, stringFor(path), id));
1032 } else if (change.includes(ChangeType.NODE_REMOVED)
1033 && ((this.eventTypes & Event.NODE_REMOVED) == Event.NODE_REMOVED)) {
1034
1035 events.add(new JcrEvent(bundle, Event.NODE_REMOVED, stringFor(path), id));
1036 }
1037
1038 if (change.includes(ChangeType.PROPERTY_CHANGED)
1039 && ((this.eventTypes & Event.PROPERTY_CHANGED) == Event.PROPERTY_CHANGED)) {
1040 for (Property property : change.getModifiedProperties()) {
1041
1042 Path propertyPath = pathFactory.create(path, stringFor(property.getName()));
1043 events.add(new JcrEvent(bundle, Event.PROPERTY_CHANGED, stringFor(propertyPath), id));
1044 }
1045 }
1046
1047
1048 if (change.includes(ChangeType.PROPERTY_ADDED)
1049 && ((this.eventTypes & Event.PROPERTY_ADDED) == Event.PROPERTY_ADDED)) {
1050 for (Property property : change.getAddedProperties()) {
1051
1052 Path propertyPath = pathFactory.create(path, stringFor(property.getName()));
1053 events.add(new JcrEvent(bundle, Event.PROPERTY_ADDED, stringFor(propertyPath), id));
1054 }
1055 }
1056
1057 if (change.includes(ChangeType.PROPERTY_REMOVED)
1058 && ((this.eventTypes & Event.PROPERTY_REMOVED) == Event.PROPERTY_REMOVED)) {
1059 for (Name name : change.getRemovedProperties()) {
1060
1061 Path propertyPath = pathFactory.create(path, name);
1062 events.add(new JcrEvent(bundle, Event.PROPERTY_REMOVED, stringFor(propertyPath), id));
1063 }
1064 }
1065 }
1066
1067
1068 if (!events.isEmpty()) {
1069 this.delegate.onEvent(new JcrEventIterator(events));
1070 }
1071 }
1072
1073
1074
1075
1076 private boolean shouldCheckNodeType() {
1077 return ((this.nodeTypeNames != null) && (this.nodeTypeNames.length != 0));
1078 }
1079 }
1080
1081 }