| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sling.jcr.classloader.internal; |
| |
| import java.beans.Introspector; |
| import java.io.IOException; |
| import java.net.URL; |
| import java.security.AccessController; |
| import java.security.PrivilegedExceptionAction; |
| import java.security.SecureClassLoader; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| |
| 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.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * The <code>DynamicRepositoryClassLoader</code> class 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. |
| */ |
| public final class DynamicRepositoryClassLoader |
| extends SecureClassLoader implements EventListener { |
| |
| /** |
| * The special resource representing a resource which could not be |
| * found in the class path. |
| * |
| * @see #cache |
| * @see #findClassLoaderResource(String) |
| */ |
| private static final ClassLoaderResource NOT_FOUND_RESOURCE = |
| new ClassLoaderResource(null, "[sentinel]", null) { |
| public boolean isExpired() { |
| return false; |
| } |
| }; |
| |
| |
| /** default log category */ |
| private final Logger log = LoggerFactory.getLogger(this.getClass().getName()); |
| |
| /** |
| * 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 final Map<String, ClassLoaderResource> modTimeCache = new HashMap<String, ClassLoaderResource>(); |
| |
| /** |
| * Flag indicating whether there are loaded classes which have later been |
| * expired (e.g. invalidated or modified) |
| */ |
| private boolean dirty = false; |
| |
| /** The registered event listeners. */ |
| private EventListener[] proxyListeners; |
| |
| /** |
| * The classpath which this classloader searches for class definitions. |
| * Each element of the vector should be either a directory, a .zip |
| * file, or a .jar file. |
| * <p> |
| * It may be empty when only system classes are controlled. |
| */ |
| private ClassPathEntry[] repository; |
| |
| /** |
| * The list of paths to use as a classpath. |
| */ |
| private String[] paths; |
| |
| /** |
| * The <code>Session</code> grants access to the Repository to access the |
| * resources. |
| * <p> |
| * This field is not final such that it may be cleared when the class loader |
| * is destroyed. |
| */ |
| private Session session; |
| |
| /** |
| * Cache of resources found or not found in the class path. The map is |
| * indexed by resource name and contains mappings to instances of the |
| * {@link ClassLoaderResource} class. If a resource has been tried to be |
| * loaded, which could not be found, the resource is cached with the |
| * special mapping to {@link #NOT_FOUND_RESOURCE}. |
| * |
| * @see #NOT_FOUND_RESOURCE |
| * @see #findClassLoaderResource(String) |
| */ |
| private final Map<String, ClassLoaderResource> cache = new HashMap<String, ClassLoaderResource>(); |
| |
| /** |
| * Flag indicating whether the {@link #destroy()} method has already been |
| * called (<code>true</code>) or not (<code>false</code>) |
| */ |
| private boolean destroyed = false; |
| |
| /** |
| * 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(final Session session, |
| final String[] classPath, |
| final ClassLoader parent) { |
| // initialize the super class with an empty class path |
| super(parent); |
| |
| // check session and handles |
| if (session == null) { |
| throw new NullPointerException("session"); |
| } |
| if (classPath == null || classPath.length == 0) { |
| throw new NullPointerException("handles"); |
| } |
| |
| // set fields |
| this.session = session; |
| this.paths = classPath; |
| |
| // build the class repositories list |
| buildRepository(); |
| |
| // register with observation service and path pattern list |
| registerListeners(); |
| |
| log.debug("DynamicRepositoryClassLoader: {} ready", this); |
| } |
| |
| /** |
| * 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(final Session session, |
| final DynamicRepositoryClassLoader old, |
| final ClassLoader parent) { |
| // initialize the super class with an empty class path |
| super(parent); |
| |
| // check session and handles |
| if (session == null) { |
| throw new NullPointerException("session"); |
| } |
| // set fields |
| this.session = session; |
| this.paths = old.paths; |
| |
| repository = old.repository; |
| buildRepository(); |
| |
| // register with observation service and path pattern list |
| registerListeners(); |
| |
| // finally finalize the old class loader |
| old.destroy(); |
| |
| log.debug( |
| "DynamicRepositoryClassLoader: Copied {}. Do not use that anymore", |
| old); |
| } |
| |
| /** |
| * 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 (destroyed) { |
| log.debug("Instance is already destroyed"); |
| return; |
| } |
| |
| // remove ourselves as listeners from other places |
| unregisterListeners(); |
| |
| // set destroyal guard |
| destroyed = true; |
| |
| // clear caches and references |
| repository = null; |
| paths = null; |
| session = null; |
| |
| // clear the cache of loaded resources and flush cached class |
| // introspections of the JavaBean framework |
| final Iterator<ClassLoaderResource> ci = cache.values().iterator(); |
| while ( ci.hasNext() ) { |
| final ClassLoaderResource res = ci.next(); |
| if (res.getLoadedClass() != null) { |
| Introspector.flushFromCaches(res.getLoadedClass()); |
| res.setLoadedClass(null); |
| } |
| } |
| cache.clear(); |
| modTimeCache.clear(); |
| } |
| |
| //---------- URLClassLoader overwrites ------------------------------------- |
| |
| /** |
| * Finds and loads the class with the specified name from the class path. |
| * |
| * @param name the name of the class |
| * @return the resulting class |
| * |
| * @throws ClassNotFoundException If the named class could not be found or |
| * if this class loader has already been destroyed. |
| */ |
| protected Class<?> findClass(final String name) throws ClassNotFoundException { |
| |
| if (destroyed) { |
| throw new ClassNotFoundException(name + " (Classloader destroyed)"); |
| } |
| |
| log.debug("findClass: Try to find class {}", name); |
| |
| try { |
| return AccessController.doPrivileged( |
| new PrivilegedExceptionAction<Class<?>>() { |
| |
| public Class<?> run() throws ClassNotFoundException { |
| return findClassPrivileged(name); |
| } |
| }); |
| } catch (java.security.PrivilegedActionException pae) { |
| throw (ClassNotFoundException) pae.getException(); |
| } |
| } |
| |
| /** |
| * Finds the resource with the specified name on the search path. |
| * |
| * @param name the name of the resource |
| * |
| * @return a <code>URL</code> for the resource, or <code>null</code> |
| * if the resource could not be found or if the class loader has |
| * already been destroyed. |
| */ |
| public URL findResource(String name) { |
| |
| if (destroyed) { |
| log.warn("Destroyed class loader cannot find a resource"); |
| return null; |
| } |
| |
| log.debug("findResource: Try to find resource {}", name); |
| |
| ClassLoaderResource res = findClassLoaderResource(name); |
| if (res != null) { |
| log.debug("findResource: Getting resource from {}, created {}", |
| res, new Date(res.getLastModificationTime())); |
| return res.getURL(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns an Enumeration of URLs representing all of the resources |
| * on the search path having the specified name. |
| * |
| * @param name the resource name |
| * |
| * @return an <code>Enumeration</code> of <code>URL</code>s. This is an |
| * empty enumeration if no resources are found by this class loader |
| * or if this class loader has already been destroyed. |
| */ |
| public Enumeration<URL> findResources(String name) { |
| |
| if (destroyed) { |
| log.warn("Destroyed class loader cannot find resources"); |
| return new Enumeration<URL>() { |
| public boolean hasMoreElements() { |
| return false; |
| } |
| public URL nextElement() { |
| throw new NoSuchElementException("No Entries"); |
| } |
| }; |
| } |
| |
| log.debug("findResources: Try to find resources for {}", name); |
| |
| List<URL> list = new LinkedList<URL>(); |
| for (int i=0; i < repository.length; i++) { |
| final ClassPathEntry cp = repository[i]; |
| log.debug("findResources: Trying {}", cp); |
| |
| ClassLoaderResource res = cp.getResource(name); |
| if (res != null) { |
| log.debug("findResources: Adding resource from {}, created {}", |
| res, new Date(res.getLastModificationTime())); |
| URL url = res.getURL(); |
| if (url != null) { |
| list.add(url); |
| } |
| } |
| |
| } |
| |
| // return the enumeration on the list |
| return Collections.enumeration(list); |
| } |
| |
| //---------- Property access ---------------------------------------------- |
| |
| /** |
| * Removes all entries from the cache of loaded resources, which mark |
| * resources, which have not been found as of yet. |
| * |
| * @throws NullPointerException If this class loader has already been |
| * destroyed. |
| */ |
| private void cleanCache() { |
| final Iterator<ClassLoaderResource> ci = this.cache.values().iterator(); |
| while (ci.hasNext()) { |
| if (ci.next() == NOT_FOUND_RESOURCE) { |
| ci.remove(); |
| } |
| } |
| } |
| |
| //---------- internal ------------------------------------------------------ |
| |
| /** |
| * 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. |
| */ |
| private synchronized void buildRepository() { |
| List<ClassPathEntry> newRepository = new ArrayList<ClassPathEntry>(paths.length); |
| |
| // build repository from path patterns |
| for (int i=0; i < paths.length; i++) { |
| final String entry = paths[i]; |
| ClassPathEntry cp = null; |
| |
| // try to find repository based on this path |
| if (repository != null) { |
| for (int j=0; j < repository.length; j++) { |
| final ClassPathEntry tmp = repository[i]; |
| if (tmp.getPath().equals(entry)) { |
| cp = tmp; |
| break; |
| } |
| } |
| } |
| |
| // not found, creating new one |
| if (cp == null) { |
| cp = ClassPathEntry.getInstance(session, entry); |
| } |
| |
| if (cp != null) { |
| log.debug("Adding path {}", entry); |
| newRepository.add(cp); |
| } else { |
| log.debug("Cannot get a ClassPathEntry for {}", entry); |
| } |
| } |
| |
| // replace old repository with new one |
| ClassPathEntry[] newClassPath = new ClassPathEntry[newRepository.size()]; |
| newRepository.toArray(newClassPath); |
| repository = newClassPath; |
| |
| // clear un-found resource cache |
| cleanCache(); |
| } |
| |
| /** |
| * Tries to find the class in the class path from within a |
| * <code>PrivilegedAction</code>. Throws <code>ClassNotFoundException</code> |
| * if no class can be found for the name. |
| * |
| * @param name the name of the class |
| * |
| * @return the resulting class |
| * |
| * @throws ClassNotFoundException if the class could not be found |
| * @throws NullPointerException If this class loader has already been |
| * destroyed. |
| */ |
| private Class<?> findClassPrivileged(String name) throws ClassNotFoundException { |
| |
| // prepare the name of the class |
| final String path = name.replace('.', '/').concat(".class"); |
| log.debug("findClassPrivileged: Try to find path {} for class {}", |
| path, name); |
| |
| ClassLoaderResource res = findClassLoaderResource(path); |
| if (res != null) { |
| |
| // try defining the class, error aborts |
| try { |
| log.debug( |
| "findClassPrivileged: Loading class from {}, created {}", |
| res, new Date(res.getLastModificationTime())); |
| |
| Class<?> c = defineClass(name, res); |
| if (c == null) { |
| log.warn("defineClass returned null for class {}", name); |
| throw new ClassNotFoundException(name); |
| } |
| return c; |
| |
| } catch (IOException ioe) { |
| log.debug("defineClass failed", ioe); |
| throw new ClassNotFoundException(name, ioe); |
| } catch (Throwable t) { |
| log.debug("defineClass failed", t); |
| throw new ClassNotFoundException(name, t); |
| } |
| } |
| |
| throw new ClassNotFoundException(name); |
| } |
| |
| /** |
| * Returns a {@link ClassLoaderResource} for the given <code>name</code> or |
| * <code>null</code> if not existing. If the resource has already been |
| * loaded earlier, the cached instance is returned. If the resource has |
| * not been found in an earlier call to this method, <code>null</code> is |
| * returned. Otherwise the resource is looked up in the class path. If |
| * found, the resource is cached and returned. If not found, the |
| * {@link #NOT_FOUND_RESOURCE} is cached for the name and <code>null</code> |
| * is returned. |
| * |
| * @param name The name of the resource to return. |
| * |
| * @return The named <code>ClassLoaderResource</code> if found or |
| * <code>null</code> if not found. |
| * |
| * @throws NullPointerException If this class loader has already been |
| * destroyed. |
| */ |
| private ClassLoaderResource findClassLoaderResource(String name) { |
| |
| // check for cached resources first |
| ClassLoaderResource res = cache.get(name); |
| if (res == NOT_FOUND_RESOURCE) { |
| log.debug("Resource '{}' known to not exist in class path", name); |
| return null; |
| } else if (res == null) { |
| // walk the repository list and try to find the resource |
| for (int i = 0; i < repository.length; i++) { |
| final ClassPathEntry cp = repository[i]; |
| log.debug("Checking {}", cp); |
| |
| res = cp.getResource(name); |
| if (res != null) { |
| log.debug("Found resource in {}, created ", res, new Date( |
| res.getLastModificationTime())); |
| cache.put(name, res); |
| break; |
| } |
| } |
| if ( res == null ) { |
| log.debug("No classpath entry contains {}", name); |
| cache.put(name, NOT_FOUND_RESOURCE); |
| return null; |
| } |
| } |
| // if it could be found, we register it with the caches |
| // 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; |
| } |
| |
| /** |
| * Defines a class getting the bytes for the class from the resource |
| * |
| * @param name The fully qualified class name |
| * @param res The resource to obtain the class bytes from |
| * |
| * @throws RepositoryException If a problem occurrs getting at the data. |
| * @throws IOException If a problem occurrs reading the class bytes from |
| * the resource. |
| * @throws ClassFormatError If the class bytes read from the resource are |
| * not a valid class. |
| */ |
| private Class<?> defineClass(String name, ClassLoaderResource res) |
| throws IOException, RepositoryException { |
| |
| log.debug("defineClass({}, {})", name, res); |
| |
| Class<?> clazz = res.getLoadedClass(); |
| if (clazz == null) { |
| final byte[] data = res.getBytes(); |
| clazz = defineClass(name, data, 0, data.length); |
| res.setLoadedClass(clazz); |
| } |
| |
| return clazz; |
| } |
| |
| //---------- reload support ------------------------------------------------ |
| |
| /** |
| * Returns whether the class loader is dirty. This can be the case if any |
| * of the 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 destroyed || dirty || !session.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 {} with parent {}", this, parent); |
| |
| if (destroyed) { |
| 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; |
| } |
| |
| //---------- EventListener 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(); |
| String path; |
| try { |
| path = event.getPath(); |
| } catch (RepositoryException re) { |
| log.warn("onEvent: Cannot get path of event, ignoring", re); |
| continue; |
| } |
| |
| log.debug( |
| "onEvent: Item {} has been modified, checking with cache", path); |
| |
| ClassLoaderResource resource = 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(); |
| } |
| } |
| |
| } |
| } |
| |
| //----------- Object overwrite --------------------------------------------- |
| |
| /** |
| * Returns a string representation of this class loader. |
| */ |
| public String toString() { |
| StringBuilder buf = new StringBuilder(getClass().getName()); |
| if (destroyed) { |
| buf.append(" - destroyed"); |
| } else { |
| buf.append(": parent: { "); |
| buf.append(getParent()); |
| buf.append(" }, user: "); |
| buf.append(session.getUserID()); |
| buf.append(", dirty: "); |
| buf.append(isDirty()); |
| } |
| return buf.toString(); |
| } |
| |
| //---------- internal ------------------------------------------------------ |
| |
| /** |
| * 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 registerListeners() { |
| log.debug("registerListeners: Registering to the observation service"); |
| |
| this.proxyListeners = new EventListener[this.paths.length]; |
| for(int i=0; i < paths.length; i++ ) { |
| final String path = paths[i]; |
| try { |
| final EventListener listener = new ProxyEventListener(this); |
| final ObservationManager om = session.getWorkspace().getObservationManager(); |
| om.addEventListener(listener, 255, path, true, null, null, false); |
| proxyListeners[i] = listener; |
| } 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 unregisterListeners() { |
| log.debug("unregisterListeners: Deregistering from the observation service"); |
| if ( this.proxyListeners != null ) { |
| // check session first! |
| if ( session.isLive() ) { |
| for(final EventListener listener : this.proxyListeners) { |
| if ( listener != null ) { |
| try { |
| final ObservationManager om = session.getWorkspace().getObservationManager(); |
| om.removeEventListener(listener); |
| } catch (RepositoryException re) { |
| log.error("unregisterListener: Cannot unregister " + |
| this + " from observation manager", re); |
| } |
| } |
| } |
| } |
| this.proxyListeners = null; |
| } |
| } |
| |
| /** |
| * 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: {}", new Boolean(isDirty())); |
| |
| // return the expiry status |
| return exp; |
| } |
| |
| protected final static class ProxyEventListener implements EventListener { |
| |
| private final EventListener delegatee; |
| |
| public ProxyEventListener(final EventListener delegatee) { |
| this.delegatee = delegatee; |
| } |
| /** |
| * @see javax.jcr.observation.EventListener#onEvent(javax.jcr.observation.EventIterator) |
| */ |
| public void onEvent(EventIterator events) { |
| this.delegatee.onEvent(events); |
| } |
| } |
| } |