Return-Path: Delivered-To: apmail-incubator-jackrabbit-commits-archive@www.apache.org Received: (qmail 66047 invoked from network); 21 Dec 2005 20:19:12 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 21 Dec 2005 20:19:12 -0000 Received: (qmail 79422 invoked by uid 500); 21 Dec 2005 20:19:11 -0000 Mailing-List: contact jackrabbit-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: jackrabbit-dev@incubator.apache.org Delivered-To: mailing list jackrabbit-commits@incubator.apache.org Received: (qmail 79405 invoked by uid 500); 21 Dec 2005 20:19:11 -0000 Delivered-To: apmail-incubator-jackrabbit-cvs@incubator.apache.org Received: (qmail 79394 invoked by uid 99); 21 Dec 2005 20:19:10 -0000 Received: from asf.osuosl.org (HELO asf.osuosl.org) (140.211.166.49) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 21 Dec 2005 12:19:10 -0800 X-ASF-Spam-Status: No, hits=-9.4 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [209.237.227.194] (HELO minotaur.apache.org) (209.237.227.194) by apache.org (qpsmtpd/0.29) with SMTP; Wed, 21 Dec 2005 12:19:05 -0800 Received: (qmail 61254 invoked by uid 65534); 21 Dec 2005 20:18:45 -0000 Message-ID: <20051221201845.61249.qmail@minotaur.apache.org> Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r358365 [2/4] - in /incubator/jackrabbit/trunk/contrib/extension-framework: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/jackrabbit/ src/main/java/org/apache/jackrabbit/extension/ sr... Date: Wed, 21 Dec 2005 20:18:36 -0000 To: jackrabbit-cvs@incubator.apache.org From: fmeschbe@apache.org X-Mailer: svnmailer-1.0.5 X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Added: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/ExtensionType.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/ExtensionType.java?rev=358365&view=auto ============================================================================== --- incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/ExtensionType.java (added) +++ incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/ExtensionType.java Wed Dec 21 12:17:51 2005 @@ -0,0 +1,394 @@ +/* + * 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 ExtensionType 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. + *

+ * 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 ({0}) and the extension type identificatio ({1}). + * + * @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 ({0}), the extension type + * identification ({1}) and the extension name ({1}). + * + * @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 RepositoryClassLoader 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 Session for + * extensions of this type returning an Iterator of + * {@link ExtensionDescriptor} instances. If root is non-null + * the search for extensions only takes place in the indicated subtree. + *

+ * NOTE: 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 id/name pairs. + *

+ * 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 null 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 Session for + * an extension with the given name of type id. + * If root is non-null the search for extensions + * only takes place in the indicated subtree. + *

+ * 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. + *

+ * Two consecutive calls to this method with the same arguments, namely + * the same id and name 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 null or empty, the search + * takes place in the complete workspace. + * + * @return The named {@link ExtensionDescriptor} instances. + * + * @throws IllegalArgumentException If name is empty or + * null. + * @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 ClassLoader 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 extension is + * known to the loader. + * + * @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 true if obj is this or + * if it is an ExtensionType with the same extension type + * identification as this. + */ + 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 extNode. 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 Node containing the extension definition + * to be loaded if this instance has not loaded the named extension + * yet. This may be null to prevent loading an extension + * descriptor if the named extension has not been loaded yet. + * + * @return The name {@link ExtensionDescriptor} or null if this + * instance has not loaded the named extension yet and + * extNode is null. + * + * @throws ExtensionException If an error occurrs loading the extension + * descriptor from the extNode. + */ + /* 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 name reading the descriptor from the + * extNode. + * + * @param name The name of the extension to create. This is the name used to + * register the extension as. + * @param extNode The Node 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 extNode. + */ + private ExtensionDescriptor createExtension(String name, Node extNode) + throws ExtensionException { + ExtensionDescriptor ed = new ExtensionDescriptor(this, extNode); + extensions.put(name, ed); + return ed; + } +} Propchange: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/ExtensionType.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/ExtensionType.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url Added: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/NodeTypeSupport.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/NodeTypeSupport.java?rev=358365&view=auto ============================================================================== --- incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/NodeTypeSupport.java (added) +++ incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/NodeTypeSupport.java Wed Dec 21 12:17:51 2005 @@ -0,0 +1,152 @@ +/* + * 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.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.List; + +import javax.jcr.RepositoryException; +import javax.jcr.Workspace; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException; +import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; +import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; +import org.apache.jackrabbit.core.nodetype.compact.CompactNodeTypeDefReader; +import org.apache.jackrabbit.core.nodetype.compact.ParseException; + +/** + * The NodeTypeSupport contains a single utility method + * {@link #registerNodeType(Workspace)} to register the required mixin node + * type rep:jarFile with the repository. + *

+ * If the class loader is not used on a Jackrabbit based repository, loading + * this class or calling the {@link #registerNodeType(Workspace)} methods may + * fail with link errors. + * + * @author Felix Meschberger + * @version $Rev:$, $Date$ + */ +/* package */ class NodeTypeSupport { + + /** Default log */ + private static final Log log = LogFactory.getLog(NodeTypeSupport.class); + + /** + * The name of the class path resource containing the node type definition + * file used by the {@link #registerNodeType(Workspace)} method to register + * the required mixin node type (value is "type.cnd"). + */ + private static final String TYPE_FILE = "type.cnd"; + + /** + * The encoding used to read the node type definition file (value is + * "ISO-8859-1"). + */ + private static final String ENCODING = "ISO-8859-1"; + + /** + * Registers the required node type (rep:jarFile) with the + * node type manager available from the given workspace. + *

+ * The NodeTypeManager returned by the workspace + * is expected to be of type + * org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl for + * the node type registration to succeed. + *

+ * This method is not synchronized. It is up to the calling method to + * prevent paralell execution. + * + * @param workspace The Workspace providing the node type + * manager through which the node type is to be registered. + * + * @return true if this class can be used to handle archive + * class path entries. See above for a description of the test used. + */ + /* package */ static boolean registerNodeType(Workspace workspace) { + + // Access the node type definition file, "fail" if not available + InputStream ins = NodeTypeSupport.class.getResourceAsStream(TYPE_FILE); + if (ins == null) { + log.error("Node type definition file " + TYPE_FILE + + " not in class path. Cannot define required node type"); + return false; + } + + // Wrap the stream with a reader + InputStreamReader reader = null; + try { + reader = new InputStreamReader(ins, ENCODING); + } catch (UnsupportedEncodingException uee) { + log.warn("Required Encoding " + ENCODING + " not supported, " + + "using platform default encoding", uee); + + reader = new InputStreamReader(ins); + } + + try { + // Create a CompactNodeTypeDefReader + CompactNodeTypeDefReader cndReader = + new CompactNodeTypeDefReader(reader, TYPE_FILE); + + // Get the List of NodeTypeDef objects + List ntdList = cndReader.getNodeTypeDefs(); + + // Get the NodeTypeManager from the Workspace. + // Note that it must be cast from the generic JCR NodeTypeManager + // to the Jackrabbit-specific implementation. + NodeTypeManagerImpl ntmgr = + (NodeTypeManagerImpl) workspace.getNodeTypeManager(); + + // Acquire the NodeTypeRegistry + NodeTypeRegistry ntreg = ntmgr.getNodeTypeRegistry(); + + // register the node types from the file in a batch + ntreg.registerNodeTypes(ntdList); + + // get here and succeed + return true; + + } catch (ParseException pe) { + log.error("Unexpected failure to parse compact node defintion " + TYPE_FILE, pe); + + } catch (InvalidNodeTypeDefException ie) { + log.error("Cannot define required node type", ie); + + } catch (RepositoryException re) { + log.error("General problem accessing the repository", re); + + } catch (ClassCastException cce) { + log.error("Unexpected object type encountered", cce); + + } finally { + // make sure the reader is closed - expect to be non-null here ! + try { + reader.close(); + } catch (IOException ioe) { + // ignore + } + } + + // fall back to failure + return false; + } +} Propchange: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/NodeTypeSupport.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/NodeTypeSupport.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url Added: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/ConfigurationIODelegate.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/ConfigurationIODelegate.java?rev=358365&view=auto ============================================================================== --- incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/ConfigurationIODelegate.java (added) +++ incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/ConfigurationIODelegate.java Wed Dec 21 12:17:51 2005 @@ -0,0 +1,295 @@ +/* + * 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.configuration; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.FileConfiguration; + +/** + * The ConfigurationIODelegate class provides common IO + * functionality for the + * {@link org.apache.jackrabbit.extension.configuration.PropertiesNodeConfiguration} and + * {@link org.apache.jackrabbit.extension.configuration.XMLNodeConfiguration} classes to + * access configuration Repository Properties to load and save configuration + * data. In fact, this class may be used to extend any + * FileConfiguration implementation with support for loading and + * saveing from/to a JCR repository, not just the above mentioned. + * + * @author Felix Meschberger + * @version $Rev:$, $Date$ + */ +public class ConfigurationIODelegate { + + /** + * The FileConfiguration object used to write the + * configuration. + */ + private final FileConfiguration config; + + /** + * The Node from which the configuration is loaded. + */ + private Node jcrNode; + + /** + * The default character encoding when serializing strings from/to files + * (value is "UTF-8"). + */ + /* package */ static final String ENCODING = "UTF-8"; + + /** + * Creates a new instance delegating actual writing of the data to the + * underlying repository to the given FileConfiguration. + * + * @param config The FileConfiguration used for + * (de-)serializing the configuration data. + */ + /* package */ ConfigurationIODelegate(FileConfiguration config) { + this.config = config; + } + + /** + * Returns the repository Node from which the configuration is + * loaded resp. to which it is stored. + */ + /* package */ Node getNode() { + return jcrNode; + } + + /** + * Sets the repository Node from which the configuration is + * loaded resp. to whch it is stored. + */ + /* package */ void setNode(Node node) { + this.jcrNode = node; + } + + /** + * Calls the {@link #load(Node)} method if a repository Node + * has been set on this delegate. Otherwise calls the load() + * method of the FileConfiguration object which has been + * given to this instance at construction time. + * + * @throws ConfigurationException If an error occurrs loading the + * configuration. + */ + public void load() throws ConfigurationException { + if (jcrNode != null) { + load(jcrNode); + } else { + config.load(); + } + } + + /** + * Accesses the configuration property of the given repository + * Node to open an InputStream and calls the + * FileConfiguration's load(InputStream) method + * to actually load the configuration. + * + * @param node The configuration Node from which the + * configuration is to be read. + * + * @throws ConfigurationException If an error occurrs accessing the + * repository or loading the configuration. + */ + public void load(Node node) throws ConfigurationException { + InputStream ins = null; + try { + Property configProp = getConfigurationProperty(node); + ins = configProp.getStream(); + + config.load(ins); + + } catch (RepositoryException re) { + throw new ConfigurationException(re); + } finally { + tryClose(ins); + } + } + + /** + * Calls the {@link #save(Node)} method if a repository Node + * has been set on this delegate. Otherwise calls the save() + * method of the FileConfiguration object which has been + * given to this instance at construction time. + * + * @throws ConfigurationException If an error occurrs saving the + * configuration. + */ + public void save() throws ConfigurationException { + if (jcrNode != null) { + save(jcrNode); + } else { + config.save(); + } + } + + /** + * Calls the save(OutputStream) method of the + * FileConfiguration to store the configuration data into a + * temporary file, which is then fed into the configuration property + * retrieved from the given Node. + * + * @param node The configuration Node to which the + * configuration is to be saved. + * + * @throws ConfigurationException If an error occurrs accessing the + * repository or saving the configuration. + */ + public void save(javax.jcr.Node node) throws ConfigurationException { + // write the configuration to a temporary file + OutputStream out = null; + File tmp = null; + boolean success = false; + try { + tmp = File.createTempFile("srvcfg", ".tmp"); + out = new FileOutputStream(tmp); + config.save(out); + success = true; + } catch (IOException ioe) { + throw new ConfigurationException(ioe); + } finally { + tryClose(out); + + // delete the temp file, if saving failed (--> success == false) + if (!success && tmp != null) { + tmp.delete(); + } + } + + InputStream ins = null; + try { + ins = new FileInputStream(tmp); + Property configProp = getConfigurationProperty(node); + + // create version before update ??? + boolean doCheckIn = false; + if (configProp.getParent().isNodeType("mix:versionable") && + !configProp.getParent().isCheckedOut()) { + configProp.getParent().checkout(); + doCheckIn = true; + } + + configProp.setValue(ins); + configProp.save(); + + if (doCheckIn) { + configProp.getParent().checkin(); + } + + } catch (IOException ioe) { + throw new ConfigurationException(ioe); + } catch (RepositoryException re) { + throw new ConfigurationException(re); + } finally { + tryClose(ins); + tmp.delete(); + } + } + + /** + * Returns the property containing configuration data in the given + * configurationNode. The property to use is found following + * the the node's primary item trail: While the primary item is a node, + * the node's primary item is accessed. If it is a property which is not a + * reference, the property is returned. If the property is a reference, + * the reference is resolved and this step is repeated. + *

+ * If no configuration property can be found this method throws a + * RepositoryException. + * + * @param configurationNode The Node containing property to + * access at the end of the primary item trail. + * + * @return The property containing the configuration. + * + * @throws RepositoryException If an error occurrs accessing the node or if + * no configuration property can be found. + */ + /* package */ static Property getConfigurationProperty( + Node configurationNode) throws RepositoryException { + + // find the primary item now + for (;;) { + Item item = configurationNode.getPrimaryItem(); + if (!item.isNode()) { + Property prop = (Property) item; + + // if the property is not a reference return it + if (prop.getType() != PropertyType.REFERENCE) { + return prop; + } + + // otherwise get the referred to node and continue finding + // the primary item + item = prop.getNode(); + } + + configurationNode = (Node) item; + } + } + + /** + * Closes the InputStream in if not + * null and ignores a potential IOException thrown + * from closing the stream. + * + * @param in The InputStream to close. This may be + * null. + */ + public static void tryClose(InputStream in) { + if (in != null) { + try { + in.close(); + } catch (IOException ioe) { + // ignored by intent + } + } + } + + /** + * Closes the OutputStream out if not + * null and ignores a potential IOException thrown + * from closing the stream. + * + * @param out The OutputStream to close. This may be + * null. + */ + public static void tryClose(OutputStream out) { + if (out != null) { + try { + out.close(); + } catch (IOException ioe) { + // ignored by intent + } + } + } +} Propchange: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/ConfigurationIODelegate.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/ConfigurationIODelegate.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url Added: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/ItemConfiguration.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/ItemConfiguration.java?rev=358365&view=auto ============================================================================== --- incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/ItemConfiguration.java (added) +++ incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/ItemConfiguration.java Wed Dec 21 12:17:51 2005 @@ -0,0 +1,821 @@ +/* + * 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.configuration; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.jcr.NodeIterator; +import javax.jcr.PathNotFoundException; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.ValueFactory; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.ConfigurationKey; +import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jackrabbit.extension.ExtensionDescriptor; + +/** + * The ItemConfiguration extends the + * HierarchicalConfiguration class providing support to load the + * configuration from a repository. It represents the repository subtree from + * which the configuration is loaded as a configuration tree of configuration + * nodes and attributes. + *

+ * The configuration is rooted at a user supplied repository node which must be + * defined such, that properties and child nodes of any type and name may be + * added. The best way to achieve this is to define the node as of type + * nt:unstructured. + *

+ * Note on names + *

+ * This implementation uses the repository item names as (basis of) the names of + * the hierarchy configuration nodes. As such there exists a restriction on + * those names: The HierarchicalConfiguration extended by this + * class uses dots (.) as hierarchy level separators. Therefore + * any configuration node's name with a dot in it will likely lead to unsuable + * configuration. + *

+ * Therefore it is strongly recommended to not use dots in repository element + * names to be used by this configuration class. + *

+ * Data Type Conversion + *

+ * This implementation tries its best to preserve the configuration data type + * when loading or saving the configuration data. Because the mapping between + * Java data types supported by the configuration objects and the data types + * supported by the repository, a mapping has to be applied, which may lead to a + * certain but acceptable loss of accuracy. + *

+ * When loading values from the repository, the following type conversion + * applies: + * + * + * + * + * + * + * + * + * + * + * + * + *
JCR Type + * Java Type
Boolean + * Boolean
Date + * Calendar
Double + * Double
Long + * Long
Binary, Name, Path, Reference, String, Undefined + * String
+ *

+ * When saveing configuaration data to the repository, the following type + * conversion applies: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Java Type + * JCR Type
String + * String
Boolean + * Boolean
Calendar + * Date
Double or Float + * Double
Number except Double and Float + * Long
Other types, incl. null + * String
+ * + * @author Felix Meschberger + * @version $Rev:$, $Date$ + */ +public class ItemConfiguration extends HierarchicalConfiguration implements + RepositoryConfiguration { + + /** default log */ + private static final Log log = LogFactory.getLog(ExtensionDescriptor.class); + + /** + * The name of the property providing the configuration value of a + * configuration node. + */ + private static final String NODE_CONTENT_PROPERTY = "__DEFAULT__"; + + /** + * The Node to which this configuration is attached. The + * configuration data itself is loaded and saved from/to the + * configuration child node of this node. + * + * @see #load(javax.jcr.Node) + * @see #save(javax.jcr.Node) + */ + private javax.jcr.Node jcrNode; + + /** + * The backlog of absolute paths of items which backed removed configuration + * data. This set is worked through to remove the items when the + * configuration is saved. + * + * @see #save(javax.jcr.Node) + * @see ItemNode#removeReference() + */ + private Set deleteBackLog; + + /** + * Creates an empty configuration not hooked to any node. + */ + public ItemConfiguration() { + super(); + } + + /** + * Creates a configuration attached to the given node and + * load the configuration data from the configuration child + * node. + *

+ * If node is null, this constructor has the same + * effect as the default constructor ({@link #ItemConfiguration()} in that + * this configuration is not attached to a Node and + * configuration is not loaded. + * + * @param node The Node containing the configuration data. + * + * @throws ConfigurationException If an error occurrs loading the + * configuration data. + */ + public ItemConfiguration(javax.jcr.Node node) throws ConfigurationException { + super(); + + setNode(node); + load(); + } + + /** + * Returns the Node to which this configuration is attached. + * If this configuration is not attached to a node, this method returns + * null. + */ + public javax.jcr.Node getNode() { + return jcrNode; + } + + /** + * Attaches this configuration to the given node to provide + * ({@link #load(javax.jcr.Node)}) or take ({@link #save(javax.jcr.Node)}) + * configuration data. To detach this configuration from the repository, + * set node to null. + * + * @param node The Node to which this configuration is + * attached or null to detach the configuration. + */ + public void setNode(javax.jcr.Node node) { + // if the new node is different from the old node, remove the current + // configuration's references + if (isDifferent(node)) { + removeReferences(getRoot()); + } + + // set the new node + this.jcrNode = node; + } + + /** + * Creates an instance of the ItemNode class with an empty + * reference. + *

+ * As noted in the class comment, the name should not contain a dot, + * otherwise the HierarchicalConfiguration class will have + * problems resolving the configuration. + * + * @param name The name of the new configuratio node. + */ + protected Node createNode(String name) { + return new ItemNode(name, null); + } + + /** + * Loads the configuration data from the Node to which this + * configuration is attached. If this configuration is not attached to + * a Node, this method has no effect. + *

+ * If configuration data is already present in this configuration, the data + * is extended by the data loaded from the Node. To prevent + * such additions, clear this configuration before loading new data. + * + * @throws ConfigurationException If an error occurrs loading the + * configuration data. + * + * @see #load(javax.jcr.Node) + */ + public void load() throws ConfigurationException { + if (jcrNode != null) { + load(jcrNode); + } + } + + /** + * Loads the configuration data from the given node. If + * node is null, a NullPointerException + * is thrown. + *

+ * If configuration data is already present in this configuration, the data + * is extended by the data loaded from the Node. To prevent + * such additions, clear this configuration before loading new data. + * + * @param node The Node containing the configuration to be + * loaded into this configuration. This must no be null. + * + * @throws NullPointerException if node is null. + * @throws ConfigurationException If an error occurrs loading the + * configuration data. + */ + public void load(javax.jcr.Node node) throws ConfigurationException { + try { + boolean sameNode = !isDifferent(node); + + // construct the hierarchy and record references if loading + // from the node this configuration is attached to + constructHierarchy(getRoot(), node, sameNode); + } catch (RepositoryException re) { + throw new ConfigurationException(re); + } + } + + /** + * Saves the configuration data to the Node to which this + * configuration is attached. If this configuration is not attached to + * a Node, this method has no effect. + * + * @throws ConfigurationException If an error occurrs saving the + * configuration data. + * + * @see #save(javax.jcr.Node) + */ + public void save() throws ConfigurationException { + if (jcrNode != null) { + save(jcrNode); + } + } + + /** + * Saves the configuration data to the given node. If + * node is null, a NullPointerException + * is thrown. + * + * @param node The Node to store the configuration to. This + * must no be null. + * + * @throws NullPointerException if node is null. + * @throws ConfigurationException If an error occurrs saving the + * configuration data. + */ + public void save(javax.jcr.Node node) throws ConfigurationException { + boolean lockable = false; + try { + // remove the node references from the current configuration + // nodes if the destination is different from the node to which + // the configuration is attached + if (isDifferent(node)) { + removeReferences(getRoot()); + } + + lockable = node.isNodeType("mix:lockable"); + if (lockable) { + if (node.isLocked()) { + // trick: reset lockable to not unlock in finally{} + lockable = false; + throw new ConfigurationException("Configuration node is locked"); + } + + // deep session lock + node.lock(true, true); + } + + // check whether the node is versionable + boolean versionable = node.isNodeType("mix:versionable"); + + // make sure the node is checked out for modification + if (versionable && !node.isCheckedOut()) { + node.checkout(); + } + + // remove all items which have to be removed because the + // configuration which were backed by them has been removed + if (deleteBackLog != null) { + Session session = node.getSession(); + for (Iterator di=deleteBackLog.iterator(); di.hasNext(); ) { + String itemPath = (String) di.next(); + try { + session.getItem(itemPath).remove(); + } catch (PathNotFoundException pnfe) { + // might have already been removed, ignore + log.debug("Item " + itemPath + " cannot be accessed for removal", + pnfe); + } + } + } + + // store now + ItemBuilderVisitor builder = new ItemBuilderVisitor(node); + builder.processDocument(getRoot()); + + // save modifications + node.save(); + + // checkin after saving + if (versionable) { + node.checkin(); + } + + } catch (RepositoryException re) { + throw new ConfigurationException("Cannot save configuration", re); + } finally { + // if the node is still modified, this is an error and we + // rollback + try { + if (node.isModified()) { + node.refresh(false); + } else { + // reset deleteBackLog, because all items have been removed + // and need not be removed the next time. + // (If an error occurred saving the configuration, the back + // log must remain, such that the deleted items may be + // removed the next time, save() is called). + deleteBackLog = null; + } + } catch (RepositoryException re) { + log.error("Problem refreshing persistent config state", re); + } + + // unlock the node again + try { + if (lockable && node.isLocked()) { + node.unlock(); + } + } catch (RepositoryException re) { + log.warn("Cannot unlock configuration node", re); + } + } + } + + /** + * Returns true if newNode is not the same + * repository Node as the Node to which this + * configuration is currently associated. + *

+ * Removing the references makes sure that the complete configuration data + * is written to the repository the next time {@link #save()} is called. + * + * @param newNode The repository Node to which the current + * base Node is compared. + * + * @return true if newNode is different to the + * Node to which the configuration is currently attached. + */ + private boolean isDifferent(javax.jcr.Node newNode) { + // return false if the objects are the same + if (jcrNode == newNode) { + return false; + } + + // return true if no node yet and new is not null + if (jcrNode == null) { + return newNode != null; + } + + // return true if the new node is null and the old is set + if (newNode == null) { + return jcrNode != null; + } + + // otherwise try to compare the new to the old node + try { + return !jcrNode.isSame(newNode); + } catch (RepositoryException re) { + // cannot check whether the nodes are different, assume yes + log.warn("Cannot check whether the current and new nodes " + + "are different, assuming they are", re); + } + + // fallback to different in case of problems + return true; + } + + /** + * Vists all configuration nodes starting from the given node + * and resets all node's reference fields to null. This forces + * complete configuration storage on the next call to the {@link #save()} or + * {@link #save(javax.jcr.Node)} methods. + * + * @param node The Node at which to start removing references + */ + private static void removeReferences(Node node) { + // remove repository item references from the nodes + node.visit(new NodeVisitor() { + public void visitBeforeChildren(Node node, ConfigurationKey key) { + node.setReference(null); + }; + }, null); + } + + /** + * Creates the internal configuration hierarchy of {@link ItemNode}s from + * the items in the repository. + * + * @param node The configuration node to which the new configuration is + * attached. + * @param element The JCR Node from which the configuration + * is read. + * @param elemRefs true if the configuration nodes created + * while reading the repository items get the reference fields set to + * the corresponding repository item. + * + * @throws RepositoryException If an error occurrs reading from the + * repository. + */ + private void constructHierarchy(Node node, javax.jcr.Node element, + boolean elemRefs) throws RepositoryException { + + // create attribute child nodes for the element's properties + processAttributes(node, element, elemRefs); + + // read the element's child nodes as child nodes into the configuration + NodeIterator list = element.getNodes(); + while (list.hasNext()) { + javax.jcr.Node jcrNode = list.nextNode(); + + // ignore protected nodes + if (jcrNode.getDefinition().isProtected()) { + continue; + } + + Node childNode = new ItemNode(jcrNode.getName(), + elemRefs ? jcrNode.getPath() : null); + constructHierarchy(childNode, jcrNode, elemRefs); + node.addChild(childNode); + } + } + + /** + * Helper method for constructing node objects for the attributes of the + * given XML element. + * + * @param node the actual node + * @param element the actual XML element + * @param elemRefs a flag whether references to the XML elements should be + * set + * @param node The configuration node to which the new configuration is + * attached. + * @param element The JCR Node whose properties are to be + * read and attached. + * @param elemRefs true if the configuration nodes created + * while reading the properties get the reference fields set to the + * corresponding property. + * + * @throws RepositoryException If an error occurrs reading from the + * repository. + */ + private void processAttributes(Node node, javax.jcr.Node element, + boolean elemRefs) throws RepositoryException { + + PropertyIterator attributes = element.getProperties(); + while (attributes.hasNext()) { + Property prop = attributes.nextProperty(); + + // ignore protected properties + if (prop.getDefinition().isProtected()) { + continue; + } + + Value[] values; + if (prop.getDefinition().isMultiple()) { + values = prop.getValues(); + } else { + values = new Value[] { prop.getValue() }; + } + + if (NODE_CONTENT_PROPERTY.equals(prop.getName())) { + // this is the value of the node itself + // only consider the first value + if (values.length > 0) { + node.setValue(importValue(values[0])); + } + } else { + String name = ConfigurationKey.constructAttributeKey(prop.getName()); + String ref = elemRefs ? prop.getPath() : null; + for (int i = 0; i < values.length; i++) { + Node child = new ItemNode(name, ref); + child.setValue(importValue(values[i])); + node.addChild(child); + } + } + } + } + + /** + * The ItemNode class extends the standard Node + * class by support for removing underlying repository items in case of + * removal of a configuration node. + * + * @author Felix Meschberger + * @version $Rev:$, $Date$ + */ + private class ItemNode extends Node { + + /* + * This class is not static to have a reference to the owning instance + * such that the deleteBackLog set may be accessed which is used to + * record items to be removed due to ItemNode removals + */ + + /** fake serialVersionUID */ + private static final long serialVersionUID = 1L; + + /** + * Creates an instance of this node type presetting the reference. + * + * @param name The name of the new configuration node. + * @param reference The (optional) reference to initially set on the + * new configuration node. This may be null. + */ + protected ItemNode(String name, String reference) { + super(name); + setReference(reference); + } + + /** + * Removes the associated repository item if this node is removed + * from the configuration. + */ + protected void removeReference() { + if (getReference() != null) { + + if (ConfigurationKey.isAttributeKey(getName())) { + List list = getParent().getChildren(getName()); + if (list != null && list.size() > 0) { + for (Iterator ci=list.iterator(); ci.hasNext(); ) { + // clear references of sibblings + ((Node) ci.next()).setReference(null); + } + } + } + + if (deleteBackLog == null) { + deleteBackLog = new HashSet(); + } + deleteBackLog.add(getReference()); + } + } + } + + /** + * The ItemBuilderVisitor class stores the configuration + * rooted at a given Node to the repository Node + * defined at construction time. + *

+ * This visitor just adds nodes and properties to the repository and does + * not care whether the operations actually overwrite data or not. It is + * recommended that the JCR Node from which the visitor is + * created be cleared before processing the configuration through the + * {@link #processDocument(Node)} method. + * + * @author Felix Meschberger + * @version $Rev:$, $Date$ + */ + private static class ItemBuilderVisitor extends BuilderVisitor { + + /** Stores the document to be constructed. */ + private javax.jcr.Node jcrNode; + + /** + * Creates a new instance of ItemBuilderVisitor storing the + * configuration at and below the given jcrNode. + * + * @param jcrNode The JCR Node to take the configuration. + */ + public ItemBuilderVisitor(javax.jcr.Node jcrNode) { + this.jcrNode = jcrNode; + } + + /** + * Processes the node hierarchy and adds new items to the repository + * + * @param rootNode The configuration Node to start at in + * the configuration hierarchy. + */ + public void processDocument(Node rootNode) throws RepositoryException { + rootNode.setReference(jcrNode.getPath()); + rootNode.visit(this, null); + } + + /** + * Inserts a new node. This implementation ensures that the correct XML + * element is created and inserted between the given siblings. + * + * @param newNode the node to insert + * @param parent the parent node + * @param sibling1 the first sibling + * @param sibling2 the second sibling + * @return the new node + */ + protected Object insert(Node newNode, Node parent, Node sibling1, + Node sibling2) { + + try { + // get the parent's owning node + javax.jcr.Node parentNode; + if (parent.getName() == null) { + parentNode = jcrNode; + } else { + String ref = (String) parent.getReference(); + parentNode = (javax.jcr.Node) jcrNode.getSession().getItem(ref); + } + + // if the configuration node is an attribute, set the respective + // property and return. + if (ConfigurationKey.isAttributeKey(newNode.getName())) { + updateAttribute(parent, parentNode, newNode.getName()); + return null; + } + + // create the repository node for the configuration node + javax.jcr.Node elem = parentNode.addNode(newNode.getName()); + + // if the configuration node has a value, set the __DEFAULT__ + // property to this value + if (newNode.getValue() != null) { + Value value = + exportValue(elem.getSession().getValueFactory(), + newNode.getValue()); + elem.setProperty(NODE_CONTENT_PROPERTY, value); + } + + // order before sibling2 if defined, ignore sibling1 + if (parentNode.getPrimaryNodeType().hasOrderableChildNodes()) { + if (sibling2 != null) { + parentNode.orderBefore(newNode.getName(), + sibling2.getName()); + } + } + + return elem.getPath(); + } catch (RepositoryException re) { + log.warn("Cannot update repository for configuration node " + + newNode.getName(), re); + } + + // fallback to returning nothing + return null; + } + + /** + * Helper method for updating the value of the specified node's + * attribute with the given name. + * + * @param node the affected node + * @param elem the element that is associated with this node + * @param name the name of the affected attribute + */ + private void updateAttribute(Node node, javax.jcr.Node elem, + String name) throws RepositoryException { + if (node != null && elem != null) { + String propName = ConfigurationKey.attributeName(name); + + // copy the values of all like named attributes to another list + List attrs = node.getChildren(name); + List values = new ArrayList(); + for (Iterator ai = attrs.iterator(); ai.hasNext();) { + Node attr = (Node) ai.next(); + if (attr.getValue() != null) { + values.add(attr.getValue()); + } + } + + // remove property before trying to set + if (elem.hasProperty(propName)) { + elem.getProperty(propName).remove(); + } + + Property attrProp; + ValueFactory vf = elem.getSession().getValueFactory(); + if (values.size() == 0) { + // no attribute values + attrProp = null; + } else if (values.size() == 1) { + // single valued property + attrProp = + elem.setProperty(propName, exportValue(vf, values.get(0))); + } else { + Value[] valArray = new Value[values.size()]; + for (int i = 0; i < valArray.length; i++) { + valArray[i] = exportValue(vf, values.get(i)); + } + attrProp = elem.setProperty(propName, valArray); + } + + // set the references on the attribute nodes + String ref = attrProp != null ? attrProp.getPath() : null; + for (Iterator ai = attrs.iterator(); ai.hasNext();) { + Node attr = (Node) ai.next(); + attr.setReference(ref); + } + } + } + } + + //---------- Data type helpers for loading and storing -------------------- + + /** + * Converts the JCR Value object to a configuration value of + * the corresponding runtime Java type. See the class comment for information on the type + * conversion applied. + * + * @param jcrValue The JCR Value to convert into a + * configuration value object. + * @return The configuration value object. + * @throws NullPointerException if jcrValue is + * null. + */ + private static Object importValue(Value jcrValue) + throws RepositoryException { + + switch (jcrValue.getType()) { + case PropertyType.BOOLEAN: + return new Boolean(jcrValue.getBoolean()); + case PropertyType.DATE: + return jcrValue.getDate(); + case PropertyType.DOUBLE: + return new Double(jcrValue.getDouble()); + case PropertyType.LONG: + return new Long(jcrValue.getLong()); + default: + // Binary, Name, Path, Reference, String, Undefined + return jcrValue.getString(); + } + } + + /** + * Converts the value object to a JCR Value instance + * according to the runtime type of the value. See the class comment for information on the type + * conversion applied. + * + * @param vf The ValueFactory used to create JCR + * Value objects. + * @param value The configuration value to convert (export) to a JCR + * Value object. + * @return The JCR Value object representing the + * configuration value. + */ + private static Value exportValue(ValueFactory vf, Object value) { + if (value instanceof String) { + return vf.createValue((String) value); + } else if (value instanceof Boolean) { + return vf.createValue(((Boolean) value).booleanValue()); + } else if (value instanceof Calendar) { + return vf.createValue((Calendar) value); + } else if (value instanceof Double || value instanceof Float) { + // handle float and double values as double + return vf.createValue(((Number) value).doubleValue()); + } else if (value instanceof Number) { + // handle other numbers (float and double above) as long + return vf.createValue(((Number) value).longValue()); + } else { + return vf.createValue(String.valueOf(value)); + } + } +} \ No newline at end of file Propchange: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/ItemConfiguration.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/ItemConfiguration.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url Added: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/PropertiesNodeConfiguration.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/PropertiesNodeConfiguration.java?rev=358365&view=auto ============================================================================== --- incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/PropertiesNodeConfiguration.java (added) +++ incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/PropertiesNodeConfiguration.java Wed Dec 21 12:17:51 2005 @@ -0,0 +1,185 @@ +/* + * 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.configuration; + +import java.io.File; +import java.net.URL; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; + +/** + * The PropertiesNodeConfiguration extends the Apache Commons + * PropertiesConfiguration by support for loading the properties + * from a repository property in addition to the standard loading source such + * as file, URL, and streams. + * + * @author Felix Meschberger + * @version $Rev:$, $Date$ + */ +public class PropertiesNodeConfiguration extends PropertiesConfiguration + implements RepositoryConfiguration { + + /** + * The delegate object which takes care for actually loading and saving + * configuration to and from the repository. + */ + private final ConfigurationIODelegate delegate = + new ConfigurationIODelegate(this); + + /** + * Creates an empty PropertiesNodeConfiguration object which + * can be used to synthesize a new Properties file by adding values and then + * saving(). An object constructed by this constructor can not be tickled + * into loading included files because it cannot supply a base for relative + * includes. + */ + public PropertiesNodeConfiguration() { + super(); + } + + /** + * Creates and loads the extended properties from the specified file. The + * specified file can contain "include = " properties which then are loaded + * and merged into the properties. + * + * @param fileName The name of the properties file to load. + * + * @throws ConfigurationException Error while loading the properties file + */ + public PropertiesNodeConfiguration(String fileName) + throws ConfigurationException { + super(fileName); + } + + /** + * Creates and loads the extended properties from the specified file. The + * specified file can contain "include = " properties which then are loaded + * and merged into the properties. + * + * @param file The properties file to load. + * + * @throws ConfigurationException Error while loading the properties file + */ + public PropertiesNodeConfiguration(File file) throws ConfigurationException { + super(file); + } + + /** + * Creates and loads the extended properties from the specified URL. The + * specified file can contain "include = " properties which then are loaded + * and merged into the properties. + * + * @param url The location of the properties file to load. + * + * @throws ConfigurationException Error while loading the properties file + */ + public PropertiesNodeConfiguration(URL url) throws ConfigurationException { + super(url); + } + + /** + * Creates and loads the extended properties from the specified + * node. An object constructed by this constructor can not be + * tickled into loading included files because it cannot supply a base for + * relative includes. + * + * @param node The Node from which to load the configuration. + * + * @throws ConfigurationException Error while loading the properties file + * + */ + public PropertiesNodeConfiguration(javax.jcr.Node node) + throws ConfigurationException { + super(); + setIncludesAllowed(false); + setNode(node); + load(); + } + + /** + * Returns the Node on which this configuration is based. If + * this is not a repository-based configuration object or has not been + * configured to load from the repository, this method returns + * null. + */ + public javax.jcr.Node getNode() { + return delegate.getNode(); + } + + /** + * Sets the Node on which this configuration is based. + */ + public void setNode(javax.jcr.Node node) { + delegate.setNode(node); + } + + /** + * Loads the configuration from the underlying location. + * + * @throws ConfigurationException if loading of the configuration fails + */ + public void load() throws ConfigurationException { + delegate.load(); + } + + /** + * Loads the configuration from the node. The property to + * use is found following the the node's primary item trail: While the + * primary item is a node, the node's primary item is accessed. If it is a + * property which is not a reference, the property is returned. If the + * property is a reference, the reference is resolved and this step is + * repeated. + *

+ * If no property can be found using above mentioned algorithm, loading the + * configuration fails. + * + * @param node The Node of the repository based configuration + * to load from. + * @throws ConfigurationException if an error occurs during the load + * operation or if no property can be found containing the + * properties "file". + */ + public void load(javax.jcr.Node node) throws ConfigurationException { + delegate.load(node); + } + + /** + * Saves the configuration to the underlying location. + * + * @throws ConfigurationException if saving of the configuration fails + */ + public void save() throws ConfigurationException { + delegate.save(); + } + + /** + * Saves the configuration in the node. The same algorithm + * applies for finding the property to store the configuration in as is + * applied by the {@link #load(javax.jcr.Node)} method. If no property can + * be found, the configuration cannot be saved. + * + * @param node The Node of the repository based configuration + * to save the configuration in. + * + * @throws ConfigurationException if an error occurs during the save + * operation. + */ + public void save(javax.jcr.Node node) throws ConfigurationException { + delegate.save(node); + } +} Propchange: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/PropertiesNodeConfiguration.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/PropertiesNodeConfiguration.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url Added: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/RepositoryConfiguration.java URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/RepositoryConfiguration.java?rev=358365&view=auto ============================================================================== --- incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/RepositoryConfiguration.java (added) +++ incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/RepositoryConfiguration.java Wed Dec 21 12:17:51 2005 @@ -0,0 +1,133 @@ +/* + * 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.configuration; + +import javax.jcr.Node; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; + +/** + * The RepositoryConfiguration interface extends the + * Configuration with support for loading configuration from + * a JCR repository and storing the configuration in a JCR repository. + * + * @author Felix Meschberger + * @version $Rev:$, $Date$ + */ +public interface RepositoryConfiguration extends Configuration { + + /** + * Returns the repository Node to which this configuration + * is attached. + */ + Node getNode(); + + /** + * Attaches this configuration to the repository Node. + * + * @param node The Node to which the configuration object + * is attached or null to actually detach the + * configuration from the configuration node. + */ + void setNode(Node node); + + /** + * Loads the configuration from the node and child items. + * + * @throws ConfigurationException if an error occurs during the load + * operation + */ + void load() throws ConfigurationException; + + /** + * Loads the configuration from the node and child items. + * + * @param node The root Node of the repository based + * configuration to load from. + * + * @throws ConfigurationException if an error occurs during the load + * operation + */ + void load(Node node) throws ConfigurationException; + + /** + * Save the configuration to the specified node. + *

+ * Before actually storing any configuration data all properties and child + * nodes of the node are removed to be able to write clean + * configuration. + *

+ * If the node is versionable, the node is checked out (if required) before + * saving the configuration and checked in again after saving the + * configuration. + *

+ * Invariants: + *

    + *
  • If an error occurrs, all modifications must be rolled back and the + * node and all properties and child nodes must remain + * in the former state. + *
  • If all goes well, the node, properties and child nodes + * reflect the current state of the configuration and the + * node has been persisted in the repository. + *
  • If all goes well and the node is versionable, the + * node is checked in. If an error occurrs it is not + * specified whether the versionable node is checked in + * or not. + *
+ * + * @throws ConfigurationException if an error occurs during the save + * operation + * + * @see #setNode(Node) + * @see #getNode() + */ + void save() throws ConfigurationException; + + /** + * Save the configuration to the specified node. + *

+ * Before actually storing any configuration data all properties and child + * nodes of the node are removed to be able to write clean + * configuration. + *

+ * If the node is versionable, the node is checked out (if required) before + * saving the configuration and checked in again after saving the + * configuration. + *

+ * Invariants: + *

    + *
  • If an error occurrs, all modifications must be rolled back and the + * node and all properties and child nodes must remain + * in the former state. + *
  • If all goes well, the node, properties and child nodes + * reflect the current state of the configuration and the + * node has been persisted in the repository. + *
  • If all goes well and the node is versionable, the + * node is checked in. If an error occurrs it is not + * specified whether the versionable node is checked in + * or not. + *
+ * + * @param node The root Node of the repository based + * configuration to save to. + * + * @throws ConfigurationException if an error occurs during the save + * operation + */ + void save(Node node) throws ConfigurationException; +} Propchange: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/RepositoryConfiguration.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/jackrabbit/trunk/contrib/extension-framework/src/main/java/org/apache/jackrabbit/extension/configuration/RepositoryConfiguration.java ------------------------------------------------------------------------------ svn:keywords = author date id revision url