You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@rave.apache.org by un...@apache.org on 2012/05/03 14:13:45 UTC

svn commit: r1333418 [1/2] - in /rave/sandbox/content-services/rave-jcr-config: ./ src/main/java/org/apache/rave/jcr/ src/main/java/org/apache/rave/jcr/bootstrapping/ src/main/java/org/apache/rave/jcr/servlet/ src/test/java/org/apache/rave/jcr/bootstra...

Author: unico
Date: Thu May  3 12:13:44 2012
New Revision: 1333418

URL: http://svn.apache.org/viewvc?rev=1333418&view=rev
Log:
RAVE-603 a system bootstrap mechanism based on json module descriptors can import namespaces, node types, content and resources. such items can be reloaded when the version of the module in which they live changes: downstream items are reloaded recursively

Added:
    rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/RaveSystem.java
    rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/System.java
    rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/SystemContext.java
    rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/
    rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/BootstrapException.java
    rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/Module.java
    rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleImporter.java
    rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleRegistry.java
    rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleScanner.java
    rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/servlet/
    rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/servlet/ServletSystemContext.java
    rave/sandbox/content-services/rave-jcr-config/src/test/java/org/apache/rave/jcr/bootstrapping/
    rave/sandbox/content-services/rave-jcr-config/src/test/java/org/apache/rave/jcr/bootstrapping/ModuleRegistryTest.java
    rave/sandbox/content-services/rave-jcr-config/src/test/java/org/apache/rave/jcr/bootstrapping/ModuleScannerTest.java
    rave/sandbox/content-services/rave-jcr-config/src/test/resources/META-INF/
    rave/sandbox/content-services/rave-jcr-config/src/test/resources/META-INF/rave/
    rave/sandbox/content-services/rave-jcr-config/src/test/resources/META-INF/rave/module.json
Modified:
    rave/sandbox/content-services/rave-jcr-config/   (props changed)

Propchange: rave/sandbox/content-services/rave-jcr-config/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Thu May  3 12:13:44 2012
@@ -0,0 +1,2 @@
+rave-jcr-config.iml
+target

Added: rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/RaveSystem.java
URL: http://svn.apache.org/viewvc/rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/RaveSystem.java?rev=1333418&view=auto
==============================================================================
--- rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/RaveSystem.java (added)
+++ rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/RaveSystem.java Thu May  3 12:13:44 2012
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rave.jcr;
+
+import javax.jcr.Repository;
+
+public final class RaveSystem extends System {
+
+    public RaveSystem(final Repository repository, final SystemContext systemContext) {
+        super(repository, systemContext);
+    }
+
+    @Override
+    protected String getSystemNamespacePrefix() {
+        return "rave";
+    }
+
+    @Override
+    protected String getSystemNamespaceUri() {
+        return "http://rave.apache.org/jcr/1.0";
+    }
+
+}

Added: rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/System.java
URL: http://svn.apache.org/viewvc/rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/System.java?rev=1333418&view=auto
==============================================================================
--- rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/System.java (added)
+++ rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/System.java Thu May  3 12:13:44 2012
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rave.jcr;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.rave.jcr.bootstrapping.BootstrapException;
+import org.apache.rave.jcr.bootstrapping.Module;
+import org.apache.rave.jcr.bootstrapping.ModuleImporter;
+import org.apache.rave.jcr.bootstrapping.ModuleRegistry;
+import org.apache.rave.jcr.bootstrapping.ModuleScanner;
+import org.apache.rave.jcr.importing.MimeTypeResolver;
+
+/**
+ * Bundles logic for initializing and bootstrapping a JCR system
+ */
+public abstract class System {
+
+    private final Repository repository;
+    private final SystemContext systemContext;
+
+    public System(final Repository repository, final SystemContext systemContext) {
+        this.repository = repository;
+        this.systemContext = systemContext;
+    }
+
+    public void initialize() throws BootstrapException, RepositoryException {
+        final Session session = getSystemSession();
+        try {
+            try {
+                session.getWorkspace().getNamespaceRegistry().registerNamespace(getSystemNamespacePrefix(), getSystemNamespaceUri());
+            } catch (NamespaceException ignore) {}
+            Node system;
+            if (!session.nodeExists("/" + getSystemNamespacePrefix() + ":system")) {
+                system = session.getRootNode().addNode(getSystemNamespacePrefix() + ":system");
+            } else {
+                system = session.getNode("/" + getSystemNamespacePrefix() + ":system");
+            }
+            if (!system.hasNode("modules")) {
+                system.addNode("modules");
+                session.save();
+            }
+        } finally {
+            session.logout();
+        }
+    }
+
+    public void update() throws BootstrapException, RepositoryException {
+        final Session session = getSystemSession();
+        try {
+            final ModuleScanner moduleScanner = new ModuleScanner(getSystemNamespacePrefix());
+            final ModuleRegistry moduleRegistry = new ModuleRegistry(session, "/" + getSystemNamespacePrefix() + ":system");
+            final Map<String, Module> current = moduleScanner.scan();
+
+            final Map<String, Module> previous = moduleRegistry.readModules();
+
+            final Collection<Module> added = getAddedModules(current, previous);
+            for (Module module : added) {
+                moduleRegistry.writeModule(module);
+            }
+
+            final Map<Module, Module> modified = getModifiedModules(current, previous);
+            for (Map.Entry<Module, Module> entry : modified.entrySet()) {
+                moduleRegistry.updateModule(entry.getKey(), entry.getValue());
+            }
+        } finally {
+            session.logout();
+        }
+    }
+
+    public void bootstrap(boolean reload) throws RepositoryException, BootstrapException {
+        final Session registrySession = getSystemSession();
+        final Session importSession = getSystemSession();
+        try {
+            final ModuleRegistry moduleRegistry = new ModuleRegistry(registrySession, "/" + getSystemNamespacePrefix() + ":system");
+            final Map<String, Module> moduleMap = moduleRegistry.readModules();
+            final ModuleImporter importer = new ModuleImporter(importSession, moduleRegistry, moduleMap.values(), reload, new MimeTypeResolver() {
+                @Override
+                public String getMimeType(final String file) {
+                    return systemContext.getMimeType(file);
+                }
+            });
+            importer.prepare();
+            importer.execute();
+        } finally {
+            registrySession.logout();
+            importSession.logout();
+        }
+    }
+
+    protected abstract String getSystemNamespacePrefix();
+
+    protected abstract String getSystemNamespaceUri();
+
+    protected final Session getSystemSession() throws RepositoryException {
+        return repository.login(new SimpleCredentials("system", new char[]{}));
+    }
+
+    private Collection<Module> getAddedModules(Map<String, Module> current, Map<String, Module> previous) {
+        Collection<Module> added = new ArrayList<Module>();
+        for (String moduleName : current.keySet()) {
+            if (!previous.containsKey(moduleName)) {
+                added.add(current.get(moduleName));
+            }
+        }
+        return added;
+    }
+
+    private Map<Module, Module> getModifiedModules(Map<String, Module> current, Map<String, Module> previous) {
+        Map<Module, Module> modified = new LinkedHashMap<Module, Module>();
+        for (Map.Entry<String, Module> entry : current.entrySet()) {
+            final String moduleName = entry.getKey();
+            final Module currentModule = entry.getValue();
+            if (previous.containsKey(moduleName) && !currentModule.equals(previous.get(moduleName))) {
+                modified.put(currentModule, previous.get(moduleName));
+            }
+        }
+        return modified;
+    }
+
+}

Added: rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/SystemContext.java
URL: http://svn.apache.org/viewvc/rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/SystemContext.java?rev=1333418&view=auto
==============================================================================
--- rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/SystemContext.java (added)
+++ rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/SystemContext.java Thu May  3 12:13:44 2012
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rave.jcr;
+
+public interface SystemContext {
+
+    String getMimeType(String file);
+
+    String getParameter(String name);
+
+}

