jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From fmesc...@apache.org
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 GMT
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 <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;
+    }
+}

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 <code>NodeTypeSupport</code> contains a single utility method
+ * {@link #registerNodeType(Workspace)} to register the required mixin node
+ * type <code>rep:jarFile</code> with the repository.
+ * <p>
+ * 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 (<code>rep:jarFile</code>) with the
+     * node type manager available from the given <code>workspace</code>.
+     * <p>
+     * The <code>NodeTypeManager</code> returned by the <code>workspace</code>
+     * is expected to be of type
+     * <code>org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl</code> for
+     * the node type registration to succeed.
+     * <p>
+     * This method is not synchronized. It is up to the calling method to
+     * prevent paralell execution.
+     *
+     * @param workspace The <code>Workspace</code> providing the node type
+     *      manager through which the node type is to be registered.
+     *
+     * @return <code>true</code> 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 <code>ConfigurationIODelegate</code> 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
+ * <code>FileConfiguration</code> 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 <code>FileConfiguration</code> object used to write the
+     * configuration.
+     */
+    private final FileConfiguration config;
+
+    /**
+     * The <code>Node</code> 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 <code>FileConfiguration</code>.
+     *
+     * @param config The <code>FileConfiguration</code> used for
+     *      (de-)serializing the configuration data.
+     */
+    /* package */ ConfigurationIODelegate(FileConfiguration config) {
+        this.config = config;
+    }
+
+    /**
+     * Returns the repository <code>Node</code> from which the configuration is
+     * loaded resp. to which it is stored.
+     */
+    /* package */ Node getNode() {
+        return jcrNode;
+    }
+
+    /**
+     * Sets the repository <code>Node</code> 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 <code>Node</code>
+     * has been set on this delegate. Otherwise calls the <code>load()</code>
+     * method of the <code>FileConfiguration</code> 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
+     * <code>Node</code> to open an <code>InputStream</code> and calls the
+     * <code>FileConfiguration</code>'s <code>load(InputStream)</code> method
+     * to actually load the configuration.
+     *
+     * @param node The configuration <code>Node</code> 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 <code>Node</code>
+     * has been set on this delegate. Otherwise calls the <code>save()</code>
+     * method of the <code>FileConfiguration</code> 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 <code>save(OutputStream)</code> method of the
+     * <code>FileConfiguration</code> to store the configuration data into a
+     * temporary file, which is then fed into the configuration property
+     * retrieved from the given <code>Node</code>.
+     *
+     * @param node The configuration <code>Node</code> 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
+     * <code>configurationNode</code>. 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.
+     * <p>
+     * If no configuration property can be found this method throws a
+     * <code>RepositoryException</code>.
+     *
+     * @param configurationNode The <code>Node</code> 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 <code>InputStream</code> <code>in</code> if not
+     * <code>null</code> and ignores a potential <code>IOException</code> thrown
+     * from closing the stream.
+     *
+     * @param in The <code>InputStream</code> to close. This may be
+     *          <code>null</code>.
+     */
+    public static void tryClose(InputStream in) {
+        if (in != null) {
+            try {
+                in.close();
+            } catch (IOException ioe) {
+                // ignored by intent
+            }
+        }
+    }
+
+    /**
+     * Closes the <code>OutputStream</code> <code>out</code> if not
+     * <code>null</code> and ignores a potential <code>IOException</code> thrown
+     * from closing the stream.
+     *
+     * @param out The <code>OutputStream</code> to close. This may be
+     *          <code>null</code>.
+     */
+    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 <code>ItemConfiguration</code> extends the
+ * <code>HierarchicalConfiguration</code> 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.
+ * <p>
+ * 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
+ * <code>nt:unstructured</code>.
+ * <p>
+ * <b>Note on names</b>
+ * <p>
+ * 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 <code>HierarchicalConfiguration</code> extended by this
+ * class uses dots (<code>.</code>) as hierarchy level separators. Therefore
+ * any configuration node's name with a dot in it will likely lead to unsuable
+ * configuration.
+ * <p>
+ * <i>Therefore it is strongly recommended to not use dots in repository element
+ * names to be used by this configuration class.</i>
+ * <p id="dataTypeConversion">
+ * <b>Data Type Conversion</b>
+ * <p>
+ * 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.
+ * <p>
+ * When loading values from the repository, the following type conversion
+ * applies: <table>
+ * <tr>
+ * <th>JCR Type
+ * <th>Java Type</tr>
+ * <tr>
+ * <td>Boolean
+ * <td>Boolean</tr>
+ * <tr>
+ * <td>Date
+ * <td>Calendar</tr>
+ * <tr>
+ * <td>Double
+ * <td>Double</tr>
+ * <tr>
+ * <td>Long
+ * <td>Long</tr>
+ * <tr>
+ * <td>Binary, Name, Path, Reference, String, Undefined
+ * <td>String</tr>
+ * </table>
+ * <p>
+ * When saveing configuaration data to the repository, the following type
+ * conversion applies: <table>
+ * <tr>
+ * <th>Java Type
+ * <th>JCR Type</tr>
+ * <tr>
+ * <td>String
+ * <td>String</tr>
+ * <tr>
+ * <td>Boolean
+ * <td>Boolean</tr>
+ * <tr>
+ * <td>Calendar
+ * <td>Date</tr>
+ * <tr>
+ * <td>Double or Float
+ * <td>Double</tr>
+ * <tr>
+ * <td>Number except Double and Float
+ * <td>Long</tr>
+ * <tr>
+ * <td>Other types, incl. <code>null</code>
+ * <td>String</tr>
+ * </table>
+ *
+ * @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 <code>Node</code> to which this configuration is attached. The
+     * configuration data itself is loaded and saved from/to the
+     * <code>configuration</code> 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 <code>node</code> and
+     * load the configuration data from the <code>configuration</code> child
+     * node.
+     * <p>
+     * If <code>node</code> is <code>null</code>, this constructor has the same
+     * effect as the default constructor ({@link #ItemConfiguration()} in that
+     * this configuration is not attached to a <code>Node</code> and
+     * configuration is not loaded.
+     *
+     * @param node The <code>Node</code> 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 <code>Node</code> to which this configuration is attached.
+     * If this configuration is not attached to a node, this method returns
+     * <code>null</code>.
+     */
+    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 <code>node</code> to <code>null</code>.
+     *
+     * @param node The <code>Node</code> to which this configuration is
+     *            attached or <code>null</code> 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 <code>ItemNode</code> class with an empty
+     * reference.
+     * <p>
+     * As noted in the class comment, the name should not contain a dot,
+     * otherwise the <code>HierarchicalConfiguration</code> 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 <code>Node</code> to which this
+     * configuration is attached. If this configuration is not attached to
+     * a <code>Node</code>, this method has no effect.
+     * <p>
+     * If configuration data is already present in this configuration, the data
+     * is extended by the data loaded from the <code>Node</code>. 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 <code>node</code>. If
+     * <code>node</code> is <code>null</code>, a <code>NullPointerException</code>
+     * is thrown.
+     * <p>
+     * If configuration data is already present in this configuration, the data
+     * is extended by the data loaded from the <code>Node</code>. To prevent
+     * such additions, clear this configuration before loading new data.
+     *
+     * @param node The <code>Node</code> containing the configuration to be
+     *      loaded into this configuration. This must no be <code>null</code>.
+     *
+     * @throws NullPointerException if <code>node</code> is <code>null</code>.
+     * @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 <code>Node</code> to which this
+     * configuration is attached. If this configuration is not attached to
+     * a <code>Node</code>, 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 <code>node</code>. If
+     * <code>node</code> is <code>null</code>, a <code>NullPointerException</code>
+     * is thrown.
+     *
+     * @param node The <code>Node</code> to store the configuration to. This
+     *      must no be <code>null</code>.
+     *
+     * @throws NullPointerException if <code>node</code> is <code>null</code>.
+     * @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 <code>true</code> if <code>newNode</code> is not the same
+     * repository <code>Node</code> as the <code>Node</code> to which this
+     * configuration is currently associated.
+     * <p>
+     * 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 <code>Node</code> to which the current
+     *      base <code>Node</code> is compared.
+     *
+     * @return <code>true</code> if <code>newNode</code> is different to the
+     *      <code>Node</code> 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 <code>node</code>
+     * and resets all node's reference fields to <code>null</code>. This forces
+     * complete configuration storage on the next call to the {@link #save()} or
+     * {@link #save(javax.jcr.Node)} methods.
+     *
+     * @param node The <code>Node</code> 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 <code>Node</code> from which the configuration
+     *      is read.
+     * @param elemRefs <code>true</code> 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 <code>Node</code> whose properties are to be
+     *      read and attached.
+     * @param elemRefs <code>true</code> 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 <code>ItemNode</code> class extends the standard <code>Node</code>
+     * 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 <code>null</code>.
+         */
+        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 <code>ItemBuilderVisitor</code> class stores the configuration
+     * rooted at a given <code>Node</code> to the repository <code>Node</code>
+     * defined at construction time.
+     * <p>
+     * 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 <code>Node</code> 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 <code>ItemBuilderVisitor</code> storing the
+         * configuration at and below the given <code>jcrNode</code>.
+         *
+         * @param jcrNode The JCR <code>Node</code> 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 <code>Node</code> 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 <code>Value</code> object to a configuration value of
+     * the corresponding runtime Java type. See the <a
+     * href="#dataTypeConversion">class comment</a> for information on the type
+     * conversion applied.
+     *
+     * @param jcrValue The JCR <code>Value</code> to convert into a
+     *            configuration value object.
+     * @return The configuration value object.
+     * @throws NullPointerException if <code>jcrValue</code> is
+     *             <code>null</code>.
+     */
+    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 <code>Value</code> instance
+     * according to the runtime type of the <code>value</code>. See the <a
+     * href="#dataTypeConversion">class comment</a> for information on the type
+     * conversion applied.
+     *
+     * @param vf The <code>ValueFactory</code> used to create JCR
+     *            <code>Value</code> objects.
+     * @param value The configuration value to convert (export) to a JCR
+     *            <code>Value</code> object.
+     * @return The JCR <code>Value</code> 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 <code>PropertiesNodeConfiguration</code> extends the Apache Commons
+ * <code>PropertiesConfiguration</code> 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 <code>PropertiesNodeConfiguration</code> 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
+     * <code>node</code>. 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 <code>Node</code> 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 <code>Node</code> 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
+     * <code>null</code>.
+     */
+    public javax.jcr.Node getNode() {
+        return delegate.getNode();
+    }
+
+    /**
+     * Sets the <code>Node</code> 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 <code>node</code>. 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.
+     * <p>
+     * If no property can be found using above mentioned algorithm, loading the
+     * configuration fails.
+     *
+     * @param node The <code>Node</code> 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 <code>node</code>. 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 <code>Node</code> 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 <code>RepositoryConfiguration</code> interface extends the
+ * <code>Configuration</code> 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 <code>Node</code> to which this configuration
+     * is attached.
+     */
+    Node getNode();
+
+    /**
+     * Attaches this configuration to the repository <code>Node</code>.
+     *
+     * @param node The <code>Node</code> to which the configuration object
+     *      is attached or <code>null</code> to actually detach the
+     *      configuration from the configuration node.
+     */
+    void setNode(Node node);
+
+    /**
+     * Loads the configuration from the <code>node</code> and child items.
+     *
+     * @throws ConfigurationException if an error occurs during the load
+     *      operation
+     */
+    void load() throws ConfigurationException;
+
+    /**
+     * Loads the configuration from the <code>node</code> and child items.
+     *
+     * @param node The root <code>Node</code> 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.
+     * <p>
+     * Before actually storing any configuration data all properties and child
+     * nodes of the <code>node</code> are removed to be able to write clean
+     * configuration.
+     * <p>
+     * If the node is versionable, the node is checked out (if required) before
+     * saving the configuration and checked in again after saving the
+     * configuration.
+     * <p>
+     * Invariants:
+     * <ul>
+     * <li>If an error occurrs, all modifications must be rolled back and the
+     *      <code>node</code> and all properties and child nodes must remain
+     *      in the former state.
+     * <li>If all goes well, the <code>node</code>, properties and child nodes
+     *      reflect the current state of the configuration and the
+     *      <code>node</code> has been persisted in the repository.
+     * <li>If all goes well and the <code>node</code> is versionable, the
+     *      <code>node</code> is checked in. If an error occurrs it is not
+     *      specified whether the versionable <code>node</code> is checked in
+     *      or not.
+     * </ul>
+     *
+     * @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.
+     * <p>
+     * Before actually storing any configuration data all properties and child
+     * nodes of the <code>node</code> are removed to be able to write clean
+     * configuration.
+     * <p>
+     * If the node is versionable, the node is checked out (if required) before
+     * saving the configuration and checked in again after saving the
+     * configuration.
+     * <p>
+     * Invariants:
+     * <ul>
+     * <li>If an error occurrs, all modifications must be rolled back and the
+     *      <code>node</code> and all properties and child nodes must remain
+     *      in the former state.
+     * <li>If all goes well, the <code>node</code>, properties and child nodes
+     *      reflect the current state of the configuration and the
+     *      <code>node</code> has been persisted in the repository.
+     * <li>If all goes well and the <code>node</code> is versionable, the
+     *      <code>node</code> is checked in. If an error occurrs it is not
+     *      specified whether the versionable <code>node</code> is checked in
+     *      or not.
+     * </ul>
+     *
+     * @param node The root <code>Node</code> 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



Mime
View raw message