| /* |
| * 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; |
| } |
| } |