Added: rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/BootstrapException.java
URL: http://svn.apache.org/viewvc/rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/BootstrapException.java?rev=1333418&view=auto
==============================================================================
--- rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/BootstrapException.java (added)
+++ rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/BootstrapException.java Thu May  3 12:13:44 2012
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rave.jcr.bootstrapping;
+
+/**
+ * Signifies that a problem occurred during bootstrap.
+ */
+public class BootstrapException extends Exception {
+
+    public BootstrapException(final String message) {
+        super(message);
+    }
+
+    public BootstrapException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+}

Added: rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/Module.java
URL: http://svn.apache.org/viewvc/rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/Module.java?rev=1333418&view=auto
==============================================================================
--- rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/Module.java (added)
+++ rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/Module.java Thu May  3 12:13:44 2012
@@ -0,0 +1,609 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rave.jcr.bootstrapping;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.rave.jcr.importing.ImportBehavior;
+
+/**
+ * Represents a content module descriptor. Module descriptors are json files that declare
+ * namespaces, node types, content, and resources to be imported.
+ *
+ * <p>A module descriptor must have a name that is unique in the application.</p>
+ *
+ * <p>A module descriptor may specify a version number.
+ * When a version is specified for a module, module items except namespaces are reloaded when
+ * a new version is detected, unless the item explicitly declares it is not to be reloaded.</p>
+ *
+ * <p>A module descriptor may specify a list of modules it depends on.
+ * Items in those modules will be loaded before items of the same type in this module.
+ * This is for instance necessary when a cnd declares a node type that extends a node type
+ * that is defined by a cnd in another module, or when a content item want to import content
+ * to a node that is imported by another module.
+ * </p>
+ *
+ * <pre>
+ * {
+ *     "name" : "rave-ocm-demo",
+ *     "version" : 3,
+ *     "dependencies" : [ "rave-ocm-core" ],
+ *     "namespaces" : {
+ *       "ocmdemo" : "http://rave.apache.org/jcr/ocmdemo"
+ *     },
+ *     "cnds" : {
+ *       "ocmdemo" : {
+ *         "file" : "ocmdemo.cnd",
+ *         "reload" : false
+ *       }
+ *     },
+ *     "contents" : {
+ *       "ocmdemo" : {
+ *         "file" : "ocmdemo.json"
+ *         "parent" : "/",
+ *         "importBehavior" : "replace"
+ *       }
+ *     },
+ *     "resources" : {
+ *       "ocmdemo" : {
+ *         "path" : "resources",
+ *         "parent" : "/"
+ *       }
+ *     }
+ * }
+ * </pre>
+ *
+ * For the compact node type definition syntax see http://jackrabbit.apache.org/node-type-notation.html<br/>
+ * For the json content definition syntax see {@link org.apache.rave.jcr.importing.ContentImporter}<br/>
+ * <p>
+ * Resources are directory or file entries in the jar that are to be imported as nt:folder and nt:file nodes.
+ * This is done recursively in the case of directory entries. File names are taken as node names.
+ * </p>
+ */
+public final class Module {
+
+    enum Status {
+        EXISTING, NEW, REMOVED, GRADED, FAILED
+    }
+
+    private final String name;
+    private final String baseUrl;
+    private final long version;
+
+    private Status status;
+
+    private List<String> dependencies;
+    private List<Namespace> namespaces;
+    private List<Cnd> cnds;
+    private List<Content> contents;
+    private List<Resource> resources;
+
+    public Module(final String name, final String baseUrl, final long version) {
+        this.name = name;
+        this.baseUrl = baseUrl;
+        this.version = version;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public long getVersion() {
+        return version;
+    }
+
+    String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public Status getStatus() {
+        return status;
+    }
+
+    void setStatus(Status status) {
+        this.status = status;
+    }
+
+    public List<String> getDependencies() {
+        if (dependencies == null) {
+            return Collections.emptyList();
+        }
+        return dependencies;
+    }
+
+    void addDependency(String dependency) {
+        if (dependencies == null) {
+            dependencies = new ArrayList<String>();
+        }
+        dependencies.add(dependency);
+    }
+
+    public List<Namespace> getNamespaces() {
+        if (namespaces == null) {
+            return Collections.emptyList();
+        }
+        return namespaces;
+    }
+
+    void addNamespace(String prefix, String uri, Status status) {
+        if (namespaces == null) {
+            namespaces = new ArrayList<Namespace>();
+        }
+        namespaces.add(new Namespace(prefix, uri, status));
+    }
+
+    public List<Cnd> getCnds() {
+        if (cnds == null) {
+            return Collections.emptyList();
+        }
+        return cnds;
+    }
+
+    void addCnd(String name, String file, boolean reload, Status status) {
+        if (cnds == null) {
+            cnds = new ArrayList<Cnd>();
+        }
+        cnds.add(new Cnd(name, file, reload, status));
+    }
+
+    public List<Content> getContents() {
+        if (contents == null) {
+            return Collections.emptyList();
+        }
+        return contents;
+    }
+
+    void addContent(String name, String file, String parent, boolean reload, ImportBehavior importBehavior, Status status) {
+        if (contents == null) {
+            contents = new ArrayList<Content>();
+        }
+        contents.add(new Content(name, file, parent, reload, importBehavior, status));
+    }
+
+    public List<Resource> getResources() {
+        if (resources == null) {
+            return Collections.emptyList();
+        }
+        return resources;
+    }
+
+    void addResource(String name, String path, String parent, boolean reload, ImportBehavior importBehavior, Status status) {
+        if (resources == null) {
+            resources = new ArrayList<Resource>();
+        }
+        resources.add(new Resource(name, path, parent, reload, importBehavior, status));
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null) {
+            return false;
+        }
+        if (getClass() != o.getClass()) {
+            return false;
+        }
+
+        final Module module = (Module) o;
+
+        if (!name.equals(module.name)) {
+            return false;
+        }
+        if (!baseUrl.equals(module.baseUrl)) {
+            return false;
+        }
+        if (version != module.version) {
+            return false;
+        }
+        if (cnds != null ? !cnds.equals(module.cnds) : module.cnds != null) {
+            return false;
+        }
+        if (contents != null ? !contents.equals(module.contents) : module.contents != null) {
+            return false;
+        }
+        if (dependencies != null ? !dependencies.equals(module.dependencies) : module.dependencies != null) {
+            return false;
+        }
+        if (namespaces != null ? !namespaces.equals(module.namespaces) : module.namespaces != null) {
+            return false;
+        }
+        if (resources != null ? !resources.equals(module.resources) : module.resources != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = name.hashCode();
+        result = 31 * result + baseUrl.hashCode();
+        result = 31 * result + (int) (version ^ (version >>> 32));
+        result = 31 * result + (dependencies != null ? dependencies.hashCode() : 0);
+        result = 31 * result + (namespaces != null ? namespaces.hashCode() : 0);
+        result = 31 * result + (cnds != null ? cnds.hashCode() : 0);
+        result = 31 * result + (contents != null ? contents.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "Module{" +
+                "name='" + name + '\'' +
+                ", baseUrl='" + baseUrl + '\'' +
+                ", version=" + version +
+                ", dependencies=" + dependencies +
+                ", namespaces=" + namespaces +
+                ", cnds=" + cnds +
+                ", contents=" + contents +
+                '}';
+    }
+
+    public abstract class Item {
+
+        private final String name;
+        private Status status;
+
+        private Item(String name, Status status) {
+            this.name = name;
+            this.status = status;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public Status getStatus() {
+            return status;
+        }
+
+        void setStatus(Status status) {
+            this.status = status;
+        }
+
+        public boolean isReload() {
+            return false;
+        }
+
+        Module getModule() {
+            return Module.this;
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            final Item item = (Item) o;
+
+            return name.equals(item.name) && getModule().getName().equals(item.getModule().getName());
+
+        }
+
+        @Override
+        public int hashCode() {
+            return name.hashCode();
+        }
+    }
+
+    public final class Namespace extends Item {
+
+        private final String uri;
+
+        private Namespace(String prefix, String uri, Status status) {
+            super(prefix, status);
+            this.uri = uri;
+        }
+
+        public String getUri() {
+            return uri;
+        }
+
+        public String getPrefix() {
+            return getName();
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null) {
+                return false;
+            }
+            if (o.getClass() != getClass()) {
+                return false;
+            }
+            if (!super.equals(o)) {
+                return false;
+            }
+
+            final Namespace namespace = (Namespace) o;
+
+            return uri.equals(namespace.uri);
+
+        }
+
+        @Override
+        public int hashCode() {
+            int result = getName().hashCode();
+            result = 31 * result + uri.hashCode();
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "Namespace{" +
+                    "name='" + getName() + '\'' +
+                    "prefix='" + getPrefix() + '\'' +
+                    "status='" + getStatus() + '\'' +
+                    ", uri='" + uri + '\'' +
+                    '}';
+        }
+    }
+
+    public final class Cnd extends Item {
+
+        private final String file;
+        private final boolean reload;
+
+        private Cnd(String name, String file, boolean reload, Status status) {
+            super(name, status);
+            this.file = file;
+            this.reload = reload;
+        }
+
+        public String getFile() {
+            return file;
+        }
+
+        public boolean isReload() {
+            return reload;
+        }
+
+        URL getURL() throws MalformedURLException {
+            return new URL((baseUrl.endsWith("/") ? baseUrl : baseUrl + "/") + file);
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null) {
+                return false;
+            }
+            if (getClass() != o.getClass()) {
+                return false;
+            }
+            if (!super.equals(o)) {
+                return false;
+            }
+
+            final Cnd cnd = (Cnd) o;
+
+            return reload == cnd.reload && file.equals(cnd.file);
+
+        }
+
+        @Override
+        public int hashCode() {
+            int result = getName().hashCode();
+            result = 31 * result + file.hashCode();
+            result = 31 * result + (reload ? 1 : 0);
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "Cnd{" +
+                    "name='" + getName() + '\'' +
+                    "status='" + getStatus() + '\'' +
+                    "file='" + file + '\'' +
+                    ", reload=" + reload +
+                    '}';
+        }
+    }
+
+    public final class Content extends Item {
+
+        private final String file;
+        private final String parent;
+        private final boolean reload;
+        private ImportBehavior importBehavior;
+
+        private Content(String name, String file, String parent, boolean reload, ImportBehavior importBehavior, Status status) {
+            super(name, status);
+            this.file = file;
+            this.parent = parent;
+            this.reload = reload;
+            this.importBehavior = importBehavior;
+        }
+
+        public String getFile() {
+            return file;
+        }
+
+        public String getParent() {
+            return parent;
+        }
+
+        String getRoot() {
+            return parent + "/" + getName();
+        }
+
+        public boolean isReload() {
+            return reload;
+        }
+
+        URL getURL() throws MalformedURLException {
+            return new URL((baseUrl.endsWith("/") ? baseUrl : baseUrl + "/") + file);
+        }
+
+        public ImportBehavior getImportBehavior() {
+            return importBehavior;
+        }
+
+        void setImportBehavior(ImportBehavior importBehavior) {
+            this.importBehavior = importBehavior;
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null) {
+                return false;
+            }
+            if (getClass() != o.getClass()) {
+                return false;
+            }
+            if (!super.equals(o)) {
+                return false;
+            }
+
+            final Content content = (Content) o;
+
+            if (reload != content.reload) {
+                return false;
+            }
+            if (!file.equals(content.file)) {
+                return false;
+            }
+            if (importBehavior != null ? !importBehavior.equals(content.importBehavior) : content.importBehavior != null) {
+                return false;
+            }
+            return parent.equals(content.parent);
+
+        }
+
+        @Override
+        public int hashCode() {
+            int result = getName().hashCode();
+            result = 31 * result + file.hashCode();
+            result = 31 * result + parent.hashCode();
+            result = 31 * result + (reload ? 1 : 0);
+            result = importBehavior == null ? result : 31 * result + importBehavior.hashCode();
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "Content{" +
+                    "name='" + getName() + '\'' +
+                    "status='" + getStatus() + '\'' +
+                    "file='" + file + '\'' +
+                    ", parent='" + parent + '\'' +
+                    ", reload=" + reload +
+                    ", importBehavior=" + importBehavior +
+                    '}';
+        }
+    }
+
+    public final class Resource extends Item {
+
+        private final String path;
+        private final String parent;
+        private final boolean reload;
+        private ImportBehavior importBehavior;
+
+        private Resource(String name, String path, String parent, boolean reload, ImportBehavior importBehavior, Status status) {
+            super(name, status);
+            this.path = path;
+            this.parent = parent;
+            this.reload = reload;
+            this.importBehavior = importBehavior;
+        }
+
+        public String getPath() {
+            return path;
+        }
+
+        public String getParent() {
+            return parent;
+        }
+
+        public ImportBehavior getImportBehavior() {
+            return importBehavior;
+        }
+
+        void setImportBehavior(ImportBehavior importBehavior) {
+            this.importBehavior = importBehavior;
+        }
+
+        URL getJarURL() throws MalformedURLException {
+            if (baseUrl.startsWith("jar:")) {
+                return new URL(baseUrl.substring(4, baseUrl.length()-2));
+            }
+            return null;
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null) {
+                return false;
+            }
+            if (getClass() != o.getClass()) {
+                return false;
+            }
+            if (!super.equals(o)) {
+                return false;
+            }
+
+            final Resource resource = (Resource) o;
+
+            if (reload != resource.reload) {
+                return false;
+            }
+            if (!parent.equals(resource.parent)) {
+                return false;
+            }
+            if (importBehavior != null ? !importBehavior.equals(resource.importBehavior) : resource.importBehavior != null) {
+                return false;
+            }
+            return path.equals(resource.path);
+
+        }
+
+        @Override
+        public int hashCode() {
+            int result = super.hashCode();
+            result = 31 * result + (path != null ? path.hashCode() : 0);
+            result = 31 * result + parent.hashCode();
+            result = 31 * result + (reload ? 1 : 0);
+            result = importBehavior == null ? result : 31 * result + importBehavior.hashCode();
+            return result;
+        }
+    }
+
+}

