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="/[EMAIL PROTECTED] != '2.1']|[EMAIL PROTECTED]'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> + * + * <jcr-repository> + * <credentials login="<i>expression</i>" password="<i>expression</i>"/> + * ... other specific configuration... + * </jcr-repository> + * + * </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><credentials></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> + * + * <jcr-repository> + * <credentials login="<i>expression</i>" password="<i>expression</i>"/> + * <home src="file://path/to/repository"/> + * <configuration src="resource://your/application/jcr/repository.xml"/> + * </jcr-repository> + * + * </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 [EMAIL PROTECTED] 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> + * + * <source-factories> + * <component 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> + * </source-factories> + * + * </pre> + * + * A <code><folder-node></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><file-node></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><content-node></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