blob: df4657f0436158eae07c6a636eae6b1cbc5cad33 [file] [log] [blame]
/*
* 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.extension;
import java.net.URL;
import java.text.MessageFormat;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jackrabbit.classloader.RepositoryClassLoader;
/**
* The <code>ExtensionType</code> class represents a collection of extensions
* sharing the same Extension Type Identification. Instances of this class
* maintain the extension types class loader and the set of extenions of the
* same type which have been loaded through this instance.
* <p>
* The equality of instances of this class is defined by the equality of the
* Extension Type Identifier. If two instances have same extension type
* identifier, they are considered equal.
*
* @author Felix Meschberger
* @version $Rev:$, $Date$
*
* @see org.apache.jackrabbit.extension.ExtensionManager
* @see org.apache.jackrabbit.extension.ExtensionDescriptor
*/
public class ExtensionType {
/** default log */
private static final Log log = LogFactory.getLog(ExtensionType.class);
/**
* Pattern used to create an XPath query to look for extensions of a certain
* type. The parameters in the patterns are the root path below which to
* search (<i>{0}</i>) and the extension type identificatio (<i>{1}</i>).
*
* @see #findExtensions(String, String)
*/
private static final MessageFormat EXTENSION_QUERY_PATTERN =
new MessageFormat("{0}//element(*, " + ExtensionManager.NODE_EXTENSION_TYPE + ")[@rep:id = ''{1}'']");
/**
* Pattern used to create an XPath query to look for a specific extension
* by its name and type type. The parameters in the patterns are the root
* path below which to search (<i>{0}</i>), the extension type
* identification (<i>{1}</i>) and the extension name (<i>{1}</i>).
*
* @see #findExtension(String, String, String)
*/
private static final MessageFormat EXTENSION_QUERY_PATTERN2 =
new MessageFormat("{0}//element(*, " + ExtensionManager.NODE_EXTENSION_TYPE + ")[@rep:id = ''{1}'' and @rep:name = ''{2}'']");
/**
* The {@link ExtensionManager} responsible for accessing this instance.
*/
private final ExtensionManager manager;
/**
* The Extension Type Identification of this extension type instance. No
* two instances of this class with the same manager share their id.
*/
private final String id;
/**
* The set of extensions loaded with this extension type, indexed by their
* names.
*/
private final Map extensions;
/**
* The <code>RepositoryClassLoader</code> used for extensions of this type.
* This field is set on demand by the
* {@link #getClassLoader(ExtensionDescriptor)} method.
*/
private RepositoryClassLoader loader;
/**
* Creates a new extension type instance in the {@link ExtensionManager}
* with the given extension type identification.
*
* @param manager The {@link ExtensionManager} managing this extension
* type and its extensions.
* @param id The Extension Type Identification of this instance.
*/
/* package */ ExtensionType(ExtensionManager manager, String id) {
this.manager = manager;
this.id = id;
this.extensions = new TreeMap();
}
/**
* Returns the Extension Type Identification of this extension type.
*/
public String getId() {
return id;
}
/**
* Searches in the workspace of this instance's <code>Session</code> for
* extensions of this type 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 this type. 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/name</code> pairs.
* <p>
* Calling this method multiple times will return the same
* {@link ExtensionDescriptor} instances. Previously available instances
* will not be returned though if their extension node has been removed in
* the meantime. Such instances will still be available through
* {@link #getExtension(String, String)} but will not be available on next
* system restart.
*
* @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 {@link ExtensionIterator} providing the extensions of this
* type.
*
* @throws ExtensionException If an error occurrs looking for extensions.
*/
public ExtensionIterator getExtensions(String root) throws ExtensionException {
// make sure root is not null and has no leading slash
if (root == null) {
root = "";
} else if (root.length() >= 1 && root.charAt(0) == '/') {
root = root.substring(1);
}
// build the query string from the query pattern
String queryXPath;
synchronized (EXTENSION_QUERY_PATTERN) {
queryXPath = EXTENSION_QUERY_PATTERN.format(new Object[]{ root, id });
}
log.debug("Looking for extensions of type " + id + " below /" + root);
NodeIterator nodes = manager.findNodes(queryXPath);
return new ExtensionIterator(this, nodes);
}
/**
* 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 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>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 name, String root)
throws ExtensionException {
// check name
if (name == null || name.length() == 0) {
throw new IllegalArgumentException("Extension name must not be" +
" null or empty string");
}
// check whether we already loaded the extension
ExtensionDescriptor ed = getOrCreateExtension(name, null);
if (ed != null) {
return ed;
}
// make sure root is not null and has no leading slash
if (root == null) {
root = "";
} else if (root.length() >= 1 && root.charAt(0) == '/') {
root = root.substring(1);
}
// build the query string from the query pattern
String queryXPath;
synchronized (EXTENSION_QUERY_PATTERN2) {
queryXPath = EXTENSION_QUERY_PATTERN2.format(new Object[]{ root, id, name});
}
log.debug("Looking for extension " + id + "/" + name + " below /" + root);
NodeIterator nodes = manager.findNodes(queryXPath);
if (!nodes.hasNext()) {
throw new ExtensionException("Extension " + id + "/" + name +
" not found");
}
Node extNode = nodes.nextNode();
if (nodes.hasNext()) {
throw new ExtensionException("More than one extension " +
id + "/" + name + " found");
}
// load the descriptor and return
return createExtension(name, extNode);
}
/**
* Returns a repository class loader for the given extension. If the
* extension contains a class path definition, that class path is added to
* the class loader before returning.
*
* @param extension The {@link ExtensionDescriptor} for which to return
* the class loader.
*
* @return The <code>ClassLoader</code> used to load the extension and
* extension configuration class.
*
* @see ExtensionDescriptor#getExtensionLoader()
* @see ExtensionDescriptor#getExtension()
*/
/* package */ ClassLoader getClassLoader(ExtensionDescriptor extension) {
if (loader == null) {
// not created yet, so we create
loader = manager.createClassLoader();
}
// make sure the class path for the class path is already defined
fixClassPath(loader, extension);
// return the class loader now
return loader;
}
/**
* Makes sure, the class path defined in the <code>extension</code> is
* known to the <code>loader</code>.
*
* @param loader The repository class loader whose current class path is
* ensured to contain the extension's class path.
* @param extension The extension providing additions to the repository
* class loader's class path.
*/
private static void fixClassPath(RepositoryClassLoader loader,
ExtensionDescriptor extension) {
if (extension.getClassPath() == null) {
return;
}
URL[] urls = loader.getURLs();
Set paths = new HashSet();
for (int i=0; i < urls.length; i++) {
paths.add(urls[i].getPath());
}
String[] classPath = extension.getClassPath();
for (int i=0; i < classPath.length; i++) {
if (!paths.contains(classPath[i])) {
loader.addHandle(classPath[i]);
}
}
}
//---------- Object overwrite ---------------------------------------------
/**
* Returns the hash code of this types extension type identification.
*/
public int hashCode() {
return id.hashCode();
}
/**
* Returns <code>true</code> if <code>obj</code> is <code>this</code> or
* if it is an <code>ExtensionType</code> with the same extension type
* identification as <code>this</code>.
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj instanceof ExtensionType) {
return id.equals(((ExtensionType) obj).getId());
} else {
return false;
}
}
/**
* Returns a string representation of this instance including the extension
* type identification.
*/
public String toString() {
return "Extension type " + getId();
}
//--------- internal helper -----------------------------------------------
/**
* Returns an {@link ExtensionDescriptor} for the name extension optionally
* loaded from the <code>extNode</code>. If this type has already loaded
* an extension with the given name, that extension descriptor is returned.
* Otherwise a new extension descriptor is created from the extension node
* and internally cached before being returned.
*
* @param name The name of the extension for which to return the descriptor.
* @param extNode The <code>Node</code> containing the extension definition
* to be loaded if this instance has not loaded the named extension
* yet. This may be <code>null</code> to prevent loading an extension
* descriptor if the named extension has not been loaded yet.
*
* @return The name {@link ExtensionDescriptor} or <code>null</code> if this
* instance has not loaded the named extension yet and
* <code>extNode</code> is <code>null</code>.
*
* @throws ExtensionException If an error occurrs loading the extension
* descriptor from the <code>extNode</code>.
*/
/* package */ ExtensionDescriptor getOrCreateExtension(String name, Node extNode)
throws ExtensionException {
// check whether we already loaded the extension
ExtensionDescriptor ed = (ExtensionDescriptor) extensions.get(name);
if (ed != null) {
return ed;
}
if (extNode != null) {
return createExtension(name, extNode);
}
// fallback to nothing
return null;
}
/**
* Creates and locally registers an {@link ExtensionDescriptor} instance
* with the given <code>name</code> reading the descriptor from the
* <code>extNode</code>.
*
* @param name The name of the extension to create. This is the name used to
* register the extension as.
* @param extNode The <code>Node</code> from which the extension is
* loaded.
*
* @return The newly created and registered {@link ExtensionDescriptor}.
*
* @throws ExtensionException If an error occurrs loading the
* {@link ExtensionDescriptor} from the <code>extNode</code>.
*/
private ExtensionDescriptor createExtension(String name, Node extNode)
throws ExtensionException {
ExtensionDescriptor ed = new ExtensionDescriptor(this, extNode);
extensions.put(name, ed);
return ed;
}
}