Added: rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleImporter.java
URL: http://svn.apache.org/viewvc/rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleImporter.java?rev=1333418&view=auto
==============================================================================
--- rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleImporter.java (added)
+++ rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleImporter.java Thu May  3 12:13:44 2012
@@ -0,0 +1,376 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rave.jcr.bootstrapping;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.commons.cnd.CndImporter;
+import org.apache.jackrabbit.commons.cnd.ParseException;
+import org.apache.rave.jcr.importing.ContentImporter;
+import org.apache.rave.jcr.importing.ImportBehavior;
+import org.apache.rave.jcr.importing.ImportException;
+import org.apache.rave.jcr.importing.MimeTypeResolver;
+import org.apache.rave.jcr.importing.ResourceImporter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Scans a list of modules to be imported for new or stale items and imports them in the correct order.
+ * When an item is to be reloaded, any downstream items are reloaded as well (only applies to content items).
+ */
+public final class ModuleImporter {
+
+    private static final Logger log = LoggerFactory.getLogger(ModuleImporter.class);
+
+    private final Session session;
+    private final ModuleRegistry moduleRegistry;
+    private final Collection<Module> modules;
+    private final boolean reload;
+    private final MimeTypeResolver mimeTypeResolver;
+
+    private final List<Module.Namespace> namespaces = new ArrayList<Module.Namespace>();
+    private final List<Module.Cnd> cnds = new ArrayList<Module.Cnd>();
+    private final List<Module.Content> contents = new ArrayList<Module.Content>();
+    private final List<Module.Resource> resources = new ArrayList<Module.Resource>();
+
+    public ModuleImporter(Session session, ModuleRegistry moduleRegistry, Collection<Module> modules, boolean reload, MimeTypeResolver mimeTypeResolver) {
+        this.session = session;
+        this.moduleRegistry = moduleRegistry;
+        this.modules = modules;
+        this.reload = reload;
+        this.mimeTypeResolver = mimeTypeResolver;
+    }
+
+    public void prepare() throws RepositoryException {
+
+        log.info("Preparing for import");
+
+        final Collection<Module.Content> reloadContents = new ArrayList<Module.Content>();
+        for (final Module module : modules) {
+            for (final Module.Namespace namespace : module.getNamespaces()) {
+                if (namespace.getStatus() == Module.Status.NEW) {
+                    namespaces.add(namespace);
+                }
+            }
+            for (final Module.Cnd cnd : module.getCnds()) {
+                if (cnd.getStatus() == Module.Status.NEW || cnd.getStatus() == Module.Status.FAILED) {
+                    cnds.add(cnd);
+                } else if (cnd.getModule().getStatus() == Module.Status.GRADED) {
+                    if (cnd.isReload() && cnd.getStatus() != Module.Status.REMOVED) {
+                        cnds.add(cnd);
+                    }
+                }
+            }
+            for (final Module.Content content : module.getContents()) {
+                if (content.getStatus() == Module.Status.NEW || content.getStatus() == Module.Status.FAILED) {
+                    contents.add(content);
+                } else if (content.getModule().getStatus() == Module.Status.GRADED) {
+                    if (content.isReload() && content.getStatus() != Module.Status.REMOVED) {
+                        if (content.getImportBehavior() == ImportBehavior.MERGE) {
+                            // there is currently no reload support for the case where the content item is expected
+                            // to merge with existing content
+                            // adding that would probably require a reload of all upstream items and by extension their downstream
+                            // counterparts, and that would be the whole content tree
+                            log.info("Reloading content that is expected to merge with existing content is not supported");
+                        } else if (content.getImportBehavior() == ImportBehavior.SKIP) {
+                            // can't tell if content was previously imported or skipped, can't reload either
+                            log.info("Reloading content that might have been skipped is not supported");
+                        } else {
+                            contents.add(content);
+                            reloadContents.add(content);
+                            content.setImportBehavior(ImportBehavior.REPLACE);
+                        }
+                    }
+                }
+            }
+            for (Module.Resource resource : module.getResources()) {
+                if (resource.getStatus() == Module.Status.NEW || resource.getStatus() == Module.Status.FAILED) {
+                    resources.add(resource);
+                } else if (resource.getModule().getStatus() == Module.Status.GRADED) {
+                    if (resource.isReload() && resource.getStatus() != Module.Status.REMOVED) {
+                        resources.add(resource);
+                    }
+                }
+            }
+
+        }
+
+        if (!reloadContents.isEmpty()) {
+            final Map<String, Module> allModules = moduleRegistry.readModules();
+            for (final Module.Content content : reloadContents) {
+                for (final Module.Content downstream : getDownstreamContentItems(content, allModules.values())) {
+                    if (!contents.contains(downstream)) {
+                        contents.add(downstream);
+                    }
+                }
+            }
+        }
+
+
+        Collections.sort(cnds, CndComparator.INSTANCE);
+        Collections.sort(contents, ContentComparator.INSTANCE);
+
+    }
+
+    public void execute() throws RepositoryException {
+        log.info("Importing modules " + (reload ? "with" : "without") + " reload enabled");
+        registerNamespaces();
+        registerNodeTypes();
+        importContent();
+        importResources();
+    }
+
+    public List<Module.Namespace> getNamespaces() {
+        return namespaces;
+    }
+
+    public List<Module.Cnd> getCnds() {
+        return cnds;
+    }
+
+    public List<Module.Content> getContents() {
+        return contents;
+    }
+
+    private Collection<Module.Content> getDownstreamContentItems(final Module.Content content, final Collection<Module> allModules) {
+        final Collection<Module.Content> downstreamContentItems = new ArrayList<Module.Content>();
+        for (final Module module : allModules) {
+            for (final Module.Content downstream : module.getContents()) {
+                if (!downstream.equals(content) && downstream.getRoot().startsWith(content.getRoot())) {
+                    downstreamContentItems.add(downstream);
+                }
+            }
+        }
+        return downstreamContentItems;
+    }
+
+    private void registerNamespaces() throws RepositoryException {
+        if (namespaces.isEmpty()) {
+            log.info("No namespaces to register");
+            return;
+        }
+        final NamespaceRegistry namespaceRegistry = session.getWorkspace().getNamespaceRegistry();
+        for (Module.Namespace namespace : namespaces) {
+            final Module module = namespace.getModule();
+            log.info("Registering namespace " + namespace.getPrefix() + " = " + namespace.getUri() + " in module " + module.getName());
+            try {
+                final String existingUri = namespaceRegistry.getURI(namespace.getPrefix());
+                log.warn("Prefix '" + namespace.getPrefix() + "' is already mapped to uri " + existingUri);
+                if (!existingUri.equals(namespace.getUri())) {
+                    updateItemStatus(namespace, Module.Status.FAILED);
+                } else {
+                    updateItemStatus(namespace, Module.Status.EXISTING);
+                }
+            } catch (NamespaceException expected) {
+                try {
+                    namespaceRegistry.registerNamespace(namespace.getPrefix(), namespace.getUri());
+                    updateItemStatus(namespace, Module.Status.EXISTING);
+                } catch (NamespaceException e) {
+                    log.error("Failed to register namespace " + namespace.getPrefix() + "=" + namespace.getUri(), e);
+                    updateItemStatus(namespace, Module.Status.FAILED);
+                } catch (RepositoryException e) {
+                    log.error("Failed to register namespace " + namespace.getPrefix() + "=" + namespace.getUri(), e);
+                    updateItemStatus(namespace, Module.Status.FAILED);
+                }
+            } catch (RepositoryException e) {
+                log.error("Failed to register namespace " + namespace.getPrefix() + "=" + namespace.getUri(), e);
+                updateItemStatus(namespace, Module.Status.FAILED);
+            }
+        }
+    }
+
+    private void registerNodeTypes() {
+        if (cnds.isEmpty()) {
+            log.info("No node types to register");
+            return;
+        }
+        for (Module.Cnd cnd : cnds) {
+            final Module module = cnd.getModule();
+            final boolean reregister = cnd.getStatus() != Module.Status.NEW;
+            log.info((reregister ? "Rer" : "R") + "egistering node types from " + cnd.getFile() + " in module " + module.getName());
+            try {
+                CndImporter.registerNodeTypes(new InputStreamReader(cnd.getURL().openStream()), session, reregister);
+                updateItemStatus(cnd, Module.Status.EXISTING);
+            } catch (ParseException e) {
+                log.error("Failed to parse cnd " + cnd.getFile() + " in module " + module.getName(), e);
+                updateItemStatus(cnd, Module.Status.FAILED);
+            } catch (IOException e) {
+                log.error("Failed to read cnd " + cnd.getFile() + " in module " + module.getName(), e);
+                updateItemStatus(cnd, Module.Status.FAILED);
+            } catch (RepositoryException e) {
+                log.error("Failed to import cnd " + cnd.getFile() + " in module " + module.getName(), e);
+                updateItemStatus(cnd, Module.Status.FAILED);
+            }
+        }
+    }
+
+    private void importContent() throws RepositoryException {
+        if (contents.isEmpty()) {
+            log.info("No content to import");
+            return;
+        }
+        final ContentImporter contentImporter = new ContentImporter(session);
+        for (Module.Content content : contents) {
+            final Module module = content.getModule();
+            if (!session.nodeExists(content.getParent())) {
+                log.error("Cannot import content from " + content.getFile() + " in module " + module.getName()
+                        + ": parent " + content.getParent() + " does not exist");
+                updateItemStatus(content, Module.Status.FAILED);
+                continue;
+            }
+            InputStream is = null;
+            try {
+                is = content.getURL().openStream();
+                log.info("Importing content from " + content.getFile() + " in module " + module.getName());
+                contentImporter.importContent(content.getParent(), content.getName(), is, content.getImportBehavior());
+                session.save();
+                updateItemStatus(content, Module.Status.EXISTING);
+            } catch (IOException e) {
+                log.error("Failed to read content " + content.getFile() + " in module " + module.getName(), e);
+                updateItemStatus(content, Module.Status.FAILED);
+            } catch (ImportException e) {
+                log.error("Failed to import content " + content.getFile() + " from module " + module.getName() + ": " + e.getMessage());
+                updateItemStatus(content, Module.Status.FAILED);
+            } catch (RepositoryException e) {
+                log.error("Failed to import content " + content.getFile() + " from module " + module.getName(), e);
+                updateItemStatus(content, Module.Status.FAILED);
+            } finally {
+                if (is != null) {
+                    try { is.close(); } catch (IOException ignore) {}
+                }
+            }
+        }
+    }
+
+    private void importResources() throws RepositoryException {
+        if (resources.isEmpty()) {
+            log.info("No resources to import");
+            return;
+        }
+        final ResourceImporter resourceImporter = new ResourceImporter(session, mimeTypeResolver);
+        for (Module.Resource resource : resources) {
+            final Module module = resource.getModule();
+            if (!session.nodeExists(resource.getParent())) {
+                log.error("Cannot import resources " + resource.getName() + " in module " + module.getName() +
+                        ": parent " + resource.getParent() + " does not exist");
+                updateItemStatus(resource, Module.Status.FAILED);
+                continue;
+            }
+            InputStream is = null;
+            try {
+                final URL jarURL = resource.getJarURL();
+                if (jarURL == null) {
+                    log.error("Module does not seem to be packaged as a jar but resources can only be imported from jar file");
+                    continue;
+                }
+                is = jarURL.openStream();
+                log.info("Importing resources " + resource.getName() + " in module " + module.getName());
+                resourceImporter.importResource(resource.getParent(), resource.getPath(), is, resource.getImportBehavior());
+                session.save();
+                updateItemStatus(resource, Module.Status.EXISTING);
+            } catch (IOException e) {
+                log.error("Failed to read resources in module " + module.getName(), e);
+            } catch (ImportException e) {
+                log.error("Failed import resources from module " + module.getName() + ": " + e.getMessage());
+            } finally {
+                if (is != null) {
+                    try { is.close(); } catch (IOException ignore) {}
+                }
+            }
+        }
+    }
+
+    private void updateItemStatus(Module.Item item, Module.Status status) {
+        item.setStatus(status);
+        try {
+            moduleRegistry.updateItemStatus(item);
+        } catch (RepositoryException e) {
+            log.error("Failed to update item status: " + e.getClass().getName() + ": " + e.getMessage());
+        }
+    }
+
+    private static class CndComparator implements Comparator<Module.Cnd> {
+
+        private static final CndComparator INSTANCE = new CndComparator();
+
+        @Override
+        public int compare(final Module.Cnd cnd1, final Module.Cnd cnd2) {
+            if (cnd1.getModule() == cnd2.getModule()) {
+                final List<Module.Cnd> cnds = cnd1.getModule().getCnds();
+                return cnds.indexOf(cnd1) - cnds.indexOf(cnd2);
+            }
+            if (cnd1.getModule().getDependencies().contains(cnd2.getModule().getName())) {
+                return 1;
+            }
+            if (cnd2.getModule().getDependencies().contains(cnd1.getModule().getName())) {
+                return -1;
+            }
+            return 0;
+        }
+    }
+
+    private static class ContentComparator implements Comparator<Module.Content> {
+
+        private static final ContentComparator INSTANCE = new ContentComparator();
+
+        @Override
+        public int compare(final Module.Content c1, final Module.Content c2) {
+            if (c1.getModule() == c2.getModule()) {
+                final List<Module.Content> contents = c1.getModule().getContents();
+                return contents.indexOf(c1) - contents.indexOf(c2);
+            }
+            if (c1.getModule().getDependencies().contains(c2.getModule().getName())) {
+                return 1;
+            }
+            if (c2.getModule().getDependencies().contains(c1.getModule().getName())) {
+                return -1;
+            }
+            if (c1.getRoot().equals(c2.getRoot())) {
+                if (c1.getImportBehavior() == ImportBehavior.MERGE && c2.getImportBehavior() != ImportBehavior.MERGE) {
+                    return 1;
+                }
+                if (c2.getImportBehavior() == ImportBehavior.MERGE && c1.getImportBehavior() != ImportBehavior.MERGE) {
+                    return -1;
+                }
+                return 0;
+            }
+            if (c1.getRoot().startsWith(c2.getRoot())) {
+                return 1;
+            }
+            if (c2.getRoot().startsWith(c1.getRoot())) {
+                return -1;
+            }
+            return 0;
+        }
+    }
+}

