You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by sy...@apache.org on 2005/04/23 09:56:42 UTC

svn commit: r164365 - in /cocoon/blocks/jcr: ./ trunk/ trunk/WEB-INF/ trunk/WEB-INF/xconf/ trunk/conf/ trunk/java/ trunk/java/org/ trunk/java/org/apache/ trunk/java/org/apache/cocoon/ trunk/java/org/apache/cocoon/jcr/ trunk/java/org/apache/cocoon/jcr/source/ trunk/test/ trunk/test/org/ trunk/test/org/apache/ trunk/test/org/apache/cocoon/ trunk/test/org/apache/cocoon/jcr/ trunk/test/org/apache/cocoon/jcr/source/

Author: sylvain
Date: Sat Apr 23 00:56:42 2005
New Revision: 164365

URL: http://svn.apache.org/viewcvs?rev=164365&view=rev
Log:
New JCR block

Added:
    cocoon/blocks/jcr/
    cocoon/blocks/jcr/trunk/
    cocoon/blocks/jcr/trunk/WEB-INF/
    cocoon/blocks/jcr/trunk/WEB-INF/xconf/
    cocoon/blocks/jcr/trunk/WEB-INF/xconf/jcr.xconf   (with props)
    cocoon/blocks/jcr/trunk/conf/
    cocoon/blocks/jcr/trunk/conf/jcr.xroles
    cocoon/blocks/jcr/trunk/java/
    cocoon/blocks/jcr/trunk/java/org/
    cocoon/blocks/jcr/trunk/java/org/apache/
    cocoon/blocks/jcr/trunk/java/org/apache/cocoon/
    cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/
    cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/AbstractRepository.java   (with props)
    cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/JackrabbitRepository.java   (with props)
    cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/jcr.roles   (with props)
    cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/
    cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSource.java   (with props)
    cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSourceValidity.java   (with props)
    cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRSourceFactory.java   (with props)
    cocoon/blocks/jcr/trunk/test/
    cocoon/blocks/jcr/trunk/test/org/
    cocoon/blocks/jcr/trunk/test/org/apache/
    cocoon/blocks/jcr/trunk/test/org/apache/cocoon/
    cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/
    cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/repository.xml   (with props)
    cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/
    cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTestCase.java   (with props)
    cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTestCase.xtest

Added: cocoon/blocks/jcr/trunk/WEB-INF/xconf/jcr.xconf
URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/WEB-INF/xconf/jcr.xconf?rev=164365&view=auto
==============================================================================
--- cocoon/blocks/jcr/trunk/WEB-INF/xconf/jcr.xconf (added)
+++ cocoon/blocks/jcr/trunk/WEB-INF/xconf/jcr.xconf Sat Apr 23 00:56:42 2005
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<!--
+  Copyright 1999-2005 The Apache Software Foundation
+
+  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.
+-->
+<!-- $Id$ -->
+<components>
+  <include src="resource://org/apache/cocoon/jcr/jcr.roles"/>
+</components>
\ No newline at end of file

Propchange: cocoon/blocks/jcr/trunk/WEB-INF/xconf/jcr.xconf
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/jcr/trunk/WEB-INF/xconf/jcr.xconf
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/jcr/trunk/conf/jcr.xroles
URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/conf/jcr.xroles?rev=164365&view=auto
==============================================================================
--- cocoon/blocks/jcr/trunk/conf/jcr.xroles (added)
+++ cocoon/blocks/jcr/trunk/conf/jcr.xroles Sat Apr 23 00:56:42 2005
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<!--
+  Copyright 1999-2005 The Apache Software Foundation
+
+  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.
+-->
+<!-- $Id$ -->
+<xroles xpath="/role-list" unless="/role-list[@cocoon-version != '2.1']|role[@shorthand='jcr-repository']">
+  
+  <!-- JCR repository, no default class -->
+  <role name="javax.jcr.Repository" 
+    shorthand="jcr-repository" 
+  />
+</xroles>

