blob: ed3e972c661f42b86f99a4c0454bcc1a39998db9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. 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.jackrabbit.classloader;
import java.util.ArrayList;
import java.util.Collection;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import javax.jcr.observation.ObservationManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* The <code>DynamicPatternPath</code> class is a {@link PatternPath}
* which registers for modifications in the repository which may affect the
* result of calling the <code>getExpandedPaths</code> method. If also supports
* for clients registering with instances of this class to be notified if such
* an event happens.
* <p>
* To free the system from too much work, instances of this class are only
* registered with the session's observation manager if at least one listener is
* interested in notification to changes in the matched path list.
*
* @author Felix Meschberger
*/
/* package */ class DynamicPatternPath extends PatternPath
implements EventListener {
/** default logger */
private static final Log log =
LogFactory.getLog(DynamicPatternPath.class);
/** The list of registered listeners for this list */
private final ArrayList listeners = new ArrayList();
/**
* <code>true</code> if this instance is registered with the session's
* observation manager.
*/
private boolean isRegistered;
/**
* Creates an instance of the <code>DynamicPatternPath</code> from
* a collection of path patterns.
*
* @param session The session to access the Repository to expand the paths
* and to register as an event listener.
* @param pathPatterns The array of path patterns to add.
*
* @throws NullPointerException if the <code>pathPatterns</code> array is
* <code>null</code>.
*
* @see PatternPath#PathPatternList(Session, String[])
*/
/* package */ DynamicPatternPath(Session session, String[] pathPatterns) {
super(session, pathPatterns);
}
//---------- notification listener registration and interface -------------
/**
* Adds the given listener to the list of registered listeners. If the
* listener is already registered, it is not added a second time.
* <p>
* This is synchronized to prevent multiple parallel modification of the
* listeners list by mutliple threads.
*
* @param listener The listener to register. This must not be
* <code>null</code>.
*
* @throws NullPointerException if the <code>listener</code> parameter is
* <code>null</code>.
*/
/* package */ synchronized void addListener(Listener listener) {
// check listener
if (listener == null) {
throw new NullPointerException("listener");
}
// make sure we get updated on changes to be able to notify listeners
// we are pretty sure our listeners list will not be empty :-)
if (!isRegistered) {
log.debug("addListener: Register with observation service");
registerEventListener();
}
// guarded add
if (!listeners.contains(listener)) {
log.debug("addListener: Listener " + listener);
listeners.add(listener);
} else {
log.info("addListener: Listener " + listener + " already added");
}
}
/**
* Removes the given listener from the list of registered listeners. If the
* listener is not registered, the list of registered listeners is not
* modified.
* <p>
* This is synchronized to prevent multiple parallel modification of the
* listeners list by mutliple threads.
*
* @param listener The listener to deregister. This must not be
* <code>null</code>.
*
* @throws NullPointerException if the <code>listener</code> parameter is
* <code>null</code>.
*/
/* package */ synchronized void removeListener(Listener listener) {
// check listener
if (listener == null) {
throw new NullPointerException("listener");
}
// guarded removal
if (listeners.remove(listener)) {
log.debug("removeListener: Listener " + listener);
} else {
log.info("removeListener: Listener " + listener + " not registered");
}
// deregister if no listener is registered anymore
// we are pretty sure to be registered
if (listeners.size() == 0) {
log.debug("removeListener: Deregister from observation service");
unregisterEventListener();
}
}
//---------- EventListener interface --------------------------------------
/**
* Handles the case where any change occurrs to the set of matched paths.
* This is, if either a newly created item matches or a previously matching
* item has been removed.
* <p>
* This method ignores <code>PROPERTY_CHANGED</code> events, as these
* events do not have an influence on the set of matched paths.
* <p>
* The events in the iterator are analyzed until any non-property-change
* event has an influence on the set of matched paths. As soon as such a
* path is encountered, the listeners are notified and this method
* terminates without further inspection of the events.
*
* @param events The iterator on the events being sent
*/
public void onEvent(EventIterator events) {
// check whether any of the events match the pattern list. If so
// notify listeners on first match found and ignore rest for testing
while (events.hasNext()) {
Event event = events.nextEvent();
// ignore property modifications
if (event.getType() == Event.PROPERTY_CHANGED) {
continue;
}
try {
String path= event.getPath();
if (matchPath(path)) {
log.debug("onEvent: Listener Notification due to " +
path);
notifyListeners();
return;
}
} catch (RepositoryException re) {
log.info("onEvent: Cannot check events", re);
}
}
}
/**
* Registers this list object with the session's observation manager to get
* information on item updates.
*/
private void registerEventListener() {
// make sure we are not registered yet
if (isRegistered) {
log.debug("registerModificationListener: Already registered");
return;
}
try {
ObservationManager om =
getSession().getWorkspace().getObservationManager();
om.addEventListener(this, 0xffff, "/", true, null, null, false);
isRegistered = true;
} catch (RepositoryException re) {
log.warn("registerModificationListener", re);
}
}
/**
* Unregisters this list object from the observation manager to not get
* information on item updates anymore. This method is called when no more
* listeners are interested on updates. This helps garbage collect this
* object in the case no reference is held to the list anymore. If no one
* is interested in changes anymore, we are not interested either, so we
* may as well unregister.
*/
private void unregisterEventListener() {
// make sure we are registered
if (!isRegistered) {
log.debug("deregisterModificationListener: Not registered");
return;
}
try {
ObservationManager om =
getSession().getWorkspace().getObservationManager();
om.removeEventListener(this);
isRegistered = false;
} catch (RepositoryException re) {
log.warn("deregisterModificationListener", re);
}
}
/**
* Notifies all registered listeners on the change in the set of matched
* paths by calling their <code>pathListChanged</code> method.
*/
private void notifyListeners() {
for (int i=0; i < listeners.size(); i++) {
Listener listener = (Listener) listeners.get(i);
log.debug("notifyListeners: Notifying listener " + listener);
try {
listener.pathChanged();
} catch (Exception e) {
log.warn("notifyListeners: Listener " + listener + " threw: " + e);
log.debug("dump", e);
}
}
}
/**
* The <code>PatternPath.Listener</code> interface may be implemented
* by interested classes to be notified as soon as the
* {@link PatternPath#getExpandedPaths} method will return a
* different result on the next invocation. This happens as soon as the set
* of paths to which the list of patterns matches would change.
*/
/* package */ interface Listener {
/**
* This method is called if the listener is to be notified of an event
* resulting in the set of paths matched by the list of patterns to be
* different.
*/
public void pathChanged();
}
}