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 org.fcrepo.kernel.modeshape.utils.UncheckedFunction.uncheck; 022import static java.util.Arrays.asList; 023import static javax.jcr.observation.Event.PROPERTY_ADDED; 024import static javax.jcr.observation.Event.PROPERTY_CHANGED; 025import static javax.jcr.observation.Event.PROPERTY_REMOVED; 026 027import java.util.Iterator; 028import java.util.List; 029import java.util.function.Function; 030 031import javax.jcr.RepositoryException; 032import javax.jcr.observation.Event; 033 034import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 035import org.fcrepo.kernel.api.observer.FedoraEvent; 036import org.fcrepo.kernel.api.observer.eventmappings.InternalExternalEventMapper; 037import org.fcrepo.kernel.api.utils.EventType; 038import org.fcrepo.kernel.modeshape.observer.FedoraEventImpl; 039 040import org.slf4j.Logger; 041 042import com.google.common.collect.Multimap; 043 044/** 045 * Maps all JCR {@link Event}s concerning one JCR node to one {@link FedoraEvent}. Adds the types of those JCR events 046 * together to calculate the final type of the emitted FedoraEvent. 047 * TODO stop aggregating events in the heap, if possible 048 * 049 * @author ajs6f 050 * @since Feb 27, 2014 051 */ 052public class AllNodeEventsOneEvent implements InternalExternalEventMapper { 053 054 private static final List<Integer> PROPERTY_EVENT_TYPES = asList(PROPERTY_ADDED, PROPERTY_CHANGED, 055 PROPERTY_REMOVED); 056 057 private final static Logger LOGGER = getLogger(AllNodeEventsOneEvent.class); 058 059 /** 060 * Extracts an identifier from a JCR {@link Event} by building an id from nodepath and user to collapse multiple 061 * events from repository mutations 062 */ 063 private static final Function<Event, String> EXTRACT_NODE_ID = uncheck(ev -> { 064 final FedoraEvent event = new FedoraEventImpl(ev); 065 final String id = event.getPath() + "-" + event.getUserID(); 066 LOGGER.debug("Sorting an event by identifier: {}", id); 067 return id; 068 }); 069 070 @Override 071 public Iterator<FedoraEvent> apply(final Iterator<Event> events) { 072 return new FedoraEventIterator(events); 073 } 074 075 private static class FedoraEventIterator implements Iterator<FedoraEvent> { 076 077 // JCR events in a Multimap keyed by identifier 078 private final Multimap<String, Event> sortedEvents; 079 080 private final Iterator<String> ids; 081 082 public FedoraEventIterator(final Iterator<Event> events) { 083 sortedEvents = index(events, EXTRACT_NODE_ID::apply); 084 ids = sortedEvents.keySet().iterator(); 085 } 086 087 @Override 088 public boolean hasNext() { 089 return ids.hasNext(); 090 } 091 092 @Override 093 public FedoraEvent next() { 094 final Iterator<Event> nodeSpecificEvents = sortedEvents.get(ids.next()).iterator(); 095 // we can safely call next() immediately on nodeSpecificEvents because if there was no event at all, there 096 // would appear no entry in our Multimap under this key 097 final Event firstEvent = nodeSpecificEvents.next(); 098 099 final FedoraEvent fedoraEvent = new FedoraEventImpl(firstEvent); 100 101 addProperty(fedoraEvent, firstEvent); 102 while (nodeSpecificEvents.hasNext()) { 103 // add the event type and property name to the event we are building up to emit 104 // we could aggregate other information here if that seems useful 105 final Event otherEvent = nodeSpecificEvents.next(); 106 fedoraEvent.addType(EventType.valueOf(otherEvent.getType())); 107 addProperty(fedoraEvent, otherEvent); 108 } 109 return fedoraEvent; 110 } 111 112 @Override 113 public void remove() { 114 // the underlying Multimap is immutable anyway 115 throw new UnsupportedOperationException(); 116 } 117 118 private static void addProperty( final FedoraEvent fedoraEvent, final Event ev ) { 119 try { 120 if ( ev.getPath().contains(JCR_CONTENT)) { 121 fedoraEvent.addProperty("fedora:hasContent"); 122 } 123 if (PROPERTY_EVENT_TYPES.contains(ev.getType())) { 124 final String eventPath = ev.getPath(); 125 fedoraEvent.addProperty(eventPath.substring(eventPath.lastIndexOf('/') + 1)); 126 } else { 127 LOGGER.trace("Not adding non-event property: {}, {}", fedoraEvent, ev); 128 } 129 } catch (final RepositoryException e) { 130 throw new RepositoryRuntimeException(e); 131 } 132 } 133 } 134}