Added: rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleRegistry.java
URL: http://svn.apache.org/viewvc/rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleRegistry.java?rev=1333418&view=auto
==============================================================================
--- rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleRegistry.java (added)
+++ rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleRegistry.java Thu May  3 12:13:44 2012
@@ -0,0 +1,466 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rave.jcr.bootstrapping;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+
+import org.apache.rave.jcr.importing.ImportBehavior;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Stores and retrieves module descriptors in and from the repository. This allows operational information about modules
+ * to be stored and updated such as version numbers which are needed to determine if a module is to be reloaded, and
+ * status information about whether a module was imported successfully or not.
+ */
+public final class ModuleRegistry {
+
+    private static final Logger log = LoggerFactory.getLogger(ModuleRegistry.class);
+
+    private static final String MODULES = "modules";
+    private static final String DEPENDENCIES = "dependencies";
+    private static final String NAMESPACES = "namespaces";
+    private static final String CNDS = "cnds";
+    private static final String CONTENTS = "contents";
+    private static final String RESOURCES = "resources";
+    private static final String VERSION = "version";
+    private static final String BASEURL = "baseUrl";
+    private static final String STATUS = "status";
+    private static final String FILE = "file";
+    private static final String RELOAD = "reload";
+    private static final String PARENT = "parent";
+    private static final String URI = "uri";
+    private static final String PATH = "path";
+    private static final String IMPORTBEHAVIOR = "importBehavior";
+
+    private final Session session;
+    private final String modulesPath;
+    /** Modules cache */
+    private Map<String, Module> modules;
+
+    public ModuleRegistry(final Session session, final String systemPath) {
+        this.session = session;
+        modulesPath = systemPath + "/" + MODULES;
+    }
+
+    public void updateItemStatus(Module.Item item) throws RepositoryException {
+        final Node modules = session.getNode(modulesPath);
+        final Node moduleNode = modules.getNode(item.getModule().getName());
+        Node itemsNode = null;
+        if (item instanceof Module.Namespace) {
+            itemsNode = moduleNode.getNode(NAMESPACES);
+        }
+        if (item instanceof Module.Cnd) {
+            itemsNode = moduleNode.getNode(CNDS);
+        }
+        if (item instanceof Module.Content) {
+            itemsNode = moduleNode.getNode(CONTENTS);
+        }
+        if (itemsNode != null) {
+            try {
+                final Node itemNode = itemsNode.getNode(item.getName());
+                itemNode.setProperty(STATUS, item.getStatus().toString());
+                session.save();
+            } finally {
+                session.refresh(false);
+            }
+        }
+    }
+
+    public void updateModule(Module current, Module previous) throws RepositoryException {
+        log.info("Updating module '" + current.getName() + "'");
+
+        final Node modulesNode = session.getNode(modulesPath);
+        final Node node = modulesNode.getNode(current.getName());
+
+        try {
+            if (current.getVersion() != previous.getVersion()) {
+                node.setProperty(VERSION, current.getVersion());
+                setStatusProperty(node, Module.Status.GRADED);
+                current.setStatus(Module.Status.GRADED);
+            }
+
+            if (current.getBaseUrl().equals(previous.getBaseUrl())) {
+                node.setProperty(BASEURL, current.getBaseUrl());
+            }
+
+            if (!current.getDependencies().equals(previous.getDependencies())) {
+                if (current.getDependencies().isEmpty()) {
+                    node.getProperty(DEPENDENCIES).remove();
+                } else {
+                    setDependencies(node, current);
+                }
+            }
+
+            if (!current.getNamespaces().equals(previous.getNamespaces())) {
+                final Node namespacesNode = getOrCreateNode(node, NAMESPACES);
+                for (Module.Namespace namespace : getRemoved(current.getNamespaces(), previous.getNamespaces())) {
+                    setStatusProperty(namespacesNode.getNode(namespace.getName()), Module.Status.REMOVED);
+                    namespace.setStatus(Module.Status.REMOVED);
+                }
+                for (Module.Namespace namespace : getModified(current.getNamespaces(), previous.getNamespaces())) {
+                    final Node namespaceNode = namespacesNode.getNode(namespace.getName());
+                    setNamespaceProperties(namespaceNode, namespace);
+                }
+                for (Module.Namespace namespace : getAdded(current.getNamespaces(), previous.getNamespaces())) {
+                    final Node namespaceNode = namespacesNode.addNode(namespace.getName());
+                    setNamespaceProperties(namespaceNode, namespace);
+                    setStatusProperty(namespaceNode, Module.Status.NEW);
+                    namespace.setStatus(Module.Status.NEW);
+                }
+            }
+
+            if (!current.getCnds().equals(previous.getCnds())) {
+                final Node cndsNode = getOrCreateNode(node, CNDS);
+                for (Module.Cnd cnd : getRemoved(current.getCnds(), previous.getCnds())) {
+                    setStatusProperty(cndsNode.getNode(cnd.getName()), Module.Status.REMOVED);
+                    cnd.setStatus(Module.Status.REMOVED);
+                }
+                for (Module.Cnd cnd : getModified(current.getCnds(), previous.getCnds())) {
+                    final Node cndNode = cndsNode.getNode(cnd.getName());
+                    setCndProperties(cndNode, cnd);
+                }
+                for (Module.Cnd cnd : getAdded(current.getCnds(), previous.getCnds())) {
+                    final Node cndNode = cndsNode.addNode(cnd.getName());
+                    setCndProperties(cndNode, cnd);
+                    setStatusProperty(cndNode, Module.Status.NEW);
+                    cnd.setStatus(Module.Status.NEW);
+                }
+            }
+
+            if (!current.getContents().equals(previous.getContents())) {
+                final Node contentsNode = getOrCreateNode(node, CONTENTS);
+                for (Module.Content content : getRemoved(current.getContents(), previous.getContents())) {
+                    setStatusProperty(contentsNode.getNode(content.getName()), Module.Status.REMOVED);
+                    content.setStatus(Module.Status.REMOVED);
+                }
+                for (Module.Content content : getModified(current.getContents(), previous.getContents())) {
+                    final Node contentNode = contentsNode.getNode(content.getName());
+                    setContentProperties(contentNode, content);
+                }
+                for (Module.Content content : getAdded(current.getContents(), previous.getContents())) {
+                    final Node contentNode = contentsNode.getNode(content.getName());
+                    setContentProperties(contentNode, content);
+                    setStatusProperty(contentNode, Module.Status.NEW);
+                    content.setStatus(Module.Status.NEW);
+                }
+            }
+
+            if (!current.getResources().equals(previous.getResources())) {
+                final Node resourcesNode = getOrCreateNode(node, RESOURCES);
+                for (Module.Resource resource : getRemoved(current.getResources(), previous.getResources())) {
+                    setStatusProperty(resourcesNode.getNode(resource.getName()), Module.Status.REMOVED);
+                    resource.setStatus(Module.Status.REMOVED);
+                }
+                for (Module.Resource resource : getModified(current.getResources(), previous.getResources())) {
+                    final Node resourceNode = resourcesNode.getNode(resource.getName());
+                    setResourceProperties(resourceNode, resource);
+                }
+                for (Module.Resource resource : getAdded(current.getResources(), previous.getResources())) {
+                    final Node resourceNode = resourcesNode.getNode(resource.getName());
+                    setResourceProperties(resourceNode, resource);
+                    setStatusProperty(resourceNode, Module.Status.NEW);
+                    resource.setStatus(Module.Status.NEW);
+                }
+            }
+
+            session.save();
+        } finally {
+            session.refresh(false);
+        }
+
+        if (modules != null) {
+            modules.put(current.getName(), current);
+        }
+    }
+
+    public void writeModule(Module module) throws RepositoryException {
+        log.info("Registering new module '" + module.getName() + "'");
+
+        final Node modulesNode = session.getNode(modulesPath);
+        final Node node = getOrCreateNode(modulesNode, module.getName());
+
+        try {
+            node.setProperty(VERSION, module.getVersion());
+            node.setProperty(BASEURL, module.getBaseUrl());
+            node.setProperty(STATUS, Module.Status.NEW.toString());
+
+            if (!module.getDependencies().isEmpty()) {
+                setDependencies(node, module);
+            }
+
+            if (!module.getNamespaces().isEmpty()) {
+                final Node namespacesNode = getOrCreateNode(node, NAMESPACES);
+                for (Module.Namespace namespace : module.getNamespaces()) {
+                    if (!namespacesNode.hasNode(namespace.getName())) {
+                        final Node namespaceNode = namespacesNode.addNode(namespace.getName());
+                        setNamespaceProperties(namespaceNode, namespace);
+                        setStatusProperty(namespaceNode, Module.Status.NEW);
+                        namespace.setStatus(Module.Status.NEW);
+                    }
+                }
+            }
+
+            if (!module.getCnds().isEmpty()) {
+                final Node cndsNode = getOrCreateNode(node, CNDS);
+                for (Module.Cnd cnd : module.getCnds()) {
+                    Node cndNode = getOrCreateNode(cndsNode, cnd.getName());
+                    setCndProperties(cndNode, cnd);
+                    setStatusProperty(cndNode, Module.Status.NEW);
+                    cnd.setStatus(Module.Status.NEW);
+                }
+            }
+
+            if (!module.getContents().isEmpty()) {
+                final Node contentsNode = getOrCreateNode(node, CONTENTS);
+                for (Module.Content content : module.getContents()) {
+                    final Node contentNode = getOrCreateNode(contentsNode, content.getName());
+                    setContentProperties(contentNode, content);
+                    setStatusProperty(contentNode, Module.Status.NEW);
+                    content.setStatus(Module.Status.NEW);
+                }
+            }
+
+            if (!module.getResources().isEmpty()) {
+                final Node resourcesNode = getOrCreateNode(node, RESOURCES);
+                for (Module.Resource resource : module.getResources()) {
+                    final Node resourceNode = getOrCreateNode(resourcesNode, resource.getName());
+                    setResourceProperties(resourceNode, resource);
+                    setStatusProperty(resourceNode, Module.Status.NEW);
+                    resource.setStatus(Module.Status.NEW);
+                }
+            }
+
+            session.save();
+        } finally {
+            session.refresh(false);
+        }
+        if (modules != null) {
+            modules.put(module.getName(), module);
+        }
+
+    }
+
+    public void removeModule(Module module) throws RepositoryException {
+        log.info("Removing module '" + module.getName() + "'");
+
+        final Node modulesNode = session.getNode(modulesPath);
+        try {
+            modulesNode.getNode(module.getName()).remove();
+            session.save();
+        } finally {
+            session.refresh(false);
+        }
+        if (modules != null) {
+            modules.remove(module.getName());
+        }
+    }
+
+    public Map<String, Module> readModules() throws RepositoryException {
+        if (modules != null) {
+            return modules;
+        }
+
+        log.info("Reading registered modules from " + modulesPath);
+
+        modules = new TreeMap<String, Module>();
+        final NodeIterator iter = session.getNode(modulesPath).getNodes();
+        while (iter.hasNext()) {
+            final Module module = readModule(iter.nextNode());
+            modules.put(module.getName(), module);
+        }
+
+        return modules;
+    }
+
+    public Module readModule(String name) throws RepositoryException {
+        return readModule(session.getNode(modulesPath + "/" + name));
+    }
+
+    private Module readModule(Node node) throws RepositoryException {
+        final String name = node.getName();
+        final String baseUrl = node.getProperty(BASEURL).getString();
+        final long version = node.getProperty(VERSION).getLong();
+
+        final Module module = new Module(name, baseUrl, version);
+
+        Module.Status status = Module.Status.valueOf(node.getProperty(STATUS).getString());
+        module.setStatus(status);
+
+        if (node.hasProperty(DEPENDENCIES)) {
+            for (Value value : node.getProperty(DEPENDENCIES).getValues()) {
+                module.addDependency(value.getString());
+            }
+        }
+
+        if (node.hasNode(NAMESPACES)) {
+            final NodeIterator iter = node.getNode(NAMESPACES).getNodes();
+            while (iter.hasNext()) {
+                final Node item = iter.nextNode();
+                final String prefix = item.getName();
+                final String uri = item.getProperty(URI).getString();
+                status = Module.Status.valueOf(item.getProperty(STATUS).getString());
+                module.addNamespace(prefix, uri, status);
+            }
+        }
+
+        if (node.hasNode(CNDS)) {
+            final NodeIterator iter = node.getNode(CNDS).getNodes();
+            while (iter.hasNext()) {
+                final Node item = iter.nextNode();
+                final String file = item.getProperty(FILE).getString();
+                final boolean reload = !item.hasProperty(RELOAD) || item.getProperty(RELOAD).getBoolean();
+                status = Module.Status.valueOf(item.getProperty(STATUS).getString());
+                module.addCnd(item.getName(), file, reload, status);
+            }
+        }
+
+        if (node.hasNode(CONTENTS)) {
+            final NodeIterator iter = node.getNode(CONTENTS).getNodes();
+            while (iter.hasNext()) {
+                final Node item = iter.nextNode();
+                final String file = item.getProperty(FILE).getString();
+                final String parent = item.getProperty(PARENT).getString();
+                final boolean reload = !item.hasProperty(RELOAD) || item.getProperty(RELOAD).getBoolean();
+                ImportBehavior importBehavior = null;
+                if (item.hasProperty(IMPORTBEHAVIOR)) {
+                    importBehavior = ImportBehavior.valueOf(item.getProperty(IMPORTBEHAVIOR).getString().toUpperCase());
+                }
+                status = Module.Status.valueOf(item.getProperty(STATUS).getString());
+                module.addContent(item.getName(), file, parent, reload, importBehavior, status);
+            }
+        }
+
+        if (node.hasNode(RESOURCES)) {
+            final NodeIterator iter = node.getNode(RESOURCES).getNodes();
+            while (iter.hasNext()) {
+                final Node item = iter.nextNode();
+                final String path = item.getProperty(PATH).getString();
+                final String parent = item.getProperty(PARENT).getString();
+                final boolean reload = !item.hasProperty(RELOAD) || item.getProperty(RELOAD).getBoolean();
+                ImportBehavior importBehavior = null;
+                if (item.hasProperty(IMPORTBEHAVIOR)) {
+                    importBehavior = ImportBehavior.valueOf(item.getProperty(IMPORTBEHAVIOR).getString().toUpperCase());
+                }
+                status = Module.Status.valueOf(item.getProperty(STATUS).getString());
+                module.addResource(item.getName(), path, parent, reload, importBehavior, status);
+            }
+        }
+
+        return module;
+    }
+
+    private Node getOrCreateNode(Node parent, String name) throws RepositoryException {
+        if (parent.hasNode(name)) {
+            return parent.getNode(name);
+        }
+        return parent.addNode(name);
+    }
+
+    private void setStatusProperty(Node node, Module.Status status) throws RepositoryException {
+        node.setProperty(STATUS, status.toString());
+    }
+
+    private void setNamespaceProperties(Node namespaceNode, Module.Namespace namespace) throws RepositoryException {
+        namespaceNode.setProperty(URI, namespace.getUri());
+    }
+
+    private void setCndProperties(Node cndNode, Module.Cnd cnd) throws RepositoryException {
+        cndNode.setProperty(FILE, cnd.getFile());
+        cndNode.setProperty(RELOAD, cnd.isReload());
+    }
+
+    private void setContentProperties(Node contentNode, Module.Content content) throws RepositoryException {
+        contentNode.setProperty(FILE, content.getFile());
+        contentNode.setProperty(RELOAD, content.isReload());
+        contentNode.setProperty(PARENT, content.getParent());
+        if (content.getImportBehavior() != null) {
+            contentNode.setProperty(IMPORTBEHAVIOR, content.getImportBehavior().toString());
+        }
+    }
+
+    private void setResourceProperties(final Node resourceNode, final Module.Resource resource) throws RepositoryException {
+        resourceNode.setProperty(PATH, resource.getPath());
+        resourceNode.setProperty(PARENT, resource.getParent());
+        resourceNode.setProperty(RELOAD, resource.isReload());
+        if (resource.getImportBehavior() != null) {
+            resourceNode.setProperty(IMPORTBEHAVIOR, resource.getImportBehavior().toString());
+        }
+    }
+
+
+    private void setDependencies(Node node, Module module) throws RepositoryException {
+        ValueFactory valueFactory = session.getValueFactory();
+        Collection<Value> values = new ArrayList<Value>(module.getDependencies().size());
+        for (String dependency : module.getDependencies()) {
+            values.add(valueFactory.createValue(dependency));
+        }
+        node.setProperty(DEPENDENCIES, values.toArray(new Value[values.size()]));
+    }
+
+    private <T extends Module.Item> Collection<T> getModified(Collection<T> current, Collection<T> previous) {
+        Collection<T> modified = new ArrayList<T>(current.size());
+        for (T currentItem : current) {
+            T previousItem = getItem(currentItem.getName(), previous);
+            if (previousItem != null && !currentItem.equals(previousItem)) {
+                modified.add(currentItem);
+            }
+        }
+        return modified;
+    }
+
+    private <T extends Module.Item> Collection<T> getRemoved(Collection<T> current, Collection<T> previous) {
+        Collection<T> removed = new ArrayList<T>(previous.size());
+        for (T previousItem : previous) {
+            if (getItem(previousItem.getName(), current) == null) {
+                removed.add(previousItem);
+            }
+        }
+        return removed;
+    }
+
+    private <T extends Module.Item> Collection<T> getAdded(Collection<T> current, Collection<T> previous) {
+        Collection<T> added = new ArrayList<T>(current.size());
+        for (T currentItem : current) {
+            if (getItem(currentItem.getName(), previous) == null) {
+                added.add(currentItem);
+            }
+        }
+        return added;
+    }
+
+    private <T extends Module.Item> T getItem(String name, Collection<T> items) {
+        for (T item : items) {
+            if (item.getName().equals(name)) {
+                return item;
+            }
+        }
+        return null;
+    }
+}

