001/** 002 * Copyright 2015 DuraSpace, Inc. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.fcrepo.kernel.modeshape.observer.eventmappings; 017 018import static com.google.common.collect.Multimaps.index; 019import static org.modeshape.jcr.api.JcrConstants.JCR_CONTENT; 020import static org.slf4j.LoggerFactory.getLogger; 021import static java.util.Arrays.asList; 022import static javax.jcr.observation.Event.PROPERTY_ADDED; 023import static javax.jcr.observation.Event.PROPERTY_CHANGED; 024import static javax.jcr.observation.Event.PROPERTY_REMOVED; 025 026import java.util.Iterator; 027import java.util.List; 028import java.util.function.Function; 029 030import javax.jcr.RepositoryException; 031import javax.jcr.observation.Event; 032 033import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 034import org.fcrepo.kernel.api.observer.FedoraEvent; 035import org.fcrepo.kernel.api.observer.eventmappings.InternalExternalEventMapper; 036 037import org.slf4j.Logger; 038 039import com.google.common.collect.Multimap; 040 041/** 042 * Maps all JCR {@link Event}s concerning one JCR node to one {@link FedoraEvent}. Adds the types of those JCR events 043 * together to calculate the final type of the emitted FedoraEvent. 044 * TODO stop aggregating events in the heap, if possible 045 * 046 * @author ajs6f 047 * @since Feb 27, 2014 048 */ 049public class AllNodeEventsOneEvent implements InternalExternalEventMapper { 050 051 private static final List<Integer> PROPERTY_EVENT_TYPES = asList(PROPERTY_ADDED, PROPERTY_CHANGED, 052 PROPERTY_REMOVED); 053 054 private final static Logger LOGGER = getLogger(AllNodeEventsOneEvent.class); 055 056 /** 057 * Extracts an identifier from a JCR {@link Event} by building an id from nodepath and user to collapse multiple 058 * events from repository mutations 059 */ 060 private static final Function<Event, String> EXTRACT_NODE_ID = ev -> { 061 final String id = FedoraEvent.getPath(ev).replaceAll("/" + JCR_CONTENT,"") + "-" + ev.getUserID(); 062 LOGGER.debug("Sorting an event by identifier: {}", id); 063 return id; 064 }; 065 066 @Override 067 public Iterator<FedoraEvent> apply(final Iterator<Event> events) { 068 return new FedoraEventIterator(events); 069 } 070 071 private static class FedoraEventIterator implements Iterator<FedoraEvent> { 072 073 // JCR events in a Multimap keyed by identifier 074 private final Multimap<String, Event> sortedEvents; 075 076 private final Iterator<String> ids; 077 078 public FedoraEventIterator(final Iterator<Event> events) { 079 sortedEvents = index(events, EXTRACT_NODE_ID::apply); 080 ids = sortedEvents.keySet().iterator(); 081 } 082 083 @Override 084 public boolean hasNext() { 085 return ids.hasNext(); 086 } 087 088 @Override 089 public FedoraEvent next() { 090 final Iterator<Event> nodeSpecificEvents = sortedEvents.get(ids.next()).iterator(); 091 // we can safely call next() immediately on nodeSpecificEvents because if there was no event at all, there 092 // would appear no entry in our Multimap under this key 093 final Event firstEvent = nodeSpecificEvents.next(); 094 final FedoraEvent fedoraEvent = new FedoraEvent(firstEvent); 095 096 addProperty(fedoraEvent, firstEvent); 097 while (nodeSpecificEvents.hasNext()) { 098 // add the event type and property name to the event we are building up to emit 099 // we could aggregate other information here if that seems useful 100 final Event otherEvent = nodeSpecificEvents.next(); 101 fedoraEvent.addType(otherEvent.getType()); 102 addProperty(fedoraEvent, otherEvent); 103 } 104 return fedoraEvent; 105 } 106 107 @Override 108 public void remove() { 109 // the underlying Multimap is immutable anyway 110 throw new UnsupportedOperationException(); 111 } 112 113 private static void addProperty( final FedoraEvent fedoraEvent, final Event ev ) { 114 try { 115 if ( ev.getPath().contains(JCR_CONTENT)) { 116 fedoraEvent.addProperty("fedora:hasContent"); 117 } 118 if (PROPERTY_EVENT_TYPES.contains(ev.getType())) { 119 final String eventPath = ev.getPath(); 120 fedoraEvent.addProperty(eventPath.substring(eventPath.lastIndexOf('/') + 1)); 121 } else { 122 LOGGER.trace("Not adding non-event property: {}, {}", fedoraEvent, ev); 123 } 124 } catch (final RepositoryException e) { 125 throw new RepositoryRuntimeException(e); 126 } 127 } 128 } 129}