Added: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/AbstractRepository.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/AbstractRepository.java?rev=164365&view=auto
==============================================================================
--- cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/AbstractRepository.java (added)
+++ cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/AbstractRepository.java Sat Apr 23 00:56:42 2005
@@ -0,0 +1,186 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * 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.cocoon.jcr;
+
+import java.util.Map;
+
+import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.avalon.framework.activity.Disposable;
+import org.apache.avalon.framework.configuration.Configurable;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.context.Context;
+import org.apache.avalon.framework.context.ContextException;
+import org.apache.avalon.framework.context.Contextualizable;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+// import org.apache.cocoon.components.variables.VariableResolver;
+// import org.apache.cocoon.components.variables.VariableResolverFactory;
+import org.apache.cocoon.components.ContextHelper;
+import org.apache.cocoon.components.treeprocessor.variables.VariableResolver;
+import org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory;
+import org.apache.cocoon.sitemap.PatternException;
+
+/**
+ * Base class for JCR (aka <a
+ * href="http://www.jcp.org/en/jsr/detail?id=170">JSR-170</a>) repositories as
+ * Cocoon components. The main purpose of this class is to allow repository
+ * credentials to be specified in the component's configuration, so that the
+ * application code just has to call <code>repository.login()</code>.
+ * <p>
+ * There is no Cocoon-specific role for this component: "<code>javax.jcr.Repository</code>"
+ * should be used.
+ * <p>
+ * The configuration of this class, inherited by its subclasses, is as follows:
+ * 
+ * <pre>
+ * 
+ *    &lt;jcr-repository&gt;
+ *      &lt;credentials login="<i>expression</i>" password="<i>expression</i>"/&gt;
+ *      ... other specific configuration...
+ *    &lt;/jcr-repository&gt;
+ *  
+ * </pre>
+ * 
+ * Login and password can be specified using the sitemap expression language,
+ * thus allowing the use of input modules to compute their values, e.g.
+ * <code>password="{session-attr:jcr-password}"</code>
+ * <p>
+ * <code>&lt;credentials&gt;</code> is optional. If not specified, the
+ * application must explicitely supply credentials when calling
+ * <code>Repository.login()</code>.
+ * 
+ * @version $Id$
+ */
+public class AbstractRepository implements Repository, Contextualizable, Serviceable, Configurable, Disposable {
+
+    // TODO: on login(), keep the JCR Session in the Environment Session, this
+    // will improve performances.
+
+    protected ServiceManager manager;
+
+    protected Context context;
+
+    protected Repository delegate;
+
+    // Defined by the portal block :-(
+    // protected VariableResolverFactory variableFactory;
+
+    protected VariableResolver loginResolver = null;
+
+    protected VariableResolver passwordResolver = null;
+
+    // =============================================================================================
+    // Avalon lifecycle
+    // =============================================================================================
+
+    public void contextualize(Context context) throws ContextException {
+        this.context = context;
+    }
+
+    public void service(ServiceManager manager) throws ServiceException {
+        this.manager = manager;
+        // this.variableFactory =
+        // (VariableResolverFactory)manager.lookup(VariableResolverFactory.ROLE);
+    }
+
+    public void configure(Configuration config) throws ConfigurationException {
+        Configuration credentials = config.getChild("credentials", false);
+        if (credentials != null) {
+            String login = credentials.getAttribute("login");
+            String password = credentials.getAttribute("password");
+
+            try {
+                this.loginResolver = VariableResolverFactory.getResolver(login, this.manager);
+            } catch (PatternException e) {
+                throw new ConfigurationException("Invalid expression for 'login' at " + credentials.getLocation(), e);
+            }
+            try {
+                this.passwordResolver = VariableResolverFactory.getResolver(password, this.manager);
+            } catch (PatternException e) {
+                if (this.loginResolver instanceof Disposable)
+                    ((Disposable) this.loginResolver).dispose();
+                // this.variableFactory.release(this.loginResolver);
+                throw new ConfigurationException("Invalid expression for 'password' at " + credentials.getLocation(), e);
+            }
+        }
+    }
+
+    public void dispose() {
+        if (this.loginResolver instanceof Disposable)
+            ((Disposable) this.loginResolver).dispose();
+        if (this.passwordResolver instanceof Disposable)
+            ((Disposable) this.passwordResolver).dispose();
+        // this.variableFactory.release(this.loginResolver);
+        // this.variableFactory.release(this.passwordResolver);
+        // this.manager.release(this.variableFactory);
+    }
+
+    // =============================================================================================
+    // Repository interface
+    // =============================================================================================
+
+    public String getDescriptor(String key) {
+        return delegate.getDescriptor(key);
+    }
+
+    public String[] getDescriptorKeys() {
+        return delegate.getDescriptorKeys();
+    }
+
+    public Session login() throws LoginException, NoSuchWorkspaceException, RepositoryException {
+        Credentials creds = getCredentials();
+        return creds == null ? delegate.login() : delegate.login(creds);
+    }
+
+    public Session login(Credentials creds) throws LoginException, NoSuchWorkspaceException, RepositoryException {
+        return delegate.login(creds);
+    }
+
+    public Session login(Credentials creds, String workspace) throws LoginException, NoSuchWorkspaceException, RepositoryException {
+        return delegate.login(creds, workspace);
+    }
+
+    public Session login(String workspace) throws LoginException, NoSuchWorkspaceException, RepositoryException {
+        Credentials creds = getCredentials();
+        return creds == null ? delegate.login(workspace) : delegate.login(creds, workspace);
+    }
+
+    // =============================================================================================
+
+    private Credentials getCredentials() throws LoginException {
+        if (this.loginResolver != null) {
+            try {
+                Map objectModel = ContextHelper.getObjectModel(context);
+                String login = this.loginResolver.resolve(objectModel);
+                String password = this.loginResolver.resolve(objectModel);
+                return new SimpleCredentials(login, password.toCharArray());
+            } catch (PatternException e) {
+                throw new LoginException("Failed to evaluate credentials", e);
+            }
+        } else {
+            return null;
+        }
+    }
+}

Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/AbstractRepository.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/AbstractRepository.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/JackrabbitRepository.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/JackrabbitRepository.java?rev=164365&view=auto
==============================================================================
--- cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/JackrabbitRepository.java (added)
+++ cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/JackrabbitRepository.java Sat Apr 23 00:56:42 2005
@@ -0,0 +1,97 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * 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.cocoon.jcr;
+
+import org.apache.avalon.framework.configuration.Configurable;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.cocoon.components.source.SourceUtil;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.excalibur.source.impl.FileSource;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.xml.sax.InputSource;
+
+/**
+ * JackrabbitRepository is a JCR repository component based on <a
+ * href="http://incubator.apache.org/jackrabbit">Jackrabbit</a>
+ * <p>
+ * The configuration is as follows:
+ * 
+ * <pre>
+ * 
+ *    &lt;jcr-repository&gt;
+ *      &lt;credentials login="<i>expression</i>" password="<i>expression</i>"/&gt;
+ *      &lt;home src="file://path/to/repository"/&gt;
+ *      &lt;configuration src="resource://your/application/jcr/repository.xml"/&gt;
+ *    &lt;/jcr-repository&gt;
+ *  
+ * </pre>
+ * 
+ * The <code>home</code> URI points to the base location of the repository,
+ * and <code>configuration</code> points to the Jackrabbit repository
+ * configuration file.
+ * 
+ * @see AbstractRepository
+ * @version $Id$
+ */
+public class JackrabbitRepository extends AbstractRepository implements Configurable {
+
+    public void configure(Configuration config) throws ConfigurationException {
+
+        super.configure(config);
+
+        String homeURI = config.getChild("home").getAttribute("src");
+        String homePath;
+        String configURI = config.getChild("configuration").getAttribute("src");
+
+        // having to release sources is a major PITA...
+
+        try {
+            SourceResolver resolver = (SourceResolver) this.manager.lookup(SourceResolver.ROLE);
+
+            // Ensure home uri is a file and absolutize it
+            Source homeSrc = resolver.resolveURI(homeURI);
+            if (homeSrc instanceof FileSource) {
+                homePath = ((FileSource) homeSrc).getFile().getAbsolutePath();
+                resolver.release(homeSrc);
+            } else {
+                resolver.release(homeSrc);
+                throw new ConfigurationException("Home path '" + homeURI + "' should map to a file, at " + config.getChild("home").getLocation());
+            }
+
+            // Load the config
+            Source configSrc = resolver.resolveURI(configURI);
+            InputSource is = SourceUtil.getInputSource(configSrc);
+
+            RepositoryConfig repoConfig;
+            try {
+                repoConfig = RepositoryConfig.create(is, homePath);
+            } finally {
+                resolver.release(configSrc);
+            }
+
+            // And create the repo
+            this.delegate = RepositoryImpl.create(repoConfig);
+
+        } catch (ConfigurationException ce) {
+            throw ce;
+        } catch (Exception e) {
+            throw new ConfigurationException("Cannot access configuration information at " + config.getLocation(), e);
+        }
+    }
+}

Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/JackrabbitRepository.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/JackrabbitRepository.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/jcr.roles
URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/jcr.roles?rev=164365&view=auto
==============================================================================
--- cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/jcr.roles (added)
+++ cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/jcr.roles Sat Apr 23 00:56:42 2005
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!--
+  Copyright 1999-2005 The Apache Software Foundation
+
+  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.
+-->
+<!-- $Id$ -->
+<role-list>
+  <role name="javax.jcr.Repository"
+        shorthand="jcr-repository"
+  />
+</role-list>

Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/jcr.roles
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/jcr.roles
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSource.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSource.java?rev=164365&view=auto
==============================================================================
--- cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSource.java (added)
+++ cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSource.java Sat Apr 23 00:56:42 2005
@@ -0,0 +1,473 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * 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.cocoon.jcr.source;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.GregorianCalendar;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.cocoon.CascadingIOException;
+import org.apache.excalibur.source.ModifiableTraversableSource;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceException;
+import org.apache.excalibur.source.SourceNotFoundException;
+import org.apache.excalibur.source.SourceValidity;
+import org.apache.excalibur.source.TraversableSource;
+
+/**
+ * A Source for a JCR node.
+ * 
+ * @version $Id$
+ */
+public class JCRNodeSource implements Source, TraversableSource, ModifiableTraversableSource {
+
+    /** The full URI */
+    protected String computedURI;
+
+    /** The node path */
+    protected final String path;
+
+    /** The factory that created this Source */
+    protected final JCRSourceFactory factory;
+
+    /** The session this source is bound to */
+    protected final Session session;
+
+    /** The node pointed to by this source (can be null) */
+    protected Node node;
+
+    public JCRNodeSource(JCRSourceFactory factory, Session session, String path) throws SourceException {
+        this.factory = factory;
+        this.session = session;
+        this.path = path;
+
+        try {
+            Item item = session.getItem(path);
+            if (!item.isNode()) {
+                throw new SourceException("Path '" + path + "' is a property (should be a node)");
+            } else {
+                this.node = (Node) item;
+            }
+        } catch (PathNotFoundException e) {
+            // Not found
+            this.node = null;
+        } catch (RepositoryException e) {
+            throw new SourceException("Cannot lookup repository path " + path, e);
+        }
+    }
+
+    protected JCRNodeSource(JCRNodeSource parent, Node node) throws SourceException {
+        this.factory = parent.factory;
+        this.session = parent.session;
+        this.node = node;
+
+        try {
+            this.path = getChildPath(parent.path, node.getName());
+
+        } catch (RepositoryException e) {
+            throw new SourceException("Cannot get name of child of " + parent.getURI(), e);
+        }
+    }
+
+    private String getChildPath(String path, String name) {
+        StringBuffer pathBuf = new StringBuffer(path);
+        // Append '/' only if the parent isn't the root (it's path is "/" in
+        // that case)
+        if (pathBuf.length() > 1)
+            pathBuf.append('/');
+        pathBuf.append(name);
+        return pathBuf.toString();
+    }
+
+    /**
+     * Returns the JCR <code>Node</code> this source points to, or
+     * <code>null</code> if it denotes a non-existing path.
+     * 
+     * @return the JCR node.
+     */
+    public Node getNode() {
+        return this.node;
+    }
+
+    /**
+     * Returns the JCR <code>Session</code> used by this source.
+     * 
+     * @return the JCR session.
+     */
+    public Session getSession() {
+        return this.session;
+    }
+
+    /**
+     * Returns the JCR <code>Node</code> used to store the content of this
+     * source.
+     * 
+     * @return the JCR content node, or <code>null</code> if no such node
+     *         exist, either because the source is a collection or doesn't
+     *         currently contain data.
+     */
+    public Node getContentNode() {
+        if (this.node == null) {
+            return null;
+        }
+
+        if (isCollection()) {
+            return null;
+        }
+
+        try {
+            return this.factory.getContentNode(this.node);
+        } catch (RepositoryException e) {
+            return null;
+        }
+    }
+
+    // =============================================================================================
+    // Source interface
+    // =============================================================================================
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.excalibur.source.Source#exists()
+     */
+    public boolean exists() {
+        return this.node != null;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.excalibur.source.Source#getInputStream()
+     */
+    public InputStream getInputStream() throws IOException, SourceNotFoundException {
+        if (this.node == null) {
+            throw new SourceNotFoundException("Path '" + this.getURI() + "' does not exist");
+        }
+
+        if (this.isCollection()) {
+            throw new SourceException("Path '" + this.getURI() + "' is a collection");
+        }
+
+        try {
+            Property contentProp = this.factory.getContentProperty(this.node);
+            return contentProp.getStream();
+        } catch (Exception e) {
+            throw new SourceException("Error opening stream for '" + this.getURI() + "'", e);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.excalibur.source.Source#getURI()
+     */
+    public String getURI() {
+        if (this.computedURI == null) {
+            this.computedURI = this.factory.getScheme() + ":/" + this.path;
+        }
+        return this.computedURI;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.excalibur.source.Source#getScheme()
+     */
+    public String getScheme() {
+        return this.factory.getScheme();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.excalibur.source.Source#getValidity()
+     */
+    public SourceValidity getValidity() {
+        try {
+            Property prop = this.factory.getValidityProperty(this.node);
+            return prop == null ? null : new JCRNodeSourceValidity(prop.getValue());
+        } catch (RepositoryException re) {
+            return null;
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.excalibur.source.Source#refresh()
+     */
+    public void refresh() {
+        // nothing to do here
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.excalibur.source.Source#getMimeType()
+     */
+    public String getMimeType() {
+        try {
+            Property prop = this.factory.getMimeTypeProperty(this.node);
+            if (prop == null) {
+                return null;
+            } else {
+                String value = prop.getString();
+                return value.length() == 0 ? null : value;
+            }
+        } catch (RepositoryException re) {
+            return null;
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.excalibur.source.Source#getContentLength()
+     */
+    public long getContentLength() {
+        if (this.node == null) {
+            return -1;
+        }
+        try {
+            Property prop = this.factory.getContentProperty(this.node);
+            return prop == null ? -1 : prop.getLength();
+        } catch (RepositoryException re) {
+            return -1;
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.excalibur.source.Source#getLastModified()
+     */
+    public long getLastModified() {
+        try {
+            Property prop = this.factory.getLastModifiedDateProperty(this.node);
+            return prop == null ? 0 : prop.getDate().getTimeInMillis();
+        } catch (RepositoryException re) {
+            return 0;
+        }
+    }
+
+    // =============================================================================================
+    // TraversableSource interface
+    // =============================================================================================
+
+    public boolean isCollection() {
+        if (!exists())
+            return false;
+
+        try {
+            return this.factory.isCollection(this.node);
+        } catch (RepositoryException e) {
+            return false;
+        }
+    }
+
+    public Collection getChildren() throws SourceException {
+        if (!isCollection()) {
+            return Collections.EMPTY_LIST;
+        } else {
+            ArrayList children = new ArrayList();
+
+            NodeIterator nodes;
+            try {
+                nodes = this.node.getNodes();
+            } catch (RepositoryException e) {
+                throw new SourceException("Cannot get child nodes for " + getURI(), e);
+            }
+
+            while (nodes.hasNext()) {
+                children.add(new JCRNodeSource(this, nodes.nextNode()));
+            }
+            return children;
+        }
+    }
+
+    public Source getChild(String name) throws SourceException {
+        if (this.isCollection()) {
+            return new JCRNodeSource(this.factory, this.session, getChildPath(this.path, name));
+        } else {
+            throw new SourceException("Not a collection: " + getURI());
+        }
+    }
+
+    public String getName() {
+        return this.path.substring(this.path.lastIndexOf('/') + 1);
+    }
+
+    public Source getParent() throws SourceException {
+        if (this.path.length() == 1) {
+            // Root
+            return null;
+        }
+
+        int lastPos = this.path.lastIndexOf('/');
+        String parentPath = lastPos == 0 ? "/" : this.path.substring(0, lastPos);
+        return new JCRNodeSource(this.factory, this.session, parentPath);
+    }
+
+    // =============================================================================================
+    // ModifiableTraversableSource interface
+    // =============================================================================================
+
+    public OutputStream getOutputStream() throws IOException {
+        if (isCollection()) {
+            throw new SourceException("Cannot write to collection " + this.getURI());
+        }
+
+        try {
+            Node contentNode;
+            if (!exists()) {
+                JCRNodeSource parent = (JCRNodeSource) getParent();
+
+                // Create the path if it doesn't exist
+                parent.makeCollection();
+
+                // Create our node
+                this.node = this.factory.createFileNode(parent.node, getName());
+                contentNode = this.factory.createContentNode(this.node);
+            } else {
+                contentNode = this.factory.getContentNode(this.node);
+            }
+
+            return new JCRSourceOutputStream(contentNode);
+        } catch (RepositoryException e) {
+            throw new SourceException("Cannot create content node for " + getURI(), e);
+        }
+    }
+
+    public void delete() throws SourceException {
+        if (exists()) {
+            try {
+                this.node.remove();
+                this.node = null;
+                this.session.save();
+            } catch (RepositoryException e) {
+                throw new SourceException("Cannot delete " + getURI(), e);
+            }
+        }
+    }
+
+    public boolean canCancel(OutputStream os) {
+        if (os instanceof JCRSourceOutputStream) {
+            return ((JCRSourceOutputStream) os).canCancel();
+        } else {
+            return false;
+        }
+    }
+
+    public void cancel(OutputStream os) throws IOException {
+        if (canCancel(os)) {
+            ((JCRSourceOutputStream) os).cancel();
+        } else {
+            throw new IllegalArgumentException("Stream cannot be cancelled");
+        }
+    }
+
+    public void makeCollection() throws SourceException {
+        if (exists()) {
+            if (!isCollection()) {
+                throw new SourceException("Cannot make a collection with existing node at " + getURI());
+            }
+        } else {
+            try {
+                // Ensure parent exists
+                JCRNodeSource parent = (JCRNodeSource) getParent();
+                if (parent == null) {
+                    throw new RuntimeException("Problem: root node does not exist!!");
+                }
+                parent.makeCollection();
+                Node parentNode = parent.node;
+
+                String typeName = this.factory.getFolderNodeType(parentNode);
+
+                this.node = parentNode.addNode(getName(), typeName);
+                this.session.save();
+
+            } catch (RepositoryException e) {
+                throw new SourceException("Cannot make collection " + this.getURI(), e);
+            }
+        }
+    }
+
+    // ----------------------------------------------------------------------------------
+    // Private helper class for ModifiableSource implementation
+    // ----------------------------------------------------------------------------------
+
+    /**
+     * An outputStream that will save the session upon close, and discard it
+     * upon cancel.
+     */
+    private class JCRSourceOutputStream extends ByteArrayOutputStream {
+        private boolean isClosed = false;
+
+        private final Node contentNode;
+
+        public JCRSourceOutputStream(Node contentNode) {
+            this.contentNode = contentNode;
+        }
+
+        public void close() throws IOException {
+            if (!isClosed) {
+                super.close();
+                this.isClosed = true;
+                try {
+                    JCRSourceFactory.ContentTypeInfo info = (JCRSourceFactory.ContentTypeInfo) factory.getTypeInfo(contentNode);
+                    contentNode.setProperty(info.contentProp, new ByteArrayInputStream(this.toByteArray()));
+                    if (info.lastModifiedProp != null) {
+                        contentNode.setProperty(info.lastModifiedProp, new GregorianCalendar());
+                    }
+                    if (info.mimeTypeProp != null) {
+                        // FIXME: define mime type
+                        contentNode.setProperty(info.mimeTypeProp, "");
+                    }
+
+                    JCRNodeSource.this.session.save();
+                } catch (RepositoryException e) {
+                    throw new CascadingIOException("Cannot save content to " + getURI(), e);
+                }
+            }
+        }
+
+        public boolean canCancel() {
+            return !isClosed;
+        }
+
+        public void cancel() throws IOException {
+            if (isClosed) {
+                throw new IllegalStateException("Cannot cancel : outputstrem is already closed");
+            }
+        }
+    }
+}

Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSource.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSource.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSourceValidity.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSourceValidity.java?rev=164365&view=auto
==============================================================================
--- cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSourceValidity.java (added)
+++ cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSourceValidity.java Sat Apr 23 00:56:42 2005
@@ -0,0 +1,50 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * 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.cocoon.jcr.source;
+
+import javax.jcr.Value;
+
+import org.apache.excalibur.source.SourceValidity;
+
+/**
+ * Validity of a {@link JCRNodeSource}. It's a wrapper around a JCR
+ * <code>Value</code>.
+ * 
+ * @version $Id$
+ */
+public class JCRNodeSourceValidity implements SourceValidity {
+
+    private Value value;
+
+    public JCRNodeSourceValidity(Value value) {
+        this.value = value;
+    }
+
+    public int isValid() {
+        // Don't know, need another validity to compare with
+        return 0;
+    }
+
+    public int isValid(SourceValidity other) {
+        if (other instanceof JCRNodeSourceValidity) {
+            // compare the two values
+            return ((JCRNodeSourceValidity) other).value.equals(this.value) ? 1 : -1;
+        } else {
+            // invalid
+            return -1;
+        }
+    }
+}

Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSourceValidity.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSourceValidity.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRSourceFactory.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRSourceFactory.java?rev=164365&view=auto
==============================================================================
--- cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRSourceFactory.java (added)
+++ cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRSourceFactory.java Sat Apr 23 00:56:42 2005
@@ -0,0 +1,452 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * 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.cocoon.jcr.source;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.LoginException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.avalon.framework.configuration.Configurable;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceException;
+import org.apache.excalibur.source.SourceFactory;
+import org.apache.excalibur.source.SourceUtil;
+
+/**
+ * JCRSourceFactory is an implementation of
+ * <code>ModifiableTraversableSource</code> on top of a JCR (aka <a
+ * href="http://www.jcp.org/en/jsr/detail?id=170">JSR-170</a>) repository.
+ * <p>
+ * Since JCR allows a repository to define its own node types, it is necessary
+ * to configure this source factory with a description of what node types map to
+ * "files" and "folders" and the properties used to store source-related data.
+ * <p>
+ * A typical configuration for a naked Jackrabbit repository is as follows:
+ * 
+ * <pre>
+ * 
+ *    &lt;source-factories&gt;
+ *      &lt;component class=&quot;org.apache.cocoon.jcr.source.JCRSourceFactory&quot; name=&quot;jcr&quot;&gt;
+ *        &lt;folder-node type=&quot;rep:root&quot;  new-file=&quot;nt:file&quot; new-folder=&quot;nt:folder&quot;/&gt;
+ *        &lt;folder-node type=&quot;nt:folder&quot; new-file=&quot;nt:file&quot;/&gt;
+ *        &lt;file-node type=&quot;nt:file&quot; content-path=&quot;jcr:content&quot; content-type=&quot;nt:resource&quot;/&gt;
+ *        &lt;file-node type=&quot;nt:linkedFile&quot; content-ref=&quot;jcr:content&quot;/&gt;
+ *        &lt;content-node type=&quot;nt:resource&quot;
+ *                      content-prop=&quot;jcr:data&quot;
+ *                      mimetype-prop=&quot;jcr:mimeType&quot;
+ *                      lastmodified-prop=&quot;jcr:lastModified&quot;
+ *                      validity-prop=&quot;jcr:lastModified&quot;/&gt;
+ *      &lt;/component&gt;
+ *    &lt;/source-factories&gt;
+ *  
+ * </pre>
+ * 
+ * A <code>&lt;folder-node&gt;</code> defines a node type that is mapped to a
+ * non-terminal source (i.e. that can have children). The <code>new-file</code>
+ * and <code>new-folder</code> attributes respectively define what node types
+ * should be used to create a new terminal and non-terminal source.
+ * <p>
+ * A <code>&lt;file-node&gt;</code> defines a note type that is mapped to a
+ * terminal source (i.e. that can have some content). The
+ * <code>content-path</code> attribute defines the path to the node's child
+ * that actually holds the content, and <code>content-type</code> defines the
+ * type of this content node.
+ * <p>
+ * The <code>content-ref</code> attribute is used to comply with JCR's
+ * <code>nt:linkedFile</code> definition where the content node is not a
+ * direct child of the file node, but is referenced by a property of this file
+ * node. Such node types are read-only as there's no way to indicate where the
+ * referenced content node should be created.
+ * <p>
+ * A <code>&lt;content-node&gt;</code> defines a node type that actually holds
+ * the content of a <code>file-node</code>. The <code>content-prop</code>
+ * attribute must be present and gives the name of the node's binary property
+ * that will hold the actual content. Other attributes are optional:
+ * <ul>
+ * <li><code>mimetype-prop</code> defines a string property holding the
+ * content's MIME type, </li>
+ * <li><code>lastmodified-prop</code> defines a date property holding the
+ * node's last modification date. It is automatically updated when content is
+ * written to the <code>content-node</code>. </li>
+ * <li><code>validity-prop</code> defines a property that gives the validity
+ * of the content, used by Cocoon's cache. If not specified,
+ * <code>lastmodified-prop</code> is used, if present. Otherwise the source
+ * has no validity and won't be cacheable. </li>
+ * </ul>
+ * <p>
+ * The format of URIs for this source is a path in the repository, and it is
+ * therefore currently limited to repository traversal. Further work will add
+ * the ability to specify query strings.
+ * 
+ * @version $Id$
+ */
+public class JCRSourceFactory implements SourceFactory, Configurable, Serviceable {
+
+    static class NodeTypeInfo {
+        // Empty base class
+    }
+
+    static class FolderTypeInfo extends NodeTypeInfo {
+        public String newFileType;
+
+        public String newFolderType;
+    }
+
+    static class FileTypeInfo extends NodeTypeInfo {
+        public String contentPath;
+
+        public String contentType;
+
+        public String contentRef;
+    }
+
+    static class ContentTypeInfo extends NodeTypeInfo {
+        public String contentProp;
+
+        public String mimeTypeProp;
+
+        public String lastModifiedProp;
+
+        public String validityProp;
+    }
+
+    /**
+     * The repository we use
+     */
+    private Repository repo;
+
+    /**
+     * Scheme, lazily computed at the first call to getSource()
+     */
+    private String scheme;
+
+    /**
+     * The NodeTypeInfo for each of the types described in the configuration
+     */
+    private Map typeInfos;
+
+    private ServiceManager manager;
+
+    public void service(ServiceManager manager) throws ServiceException {
+        this.manager = manager;
+        this.repo = (Repository) this.manager.lookup(Repository.class.getName());
+    }
+
+    public void configure(Configuration config) throws ConfigurationException {
+        this.typeInfos = new HashMap();
+
+        Configuration[] children = config.getChildren();
+
+        for (int i = 0; i < children.length; i++) {
+            Configuration child = children[i];
+            String name = child.getName();
+
+            if ("folder-node".equals(name)) {
+                FolderTypeInfo info = new FolderTypeInfo();
+                String type = child.getAttribute("type");
+                info.newFileType = child.getAttribute("new-file");
+                info.newFolderType = child.getAttribute("new-folder", type);
+
+                this.typeInfos.put(type, info);
+
+            } else if ("file-node".equals(name)) {
+                FileTypeInfo info = new FileTypeInfo();
+                info.contentPath = child.getAttribute("content-path", null);
+                info.contentType = child.getAttribute("content-type", null);
+                info.contentRef = child.getAttribute("content-ref", null);
+                if (info.contentPath == null && info.contentRef == null) {
+                    throw new ConfigurationException("One of content-path or content-ref is required at " + child.getLocation());
+                }
+                if (info.contentPath != null && info.contentType == null) {
+                    throw new ConfigurationException("content-type must be present with content-path at " + child.getLocation());
+                }
+                this.typeInfos.put(child.getAttribute("type"), info);
+
+            } else if ("content-node".equals(name)) {
+                ContentTypeInfo info = new ContentTypeInfo();
+                info.contentProp = child.getAttribute("content-prop");
+                info.lastModifiedProp = child.getAttribute("lastmodified-prop", null);
+                info.mimeTypeProp = child.getAttribute("mimetype-prop", null);
+                info.validityProp = child.getAttribute("validity-prop", info.lastModifiedProp);
+                this.typeInfos.put(child.getAttribute("type"), info);
+
+            } else {
+                throw new ConfigurationException("Unknown configuration " + name + " at " + child.getLocation());
+            }
+        }
+
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.excalibur.source.SourceFactory#getSource(java.lang.String,
+     *      java.util.Map)
+     */
+    public Source getSource(String uri, Map parameters) throws IOException, MalformedURLException {
+
+        if (this.scheme == null) {
+            this.scheme = SourceUtil.getScheme(uri);
+        }
+
+        Session session;
+        try {
+            // TODO: accept a different workspace?
+            session = repo.login();
+        } catch (LoginException e) {
+            throw new SourceException("Login to repository failed", e);
+        } catch (RepositoryException e) {
+            throw new SourceException("Cannot access repository", e);
+        }
+
+        // Compute the path
+        String path = SourceUtil.getSpecificPart(uri);
+        if (!path.startsWith("//")) {
+            throw new MalformedURLException("Expecting " + this.scheme + "://path and got " + uri);
+        }
+        // Remove first '/'
+        path = path.substring(1);
+        int pathLen = path.length();
+        if (pathLen > 1) {
+            // Not root: ensure there's no trailing '/'
+            if (path.charAt(pathLen - 1) == '/') {
+                path = path.substring(0, pathLen - 1);
+            }
+        }
+
+        return new JCRNodeSource(this, session, path);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.excalibur.source.SourceFactory#release(org.apache.excalibur.source.Source)
+     */
+    public void release(Source source) {
+        // nothing
+    }
+
+    String getScheme() {
+        return this.scheme;
+    }
+
+    /**
+     * Get the type info for a node.
+     * 
+     * @param node the node
+     * @return the type info
+     * @throws RepositoryException if node type couldn't be accessed or if no type info is found
+     */
+    NodeTypeInfo getTypeInfo(Node node) throws RepositoryException {
+        String typeName = node.getPrimaryNodeType().getName();
+        NodeTypeInfo result = (NodeTypeInfo) this.typeInfos.get(typeName);
+        if (result == null) {
+            // TODO: build a NodeTypeInfo using introspection
+            throw new RepositoryException("No type info found for node type '" + typeName + "' at " + node.getPath());
+        }
+
+        return result;
+    }
+
+    /**
+     * Get the type info for a given node type name.
+     * @param typeName the type name
+     * @return the type info
+     * @throws RepositoryException if no type info is found
+     */
+    NodeTypeInfo getTypeInfo(String typeName) throws RepositoryException {
+        NodeTypeInfo result = (NodeTypeInfo) this.typeInfos.get(typeName);
+        if (result == null) {
+            // TODO: build a NodeTypeInfo using introspection
+            throw new RepositoryException("No type info found for node type '" + typeName + "'");
+        }
+
+        return result;
+    }
+
+    /**
+     * Get the content node for a given node
+     * 
+     * @param node the node for which we want the content node
+     * @return the content node
+     * @throws RepositoryException if some error occurs, or if the given node isn't a file node or a content node
+     */
+    Node getContentNode(Node node) throws RepositoryException {
+        NodeTypeInfo info = getTypeInfo(node);
+
+        if (info instanceof ContentTypeInfo) {
+            return node;
+
+        } else if (info instanceof FileTypeInfo) {
+            FileTypeInfo finfo = (FileTypeInfo) info;
+            if (".".equals(finfo.contentPath)) {
+                return node;
+            } else if (finfo.contentPath != null) {
+                return node.getNode(finfo.contentPath);
+            } else {
+                Property ref = node.getProperty(finfo.contentRef);
+                return getContentNode(ref.getNode());
+            }
+        } else {
+            // A folder
+            throw new RepositoryException("Can't get content node for folder node at " + node.getPath());
+        }
+    }
+
+    /**
+     * Create a child file node in a folder node.
+     * 
+     * @param folderNode the folder node
+     * @param name the child's name
+     * @return the newly created child node
+     * @throws RepositoryException if some error occurs
+     */
+    Node createFileNode(Node folderNode, String name) throws RepositoryException {
+        NodeTypeInfo info = getTypeInfo(folderNode);
+        if (!(info instanceof FolderTypeInfo)) {
+            throw new RepositoryException("Node type " + folderNode.getPrimaryNodeType().getName() + " is not a folder type");
+        }
+
+        FolderTypeInfo folderInfo = (FolderTypeInfo) info;
+        return folderNode.addNode(name, folderInfo.newFileType);
+    }
+
+    /**
+     * Create the content node for a file node.
+     * 
+     * @param fileNode the file node
+     * @return the content node for this file node
+     * @throws RepositoryException if some error occurs
+     */
+    Node createContentNode(Node fileNode) throws RepositoryException {
+
+        NodeTypeInfo info = getTypeInfo(fileNode);
+        if (!(info instanceof FileTypeInfo)) {
+            throw new RepositoryException("Node type " + fileNode.getPrimaryNodeType().getName() + " is not a file type");
+        }
+
+        FileTypeInfo fileInfo = (FileTypeInfo) info;
+        Node contentNode = fileNode.addNode(fileInfo.contentPath, fileInfo.contentType);
+
+        return contentNode;
+    }
+
+    /**
+     * Get the content property for a given node
+     * 
+     * @param node a file or content node
+     * @return the content property
+     * @throws RepositoryException if some error occurs
+     */
+    Property getContentProperty(Node node) throws RepositoryException {
+        Node contentNode = getContentNode(node);
+        ContentTypeInfo info = (ContentTypeInfo) getTypeInfo(contentNode);
+        return contentNode.getProperty(info.contentProp);
+    }
+
+    /**
+     * Get the mime-type property for a given node
+     * 
+     * @param node a file or content node
+     * @return the mime-type property, or <code>null</code> if no such property exists
+     * @throws RepositoryException if some error occurs
+     */
+    Property getMimeTypeProperty(Node node) throws RepositoryException {
+        Node contentNode = getContentNode(node);
+        ContentTypeInfo info = (ContentTypeInfo) getTypeInfo(contentNode);
+
+        String propName = info.mimeTypeProp;
+        if (propName != null && contentNode.hasProperty(propName)) {
+            return contentNode.getProperty(propName);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get the lastmodified property for a given node
+     * 
+     * @param node a file or content node
+     * @return the lastmodified property, or <code>null</code> if no such property exists
+     * @throws RepositoryException if some error occurs
+     */
+    Property getLastModifiedDateProperty(Node node) throws RepositoryException {
+        Node contentNode = getContentNode(node);
+        ContentTypeInfo info = (ContentTypeInfo) getTypeInfo(contentNode);
+
+        String propName = info.lastModifiedProp;
+        if (propName != null && contentNode.hasProperty(propName)) {
+            return contentNode.getProperty(propName);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get the validity property for a given node
+     * 
+     * @param node a file or content node
+     * @return the validity property, or <code>null</code> if no such property exists
+     * @throws RepositoryException if some error occurs
+     */
+    Property getValidityProperty(Node node) throws RepositoryException {
+        Node contentNode = getContentNode(node);
+        ContentTypeInfo info = (ContentTypeInfo) getTypeInfo(contentNode);
+
+        String propName = info.validityProp;
+        if (propName != null && contentNode.hasProperty(propName)) {
+            return contentNode.getProperty(propName);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Does a node represent a collection (i.e. folder-node)?
+     * 
+     * @param node the node
+     * @return <code>true</code> if it's a collection
+     * @throws RepositoryException if some error occurs
+     */
+    boolean isCollection(Node node) throws RepositoryException {
+        return getTypeInfo(node) instanceof FolderTypeInfo;
+    }
+
+    /**
+     * Get the node type to create a new subfolder of a given folder node.
+     * 
+     * @param folderNode
+     * @return the child folder node type
+     * @throws RepositoryException if some error occurs
+     */
+    String getFolderNodeType(Node folderNode) throws RepositoryException {
+        FolderTypeInfo info = (FolderTypeInfo) getTypeInfo(folderNode);
+        return info.newFolderType;
+    }
+}

Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRSourceFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRSourceFactory.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/repository.xml
URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/repository.xml?rev=164365&view=auto
==============================================================================
--- cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/repository.xml (added)
+++ cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/repository.xml Sat Apr 23 00:56:42 2005
@@ -0,0 +1,237 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!-- <!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Repository//EN" "file://config.dtd"> -->
+<!DOCTYPE Repository [
+    <!--
+        the Repository element configures a repository instance;
+        individual workspaces of the repository are configured through
+        separate configuration files called workspace.xml which are
+        located in a subfolder of the workspaces root directory
+        (see Workspaces element).
+
+        it consists of
+
+            a FileSystem element (the virtual file system
+            used by the repository to persist global state such as
+            registered namespaces, custom node types, etc..
+
+            a Workspaces element that specifies to the location of
+            workspaces root directory and the name of default workspace
+
+            a Workspace element that is used as a workspace configuration
+            template; it is used to create the initial workspace if there's
+            no workspace yet and for creating additional workspaces through
+            the api
+
+            a SearchIndex element that is used for configuring per workspace
+            Indexing-related settings
+
+            a Versioning element that is used for configuring 
+            versioning-related settings
+    -->
+    <!ELEMENT Repository (FileSystem,Security,Workspaces,Workspace)>
+
+    <!--
+        a virtual file system
+    -->
+    <!ELEMENT FileSystem (param*)>
+    <!ATTLIST FileSystem
+      class CDATA #REQUIRED>
+
+    <!--
+        generic parameter (name/value pair)
+    -->
+    <!ELEMENT param EMPTY>
+    <!ATTLIST param
+      name CDATA #REQUIRED
+      value CDATA #REQUIRED>
+
+     <!--
+        the Security element specifies the name (appName attribute)
+        of the JAAS configuration app-entry for this repository. 
+
+        it also specifies the access manager to be used (AccessManager element).
+    -->
+    <!ELEMENT Security (AccessManager, LoginModule?)>
+    <!ATTLIST Security
+      appName CDATA #REQUIRED>
+
+    <!--
+        the AccessManager element configures the access manager to be used by
+        this repository instance; the class attribute specifies the FQN of the
+        class implementing the AccessManager interface
+    -->
+    <!ELEMENT AccessManager (param*)>
+    <!ATTLIST AccessManager
+      class CDATA #REQUIRED>
+
+     <!--
+        the LoginModule element optionally specifies a JAAS login module to
+        authenticate users. This feature allows the use of Jackrabbit in a
+        non-JAAS environment.
+    -->
+    <!ELEMENT LoginModule (param*)>
+    <!ATTLIST LoginModule
+      class CDATA #REQUIRED>
+
+    <!--
+        the Workspaces element specifies the workspaces root directory
+        (rootPath attribute) and the name of the default workspace
+        (defaultWorkspace attribute).
+
+        individual workspaces are configured through individual workspace.xml
+        files located in a subfolder each of the workspaces root directory.
+    -->
+    <!ELEMENT Workspaces EMPTY>
+    <!ATTLIST Workspaces
+      rootPath CDATA #REQUIRED
+      defaultWorkspace CDATA #REQUIRED>
+
+    <!--
+        the Workspace element serves as a workspace configuration template;
+        it is used to create the initial workspace if there's no workspace yet
+        and for creating additional workspaces through the api
+    -->
+    <!ELEMENT Workspace (FileSystem,PersistenceManager,SearchIndex?)>
+    <!ATTLIST Workspace
+      name CDATA #REQUIRED>
+
+    <!--
+        the PersistenceManager element configures the persistence manager
+        to be used for the workspace; the class attribute specifies the
+        FQN of the class implementing PersistenceManager interface
+    -->
+    <!ELEMENT PersistenceManager (param*)>
+    <!ATTLIST PersistenceManager
+      class CDATA #REQUIRED>
+
+    <!--
+        the SearchIndex element specifies the locaction of the search index
+        (used by the QueryHandler); the class attribute specifies the
+        FQN of the class implementing the QueryHandler interface.
+    -->
+    <!ELEMENT SearchIndex (param*,FileSystem)>
+    <!ATTLIST SearchIndex
+      class CDATA #REQUIRED>
+
+    <!--
+        the Versioning element configures the persistence manager
+        to be used for persisting version state
+    -->
+    <!ELEMENT Versioning (FileSystem, PersistenceManager)>
+    <!ATTLIST Versioning
+      rootPath CDATA #REQUIRED
+    >
+]>
+<!-- Example Repository Configuration File -->
+<Repository>
+    <!--
+        virtual file system where the repository stores global state
+        (e.g. registered namespaces, custom node types, etc.)
+    -->
+    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+        <param name="path" value="${rep.home}/repository"/>
+    </FileSystem>
+
+    <!--
+        security configuration
+    -->
+    <Security appName="Jackrabbit">
+        <!--
+            access manager:
+            class: FQN of class implementing the AccessManager interface
+        -->
+        <AccessManager class="org.apache.jackrabbit.core.security.SimpleAccessManager">
+            <!-- <param name="config" value="${rep.home}/access.xml"/> -->
+        </AccessManager>
+
+        <LoginModule class="org.apache.jackrabbit.core.security.SimpleLoginModule">
+           <!-- anonymous user name ('anonymous' is the default value) -->
+           <param name="anonymousId" value="anonymous"/>
+        </LoginModule>
+    </Security>
+
+    <!--
+        location of workspaces root directory and name of default workspace
+    -->
+    <Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default"/>
+    <!--
+        workspace configuration template:
+        used to create the initial workspace if there's no workspace yet
+    -->
+    <Workspace name="${wsp.name}">
+        <!--
+            virtual file system of the workspace:
+            class: FQN of class implementing FileSystem interface
+        -->
+        <!--
+        <FileSystem class="com.day.jackrabbit.fs.cq.CQFileSystem">
+            <param name="path" value="${wsp.home}/wspStore.dat"/>
+            <param name="autoRepair" value="false"/>
+            <param name="blockSize" value="128"/>
+            <param name="autoSync" value="false"/>
+        </FileSystem>
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${wsp.home}"/>
+        </FileSystem>
+        <!--
+            persistence manager of the workspace:
+            class: FQN of class implementing PersistenceManager interface
+        -->
+        <!--
+        <PersistenceManager class="org.apache.jackrabbit.core.state.xml.XMLPersistenceManager"/>
+        -->
+        <!--
+        <PersistenceManager class="org.apache.jackrabbit.core.state.mem.InMemPersistenceManager">
+            <param name="initialCapacity" value="100000"/>
+            <param name="loadFactor" value="0.3"/>
+            <param name="persistent" value="true"/>
+        </PersistenceManager>
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.state.obj.ObjectPersistenceManager"/>
+        <!--
+            Search index and the file system it uses.
+        -->
+        <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+            <param name="useCompoundFile" value="true"/>
+            <param name="minMergeDocs" value="1000"/>
+            <param name="maxMergeDocs" value="10000"/>
+            <param name="mergeFactor" value="10"/>
+
+            <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+                <param name="path" value="${wsp.home}/index"/>
+            </FileSystem>
+        </SearchIndex>
+    </Workspace>
+
+    <!--
+        Configures the versioning
+    -->
+    <Versioning rootPath="${rep.home}/version">
+        <!--
+            Configures the filesystem to use for versioning for the respective
+            persistence manager
+        -->
+        <!--
+        <FileSystem class="com.day.jackrabbit.fs.cq.CQFileSystem">
+            <param name="path" value="${rep.home}/version/version.dat"/>
+            <param name="autoRepair" value="false"/>
+            <param name="blockSize" value="128"/>
+            <param name="autoSync" value="false"/>
+        </FileSystem>
+        -->
+    
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${rep.home}/version"/>
+        </FileSystem>
+        
+        <!--
+            Configures the perisistence manager to be used for persisting version state.
+            Please note that the current versioning implementation is based on
+            a 'normal' persistence manager, but this could change in future
+            implementations.
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.state.obj.ObjectPersistenceManager"/>
+
+    </Versioning>
+</Repository>

Propchange: cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/repository.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/repository.xml
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTestCase.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTestCase.java?rev=164365&view=auto
==============================================================================
--- cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTestCase.java (added)
+++ cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTestCase.java Sat Apr 23 00:56:42 2005
@@ -0,0 +1,245 @@
+package org.apache.cocoon.jcr.source;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.avalon.framework.CascadingRuntimeException;
+import org.apache.avalon.framework.context.DefaultContext;
+import org.apache.avalon.framework.service.ServiceSelector;
+import org.apache.cocoon.core.container.ContainerTestCase;
+import org.apache.excalibur.source.ModifiableSource;
+import org.apache.excalibur.source.ModifiableTraversableSource;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceFactory;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.excalibur.source.SourceUtil;
+import org.apache.excalibur.source.TraversableSource;
+
+public class JCRSourceTestCase extends ContainerTestCase {
+    
+    private SourceResolver resolver;
+    
+    private File tempDir;
+    
+    protected void addContext(DefaultContext context) {
+        super.addContext(context);
+        // Create a temp file
+        try {
+            tempDir = File.createTempFile("jcr-test", null);
+        } catch (IOException e) {
+            throw new CascadingRuntimeException("Cannot setup temp dir", e);
+        }
+        // and turn it to a directory
+        tempDir.delete();
+        tempDir.mkdir();
+        tempDir.deleteOnExit();
+        
+        // Setup context root as the temp dir so that relative URI used in the 
+        // repository configuration go there
+        context.put("context-root", tempDir);
+        
+        // Make VariableResolver used in repository configuration happy
+        context.put("object-model", Collections.EMPTY_MAP);
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        resolver = (SourceResolver)getManager().lookup(SourceResolver.ROLE);
+    }
+    
+    private void write(ModifiableSource src, String text) throws Exception {
+        byte[] data = text.getBytes("ISO-8859-1");
+        OutputStream os = src.getOutputStream();
+        os.write(data);
+        os.close();
+    }
+    
+    private String read(Source src) throws Exception {
+        byte[] data = new byte[(int)src.getContentLength()];
+        InputStream is = src.getInputStream();
+        assertEquals(data.length, is.read(data));
+        is.close();
+        return new String(data, "ISO-8859-1");
+    }
+    
+    protected void deleteFile(File file) {
+        File[] children = file.listFiles();
+        if (children != null) {
+            for (int i = 0; i < children.length; i++) {
+                deleteFile(children[i]);
+            }
+        }
+        file.delete();
+    }
+    
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        deleteFile(tempDir);
+    }
+    
+    public void testJCRSourceInitialization() throws Exception {
+        ServiceSelector selector = (ServiceSelector)getManager().lookup(SourceFactory.ROLE + "Selector");
+        Object jcrSourceFactory = selector.select("jcr");
+
+        assertEquals("Wrong class name for jcr protocol", jcrSourceFactory.getClass(), JCRSourceFactory.class);
+    }
+    
+    public void testGetRootNode() throws Exception {
+        
+        JCRNodeSource source = (JCRNodeSource)resolver.resolveURI("jcr://");
+
+        assertTrue("Root node should exist", source.exists());
+        System.err.println("Root node type = " + source.getNode().getPrimaryNodeType().getName());
+        assertTrue("Root node should be a collection", source.isCollection());
+    }
+    
+    public void testCreateFirstLevelFile() throws Exception {
+        
+        String someText = "Some text";
+
+        JCRNodeSource root = (JCRNodeSource)resolver.resolveURI("jcr://");
+        
+        JCRNodeSource firstChild = (JCRNodeSource)root.getChild("child1");
+        
+        assertFalse(firstChild.exists());
+        assertEquals(firstChild.getURI(), "jcr://child1");
+        
+        write(firstChild, someText);
+        
+        assertTrue(firstChild.exists());
+        
+        // Check content
+        Source child1 = resolver.resolveURI("jcr://child1");
+        assertTrue(child1.exists());
+        
+        int len = (int)child1.getContentLength();
+        assertEquals(someText.length(), len);
+        assertEquals(someText, read(child1));
+        
+    }
+    
+    public void testCreateDeepFile() throws Exception {
+        String anotherText = "another text";
+        
+        JCRNodeSource source = (JCRNodeSource)resolver.resolveURI("jcr://some/deep/path/to/file");
+        assertFalse(source.exists());
+        
+        write(source, anotherText);
+        
+        // Lookup again, using the parent, doing some traversal
+        TraversableSource dir = (TraversableSource)resolver.resolveURI("jcr://some/deep");
+        assertTrue(dir.isCollection());
+        dir = (TraversableSource)dir.getChild("path");
+        assertTrue(dir.isCollection());
+        dir = (TraversableSource)dir.getChild("to");
+        assertTrue(dir.isCollection());
+        
+        source = (JCRNodeSource)dir.getChild("file");
+        assertTrue(source.exists());
+        
+        assertEquals(anotherText, read(source));
+    }
+    
+    public void testDeleteFile() throws Exception {
+        String text = "Yeah! Some content!";
+        ModifiableSource source = (ModifiableSource)resolver.resolveURI("jcr://yet/another/deep/file");
+        
+        assertFalse(source.exists());
+        write(source, text);
+        
+        // Lookup a fresh source
+        source = (ModifiableSource)resolver.resolveURI("jcr://yet/another/deep/file");
+        assertTrue(source.exists());
+        source.delete();
+        assertFalse(source.exists());
+        
+        // Lookup again to check it was really deleted
+        source = (ModifiableSource)resolver.resolveURI("jcr://yet/another/deep/file");
+        assertFalse(source.exists());
+    }
+    
+    public void testDeleteDir() throws Exception {
+        String text = "Wow, a lot of data going there";
+        ModifiableTraversableSource source = (ModifiableTraversableSource)resolver.resolveURI("jcr://and/again/a/deep/node");
+        
+        assertFalse(source.exists());
+        write(source, text);
+        
+        // Lookup 'a' node
+        source = (ModifiableTraversableSource)resolver.resolveURI("jcr://and/again/a/");
+        assertTrue(source.isCollection());
+        source.delete();
+        assertFalse(source.exists());
+        
+        // Double check with a fresh source
+        source = (ModifiableTraversableSource)resolver.resolveURI("jcr://and/again/a/");
+        assertFalse(source.exists());
+        
+        // Check on children
+        source = (ModifiableTraversableSource)resolver.resolveURI("jcr://and/again/a/deep/node");
+        assertFalse(source.exists());
+    }
+    
+    public void testTraverseDir() throws Exception {
+        String text = "Look Ma, more data!";
+        
+        ModifiableTraversableSource dir = (ModifiableTraversableSource)resolver.resolveURI("jcr://path/to/dir");
+        dir.makeCollection();
+        
+        for (int i = 0; i < 10; i++) {
+            ModifiableTraversableSource src = (ModifiableTraversableSource)dir.getChild("file" + i);
+            write(src, text + i);
+        }
+        
+        // Lookup dir again, and inspect children
+        dir = (ModifiableTraversableSource)resolver.resolveURI("jcr://path/to/dir");
+        Collection children = dir.getChildren();
+        
+        assertEquals(10, children.size());
+        
+        for (int i = 0; i < 10; i++) {
+            Source src = dir.getChild("file" + i);
+            assertTrue(src.exists());
+            assertEquals(text + i, read(src));
+        }
+    }
+    
+    public void testCrawlUp() throws Exception {
+        String text = "Look Pa, some more!";
+        
+        ModifiableTraversableSource src = (ModifiableTraversableSource)resolver.resolveURI("jcr://path/to/very/deep/content");
+        write(src, text);
+        
+        // Do a fresh lookup
+        src = (ModifiableTraversableSource)resolver.resolveURI("jcr://path/to/very/deep/content");
+        
+        ModifiableTraversableSource parent = (ModifiableTraversableSource)src.getParent();
+        assertTrue(parent.exists());
+        assertEquals("jcr://path/to/very/deep", parent.getURI());
+
+        parent = (ModifiableTraversableSource)parent.getParent();
+        assertTrue(parent.exists());
+        assertEquals("jcr://path/to/very", parent.getURI());
+
+        parent = (ModifiableTraversableSource)parent.getParent();
+        assertTrue(parent.exists());
+        assertEquals("jcr://path/to", parent.getURI());
+
+        parent = (ModifiableTraversableSource)parent.getParent();
+        assertTrue(parent.exists());
+        assertEquals("jcr://path", parent.getURI());
+        
+        parent = (ModifiableTraversableSource)parent.getParent();
+        assertTrue(parent.exists());        
+        assertEquals("jcr://", parent.getURI());
+        
+        // Root node has no parent
+        parent = (ModifiableTraversableSource)parent.getParent();
+        assertNull(parent);
+    }
+}

Propchange: cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTestCase.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTestCase.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTestCase.xtest
URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTestCase.xtest?rev=164365&view=auto
==============================================================================
--- cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTestCase.xtest (added)
+++ cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTestCase.xtest Sat Apr 23 00:56:42 2005
@@ -0,0 +1,45 @@
+<testcase>
+
+  <context/>
+  
+  <roles>
+    <role name="javax.jcr.Repository"
+          shorthand="jcr-repository"
+          default-class="org.apache.cocoon.jcr.JackrabbitRepository"/>
+          
+    <role name="org.apache.excalibur.source.SourceFactorySelector"
+          shorthand="source-factories"
+          default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector"/>
+
+    <role name="org.apache.excalibur.source.SourceResolver"
+          shorthand="source-resolver"
+          default-class="org.apache.excalibur.source.impl.SourceResolverImpl"/>
+  </roles>
+
+  <components>
+    <jcr-repository>
+      <!-- will use the temp directory setup by the testcase -->
+      <home src="test-repository"/>
+      <configuration src="resource://org/apache/cocoon/jcr/repository.xml"/>
+      <credentials login="super" password=""/>
+    </jcr-repository>
+    
+    <source-factories>
+     <component-instance class="org.apache.cocoon.jcr.source.JCRSourceFactory" name="jcr">
+       <folder-node type="rep:root"  new-file="nt:file" new-folder="nt:folder"/>
+       <folder-node type="nt:folder" new-file="nt:file"/>
+       <file-node type="nt:file" content-path="jcr:content" content-type="nt:resource"/>
+       <file-node type="nt:linkedFile" content-ref="jcr:content"/>
+       <content-node type="nt:resource"
+	     content-prop="jcr:data"
+	     mimetype-prop="jcr:mimeType"
+	     lastmodified-prop="jcr:lastModified"
+	     validity-prop="jcr:lastModified"/>
+     
+     </component-instance>
+     <component-instance class="org.apache.excalibur.source.impl.ResourceSourceFactory" name="resource"/>
+     <component-instance class="org.apache.excalibur.source.impl.URLSourceFactory" name="*"/>
+    </source-factories>
+
+  </components>
+</testcase>
\ No newline at end of file