Added: rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleScanner.java
URL: http://svn.apache.org/viewvc/rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleScanner.java?rev=1333418&view=auto
==============================================================================
--- rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleScanner.java (added)
+++ rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/bootstrapping/ModuleScanner.java Thu May  3 12:13:44 2012
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rave.jcr.bootstrapping;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jcr.RepositoryException;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import org.apache.rave.jcr.importing.ImportBehavior;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Scans the classpath for module descriptors, parses them and instantiates {@link Module} objects
+ * that represent these descriptors.
+ */
+public final class ModuleScanner {
+
+    private static final String NAME = "name";
+    private static final String VERSION = "version";
+    private static final String DEPENDENCIES = "dependencies";
+    private static final String NAMESPACES = "namespaces";
+    private static final String CNDS = "cnds";
+    private static final String CONTENTS = "contents";
+    private static final String RESOURCES = "resources";
+    private static final String FILE = "file";
+    private static final String RELOAD = "reload";
+    private static final String PARENT = "parent";
+    private static final String PATH = "path";
+    private static final String IMPORTBEHAVIOR = "importBehavior";
+
+    private static final JsonFactory jsonFactory = new JsonFactory();
+    private static final Logger log = LoggerFactory.getLogger(ModuleScanner.class);
+
+    private final String moduleDescriptorPath;
+
+    public ModuleScanner(final String systemNamespacePrefix) {
+        this.moduleDescriptorPath = "META-INF/" + systemNamespacePrefix + "/module.json";
+    }
+
+    public Map <String, Module> scan() throws BootstrapException, RepositoryException {
+        log.info("Scanning for modules at " + moduleDescriptorPath);
+        try {
+            final Enumeration<URL> bootstrapResources = Thread.currentThread().getContextClassLoader().getResources(moduleDescriptorPath);
+            final Map<String, Module> modules = new TreeMap<String, Module>();
+            while (bootstrapResources.hasMoreElements()) {
+                URL moduleDescriptor = null;
+                try {
+                    moduleDescriptor = bootstrapResources.nextElement();
+                    final Module module = parse(moduleDescriptor);
+                    modules.put(module.getName(), module);
+                } catch (BootstrapException e) {
+                    log.error("Failed to parse module at " + moduleDescriptor + ": " + e.getMessage());
+                } catch (IOException e) {
+                    log.error("Failed to read module descriptor " + moduleDescriptor + ": " + e.getMessage());
+                }
+            }
+            return modules;
+        } catch (IOException e) {
+            throw new BootstrapException("Failed to get bootstrap resources at " + moduleDescriptorPath, e);
+        }
+    }
+
+    public Module parse(URL moduleDescriptor) throws IOException, BootstrapException {
+        final String baseUrl = getBaseURL(moduleDescriptor);
+
+        final JsonParser jsonParser = jsonFactory.createJsonParser(moduleDescriptor.openStream());
+        final ObjectNode object = (ObjectNode) new ObjectMapper().readTree(jsonParser);
+
+        if (!object.has(NAME)) {
+            throw new BootstrapException("module definition must specify a name field");
+        }
+        final String moduleName = object.get(NAME).textValue();
+
+        final long newVersion = object.has(VERSION) ? object.get(VERSION).numberValue().longValue() : -1;
+
+        final Module module = new Module(moduleName, baseUrl, newVersion);
+
+        final JsonNode dependencies = object.get(DEPENDENCIES);
+        if (dependencies != null && dependencies.isArray()) {
+            final Iterator<JsonNode> iter = dependencies.elements();
+            while (iter.hasNext()) {
+                module.addDependency(iter.next().textValue());
+            }
+        }
+
+        final JsonNode namespaces = object.get(NAMESPACES);
+        if (namespaces != null) {
+            final Iterator<String> iter = namespaces.fieldNames();
+            while (iter.hasNext()) {
+                final String prefix = iter.next();
+                final String uri = namespaces.get(prefix).textValue();
+                module.addNamespace(prefix, uri, null);
+            }
+        }
+
+        final JsonNode cnds = object.get(CNDS);
+        if (cnds != null) {
+            final Iterator<String> iter = cnds.fieldNames();
+            while (iter.hasNext()) {
+                final String name = iter.next();
+                final JsonNode cnd = cnds.get(name);
+                if (!cnd.has("file")) {
+                    throw new BootstrapException("cnd definition must specify a file field");
+                }
+                final String file = cnd.get(FILE).textValue();
+                final boolean reload = !cnd.has(RELOAD) || cnd.get(RELOAD).booleanValue();
+                module.addCnd(name, file, reload, null);
+            }
+        }
+
+        final JsonNode contents = object.get(CONTENTS);
+        if (contents != null) {
+            final Iterator<String> iter = contents.fieldNames();
+            while (iter.hasNext()) {
+                final String name = iter.next();
+                final JsonNode content = contents.get(name);
+                if (!content.has(FILE) || !content.has(PARENT)) {
+                    throw new BootstrapException("content definition must specify a file and a parent field");
+                }
+                final String file = content.get(FILE).textValue();
+                final String parent = content.get(PARENT).textValue();
+                final boolean reload = !content.has(RELOAD) || content.get(RELOAD).booleanValue();
+                ImportBehavior importBehavior = null;
+                if (content.has(IMPORTBEHAVIOR)) {
+                    importBehavior = ImportBehavior.valueOf(content.get(IMPORTBEHAVIOR).textValue().toUpperCase());
+                }
+                module.addContent(name, file, parent, reload, importBehavior, null);
+            }
+        }
+
+        final JsonNode resources = object.get(RESOURCES);
+        if (resources != null) {
+            final Iterator<String> iter = resources.fieldNames();
+            while (iter.hasNext()) {
+                final String name = iter.next();
+                final JsonNode resource = resources.get(name);
+                if (!resource.has(PARENT)) {
+                    throw new BootstrapException("resource definition must specify a parent field");
+                }
+                final String path = resource.has(PATH) ? resource.get(PATH).textValue() : "";
+                final String parent = resource.get(PARENT).textValue();
+                final boolean reload = !resource.has(RELOAD) || resource.get(RELOAD).booleanValue();
+                ImportBehavior importBehavior = null;
+                if (resource.has(IMPORTBEHAVIOR)) {
+                    importBehavior = ImportBehavior.valueOf(resource.get(IMPORTBEHAVIOR).textValue().toUpperCase());
+                }
+                module.addResource(name, path, parent, reload, importBehavior, null);
+            }
+        }
+
+        return module;
+    }
+
+
+    private String getBaseURL(URL moduleDescriptor) {
+        String s = moduleDescriptor.toString();
+        if (!s.endsWith(moduleDescriptorPath)) {
+            throw new IllegalArgumentException("Expected module descriptor to be at " + moduleDescriptorPath);
+        }
+        return s.substring(0, s.length() - moduleDescriptorPath.length());
+    }
+
+}

