001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.fcrepo.kernel.modeshape.observer.eventmappings;
019
020import static org.fcrepo.kernel.modeshape.FedoraJcrConstants.FEDORA_JCR_REFERENCE;
021import static org.fcrepo.kernel.modeshape.FedoraJcrConstants.JCR_LASTMODIFIED;
022import static org.fcrepo.kernel.modeshape.utils.UncheckedFunction.uncheck;
023import static org.fcrepo.kernel.modeshape.observer.FedoraEventImpl.from;
024import static org.fcrepo.kernel.modeshape.observer.FedoraEventImpl.getResourceTypes;
025import static org.slf4j.LoggerFactory.getLogger;
026import static java.util.stream.Collectors.groupingBy;
027import static java.util.stream.Collectors.toSet;
028import static java.util.stream.Stream.empty;
029import static java.util.stream.Stream.of;
030import static javax.jcr.observation.Event.PROPERTY_CHANGED;
031
032import java.util.ArrayList;
033import java.util.List;
034import java.util.function.Function;
035import java.util.stream.Stream;
036
037import javax.jcr.RepositoryException;
038import javax.jcr.observation.Event;
039
040import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
041import org.fcrepo.kernel.api.observer.FedoraEvent;
042import org.fcrepo.kernel.modeshape.observer.FedoraEventImpl;
043import org.fcrepo.kernel.modeshape.observer.WrappedJcrEvent;
044
045import org.slf4j.Logger;
046
047/**
048 * Maps all JCR {@link Event}s concerning one JCR node to one {@link FedoraEvent}. Adds the types of those JCR events
049 * together to calculate the final type of the emitted FedoraEvent.
050 *
051 * @author ajs6f
052 * @author acoburn
053 * @since Feb 27, 2014
054 */
055public class AllNodeEventsOneEvent implements InternalExternalEventMapper {
056
057    private final static Logger LOGGER = getLogger(AllNodeEventsOneEvent.class);
058
059    /**
060     * If the only event is a modification of the jcr:lastModified it is most likely the effect of an inbound reference.
061     * Wrap the old event in a new one that replaces the event type with FEDORA_JCR_REFERENCE for later mapping.
062     *
063     * @param ev The list of events to check.
064     * @return The original list or a list with the altered event.
065     */
066    private static List<Event> AlterReferenceEvents(final List<Event> ev) {
067        try {
068            if (ev.size() == 1 && ev.get(0).getPath().endsWith("/" + JCR_LASTMODIFIED) &&
069                ev.get(0).getType() == PROPERTY_CHANGED) {
070                final Event original = ev.get(0);
071                final Event tempEv =
072                    new WrappedJcrEvent((org.modeshape.jcr.api.observation.Event) original, FEDORA_JCR_REFERENCE);
073                final List<Event> list = new ArrayList<>();
074                list.add(tempEv);
075                return list;
076            }
077            return ev;
078        } catch (final RepositoryException e) {
079            throw new RepositoryRuntimeException(e);
080        }
081    }
082
083    /**
084     * Extracts an identifier from a JCR {@link Event} by building an id from nodepath and user to collapse multiple
085     * events from repository mutations
086     */
087    private static final Function<Event, String> EXTRACT_NODE_ID = uncheck(ev -> {
088            final FedoraEvent event = from(ev);
089            final String id = event.getPath() + "-" + event.getUserID();
090            LOGGER.debug("Sorting an event by identifier: {}", id);
091            return id;
092    });
093
094    @Override
095    public Stream<FedoraEvent> apply(final Stream<Event> events) {
096        // first, index all the events by path-userID and then flatMap over that list of values
097        // each of which returns either a singleton Stream or an empty Stream. The final result
098        // will be a concatenated Stream of FedoraEvent objects.
099        return events.collect(groupingBy(EXTRACT_NODE_ID)).entrySet().stream().flatMap(entry -> {
100            final List<Event> evts = AlterReferenceEvents(entry.getValue());
101            if (!evts.isEmpty()) {
102                // build a FedoraEvent from the first JCR Event
103                final FedoraEvent fedoraEvent = from(evts.get(0));
104                evts.stream().skip(1).forEach(evt -> {
105                    // add types to the FedoraEvent from the subsequent JCR Events
106                    fedoraEvent.getTypes().add(FedoraEventImpl.valueOf(evt.getType()));
107                    fedoraEvent.getResourceTypes().addAll(getResourceTypes(evt).collect(toSet()));
108                });
109                return of(fedoraEvent);
110            }
111            return empty();
112        });
113    }
114}