jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From fmesc...@apache.org
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 GMT
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 <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
+ * @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();
+
+    /**
+     * <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();
+    }
+}

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 <code>DynamicRepositoryClassLoader</code> 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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 <code>DynamicRepositoryClassLoader</code> from a list of item
+     * path strings containing globbing pattens for the paths defining the
+     * class path.
+     *
+     * @param session The <code>Session</code> 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 <code>ClassLoader</code>, which may be
+     *      <code>null</code>.
+     *
+     * @throws NullPointerException if either the session or the handles list
+     *      is <code>null</code>.
+     */
+    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 <code>DynamicRepositoryClassLoader</code> with the same
+     * configuration as the given <code>DynamicRepositoryClassLoader</code>.
+     * This constructor is used by the {@link #reinstantiate} method.
+     * <p>
+     * Before returning from this constructor the <code>old</code> class loader
+     * is destroyed and may not be used any more.
+     *
+     * @param session The session to associate with this class loader.
+     * @param old The <code>DynamicRepositoryClassLoader</code> to copy the
+     *            cofiguration from.
+     * @param parent The parent <code>ClassLoader</code>, which may be
+     *            <code>null</code>.
+     */
+    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.
+     * <p>
+     * <em>NOTE</em>: This method just clears all internal fields and especially
+     * the class path to render this class loader unusable.
+     * <p>
+     * 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.
+     * <p>
+     * Calling this method yields the same result as calling
+     * {@link #shouldReload(String, boolean)} with the <code>force</code>
+     * argument set to <code>false</code>.
+     *
+     * @param name The name of the resource to check.
+     *
+     * @return <code>true</code> 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
+     * <code>force</code> argument. If the argument is <code>true</code>, the
+     * class loader is marked dirty and <code>true</code> 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 <code>true</code> 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 <code>true</code> if the resource is loaded and
+     *      <code>force</code> is <code>true</code> or if the resource has
+     *      expired. <code>true</code> 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 <code>true</code> 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 <code>true</code> 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 <code>true</code> or if a loaded class has been expired
+     * through the observation.
+     * <p>
+     * This method may also return <code>true</code> if the <code>Session</code>
+     * associated with this class loader is not valid anymore.
+     * <p>
+     * Finally the method always returns <code>true</code> if the class loader
+     * has already been destroyed. Note, however, that a destroyed class loader
+     * cannot be reinstantiated. See {@link #reinstantiate(Session, ClassLoader)}.
+     * <p>
+     * If the class loader is dirty, it should be reinstantiated through the
+     * {@link #reinstantiate} method.
+     *
+     * @return <code>true</code> 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.
+     * <p>
+     * When the new class loader is returned, this class loader has been
+     * destroyed and may not be used any more.
+     *
+     * @param parent The parent <code>ClassLoader</code> for the reinstantiated
+     * 	    <code>DynamicRepositoryClassLoader</code>, which may be
+     *      <code>null</code>.
+     *
+     * @return a new instance with the same configuration as this class loader.
+     *
+     * @throws IllegalStateException if <code>this</code>
+     *      {@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.
+     * <p>
+     * If this class loader already has loaded classes using the old, replaced
+     * path list, it is set dirty.
+     * <p>
+     * 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-<code>null</code>
+     * {@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
+     *      <code>null</code> 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.
+     * <p>
+     * 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 <code>ClassLoaderResource</code> 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 <code>ExpandingArchiveClassPathEntry</code> 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 <code>ExpandingArchiveClassPathEntry</code>
+     * class.
+     *
+     * @param prop The <code>Property</code> 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 <code>ExpandingArchiveClassPathEntry</code> object
+     * by taking over its path, session and property.
+     *
+     * @param base The base <code>ExpandingArchiveClassPathEntry</code> 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 <code>.class</code>
+     *      extension attached.
+     *
+     * @return The {@link ClassLoaderResource} identified by the name or
+     *      <code>null</code> 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 <code>ClassPathEntry</code> with the same configuration as
+     * this <code>ClassPathEntry</code>.
+     * <p>
+     * The <code>ExpandingArchiveClassPathEntry</code> 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 <code>jarRoot</code> node.
+     * <p>
+     * This method leaves the subtree at and below <code>jarRoot</code> unsaved.
+     * It is the task of the caller to save or rollback as appropriate.
+     *
+     * @param jarRoot The <code>Node</code> 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 <code>path</code> relative to
+     * <code>root</code>. In other words, this method returns the node
+     * <code>root.getNode(path)</code>, creating child nodes as required. Newly
+     * created nodes are created with node type <code>nt:folder</code>.
+     * <p>
+     * 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 <code>nt:file</code> or <code>nt:folder</code>.
+     *
+     * @param root The <code>Node</code> relative to which a node representing
+     *      a folder is to created if required.
+     * @param path The path relative to <code>root</code> of the folder to
+     *      ensure.
+     *
+     * @return The <code>Node</code> representing the folder below
+     *      <code>root</code>.
+     *
+     * @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 <code>nt:file</code> node with the path
+     * <code>entry.getName()</code> relative to the <code>root</code> node. The
+     * contents of the <code>jcr:content/jcr:data</code> property of the file
+     * node is retrieved from <code>ins</code>.
+     * <p>
+     * The <code>jcr:content/jcr:lastModified</code> property is set to the
+     * value of the <code>time</code> field of the <code>entry</code>. The
+     * <code>jcr:content/jcr:mimeType</code> property is set to a best-effort
+     * guess of the content type of the entry. To guess the content type, the
+     * <code>java.net.URLConnection.guessContentType(String)</code> method
+     * is called. If this results in no content type, the default
+     * <code>application/octet-stream</code> is set.
+     *
+     * @param root The node relative to which the <code>nt:file</code> node
+     *      is created.
+     * @param entry The <code>ZipEntry</code> providing information on the
+     *      file to be created. Namely the <code>name</code> and
+     *      <code>time</code> fields are used.
+     * @param ins The <code>InputStream</code> providing the data to be written
+     *      to the <code>jcr:content/jcr:data</code> property.
+     *
+     * @throws RepositoryException If an error occurrs creating and filling
+     *      the <code>nt:file</code> 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 <code>session</code>
+     * provides access.
+     * <p>
+     * This method works as follows:
+     * <ol>
+     * <li>If the node type <code>rep:jarFile</code> is defined in the session's
+     *      repository, <code>true</code> is immediately returned.
+     * <li>If an error checking for the node type, <code>false</code> is
+     *      immediately returned.
+     * <li>Next would be to try to define the node type, this requires the
+     *      <code>session</code> to be an instance of <code>CRXSession</code>,
+     *      which is required to access the node type registry. If the session
+     *      is not a <code>CRXSession</code>, <code>false</code> is returned.
+     * <li>Finally the node type is defined and <code>true</code> is returned.
+     *      If an error occurrs defining the node type, <code>false</code> is
+     *      returned.
+     * </ol>
+     * <p>
+     * This method is synchronized such that two paralell threads do not try
+     * to create the node, which might yield wrong negatives.
+     *
+     * @param session The <code>Session</code> providing access to the
+     *      repository.
+     *
+     * @return <code>true</code> 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 <code>NodeTypeSupport</code> contains a single utility method
+ * {@link #registerNodeType(Workspace)} to register the required mixin node
+ * type <code>rep:jarFile</code> with the repository.
+ * <p>
+ * 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 (<code>rep:jarFile</code>) with the
+     * node type manager available from the given <code>workspace</code>.
+     * <p>
+     * The <code>NodeTypeManager</code> returned by the <code>workspace</code>
+     * is expected to be of type
+     * <code>org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl</code> for
+     * the node type registration to succeed.
+     * <p>
+     * This method is not synchronized. It is up to the calling method to
+     * prevent paralell execution.
+     *
+     * @param workspace The <code>Workspace</code> providing the node type
+     *      manager through which the node type is to be registered.
+     *
+     * @return <code>true</code> 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 <code>PatternPath</code> 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * Each entry in the pattern list is a path whose segments conform to the
+ * pattern syntax defined for the <code>Node.getNodes(String)</code> 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 <i>OR</i>) of these. For example,
+ * <blockquote><code>"jcr:*|foo:bar"</code></blockquote>
+ * would match <code>"foo:bar"</code>, but also <code>"jcr:whatever"</code>.
+ * <p>
+ * The EBNF for pattern is:
+ * <pre>
+ * namePattern ::= disjunct {'|' disjunct}
+ * disjunct ::= name [':' name]
+ * name ::= '*' |
+ *          ['*'] fragment {'*' fragment}['*']
+ * fragment ::= char {char}
+ * char ::= nonspace | ' '
+ * nonspace ::= (* Any Unicode character except:
+ *               '/', ':', '[', ']', '*',
+ *               ''', '"', '|' or any whitespace
+ *               character *)
+ * </pre>
+ *
+ * @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 <code>PatternPath</code> containing the elements of the
+     * string array. Each entry in the array which is either empty or
+     * <code>null</code> is ignored and not added to the list. If the array
+     * is empty or only contains empty or <code>null</code> 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 <code>pathPatterns</code> array or
+     *      the <code>session</code> is <code>null</code>.
+     */
+    /* 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.
+     * <p>
+     * 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 <code>true</code> if this object equals the other object. This
+     * implementation only returns true if the other object is the same as this
+     * object.
+     * <p>
+     * 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 <code>Object.equals</code>
+     * implementation.
+     *
+     * @param o The other object to compare to.
+     *
+     * @return <code>true</code> 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 <code>hashCode()</code> 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
+     * <code>null</code>. If the string has one or more trailing slashes
+     * (<em>/</em>) 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 <code>root</code> 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
+     * <code>true</code> if it matches, <code>false</code> otherwise.
+     * <p>
+     * <b><em>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.</em></b>
+     *
+     * @param path The path to match with the pattern list.
+     *
+     * @return <code>true</code> 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



Mime
View raw message