Return-Path: Delivered-To: apmail-incubator-jackrabbit-commits-archive@www.apache.org Received: (qmail 20076 invoked from network); 21 Dec 2005 14:15:07 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 21 Dec 2005 14:15:07 -0000 Received: (qmail 74843 invoked by uid 500); 21 Dec 2005 14:15:02 -0000 Mailing-List: contact jackrabbit-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: jackrabbit-dev@incubator.apache.org Delivered-To: mailing list jackrabbit-commits@incubator.apache.org Received: (qmail 74615 invoked by uid 500); 21 Dec 2005 14:15:01 -0000 Delivered-To: apmail-incubator-jackrabbit-cvs@incubator.apache.org Received: (qmail 74543 invoked by uid 99); 21 Dec 2005 14:15:00 -0000 Received: from asf.osuosl.org (HELO asf.osuosl.org) (140.211.166.49) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 21 Dec 2005 06:15:00 -0800 X-ASF-Spam-Status: No, hits=-8.6 required=10.0 tests=ALL_TRUSTED,INFO_TLD,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [209.237.227.194] (HELO minotaur.apache.org) (209.237.227.194) by apache.org (qpsmtpd/0.29) with SMTP; Wed, 21 Dec 2005 06:14:53 -0800 Received: (qmail 19711 invoked by uid 65534); 21 Dec 2005 14:14:33 -0000 Message-ID: <20051221141432.19709.qmail@minotaur.apache.org> Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r358296 [2/5] - in /incubator/jackrabbit/trunk/contrib/classloader: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/jackrabbit/ src/main/java/org/apache/jackrabbit/classloader/ src/main... Date: Wed, 21 Dec 2005 14:14:24 -0000 To: jackrabbit-cvs@incubator.apache.org From: fmeschbe@apache.org X-Mailer: svnmailer-1.0.5 X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Added: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/DynamicPatternPath.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/DynamicPatternPath.java?rev=358296&view=auto ============================================================================== --- incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/DynamicPatternPath.java (added) +++ incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/DynamicPatternPath.java Wed Dec 21 06:13:56 2005 @@ -0,0 +1,276 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation or its licensors, + * as applicable. + * + * Licensed 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 DynamicPatternPath class is a {@link PatternPath} + * which registers for modifications in the repository which may affect the + * result of calling the getExpandedPaths method. If also supports + * for clients registering with instances of this class to be notified if such + * an event happens. + *

