/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.sling.jcr.resource.internal; import static javax.jcr.observation.Event.NODE_ADDED; import static javax.jcr.observation.Event.NODE_REMOVED; import static javax.jcr.observation.Event.PROPERTY_ADDED; import static javax.jcr.observation.Event.PROPERTY_CHANGED; import static javax.jcr.observation.Event.PROPERTY_REMOVED; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.jcr.RepositoryException; import javax.jcr.observation.Event; import javax.jcr.observation.EventIterator; import javax.jcr.observation.EventListener; import org.apache.jackrabbit.api.observation.JackrabbitEvent; import org.apache.sling.api.resource.observation.ResourceChange; import org.apache.sling.api.resource.observation.ResourceChange.ChangeType; import org.apache.sling.spi.resource.provider.ObservationReporter; import org.apache.sling.spi.resource.provider.ObserverConfiguration; /** * The <code>JcrResourceListener</code> listens for JCR observation * events and creates resource change events which are sent through * the {@link ObservationReporter}. */ public class JcrResourceListener implements EventListener, Closeable { private volatile ObserverConfiguration config; private final JcrListenerBaseConfig baseConfig; public JcrResourceListener(final JcrListenerBaseConfig listenerConfig, final ObserverConfiguration config) throws RepositoryException { this.baseConfig = listenerConfig; this.config = config; this.baseConfig.register(this, config); } /** * Update the observation configuration. * * @param cfg The updated config */ public void update(final ObserverConfiguration cfg) { this.config = cfg; } /** * Get the observation configuration * @return The observation configuration */ public ObserverConfiguration getConfig() { return this.config; } /** * Dispose this listener. */ @Override public void close() throws IOException { // unregister from observations this.baseConfig.unregister(this); } /** * @see javax.jcr.observation.EventListener#onEvent(javax.jcr.observation.EventIterator) */ @Override public void onEvent(final EventIterator events) { final Map<String, ResourceChange> addedEvents = new HashMap<String, ResourceChange>(); final Map<String, ResourceChange> changedEvents = new HashMap<String, ResourceChange>(); final Map<String, ResourceChange> removedEvents = new HashMap<String, ResourceChange>(); while ( events.hasNext() ) { final Event event = events.nextEvent(); final String identifier; final String path; try { identifier = event.getIdentifier(); path = event.getPath(); } catch (final RepositoryException e) { // event.getPath or event.getIdentifier threw an exception // there is nothing we can do about it anyway continue; } final String eventPath = (identifier != null && identifier.startsWith("/") ? identifier : path); final int type = event.getType(); if ( type == PROPERTY_ADDED && path.endsWith("/jcr:primaryType") ) { final int lastSlash = path.lastIndexOf('/'); final String rsrcPath = path.substring(0, lastSlash); // add is stronger than update changedEvents.remove(rsrcPath); addedEvents.put(rsrcPath, createResourceChange(event, rsrcPath, ChangeType.ADDED)); } else if ( type == PROPERTY_ADDED || type == PROPERTY_REMOVED || type == PROPERTY_CHANGED ) { final String rsrcPath; if ( identifier == null || !identifier.startsWith("/") ) { final int lastSlash = eventPath.lastIndexOf('/'); rsrcPath = eventPath.substring(0, lastSlash); } else { rsrcPath = eventPath; } if ( !addedEvents.containsKey(rsrcPath) && !removedEvents.containsKey(rsrcPath) && !changedEvents.containsKey(rsrcPath) ) { changedEvents.put(rsrcPath, createResourceChange(event, rsrcPath, ChangeType.CHANGED)); } } else if ( type == NODE_ADDED ) { // add is stronger than update changedEvents.remove(eventPath); addedEvents.put(eventPath, createResourceChange(event, eventPath, ChangeType.ADDED)); } else if ( type == NODE_REMOVED) { // remove is stronger than add and change addedEvents.remove(eventPath); changedEvents.remove(eventPath); removedEvents.put(eventPath, createResourceChange(event, eventPath, ChangeType.REMOVED)); } } final List<ResourceChange> changes = new ArrayList<ResourceChange>(); changes.addAll(addedEvents.values()); changes.addAll(removedEvents.values()); changes.addAll(changedEvents.values()); this.baseConfig.getReporter().reportChanges(this.config, changes, false); } private ResourceChange createResourceChange(final Event event, final String path, final ChangeType changeType) { final String fullPath = path; final boolean isExternal = this.isExternal(event); final String userId; if (!isExternal) { userId = event.getUserID(); } else { userId = null; } return new JcrResourceChange(changeType, fullPath, isExternal, userId); } private boolean isExternal(final Event event) { if ( event instanceof JackrabbitEvent) { final JackrabbitEvent jEvent = (JackrabbitEvent)event; return jEvent.isExternal(); } return false; } @Override public String toString() { return "JcrResourceListener [" + config + "]"; } }