Package org.apache.sling.jcr.classloader.internal

Source Code of org.apache.sling.jcr.classloader.internal.RepositoryClassLoader

/*
* 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.io.IOException;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.security.SecureClassLoader;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;

import org.apache.sling.commons.classloader.DynamicClassLoader;
import org.apache.sling.jcr.classloader.internal.net.JCRURLHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* The <code>RepositoryClassLoader</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.
*/
public final class RepositoryClassLoader
    extends SecureClassLoader
    implements DynamicClassLoader {

    /** Logger */
    private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());

    /** Set of loaded resources and classes. */
    private final Set<String> usedResources = new HashSet<String>();

    /**
     * Flag indicating whether there are loaded classes which have later been
     * expired (e.g. invalidated or modified)
     */
    private volatile boolean dirty = false;

    /**
     * The path to use as a classpath.
     */
    private final String repositoryPath;

    /**
     * The <code>ClassLoaderWriterImpl</code> grants access to the repository
     * <p>
     * This field is not final such that it may be cleared when the class loader
     * is destroyed.
     */
    private final ClassLoaderWriterImpl writer;

    /**
     * Flag indicating whether the {@link #destroy()} method has already been
     * called (<code>true</code>) or not (<code>false</code>)
     */
    private volatile boolean destroyed = false;

    /**
     * Creates a <code>RepositoryClassLoader</code> for a given
     * repository path.
     *
     * @param classPath The path making up the class path of this class
     *                  loader
     * @param writer The class loader write to get a jcr session.
     * @param parent The parent <code>ClassLoader</code>, which may be
     *      <code>null</code>.
     *
     * @throws NullPointerException if either the session or the classPath
     *      is <code>null</code>.
     */
    public RepositoryClassLoader(final String classPath,
                                 final ClassLoaderWriterImpl writer,
                                 final ClassLoader parent) {
        // initialize the super class with an empty class path
        super(parent);

        // check writer and classPath
        if (writer == null) {
            throw new NullPointerException("writer");
        }
        if (classPath == null) {
            throw new NullPointerException("classPath");
        }

        // set fields
        this.writer = writer;
        this.repositoryPath = classPath;

        logger.debug("RepositoryClassLoader: {} ready", this);
    }

    /**
     * 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) {
            logger.debug("Instance is already destroyed");
            return;
        }

        // set destroyal guard
        destroyed = true;

        synchronized ( this.usedResources ) {
            this.usedResources.clear();
        }
    }

    /**
     * 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.
     */
    @Override
    protected Class<?> findClass(final String name) throws ClassNotFoundException {
        if (!this.writer.isActivate()) {
            throw new ClassNotFoundException(name + " (Classloader destroyed)");
        }

        logger.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.
     */
    @Override
    public URL findResource(final String name) {
        if (!this.writer.isActivate()) {
            logger.warn("Destroyed class loader cannot find a resource: " + name, new IllegalStateException());
            return null;
        }

        logger.debug("findResource: Try to find resource {}", name);

        final String path = this.repositoryPath + '/' + name;
        try {
            if ( findClassLoaderResource(path) ) {
                logger.debug("findResource: Getting resource from {}", path);
                return JCRURLHandler.createURL(this.writer, path);
            }
        } catch (final Exception e) {
            logger.warn("findResource: Cannot getURL for " + name, e);
        }

        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.
     */
    @Override
    public Enumeration<URL> findResources(final String name) {
        if (!this.writer.isActivate()) {
            logger.warn("Destroyed class loader cannot find a resources: " + name, new IllegalStateException());
            return new Enumeration<URL>() {
                public boolean hasMoreElements() {
                    return false;
                }
                public URL nextElement() {
                    throw new NoSuchElementException("No Entries");
                }
            };
        }

        logger.debug("findResources: Try to find resources for {}", name);

        final URL url = this.findResource(name);
        final List<URL> list = Collections.singletonList(url);
        if (url != null) {
            list.add(url);
        }

        // return the enumeration on the list
        return Collections.enumeration(list);
    }

    /**
     * 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(final String name) throws ClassNotFoundException {

        // prepare the name of the class
        logger.debug("findClassPrivileged: Try to find path {class {}",
            name);

        final String path = this.repositoryPath + '/' + name.replace('.', '/') + (".class");

         // try defining the class, error aborts
         try {
             final byte[] data = this.findClassLoaderClass(path);
             if (data != null) {

                 logger.debug("findClassPrivileged: Loading class from {} bytes", data.length);

                 final Class<?> c = defineClass(name, data);
                 if (c == null) {
                     logger.warn("defineClass returned null for class {}", name);
                     throw new ClassNotFoundException(name);
                 }
                 return c;
             }

         } catch (final IOException ioe) {
             logger.debug("defineClass failed", ioe);
             throw new ClassNotFoundException(name, ioe);
         } catch (final Throwable t) {
             logger.debug("defineClass failed", t);
             throw new ClassNotFoundException(name, t);
         }

        throw new ClassNotFoundException(name);
     }

    /**
     * Returns the contents for the given <code>path</code> or
     * <code>null</code> if not existing.
     *
     * @param path The repository path of the resource to return.
     *
     * @return The contents if found or <code>null</code> if not found.
     *
     * @throws NullPointerException If this class loader has already been
     *      destroyed.
     */
    private boolean findClassLoaderResource(final String path) throws IOException {
        Session session = null;
        boolean res = false;
        try {
            session = this.writer.createSession();
            if ( session.itemExists(path) ) {
                logger.debug("Found resource at {}", path);
                res = true;
            } else {
                logger.debug("No classpath entry contains {}", path);
            }
        } catch (final RepositoryException re) {
            logger.debug("Error while trying to get node at " + path, re);
        } finally {
            if ( session != null ) {
                session.logout();
            }
        }

        return res;
    }

    /**
     * Returns the contents for the given <code>path</code> or
     * <code>null</code> if not existing.
     *
     * @param path The repository path of the resource to return.
     *
     * @return The contents if found or <code>null</code> if not found.
     *
     * @throws NullPointerException If this class loader has already been
     *      destroyed.
     */
    private byte[] findClassLoaderClass(final String path) throws IOException {
        Session session = null;
        byte[] res = null;
        try {
            session = this.writer.createSession();
            if ( session.itemExists(path) ) {
                final Node node = (Node)session.getItem(path);
                logger.debug("Found resource at {}", path);
                res = Util.getBytes(node);
            } else {
                logger.debug("No classpath entry contains {}", path);
            }
        } catch (final RepositoryException re) {
            logger.debug("Error while trying to get node at " + path, re);
        } finally {
            if ( session != null ) {
                session.logout();
            }
        }
        if ( !this.dirty ) {
            synchronized ( this.usedResources ) {
                this.usedResources.add(path);
            }
        }
        return res;
    }

    /**
     * Defines a class using the bytes
     *
     * @param name The fully qualified class name
     * @param contents The class in bytes
     *
     * @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(final String name, final byte[] contents) {
        logger.debug("defineClass({}, {})", name, contents.length);

        final Class<?> clazz = defineClass(name, contents, 0, contents.length);

        return clazz;
    }

    /**
     * @see org.apache.sling.commons.classloader.DynamicClassLoader#isLive()
     */
    public boolean isLive() {
        return !destroyed && !dirty && this.writer.isActivate();
    }

    /**
     * Handle a modification event.
     */
    public void handleEvent(final String path) {
        synchronized ( this.usedResources ) {
            if ( this.usedResources.contains(path) ) {
                logger.debug("handleEvent: Item {} has been modified - marking class loader as dirty {}", path, this);
                this.dirty = true;
            }
        }

    }
    //----------- Object overwrite ---------------------------------------------

    /**
     * Returns a string representation of this class loader.
     */
    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder(getClass().getName());
        if (destroyed) {
            buf.append(" - destroyed");
        } else {
            buf.append(": parent: { ");
            buf.append(getParent());
            buf.append(" }, live: ");
            buf.append(isLive());
        }
        return buf.toString();
    }
}
TOP

Related Classes of org.apache.sling.jcr.classloader.internal.RepositoryClassLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.