Added: rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/servlet/ServletSystemContext.java
URL: http://svn.apache.org/viewvc/rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/servlet/ServletSystemContext.java?rev=1333418&view=auto
==============================================================================
--- rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/servlet/ServletSystemContext.java (added)
+++ rave/sandbox/content-services/rave-jcr-config/src/main/java/org/apache/rave/jcr/servlet/ServletSystemContext.java Thu May  3 12:13:44 2012
@@ -0,0 +1,45 @@
+package org.apache.rave.jcr.servlet;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+import org.apache.rave.jcr.SystemContext;
+
+public final class ServletSystemContext implements SystemContext {
+
+    private final ServletConfig servletConfig;
+    private final ServletContext servletContext;
+    private final String contextPathPostfix;
+
+    public ServletSystemContext(ServletConfig servletConfig) {
+        this.servletConfig = servletConfig;
+        this.servletContext = servletConfig.getServletContext();
+        String contextPath = servletContext.getContextPath();
+        if (!contextPath.isEmpty()) {
+            assert contextPath.startsWith("/");
+            contextPath = contextPath.substring(1);
+        }
+        contextPathPostfix = "." + contextPath;
+    }
+
+    @Override
+    public String getMimeType(final String file) {
+        return servletContext.getMimeType(file);
+    }
+
+    @Override
+    public String getParameter(final String name) {
+        String value = System.getProperty(name + contextPathPostfix);
+        if (value == null || value.isEmpty()) {
+            value = System.getProperty(name);
+        }
+        if (value == null || value.isEmpty()) {
+            value = servletConfig.getInitParameter(name);
+        }
+        if (value == null || value.isEmpty()) {
+            value = servletContext.getInitParameter(name);
+        }
+        return value;
+    }
+
+}