blob: f82dd72eaa5f3e340d0c7d20ede408296ea7420f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. The ASF licenses this file to You
* under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.extension;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Workspace;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jackrabbit.classloader.RepositoryClassLoader;
/**
* The <code>ExtensionManager</code> class provides the core functionality
* of the Jackrabbit Extension Framework by methods for finding extensions.
* <p>
* Instances of this class are created with a <code>Session</code> to the
* repository. Consequently all access to the repository is confined to the
* workspace to which the session is attached. That is, only extensions located
* in the session's workspace are found.
* <p>
* Additionally the class provides functionality to define the extension node
* types on demand.
*
* @author Felix Meschberger
*/
public final class ExtensionManager {
/** default logger */
private static final Log log = LogFactory.getLog(ExtensionManager.class);
/**
* The name of the repository node type defining the properties making up
* an extension description (value is "rep:extension").
*/
public static final String NODE_EXTENSION_TYPE = "rep:extension";
/**
* The session providing access to the repository for loading extensions.
*/
private final Session session;
/**
* The application's class loader as provided to the constructor.
*/
private final ClassLoader applicationLoader;
/**
* The map of extension types by the extension type identifiers.
*/
private Map extensionTypes;
/**
* Creates an instance of this manager accessing the repository through the
* given <code>session</code>.
* <p>
* This also confines the extensions available to this manager to the
* extensions available through the workspace accessed by the
* <code>session</code>.
* <p>
* If the <code>applicationLoader</code> parameter is <code>null</code>
* either the current thread's context class loader is used or if that one
* is <code>null</code>, too, the class loader of this class is used.
* It is recommended, that the caller of the constructor provides a
* non-<code>null</code> class loader to prevent unexpected class loading
* issues.
* <p>
* To make sure extensions may actually be correctly handled, the
* {@link #checkNodeType(Session)} method is called.
*
* @param session The <code>Session</code> to search for and load extensions.
* @param applicationLoader The <code>ClassLoader</code> used as the parent
* for the repository class loaders.
*
* @throws NullPointerException If <code>session</code> is <code>null</code>.
*/
public ExtensionManager(Session session, ClassLoader applicationLoader)
throws ExtensionException {
// check session
if (session == null) {
throw new NullPointerException("session");
}
// make sure the extension system node type is available
checkNodeType(session);
// make sure the application class loader is non-null
// - first try current thread context loader
// - otherwise use this class's class loader
if (applicationLoader == null) {
applicationLoader = Thread.currentThread().getContextClassLoader();
}
if (applicationLoader == null) {
applicationLoader = getClass().getClassLoader();
}
// assign fields
this.session = session;
this.applicationLoader = applicationLoader;
}
/**
* Searches in the workspace of this instance's <code>Session</code> for
* extensions of type <code>id</code> returning an <code>Iterator</code>
* of {@link ExtensionDescriptor} instances. If <code>root</code> is
* non-<code>null</code> the search for extensions only takes place in the
* indicated subtree.
* <p>
* <b>NOTE</B>: This method may return more than one extension with the same
* name for a given <code>id</code>. This is the only place in the Jackrabbit
* Extension Framework which handles duplicate extension names. The rest
* relies on extensions to have unique <code>id</code>/name pairs.
* <p>
* Calling this method multiple times with the same <code>id</code> will
* return the same {@link ExtensionDescriptor} instances. Previously
* available instances will not returned though if their extension node
* has been removed in the meantime. Such instances will still be available
* through {@link #getExtension(String, String, String)} but will not be
* available on next system restart.
*
* @param id The extension type identification describing the extensions to
* be found.
* @param root The root node below which the extensions are looked for. This
* path is taken as an absolute path regardless of whether it begins
* with a slash or not. If <code>null</code> or empty, the search
* takes place in the complete workspace.
*
* @return An <code>Iterator</code> of {@link ExtensionDescriptor} instances
* describing the extensions found.
*
* @throws IllegalArgumentException If <code>id</code> is empty or
* <code>null</code>.
* @throws ExtensionException If an error occurrs looking for extensions.
*/
public Iterator getExtensions(String id, String root)
throws ExtensionException {
// delegate finding/loading to the extension type
return getExtensionType(id).getExtensions(root);
}
/**
* Searches in the workspace of this instance's <code>Session</code> for
* an extension with the given <code>name</code> of type <code>id</code>.
* If <code>root</code> is non-<code>null</code> the search for extensions
* only takes place in the indicated subtree.
* <p>
* This method fails with an exception if more than one extension with the
* same name of the same type is found in the workspace. Not finding the
* requested extension also yields an exception.
* <p>
* Two consecutive calls to this method with the same arguments, namely
* the same <code>id</code> and <code>name</code> will return the same
* {@link ExtensionDescriptor} instance.
*
* @param id The extension type identification describing the extensions to
* be found.
* @param name The name of the extension of the indicated type to be found.
* @param root The root node below which the extensions are looked for. This
* path is taken as an absolute path regardless of whether it begins
* with a slash or not. If <code>null</code> or empty, the search
* takes place in the complete workspace.
*
* @return The named {@link ExtensionDescriptor} instances.
*
* @throws IllegalArgumentException If <code>id</code> or <code>name</code>
* is empty or <code>null</code>.
* @throws ExtensionException If no or more than one extensions with the
* same name and type can be found or if another error occurrs looking
* for extensions.
*/
public ExtensionDescriptor getExtension(String id, String name, String root)
throws ExtensionException {
// delegate finding/loading to the extension type
return getExtensionType(id).getExtension(name, root);
}
//---------- Extension type helper methods --------------------------------
/**
* Creates a new instance of the <code>RepositoryClassLoader</code> class
* with an empty path accessing the repository through the session of this
* manager instance.
* <p>
* Note, that each call to this method returns a new RepositoryClassLoader
* instance.
*/
/* package */ RepositoryClassLoader createClassLoader() {
return new RepositoryClassLoader(session, new String[0],
applicationLoader);
}
/**
* Executes the given XPath query on this mnanager's session and returns
* a <code>NodeIterator</code> over the nodes matching the query.
*
* @param xpath The XPath query to execute
*
* @return The <code>NodeIterator</code> on the nodes matching the query.
*
* @throws ExtensionException If an error occurrs executing the query.
* The underlying exception is available as the cause of the exception.
*/
/* package */ NodeIterator findNodes(String xpath) throws ExtensionException {
try {
// look for the extension nodes
QueryManager qm = session.getWorkspace().getQueryManager();
Query query = qm.createQuery(xpath, Query.XPATH);
QueryResult res = query.execute();
// check whether we found at least one node
return res.getNodes();
} catch (RepositoryException re) {
throw new ExtensionException("Problem executing query '" +
xpath + "'", re);
}
}
//---------- internal helper ----------------------------------------------
/**
* Returns an {@link ExtensionType} instance for the given name.
*
* @throws IllegalArgumentException if <code>id</code> is <code>null</code>
* or an empty string.
*/
/* package */ ExtensionType getExtensionType(String id) {
if (id == null || id.length() == 0) {
throw new IllegalArgumentException("Extension type identifier " +
"must not be null or empty string");
}
if (extensionTypes == null) {
extensionTypes = new TreeMap();
}
ExtensionType type = (ExtensionType) extensionTypes.get(id);
if (type == null) {
type = new ExtensionType(this, id);
extensionTypes.put(id, type);
}
return type;
}
/**
* Makes sure the <code>rep:extension</code> node type is registered with
* the <code>session</code>'s repository.
* <p>
* If the required extension descriptor node type is not defined in the
* repository yet, it is tried to be registered. If an error occurrs
* registering the node type a message is logged and an
* <code>ExtensionException</code> is thrown.
*
* @param session The <code>Session</code> used to access the repository
* to test and optionally register the node.
*
* @throws ExtensionException If an error occurrs checking ro defining the
* node type.
*/
public static void checkNodeType(Session session) throws ExtensionException {
// quick check for the node type, succeed if defined
try {
session.getWorkspace().getNodeTypeManager().getNodeType(NODE_EXTENSION_TYPE);
log.debug("Required node type exists, can expand archives");
return;
} catch (NoSuchNodeTypeException nst) {
log.debug("Required node types does not exist, try to define");
} catch (RepositoryException re) {
log.debug("Cannot check for required node type, cannot expand " +
"archives", re);
throw new ExtensionException("Cannot check for required node type", re);
}
try {
Workspace workspace = session.getWorkspace();
if (!NodeTypeSupport.registerNodeType(workspace)) {
throw new ExtensionException("Registering required node type failed");
}
} catch (ExtensionException ee) {
throw ee;
} catch (Throwable t) {
// Prevent anything from hapening if node type registration fails
// due to missing libraries or other errors
log.debug("Error registering node type", t);
throw new ExtensionException("Cannot register required node type", t);
}
}
}