+ * 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 + * @version $Rev:$, $Date:$ + */ +/* 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(); + + /** + * true if this instance is registered with the session's + * observation manager. + */ + private boolean isRegistered; + + /** + * Creates an instance of the DynamicPatternPath 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 pathPatterns array is + * null. + * + * @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. + *

+ * 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 + * null. + * + * @throws NullPointerException if the listener parameter is + * null. + */ + /* 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. + *

+ * 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 + * null. + * + * @throws NullPointerException if the listener parameter is + * null. + */ + /* 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. + *

+ * This method ignores PROPERTY_CHANGED events, as these + * events do not have an influence on the set of matched paths. + *

+ * 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 pathListChanged 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 PatternPath.Listener 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(); + } +} Propchange: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/DynamicPatternPath.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/DynamicPatternPath.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url Added: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/DynamicRepositoryClassLoader.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/DynamicRepositoryClassLoader.java?rev=358296&view=auto ============================================================================== --- incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/DynamicRepositoryClassLoader.java (added) +++ incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/DynamicRepositoryClassLoader.java Wed Dec 21 06:13:56 2005 @@ -0,0 +1,645 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation or its licensors, + * as applicable. + * + * Licensed 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.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.jcr.Property; +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; +import org.apache.jackrabbit.classloader.DynamicPatternPath.Listener; + + +/** + * The DynamicRepositoryClassLoader class extends the + * {@link org.apache.jackrabbit.classloader.RepositoryClassLoader} and provides the + * functionality to load classes and resources from the JCR Repository. + * Additionally, this class supports the notion of getting 'dirty', which means, + * that if a resource loaded through this class loader has been modified in the + * Repository, this class loader marks itself dirty, which flag can get + * retrieved. This helps the user of this class loader to decide on whether to + * {@link #reinstantiate(Session, ClassLoader) reinstantiate} it or continue + * using this class loader. + *

+ * When a user of the class loader recognizes an instance to be dirty, it can + * easily be reinstantiated with the {@link #reinstantiate} method. This + * reinstantiation will also rebuild the internal real class path from the same + * list of path patterns as was used to create the internal class path for the + * original class loader. The resulting internal class path need not be the + * same, though. + *

+ * As an additional feature the class loaders provides the functionality for + * complete reconfiguration of the list of path patterns defined at class loader + * construction time through the {@link #reconfigure(String[])} method. This + * reconfiguration replaces the internal class path with a new one built from + * the new path list and also replaces that path list. Reinstantiating a + * reconfigured class loader gets a class loader containing the same path list + * as the original class loader had after reconfiguration. That is the original + * configuration is lost. While reconfiguration is not able to throw away + * classes already loaded, it will nevertheless mark the class loader dirty, if + * any classes have already been loaded through it. + *

+ * This class is not intended to be extended by clients. + * + * @author Felix Meschberger + * @version $Rev:$, $Date$ + */ +public class DynamicRepositoryClassLoader extends RepositoryClassLoader + implements EventListener, Listener { + + /** default log category */ + private static final Log log = + LogFactory.getLog(DynamicRepositoryClassLoader.class); + + /** + * Cache of resources used to check class loader expiry. The map is indexed + * by the paths of the expiry properties of the cached resources. This map + * is not complete in terms of resources which have been loaded through this + * class loader. That is for resources loaded through an archive class path + * entry, only one of those resources (the last one loaded) is kept in this + * cache, while the others are ignored. + * + * @see #onEvent(EventIterator) + * @see #findClassLoaderResource(String) + */ + private Map modTimeCache; + + /** + * Flag indicating whether there are loaded classes which have later been + * expired (e.g. invalidated or modified) + */ + private boolean dirty; + + /** + * The list of repositories added through either the {@link #addURL} or the + * {@link #addHandle} method. + */ + private ClassPathEntry[] addedRepositories; + + /** + * Creates a DynamicRepositoryClassLoader from a list of item + * path strings containing globbing pattens for the paths defining the + * class path. + * + * @param session The Session to use to access the class items. + * @param classPath The list of path strings making up the (initial) class + * path of this class loader. The strings may contain globbing + * characters which will be resolved to build the actual class path. + * @param parent The parent ClassLoader, which may be + * null. + * + * @throws NullPointerException if either the session or the handles list + * is null. + */ + public DynamicRepositoryClassLoader(Session session, + String[] classPath, ClassLoader parent) { + + // initialize the super class with an empty class path + super(session, new DynamicPatternPath(session, classPath), parent); + + // set fields + dirty = false; + modTimeCache = new HashMap(); + + // register with observation service and path pattern list + registerModificationListener(); + + log.debug("DynamicRepositoryClassLoader: " + this + " ready"); + } + + /** + * Creates a DynamicRepositoryClassLoader with the same + * configuration as the given DynamicRepositoryClassLoader. + * This constructor is used by the {@link #reinstantiate} method. + *

+ * Before returning from this constructor the old class loader + * is destroyed and may not be used any more. + * + * @param session The session to associate with this class loader. + * @param old The DynamicRepositoryClassLoader to copy the + * cofiguration from. + * @param parent The parent ClassLoader, which may be + * null. + */ + private DynamicRepositoryClassLoader(Session session, + DynamicRepositoryClassLoader old, ClassLoader parent) { + + // initialize the super class with an empty class path + super(session, old.getHandles(), parent); + + // set the configuration and fields + dirty = false; + modTimeCache = new HashMap(); + + // create a repository from the handles - might get a different one + setRepository(resetClassPathEntries(old.getRepository())); + setAddedRepositories(resetClassPathEntries(old.getAddedRepositories())); + buildRepository(); + + // register with observation service and path pattern list + registerModificationListener(); + + // finally finalize the old class loader + old.destroy(); + + log.debug("DynamicRepositoryClassLoader: Copied " + old + ". Do not use " + + "that anymore"); + } + + /** + * Destroys this class loader. This process encompasses all steps needed + * to remove as much references to this class loader as possible. + *

+ * NOTE: This method just clears all internal fields and especially + * the class path to render this class loader unusable. + *

+ * This implementation does not throw any exceptions. + */ + public void destroy() { + // we expect to be called only once, so we stop destroyal here + if (isDestroyed()) { + log.debug("Instance is already destroyed"); + return; + } + + // remove ourselves as listeners from other places + unregisterListener(); + + addedRepositories = null; + + super.destroy(); + } + + //---------- reload support ------------------------------------------------ + + /** + * Checks whether this class loader already loaded the named resource and + * would load another version if it were instructed to do so. As a side + * effect the class loader sets itself dirty in this case. + *

+ * Calling this method yields the same result as calling + * {@link #shouldReload(String, boolean)} with the force + * argument set to false. + * + * @param name The name of the resource to check. + * + * @return true if the resource is loaded and reloading would + * take another version than currently loaded. + * + * @see #isDirty + */ + public synchronized boolean shouldReload(String name) { + return shouldReload(name, false); + } + + /** + * Checks whether this class loader already loaded the named resource and + * whether the class loader should be set dirty depending on the + * force argument. If the argument is true, the + * class loader is marked dirty and true is returned if the + * resource has been loaded, else the loaded resource is checked for expiry + * and the class loader is only set dirty if the loaded resource has + * expired. + * + * @param name The name of the resource to check. + * @param force true if the class loader should be marked dirty + * if the resource is loaded, else the class loader is only marked + * dirty if the resource is loaded and has expired. + * + * @return true if the resource is loaded and + * force is true or if the resource has + * expired. true is also returned if this class loader + * has already been destroyed. + * + * @see #isDirty + */ + public synchronized boolean shouldReload(String name, boolean force) { + if (isDestroyed()) { + log.warn("Classloader already destroyed, reload required"); + return true; + } + + ClassLoaderResource res = getCachedResource(name); + if (res != null) { + log.debug("shouldReload: Expiring cache entry " + res); + if (force) { + log.debug("shouldReload: Forced dirty flag"); + dirty = true; + return true; + } + + return expireResource(res); + } + + return false; + } + + /** + * Returns true if any of the loaded classes need reload. Also + * sets this class loader dirty. If the class loader is already set dirty + * or if this class loader has been destroyed before calling this method, + * it returns immediately. + * + * @return true if any class loader needs to be reinstantiated. + * + * @see #isDirty + */ + public synchronized boolean shouldReload() { + + // check whether we are already dirty + if (isDirty()) { + log.debug("shouldReload: Dirty, need reload"); + return true; + } + + // Check whether any class has changed + for (Iterator iter = getCachedResources(); iter.hasNext();) { + if (expireResource((ClassLoaderResource) iter.next())) { + log.debug("shouldReload: Found expired resource, need reload"); + return true; + } + } + + // No changes, no need to reload + log.debug("shouldReload: No expired resource found, no need to reload"); + return false; + } + + /** + * Returns whether the class loader is dirty. This can be the case if any + * of the {@link #shouldReload(String)} or {@link #shouldReload()} + * methods returned true or if a loaded class has been expired + * through the observation. + *

+ * This method may also return true if the Session + * associated with this class loader is not valid anymore. + *

+ * Finally the method always returns true if the class loader + * has already been destroyed. Note, however, that a destroyed class loader + * cannot be reinstantiated. See {@link #reinstantiate(Session, ClassLoader)}. + *

+ * If the class loader is dirty, it should be reinstantiated through the + * {@link #reinstantiate} method. + * + * @return true if the class loader is dirty and needs + * reinstantiation. + */ + public boolean isDirty() { + return isDestroyed() || dirty || !getSession().isLive(); + } + + /** + * Reinstantiates this class loader. That is, a new ClassLoader with no + * loaded class is created with the same configuration as this class loader. + *

+ * When the new class loader is returned, this class loader has been + * destroyed and may not be used any more. + * + * @param parent The parent ClassLoader for the reinstantiated + * DynamicRepositoryClassLoader, which may be + * null. + * + * @return a new instance with the same configuration as this class loader. + * + * @throws IllegalStateException if this + * {@link DynamicRepositoryClassLoader} has already been destroyed + * through the {@link #destroy()} method. + */ + public DynamicRepositoryClassLoader reinstantiate(Session session, ClassLoader parent) { + log.debug("reinstantiate: Copying " + this + " with parent " + parent); + + if (isDestroyed()) { + throw new IllegalStateException("Destroyed class loader cannot be recreated"); + } + + // create the new loader + DynamicRepositoryClassLoader newLoader = + new DynamicRepositoryClassLoader(session, this, parent); + + // return the new loader + return newLoader; + } + + //---------- URLClassLoader overwrites ------------------------------------- + + /** + * Reconfigures this class loader with the pattern list. That is the new + * pattern list completely replaces the current pattern list. This new + * pattern list will also be used later to configure the reinstantiated + * class loader. + *

+ * If this class loader already has loaded classes using the old, replaced + * path list, it is set dirty. + *

+ * If this class loader has already been destroyed, this method has no + * effect. + * + * @param classPath The list of path strings making up the (initial) class + * path of this class loader. The strings may contain globbing + * characters which will be resolved to build the actual class path. + */ + public void reconfigure(String[] classPath) { + log.debug("reconfigure: Reconfiguring the with " + classPath); + + // whether the loader is destroyed + if (isDestroyed()) { + log.warn("Cannot reconfigure this destroyed class loader"); + return; + } + + // deregister to old handles + ((DynamicPatternPath) getHandles()).removeListener(this); + + // assign new handles and register + setHandles(new DynamicPatternPath(getSession(), classPath)); + buildRepository(); + ((DynamicPatternPath) getHandles()).addListener(this); + + dirty = !hasLoadedResources(); + log.debug("reconfigure: Class loader is dirty now: " + + (isDirty() ? "yes" : "no")); + } + + //---------- RepositoryClassLoader overwrites ----------------------------- + + /** + * Calls the base class implementation to actually retrieve the resource. + * If the resource could be found and provides a non-null + * {@link ClassLoaderResource#getExpiryProperty() expiry property}, the + * resource is registered with an internal cache to check with when + * a repository modification is observed in {@link #onEvent(EventIterator)}. + * + * @param name The name of the resource to be found + * + * @return the {@link ClassLoaderResource} found for the name or + * null if no such resource is available in the class + * path. + * + * @throws NullPointerException If this class loader has already been + * destroyed. + */ + /* package */ ClassLoaderResource findClassLoaderResource(String name) { + // call the base class implementation to actually search for it + ClassLoaderResource res = super.findClassLoaderResource(name); + + // if it could be found, we register it with the caches + if (res != null) { + // register the resource in the expiry map, if an appropriate + // property is available + Property prop = res.getExpiryProperty(); + if (prop != null) { + try { + modTimeCache.put(prop.getPath(), res); + } catch (RepositoryException re) { + log.warn("Cannot register the resource " + res + + " for expiry", re); + } + } + } + + // and finally return the resource + return res; + } + + /** + * Builds the repository list from the list of path patterns and appends + * the path entries from any added handles. This method may be used multiple + * times, each time replacing the currently defined repository list. + * + * @throws NullPointerException If this class loader has already been + * destroyed. + */ + protected synchronized void buildRepository() { + super.buildRepository(); + + // add added repositories + ClassPathEntry[] addedPath = getAddedRepositories(); + if (addedPath != null && addedPath.length > 0) { + ClassPathEntry[] oldClassPath = getRepository(); + ClassPathEntry[] newClassPath = + new ClassPathEntry[oldClassPath.length + addedPath.length]; + + System.arraycopy(oldClassPath, 0, newClassPath, 0, + oldClassPath.length); + System.arraycopy(addedPath, 0, newClassPath, oldClassPath.length, + addedPath.length); + + setRepository(newClassPath); + } + } + + //---------- ModificationListener interface ------------------------------- + + /** + * Handles a repository item modifcation events checking whether a class + * needs to be expired. As a side effect, this method sets the class loader + * dirty if a loaded class has been modified in the repository. + * + * @param events The iterator of repository events to be handled. + */ + public void onEvent(EventIterator events) { + while (events.hasNext()) { + Event event = events.nextEvent(); + try { + String path = event.getPath(); + log.debug("onEvent: Item " + path + " has been modified, " + + "checking with cache"); + + ClassLoaderResource resource = + (ClassLoaderResource) modTimeCache.get(path); + if (resource != null) { + log.debug("pageModified: Expiring cache entry "+ resource); + expireResource(resource); + } else { + // might be in not-found cache - remove from there + if (event.getType() == Event.NODE_ADDED || + event.getType() == Event.PROPERTY_ADDED) { + log.debug("pageModified: Clearing not-found cache " + + "for possible new class"); + cleanCache(); + } + } + + } catch (RepositoryException re) { + // + } + } + } + + //----------- PatternPath.Listener interface ------------------------- + + /** + * Handles modified matched path set by setting the class loader dirty. + * The internal class path is only rebuilt when the class loader is + * reinstantiated. + */ + public void pathChanged() { + log.debug("handleListChanged: The path list has changed"); + buildRepository(); + dirty = true; + } + + //----------- Object overwrite --------------------------------------------- + + /** + * Returns a string representation of this class loader. + */ + public String toString() { + if (isDestroyed()) { + return super.toString(); + } + + StringBuffer buf = new StringBuffer(super.toString()); + buf.append(", dirty: "); + buf.append(isDirty()); + return buf.toString(); + } + + //---------- internal ------------------------------------------------------ + + /** + * Sets the list of class path entries to add to the class path after + * reconfiguration or reinstantiation. + * + * @param addedRepositories The list of class path entries to keep for + * readdition. + */ + protected void setAddedRepositories(ClassPathEntry[] addedRepositories) { + this.addedRepositories = addedRepositories; + } + + /** + * Returns the list of added class path entries to readd them to the class + * path after reconfiguring the class loader. + */ + protected ClassPathEntry[] getAddedRepositories() { + return addedRepositories; + } + + /** + * Adds the class path entry to the current class path list. If the class + * loader has already been destroyed, this method creates a single entry + * class path list with the new class path entry. + *

+ * Besides adding the entry to the current class path, it is also added to + * the list to be readded after reconfiguration and/or reinstantiation. + * + * @see #getAddedRepositories() + * @see #setAddedRepositories(ClassPathEntry[]) + */ + protected void addClassPathEntry(ClassPathEntry cpe) { + super.addClassPathEntry(cpe); + + // add the repsitory to the list of added repositories + ClassPathEntry[] oldClassPath = getAddedRepositories(); + ClassPathEntry[] newClassPath = addClassPathEntry(oldClassPath, cpe); + setAddedRepositories(newClassPath); + } + + /** + * Registers this class loader with the observation service to get + * information on page updates in the class path and to the path + * pattern list to get class path updates. + * + * @throws NullPointerException if this class loader has already been + * destroyed. + */ + private final void registerModificationListener() { + ((DynamicPatternPath) getHandles()).addListener(this); + + log.debug("registerModificationListener: Registering to the observation service"); + try { + ObservationManager om = getSession().getWorkspace().getObservationManager(); + om.addEventListener(this, 255, "/", true, null, null, false); + } catch (RepositoryException re) { + log.error("registerModificationListener: Cannot register " + + this + " with observation manager", re); + } + } + + /** + * Removes this instances registrations from the observation service and + * the path pattern list. + * + * @throws NullPointerException if this class loader has already been + * destroyed. + */ + private final void unregisterListener() { + ((DynamicPatternPath) getHandles()).removeListener(this); + + log.debug("registerModificationListener: Deregistering from the observation service"); + try { + ObservationManager om = getSession().getWorkspace().getObservationManager(); + om.removeEventListener(this); + } catch (RepositoryException re) { + log.error("unregisterListener: Cannot unregister " + + this + " from observation manager", re); + } + } + + /** + * Checks whether the page backing the resource has been updated with a + * version, such that this new version would be used to access the resource. + * In this case the resource has expired and the class loader needs to be + * set dirty. + * + * @param resource The ClassLoaderResource to check for + * expiry. + */ + private boolean expireResource(ClassLoaderResource resource) { + + // check whether the resource is expired (only if a class has been loaded) + boolean exp = resource.getLoadedClass() != null && resource.isExpired(); + + // update dirty flag accordingly + dirty |= exp; + log.debug("expireResource: Loader dirty: " + isDirty()); + + // return the expiry status + return exp; + } + + /** + * Returns the list of classpath entries after resetting each of them. + * + * @param list The list of {@link ClassPathEntry}s to reset + * + * @return The list of reset {@link ClassPathEntry}s. + */ + private static ClassPathEntry[] resetClassPathEntries( + ClassPathEntry[] oldClassPath) { + if (oldClassPath != null) { + for (int i=0; i < oldClassPath.length; i++) { + ClassPathEntry entry = oldClassPath[i]; + log.debug("resetClassPathEntries: Cloning " + entry); + oldClassPath[i] = entry.copy(); + } + } else { + log.debug("resetClassPathEntries: No list to reset"); + } + return oldClassPath; + } +} \ No newline at end of file Propchange: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/DynamicRepositoryClassLoader.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/DynamicRepositoryClassLoader.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url Added: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/ExpandingArchiveClassPathEntry.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/ExpandingArchiveClassPathEntry.java?rev=358296&view=auto ============================================================================== --- incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/ExpandingArchiveClassPathEntry.java (added) +++ incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/ExpandingArchiveClassPathEntry.java Wed Dec 21 06:13:56 2005 @@ -0,0 +1,461 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation or its licensors, + * as applicable. + * + * Licensed 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.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.Calendar; +import java.util.StringTokenizer; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Workspace; +import javax.jcr.nodetype.NoSuchNodeTypeException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The ExpandingArchiveClassPathEntry extends the + * {@link org.apache.jackrabbit.classloader.ArchiveClassPathEntry} class with support + * to automatically expand the archive (JAR or ZIP) into the repository + * below the path entry node. The path used to construct the instance is the + * path of an item resolving to a property containing the jar archive to access. + * + * @author Felix Meschberger + * @version $Rev:$, $Date$ + * + * @see org.apache.jackrabbit.classloader.ArchiveClassPathEntry + * @see org.apache.jackrabbit.classloader.ClassPathEntry + */ +/* package */ class ExpandingArchiveClassPathEntry extends ArchiveClassPathEntry { + + /** The name of the node type required to expand the archive */ + public static final String TYPE_JARFILE = "rep:jarFile"; + + /** The name of the child node taking the expanded archive */ + public static final String NODE_JARCONTENTS = "rep:jarContents"; + + /** + * The name of the property taking the time at which the archive was + * expanded + */ + public static final String PROP_EXPAND_DATE = "rep:jarExpanded"; + + /** Default logger */ + private static final Log log = + LogFactory.getLog(ExpandingArchiveClassPathEntry.class); + + /** The node of the unpacked JAR contents */ + private Node jarContents; + + /** + * Creates an instance of the ExpandingArchiveClassPathEntry + * class. + * + * @param prop The Property containing the archive and + * the session used to access the repository. + * @param path The original class path entry leading to the creation of + * this instance. This is not necessairily the same path as the + * property's path if the property was found through the primary + * item chain. + * + * @throws RepositoryException If an error occurrs retrieving the session + * from the property. + */ + ExpandingArchiveClassPathEntry(Property prop, String path) + throws RepositoryException { + super(prop, path); + } + + /** + * Clones the indicated ExpandingArchiveClassPathEntry object + * by taking over its path, session and property. + * + * @param base The base ExpandingArchiveClassPathEntry entry + * to clone. + * + * @see ClassPathEntry#ClassPathEntry(ClassPathEntry) + */ + private ExpandingArchiveClassPathEntry(ExpandingArchiveClassPathEntry base) { + super(base); + } + + /** + * Returns a {@link ClassLoaderResource} for the named resource if it + * can be found in the archive identified by the path given at + * construction time. Note that if the archive property would exist but is + * not readable by the current session, no resource is returned. + * + * @param name The name of the resource to return. If the resource would + * be a class the name must already be modified to denote a valid + * path, that is dots replaced by slashes and the .class + * extension attached. + * + * @return The {@link ClassLoaderResource} identified by the name or + * null if no resource is found for that name. + */ + public ClassLoaderResource getResource(final String name) { + + try { + // find the resource for the name in the expanded archive contents + Node jarContents = getJarContents(); + Item resItem = null; + if (jarContents.hasNode(name)) { + resItem = jarContents.getNode(name); + } else if (jarContents.hasProperty(name)) { + resItem = jarContents.getProperty(name); + } + + // if the name resolved to an item, resolve the item to a + // single-valued non-reference property + Property resProp = (resItem != null) + ? Util.getProperty(resItem) + : null; + + // if found create the resource to return + if (resProp != null) { + return new ClassLoaderResource(this, name, resProp) { + public URL getURL() { + return ExpandingArchiveClassPathEntry.this.getURL(getName()); + } + + public URL getCodeSourceURL() { + return ExpandingArchiveClassPathEntry.this.getCodeSourceURL(); + } + + public Manifest getManifest() { + return ExpandingArchiveClassPathEntry.this.getManifest(); + } + + protected Property getExpiryProperty() { + return ExpandingArchiveClassPathEntry.this.getProperty(); + } + }; + } + + log.debug("getResource: resource " + name + " not found" + + " in archive " + path); + + } catch (RepositoryException re) { + + log.warn("getResource: problem accessing the archive " + path + + " for " + name + ": " + re.toString()); + + } + // invariant : not found or problem accessing the archive + + return null; + } + + /** + * Returns a ClassPathEntry with the same configuration as + * this ClassPathEntry. + *

+ * The ExpandingArchiveClassPathEntry class has internal state. + * Therefore a new instance is created from the unmodifiable configuration + * of this instance. + */ + ClassPathEntry copy() { + return new ExpandingArchiveClassPathEntry(this); + } + + //----------- internal helper to find the entry ------------------------ + + /** + * Returns the root node of the expanded archive. If the archive's node + * does not contain the expanded archive, it is expanded on demand. If the + * archive has already been expanded, it is checked whether it is up to + * date and expanded again if not. + * + * @throws RepositoryException if an error occurrs expanding the archive + * into the repository. + */ + private Node getJarContents() throws RepositoryException { + if (jarContents == null) { + Node jarNode = null; // the node containing the jar file + Node jarRoot = null; // the root node of the expanded contents + try { + Item jarItem = session.getItem(getPath()); + jarNode = (jarItem.isNode()) ? (Node) jarItem : jarItem.getParent(); + + // if the jar been unpacked once, check for updated jar file, + // which must be unpacked + if (jarNode.isNodeType(TYPE_JARFILE)) { + long lastMod = Util.getLastModificationTime(getProperty()); + long expanded = + jarNode.getProperty(PROP_EXPAND_DATE).getLong(); + + // get the content, remove if outdated or use if ok + jarRoot = jarNode.getNode(NODE_JARCONTENTS); + + // if expanded content is outdated, remove it + if (lastMod <= expanded) { + jarRoot.remove(); + jarRoot = null; // have to unpack below + } + + } else if (!jarNode.canAddMixin(TYPE_JARFILE)) { + // this is actually a problem, because I expect to be able + // to add the mixin node type due to checkExpandArchives + // having returned true earlier + throw new RepositoryException( + "Cannot unpack JAR file contents into " + + jarNode.getPath()); + + } else { + jarNode.addMixin(TYPE_JARFILE); + jarNode.setProperty(PROP_EXPAND_DATE, Calendar.getInstance()); + } + + // if the content root is not set, unpack and save + if (jarRoot == null) { + jarRoot = jarNode.addNode(NODE_JARCONTENTS, "nt:folder"); + unpack(jarRoot); + jarNode.save(); + } + + } finally { + + // rollback changes on the jar node in case of problems + if (jarNode != null && jarNode.isModified()) { + // rollback incomplete modifications + log.warn("Rolling back unsaved changes on JAR node " + + getPath()); + + try { + jarNode.refresh(false); + } catch (RepositoryException re) { + log.warn("Cannot rollback changes after failure to " + + "expand " + getPath(), re); + } + } + } + + jarContents = jarRoot; + } + + return jarContents; + } + + /** + * Expands the archive stored in the property of this class path entry into + * the repositroy below the given jarRoot node. + *

+ * This method leaves the subtree at and below jarRoot unsaved. + * It is the task of the caller to save or rollback as appropriate. + * + * @param jarRoot The Node below which the archive is to be + * unpacked. + * + * @throws RepositoryException If an error occurrs creating the item + * structure to unpack the archive or if an error occurrs reading + * the archive. + */ + private void unpack(Node jarRoot) throws RepositoryException { + + ZipInputStream zin = null; + try { + zin = new ZipInputStream(getProperty().getStream()); + ZipEntry entry = zin.getNextEntry(); + while (entry != null) { + if (entry.isDirectory()) { + unpackFolder(jarRoot, entry.getName()); + } else { + unpackFile(jarRoot, entry, zin); + } + entry = zin.getNextEntry(); + } + } catch (IOException ioe) { + throw new RepositoryException( + "Problem reading JAR contents of " + getPath(), ioe); + } finally { + // close the JAR stream if open + if (zin != null) { + try { + zin.close(); + } catch (IOException ignore) {} + } + } + } + + /** + * Makes sure a node exists at the path relative to + * root. In other words, this method returns the node + * root.getNode(path), creating child nodes as required. Newly + * created nodes are created with node type nt:folder. + *

+ * If intermediate nodes or the actual node required already exist, they + * must be typed such, that they may either accept child node creations + * of type nt:file or nt:folder. + * + * @param root The Node relative to which a node representing + * a folder is to created if required. + * @param path The path relative to root of the folder to + * ensure. + * + * @return The Node representing the folder below + * root. + * + * @throws RepositoryException If an error occurrs accessing the repository + * or creating missing node(s). + */ + private Node unpackFolder(Node root, String path) throws RepositoryException { + + // remove trailing slash + while (path.endsWith("/")) { + path = path.substring(0, path.length()-1); + } + + // quick check if the folder already exists + if (root.hasNode(path)) { + return root.getNode(path); + } + + // go down and create the path + StringTokenizer tokener = new StringTokenizer(path, "/"); + while (tokener.hasMoreTokens()) { + String label = tokener.nextToken(); + if (root.hasNode(label)) { + root = root.getNode(label); + } else { + root = root.addNode(label, "nt:folder"); + } + } + + // return the final node + return root; + } + + /** + * Creates a nt:file node with the path + * entry.getName() relative to the root node. The + * contents of the jcr:content/jcr:data property of the file + * node is retrieved from ins. + *

+ * The jcr:content/jcr:lastModified property is set to the + * value of the time field of the entry. The + * jcr:content/jcr:mimeType property is set to a best-effort + * guess of the content type of the entry. To guess the content type, the + * java.net.URLConnection.guessContentType(String) method + * is called. If this results in no content type, the default + * application/octet-stream is set. + * + * @param root The node relative to which the nt:file node + * is created. + * @param entry The ZipEntry providing information on the + * file to be created. Namely the name and + * time fields are used. + * @param ins The InputStream providing the data to be written + * to the jcr:content/jcr:data property. + * + * @throws RepositoryException If an error occurrs creating and filling + * the nt:file node. + */ + private void unpackFile(Node root, ZipEntry entry, InputStream ins) throws RepositoryException { + int slash = entry.getName().lastIndexOf('/'); + String label = entry.getName().substring(slash+1); + Node parent = (slash <= 0) + ? root + : unpackFolder(root, entry.getName().substring(0, slash)); + + // remove existing node (and all children by the way !!) + if (parent.hasNode(label)) { + parent.getNode(label).remove(); + } + + // prepare property values + Calendar lastModified = Calendar.getInstance(); + lastModified.setTimeInMillis(entry.getTime()); + String mimeType = URLConnection.guessContentTypeFromName(label); + if (mimeType == null) { + mimeType = "application/octet-stream"; + } + + // create entry nodes + Node ntFile = parent.addNode(label, "nt:file"); + Node content = ntFile.addNode("jcr:content", "nt:resource"); + content.setProperty("jcr:mimeType", mimeType); + content.setProperty("jcr:data", ins); + content.setProperty("jcr:lastModified", lastModified); + } + + /** + * Checks whether it is possible to use this class for archive class path + * entries in the workspace (and repository) to which the session + * provides access. + *

+ * This method works as follows: + *

    + *
  1. If the node type rep:jarFile is defined in the session's + * repository, true is immediately returned. + *
  2. If an error checking for the node type, false is + * immediately returned. + *
  3. Next would be to try to define the node type, this requires the + * session to be an instance of CRXSession, + * which is required to access the node type registry. If the session + * is not a CRXSession, false is returned. + *
  4. Finally the node type is defined and true is returned. + * If an error occurrs defining the node type, false is + * returned. + *
+ *

+ * This method is synchronized such that two paralell threads do not try + * to create the node, which might yield wrong negatives. + * + * @param session The Session providing access to the + * repository. + * + * @return true if this class can be used to handle archive + * class path entries. See above for a description of the test used. + */ + /* package */ synchronized static boolean canExpandArchives(Session session) { + + // quick check for the node type, succeed if defined + try { + session.getWorkspace().getNodeTypeManager().getNodeType(TYPE_JARFILE); + log.debug("Required node type exists, can expand archives"); + return true; + } catch (NoSuchNodeTypeException nst) { + log.debug("Required node types does not exist, try to define"); + } catch (RepositoryException re) { + log.info("Cannot check for required node type, cannot expand " + + "archives", re); + return false; + } + + try { + Workspace workspace = session.getWorkspace(); + return NodeTypeSupport.registerNodeType(workspace); + } catch (Throwable t) { + // Prevent anything from hapening if node type registration fails + // due to missing libraries or other errors + log.info("Error registering node type", t); + } + + // fallback to failure + return false; + } +} \ No newline at end of file Propchange: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/ExpandingArchiveClassPathEntry.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/ExpandingArchiveClassPathEntry.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url Added: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/NodeTypeSupport.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/NodeTypeSupport.java?rev=358296&view=auto ============================================================================== --- incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/NodeTypeSupport.java (added) +++ incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/NodeTypeSupport.java Wed Dec 21 06:13:56 2005 @@ -0,0 +1,152 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation or its licensors, + * as applicable. + * + * Licensed 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.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.List; + +import javax.jcr.RepositoryException; +import javax.jcr.Workspace; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; +import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; +import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; +import org.apache.jackrabbit.core.nodetype.compact.CompactNodeTypeDefReader; +import org.apache.jackrabbit.core.nodetype.compact.ParseException; + +/** + * The NodeTypeSupport contains a single utility method + * {@link #registerNodeType(Workspace)} to register the required mixin node + * type rep:jarFile with the repository. + *

+ * If the class loader is not used on a Jackrabbit based repository, loading + * this class or calling the {@link #registerNodeType(Workspace)} methods may + * fail with link errors. + * + * @author Felix Meschberger + * @version $Rev:$, $Date:$ + */ +/* package */ class NodeTypeSupport { + + /** Default log */ + private static final Log log = LogFactory.getLog(NodeTypeSupport.class); + + /** + * The name of the class path resource containing the node type definition + * file used by the {@link #registerNodeType(Workspace)} method to register + * the required mixin node type (value is "type.cnd"). + */ + private static final String TYPE_FILE = "type.cnd"; + + /** + * The encoding used to read the node type definition file (value is + * "ISO-8859-1"). + */ + private static final String ENCODING = "ISO-8859-1"; + + /** + * Registers the required node type (rep:jarFile) with the + * node type manager available from the given workspace. + *

+ * The NodeTypeManager returned by the workspace + * is expected to be of type + * org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl for + * the node type registration to succeed. + *

+ * This method is not synchronized. It is up to the calling method to + * prevent paralell execution. + * + * @param workspace The Workspace providing the node type + * manager through which the node type is to be registered. + * + * @return true if this class can be used to handle archive + * class path entries. See above for a description of the test used. + */ + /* package */ static boolean registerNodeType(Workspace workspace) { + + // Access the node type definition file, "fail" if not available + InputStream ins = NodeTypeSupport.class.getResourceAsStream(TYPE_FILE); + if (ins == null) { + log.error("Node type definition file " + TYPE_FILE + + " not in class path. Cannot define required node type"); + return false; + } + + // Wrap the stream with a reader + InputStreamReader reader = null; + try { + reader = new InputStreamReader(ins, ENCODING); + } catch (UnsupportedEncodingException uee) { + log.warn("Required Encoding " + ENCODING + " not supported, " + + "using platform default encoding", uee); + + reader = new InputStreamReader(ins); + } + + try { + // Create a CompactNodeTypeDefReader + CompactNodeTypeDefReader cndReader = + new CompactNodeTypeDefReader(reader, TYPE_FILE); + + // Get the List of NodeTypeDef objects + List ntdList = cndReader.getNodeTypeDefs(); + + // Get the NodeTypeManager from the Workspace. + // Note that it must be cast from the generic JCR NodeTypeManager + // to the Jackrabbit-specific implementation. + NodeTypeManagerImpl ntmgr = + (NodeTypeManagerImpl) workspace.getNodeTypeManager(); + + // Acquire the NodeTypeRegistry + NodeTypeRegistry ntreg = ntmgr.getNodeTypeRegistry(); + + // register the node types from the file in a batch + ntreg.registerNodeTypes(ntdList); + + // get here and succeed + return true; + + } catch (ParseException pe) { + log.error("Unexpected failure to parse compact node defintion " + TYPE_FILE, pe); + + } catch (InvalidNodeTypeDefException ie) { + log.error("Cannot define required node type", ie); + + } catch (RepositoryException re) { + log.error("General problem accessing the repository", re); + + } catch (ClassCastException cce) { + log.error("Unexpected object type encountered", cce); + + } finally { + // make sure the reader is closed - expect to be non-null here ! + try { + reader.close(); + } catch (IOException ioe) { + // ignore + } + } + + // fall back to failure + return false; + } +} Propchange: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/NodeTypeSupport.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/NodeTypeSupport.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url Added: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/PatternPath.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/PatternPath.java?rev=358296&view=auto ============================================================================== --- incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/PatternPath.java (added) +++ incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/PatternPath.java Wed Dec 21 06:13:56 2005 @@ -0,0 +1,344 @@ +/* + * Copyright 2004-2005 The Apache Software Foundation or its licensors, + * as applicable. + * + * Licensed 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 java.util.Iterator; +import java.util.List; +import java.util.StringTokenizer; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.PropertyIterator; +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; +import org.apache.jackrabbit.util.ChildrenCollectorFilter; +import org.apache.jackrabbit.util.Text; + +/** + * The PatternPath implements a list of repository item path + * patterns providing an iterator on the expanded paths. The list of patterns is + * immutably configured from an array of strings. + *

+ * While the original list of path patterns may be retrieved for informational + * purposes by calling the {@link #getPath()} method, the primary contents of + * instances of this class are the expanded paths accessible by calling the + * {@link #getExpandedPaths()} method. + *

+ * Please note that though this list is immutable there is intentionally no + * guarantee that all invocations of the {@link #getExpandedPaths} method + * return the same contents as the patterns contained in the list may expand to + * different paths for each invocation of that method. + *

+ * Each entry in the pattern list is a path whose segments conform to the + * pattern syntax defined for the Node.getNodes(String) method. + * The pattern may be a full name or a partial name with one or more wildcard + * characters ("*"), or a disjunction (using the "|" character to represent + * logical OR) of these. For example, + *

"jcr:*|foo:bar"
+ * would match "foo:bar", but also "jcr:whatever". + *

+ * The EBNF for pattern is: + *

+ * namePattern ::= disjunct {'|' disjunct}
+ * disjunct ::= name [':' name]
+ * name ::= '*' |
+ *          ['*'] fragment {'*' fragment}['*']
+ * fragment ::= char {char}
+ * char ::= nonspace | ' '
+ * nonspace ::= (* Any Unicode character except:
+ *               '/', ':', '[', ']', '*',
+ *               ''', '"', '|' or any whitespace
+ *               character *)
+ * 
+ * + * @author Felix Meschberger + * @version $Rev:$, $Date:$ + */ +/* package */ class PatternPath { + + /** default logger */ + private static final Log log = LogFactory.getLog(PatternPath.class); + + /** The session to access the repository */ + private final Session session; + + /** The list of path patterns */ + private final String[] patterns; + + /** + * Creates a PatternPath containing the elements of the + * string array. Each entry in the array which is either empty or + * null is ignored and not added to the list. If the array + * is empty or only contains empty or null elements, the + * resulting list will consequently be empty. + * + * @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 pathPatterns array or + * the session is null. + */ + /* package */ PatternPath(Session session, String[] pathPatterns) { + + // check session + if (session == null) { + throw new NullPointerException("session"); + } + + // prepare the pattern list, excluding null/empty entries + List patternList = new ArrayList(); + for (int i=0; i < pathPatterns.length; i++) { + addChecked(patternList, pathPatterns[i]); + } + patterns = + (String[]) patternList.toArray(new String[patternList.size()]); + + this.session = session; + } + + /** + * Returns the session from which this instance has been constructed. + */ + /* package */ Session getSession() { + return session; + } + + /** + * Returns a copy of the list of path patterns from which this instance has + * been constructed. + */ + /* package */ String[] getPath() { + return (String[]) patterns.clone(); + } + + /** + * Returns the list of expanded paths matching the list of patterns. This + * list is guaranteed to only return existing items. + *

+ * Each invocation of this method expands the pattern anew and returns a + * new list instance. + * + * @return The list of paths matching the patterns. If the pattern list is + * empty or if no real paths match for any entry in the list, the + * returned list is empty. + * + * @throws RepositoryException if an error occurrs expanding the path + * pattern list. + */ + /* package */ List getExpandedPaths() throws RepositoryException { + List result = new ArrayList(patterns.length); + Node root = session.getRootNode(); + + for (int i=0; i < patterns.length; i++) { + String entry = patterns[i]; + + if (entry.indexOf('*') >= 0 || entry.indexOf('|') >= 0) { + + scan(root, entry, result); + + } else { + // add path without pattern characters without further + // checking. This allows adding paths which do not exist yet. + result.add(entry); + } + + } + + return result; + } + + //---------- Object overwrite ---------------------------------------------- + + /** + * Returns true if this object equals the other object. This + * implementation only returns true if the other object is the same as this + * object. + *

+ * The difference to the base class implementation is, that we only accept + * equality if the other object is the same than this object. This is + * actually the same implementation as the original Object.equals + * implementation. + * + * @param o The other object to compare to. + * + * @return true if the other object is the same as this. + */ + public boolean equals(Object o) { + return o == this; + } + + /** + * Returns a hashcode for this instance. This is currently the hash code + * returned by the parent implementation. While it does not violate the + * contract to not change the hashCode() implementation but + * to change the implementation of the {@link #equals} method, I think this + * is ok, because our implementation of the {@link #equals} method is just + * more specific than the base class implementation, which also allows + * the other object to be a list with the same contents. + * + * @return The hash code returned by the base class implementation. + */ + public int hashCode() { + return super.hashCode(); + } + + /** + * Returns a string representation of this instance. This is actually the + * result of the string representation of the List, this actually is, + * prefixed with the name of this class. + * + * @return The string representation of this instance. + */ + public String toString() { + StringBuffer buf = new StringBuffer("PatternPath: ["); + for (int i=0; i < patterns.length; i++) { + if (i != 0) buf.append(", "); + buf.append(patterns[i]); + } + buf.append("]"); + return buf.toString(); + } + + //---------- internal ------------------------------------------------------ + + /** + * Adds the string to the list of patterns, if neither empty nor + * null. If the string has one or more trailing slashes + * (/) they are removed before adding the string. + */ + private void addChecked(List patternList, String pattern) { + if (pattern == null || pattern.length() == 0) { + log.debug("addChecked: Not adding null/empty pattern"); + } else { + + // remove all trailing slashes + while (pattern.endsWith("/") && pattern.length() > 1) { + pattern = pattern.substring(0, pattern.length()-1); + } + + log.debug("addChecked: Adding " + pattern); + patternList.add(pattern); + } + } + + //---------- Path expansion ----------------------------------------------- + + /** + * Finds the paths of all nodes and properties matching the pattern below + * the root node. + * + * @param root The root node of the subtree to match against the path + * pattern. + * @param pathPattern The path pattern to use to find matching nodes. + * @param gather The list into which the paths of matching child items + * are added. + */ + private static void scan(Node root, String pathPattern, List gather) + throws RepositoryException { + + // initial list of candidates is the root node + List candidates = new ArrayList(); + candidates.add(root); + + StringTokenizer patterns = new StringTokenizer(pathPattern, "/"); + boolean moreTokens = patterns.hasMoreTokens(); + while (moreTokens) { + String pattern = patterns.nextToken(); + moreTokens = patterns.hasMoreTokens(); + + // new candidates are the children of the current candidates list + // matching the current pattern + List newCandidates = new ArrayList(); + for (Iterator ci=candidates.iterator(); ci.hasNext(); ) { + Node current = (Node) ci.next(); + for (NodeIterator ni=current.getNodes(pattern); ni.hasNext(); ) { + newCandidates.add(ni.nextNode()); + } + + // if pattern is the last, also consider properties + if (!moreTokens) { + PropertyIterator pi = current.getProperties(pattern); + while (pi.hasNext()) { + newCandidates.add(pi.nextProperty()); + } + } + } + + // drop old candidates and use new for next step + candidates.clear(); + candidates = newCandidates; + } + + // add paths of the candidates to the gather list + for (Iterator ci=candidates.iterator(); ci.hasNext(); ) { + Item current = (Item) ci.next(); + gather.add(current.getPath()); + } + } + + //---------- matching support --------------------------------------------- + + /** + * Applies the list of path patterns to the given path returning + * true if it matches, false otherwise. + *

+ * This method is package protected for testing purposes. This + * method is not intended to be used by clients. Its specification or + * implementation may change without notice. + * + * @param path The path to match with the pattern list. + * + * @return true if the path matches any of the patterns. + */ + /* package */ boolean matchPath(String path) { + StringTokenizer exploded = new StringTokenizer(path, "/"); + + OUTER_LOOP: + for (int i=0; i < patterns.length; i++) { + StringTokenizer exEntry = new StringTokenizer(patterns[i], "/"); + + // ignore if the number of path elements to not match + if (exploded.countTokens() != exEntry.countTokens()) { + continue; + } + + while (exploded.hasMoreTokens()) { + if (!ChildrenCollectorFilter.matches(exploded.nextToken(), + exEntry.nextToken())) { + continue OUTER_LOOP; + } + } + + // if I get here, the path matches entry[i] + return true; + } + + // if we run out, no match has been found + return false; + } +} Propchange: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/PatternPath.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/jackrabbit/trunk/contrib/classloader/src/main/java/org/apache/jackrabbit/classloader/PatternPath.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url