Return-Path: Delivered-To: apmail-cocoon-cvs-archive@www.apache.org Received: (qmail 82444 invoked from network); 23 Apr 2005 07:56:49 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 23 Apr 2005 07:56:49 -0000 Received: (qmail 45706 invoked by uid 500); 23 Apr 2005 07:57:17 -0000 Delivered-To: apmail-cocoon-cvs-archive@cocoon.apache.org Received: (qmail 45675 invoked by uid 500); 23 Apr 2005 07:57:17 -0000 Mailing-List: contact cvs-help@cocoon.apache.org; run by ezmlm Precedence: bulk Reply-To: dev@cocoon.apache.org list-help: list-unsubscribe: List-Post: Delivered-To: mailing list cvs@cocoon.apache.org Received: (qmail 45656 invoked by uid 99); 23 Apr 2005 07:57:16 -0000 X-ASF-Spam-Status: No, hits=0.2 required=10.0 tests=NO_REAL_NAME X-Spam-Check-By: apache.org Received: from minotaur.apache.org (HELO minotaur.apache.org) (209.237.227.194) by apache.org (qpsmtpd/0.28) with SMTP; Sat, 23 Apr 2005 00:57:14 -0700 Received: (qmail 82417 invoked by uid 65534); 23 Apr 2005 07:56:44 -0000 Message-ID: <20050423075644.82416.qmail@minotaur.apache.org> Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Subject: 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/ Date: Sat, 23 Apr 2005 07:56:42 -0000 To: cvs@cocoon.apache.org From: sylvain@apache.org X-Mailer: svnmailer-1.0.0-dev X-Virus-Checked: Checked X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Author: sylvain Date: Sat Apr 23 00:56:42 2005 New Revision: 164365 URL: http://svn.apache.org/viewcvs?rev=3D164365&view=3Drev 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.j= ava (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 pr= ops) 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/JCRNodeSource= Validity.java (with props) cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRSourceFact= ory.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 (wi= th props) cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/ cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTest= Case.java (with props) cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTest= Case.xtest Added: cocoon/blocks/jcr/trunk/WEB-INF/xconf/jcr.xconf URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/WEB-INF/xconf/jc= r=2Exconf?rev=3D164365&view=3Dauto =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D --- 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 @@ + + + + + + \ No newline at end of file Propchange: cocoon/blocks/jcr/trunk/WEB-INF/xconf/jcr.xconf ---------------------------------------------------------------------------= --- svn:eol-style =3D native Propchange: cocoon/blocks/jcr/trunk/WEB-INF/xconf/jcr.xconf ---------------------------------------------------------------------------= --- svn:keywords =3D Id Added: cocoon/blocks/jcr/trunk/conf/jcr.xroles URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/conf/jcr.xroles?= rev=3D164365&view=3Dauto =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D --- 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 @@ + + + + + =20 + + + Added: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/AbstractRepositor= y=2Ejava URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/java/org/apache/= cocoon/jcr/AbstractRepository.java?rev=3D164365&view=3Dauto =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D --- cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/AbstractRepository.j= ava (added) +++ cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/AbstractRepository.j= ava Sat Apr 23 00:56:42 2005 @@ -0,0 +1,186 @@ +/* + * Copyright 1999-2005 The Apache Software Foundation. + *=20 + * 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 + *=20 + * http://www.apache.org/licenses/LICENSE-2.0 + *=20 + * 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.VariableResolv= er; +import org.apache.cocoon.components.treeprocessor.variables.VariableResolv= erFactory; +import org.apache.cocoon.sitemap.PatternException; + +/** + * Base class for JCR (aka JSR-170) reposit= ories as + * Cocoon components. The main purpose of this class is to allow repository + * credentials to be specified in the component's configuration, so that t= he + * application code just has to call repository.login(). + *

+ * There is no Cocoon-specific role for this component: "javax.jcr.R= epository" + * should be used. + *

+ * The configuration of this class, inherited by its subclasses, is as fol= lows: + *=20 + *

+ *=20
+ *    <jcr-repository>
+ *      <credentials login=3D"expression" password=3D"express=
ion"/>
+ *      ... other specific configuration...
+ *    </jcr-repository>
+ * =20
+ * 
+ *=20 + * Login and password can be specified using the sitemap expression langua= ge, + * thus allowing the use of input modules to compute their values, e.g. + * password=3D"{session-attr:jcr-password}" + *

+ * <credentials> is optional. If not specified, the + * application must explicitely supply credentials when calling + * Repository.login(). + *=20 + * @version $Id$ + */ +public class AbstractRepository implements Repository, Contextualizable, S= erviceable, 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 =3D null; + + protected VariableResolver passwordResolver =3D null; + + // =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + // Avalon lifecycle + // =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + + public void contextualize(Context context) throws ContextException { + this.context =3D context; + } + + public void service(ServiceManager manager) throws ServiceException { + this.manager =3D manager; + // this.variableFactory =3D + // (VariableResolverFactory)manager.lookup(VariableResolverFactory= .ROLE); + } + + public void configure(Configuration config) throws ConfigurationExcept= ion { + Configuration credentials =3D config.getChild("credentials", false= ); + if (credentials !=3D null) { + String login =3D credentials.getAttribute("login"); + String password =3D credentials.getAttribute("password"); + + try { + this.loginResolver =3D VariableResolverFactory.getResolver= (login, this.manager); + } catch (PatternException e) { + throw new ConfigurationException("Invalid expression for '= login' at " + credentials.getLocation(), e); + } + try { + this.passwordResolver =3D VariableResolverFactory.getResol= ver(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); + } + + // =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + // Repository interface + // =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + + public String getDescriptor(String key) { + return delegate.getDescriptor(key); + } + + public String[] getDescriptorKeys() { + return delegate.getDescriptorKeys(); + } + + public Session login() throws LoginException, NoSuchWorkspaceException= , RepositoryException { + Credentials creds =3D getCredentials(); + return creds =3D=3D null ? delegate.login() : delegate.login(creds= ); + } + + public Session login(Credentials creds) throws LoginException, NoSuchW= orkspaceException, RepositoryException { + return delegate.login(creds); + } + + public Session login(Credentials creds, String workspace) throws Login= Exception, NoSuchWorkspaceException, RepositoryException { + return delegate.login(creds, workspace); + } + + public Session login(String workspace) throws LoginException, NoSuchWo= rkspaceException, RepositoryException { + Credentials creds =3D getCredentials(); + return creds =3D=3D null ? delegate.login(workspace) : delegate.lo= gin(creds, workspace); + } + + // =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + + private Credentials getCredentials() throws LoginException { + if (this.loginResolver !=3D null) { + try { + Map objectModel =3D ContextHelper.getObjectModel(context); + String login =3D this.loginResolver.resolve(objectModel); + String password =3D 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/AbstractRepo= sitory.java ---------------------------------------------------------------------------= --- svn:eol-style =3D native Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/AbstractRepo= sitory.java ---------------------------------------------------------------------------= --- svn:keywords =3D Id Added: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/JackrabbitReposit= ory.java URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/java/org/apache/= cocoon/jcr/JackrabbitRepository.java?rev=3D164365&view=3Dauto =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D --- 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. + *=20 + * 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 + *=20 + * http://www.apache.org/licenses/LICENSE-2.0 + *=20 + * 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 Jackrabbit + *

+ * The configuration is as follows: + *=20 + *

+ *=20
+ *    <jcr-repository>
+ *      <credentials login=3D"expression" password=3D"express=
ion"/>
+ *      <home src=3D"file://path/to/repository"/>
+ *      <configuration src=3D"resource://your/application/jcr/repositor=
y=2Exml"/>
+ *    </jcr-repository>
+ * =20
+ * 
+ *=20 + * The home URI points to the base location of the repository, + * and configuration points to the Jackrabbit repository + * configuration file. + *=20 + * @see AbstractRepository + * @version $Id$ + */ +public class JackrabbitRepository extends AbstractRepository implements Co= nfigurable { + + public void configure(Configuration config) throws ConfigurationExcept= ion { + + super.configure(config); + + String homeURI =3D config.getChild("home").getAttribute("src"); + String homePath; + String configURI =3D config.getChild("configuration").getAttribute= ("src"); + + // having to release sources is a major PITA... + + try { + SourceResolver resolver =3D (SourceResolver) this.manager.look= up(SourceResolver.ROLE); + + // Ensure home uri is a file and absolutize it + Source homeSrc =3D resolver.resolveURI(homeURI); + if (homeSrc instanceof FileSource) { + homePath =3D ((FileSource) homeSrc).getFile().getAbsoluteP= ath(); + 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 =3D resolver.resolveURI(configURI); + InputSource is =3D SourceUtil.getInputSource(configSrc); + + RepositoryConfig repoConfig; + try { + repoConfig =3D RepositoryConfig.create(is, homePath); + } finally { + resolver.release(configSrc); + } + + // And create the repo + this.delegate =3D 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/JackrabbitRe= pository.java ---------------------------------------------------------------------------= --- svn:eol-style =3D native Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/JackrabbitRe= pository.java ---------------------------------------------------------------------------= --- svn:keywords =3D 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=3D164365&view=3Dauto =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D --- 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 @@ + + + + + + Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/jcr.roles ---------------------------------------------------------------------------= --- svn:eol-style =3D native Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/jcr.roles ---------------------------------------------------------------------------= --- svn:keywords =3D Id Added: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSou= rce.java URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/java/org/apache/= cocoon/jcr/source/JCRNodeSource.java?rev=3D164365&view=3Dauto =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D --- 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. + *=20 + * 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 + *=20 + * http://www.apache.org/licenses/LICENSE-2.0 + *=20 + * 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. + *=20 + * @version $Id$ + */ +public class JCRNodeSource implements Source, TraversableSource, Modifiabl= eTraversableSource { + + /** 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 =3D factory; + this.session =3D session; + this.path =3D path; + + try { + Item item =3D session.getItem(path); + if (!item.isNode()) { + throw new SourceException("Path '" + path + "' is a proper= ty (should be a node)"); + } else { + this.node =3D (Node) item; + } + } catch (PathNotFoundException e) { + // Not found + this.node =3D null; + } catch (RepositoryException e) { + throw new SourceException("Cannot lookup repository path " + p= ath, e); + } + } + + protected JCRNodeSource(JCRNodeSource parent, Node node) throws Source= Exception { + this.factory =3D parent.factory; + this.session =3D parent.session; + this.node =3D node; + + try { + this.path =3D getChildPath(parent.path, node.getName()); + + } catch (RepositoryException e) { + throw new SourceException("Cannot get name of child of " + par= ent.getURI(), e); + } + } + + private String getChildPath(String path, String name) { + StringBuffer pathBuf =3D 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 Node this source points to, or + * null if it denotes a non-existing path. + *=20 + * @return the JCR node. + */ + public Node getNode() { + return this.node; + } + + /** + * Returns the JCR Session used by this source. + *=20 + * @return the JCR session. + */ + public Session getSession() { + return this.session; + } + + /** + * Returns the JCR Node used to store the content of this + * source. + *=20 + * @return the JCR content node, or null if no such node + * exist, either because the source is a collection or doesn't + * currently contain data. + */ + public Node getContentNode() { + if (this.node =3D=3D null) { + return null; + } + + if (isCollection()) { + return null; + } + + try { + return this.factory.getContentNode(this.node); + } catch (RepositoryException e) { + return null; + } + } + + // =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + // Source interface + // =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + + /* + * (non-Javadoc) + *=20 + * @see org.apache.excalibur.source.Source#exists() + */ + public boolean exists() { + return this.node !=3D null; + } + + /* + * (non-Javadoc) + *=20 + * @see org.apache.excalibur.source.Source#getInputStream() + */ + public InputStream getInputStream() throws IOException, SourceNotFound= Exception { + if (this.node =3D=3D null) { + throw new SourceNotFoundException("Path '" + this.getURI() + "= ' does not exist"); + } + + if (this.isCollection()) { + throw new SourceException("Path '" + this.getURI() + "' is a c= ollection"); + } + + try { + Property contentProp =3D this.factory.getContentProperty(this.= node); + return contentProp.getStream(); + } catch (Exception e) { + throw new SourceException("Error opening stream for '" + this.= getURI() + "'", e); + } + } + + /* + * (non-Javadoc) + *=20 + * @see org.apache.excalibur.source.Source#getURI() + */ + public String getURI() { + if (this.computedURI =3D=3D null) { + this.computedURI =3D this.factory.getScheme() + ":/" + this.pa= th; + } + return this.computedURI; + } + + /* + * (non-Javadoc) + *=20 + * @see org.apache.excalibur.source.Source#getScheme() + */ + public String getScheme() { + return this.factory.getScheme(); + } + + /* + * (non-Javadoc) + *=20 + * @see org.apache.excalibur.source.Source#getValidity() + */ + public SourceValidity getValidity() { + try { + Property prop =3D this.factory.getValidityProperty(this.node); + return prop =3D=3D null ? null : new JCRNodeSourceValidity(pro= p=2EgetValue()); + } catch (RepositoryException re) { + return null; + } + } + + /* + * (non-Javadoc) + *=20 + * @see org.apache.excalibur.source.Source#refresh() + */ + public void refresh() { + // nothing to do here + } + + /* + * (non-Javadoc) + *=20 + * @see org.apache.excalibur.source.Source#getMimeType() + */ + public String getMimeType() { + try { + Property prop =3D this.factory.getMimeTypeProperty(this.node); + if (prop =3D=3D null) { + return null; + } else { + String value =3D prop.getString(); + return value.length() =3D=3D 0 ? null : value; + } + } catch (RepositoryException re) { + return null; + } + } + + /* + * (non-Javadoc) + *=20 + * @see org.apache.excalibur.source.Source#getContentLength() + */ + public long getContentLength() { + if (this.node =3D=3D null) { + return -1; + } + try { + Property prop =3D this.factory.getContentProperty(this.node); + return prop =3D=3D null ? -1 : prop.getLength(); + } catch (RepositoryException re) { + return -1; + } + } + + /* + * (non-Javadoc) + *=20 + * @see org.apache.excalibur.source.Source#getLastModified() + */ + public long getLastModified() { + try { + Property prop =3D this.factory.getLastModifiedDateProperty(thi= s=2Enode); + return prop =3D=3D null ? 0 : prop.getDate().getTimeInMillis(); + } catch (RepositoryException re) { + return 0; + } + } + + // =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + // TraversableSource interface + // =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + + 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 =3D new ArrayList(); + + NodeIterator nodes; + try { + nodes =3D 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, getChildP= ath(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() =3D=3D 1) { + // Root + return null; + } + + int lastPos =3D this.path.lastIndexOf('/'); + String parentPath =3D lastPos =3D=3D 0 ? "/" : this.path.substring= (0, lastPos); + return new JCRNodeSource(this.factory, this.session, parentPath); + } + + // =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + // ModifiableTraversableSource interface + // =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + + public OutputStream getOutputStream() throws IOException { + if (isCollection()) { + throw new SourceException("Cannot write to collection " + this= .getURI()); + } + + try { + Node contentNode; + if (!exists()) { + JCRNodeSource parent =3D (JCRNodeSource) getParent(); + + // Create the path if it doesn't exist + parent.makeCollection(); + + // Create our node + this.node =3D this.factory.createFileNode(parent.node, get= Name()); + contentNode =3D this.factory.createContentNode(this.node); + } else { + contentNode =3D 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 =3D 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 e= xisting node at " + getURI()); + } + } else { + try { + // Ensure parent exists + JCRNodeSource parent =3D (JCRNodeSource) getParent(); + if (parent =3D=3D null) { + throw new RuntimeException("Problem: root node does no= t exist!!"); + } + parent.makeCollection(); + Node parentNode =3D parent.node; + + String typeName =3D this.factory.getFolderNodeType(parentN= ode); + + this.node =3D 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 =3D false; + + private final Node contentNode; + + public JCRSourceOutputStream(Node contentNode) { + this.contentNode =3D contentNode; + } + + public void close() throws IOException { + if (!isClosed) { + super.close(); + this.isClosed =3D true; + try { + JCRSourceFactory.ContentTypeInfo info =3D (JCRSourceFa= ctory.ContentTypeInfo) factory.getTypeInfo(contentNode); + contentNode.setProperty(info.contentProp, new ByteArra= yInputStream(this.toByteArray())); + if (info.lastModifiedProp !=3D null) { + contentNode.setProperty(info.lastModifiedProp, new= GregorianCalendar()); + } + if (info.mimeTypeProp !=3D 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 : outputstr= em is already closed"); + } + } + } +} Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNo= deSource.java ---------------------------------------------------------------------------= --- svn:eol-style =3D native Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNo= deSource.java ---------------------------------------------------------------------------= --- svn:keywords =3D Id Added: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSou= rceValidity.java URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/java/org/apache/= cocoon/jcr/source/JCRNodeSourceValidity.java?rev=3D164365&view=3Dauto =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D --- cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSource= Validity.java (added) +++ cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNodeSource= Validity.java Sat Apr 23 00:56:42 2005 @@ -0,0 +1,50 @@ +/* + * Copyright 1999-2005 The Apache Software Foundation. + *=20 + * 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 + *=20 + * http://www.apache.org/licenses/LICENSE-2.0 + *=20 + * 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 + * Value. + *=20 + * @version $Id$ + */ +public class JCRNodeSourceValidity implements SourceValidity { + + private Value value; + + public JCRNodeSourceValidity(Value value) { + this.value =3D 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/JCRNo= deSourceValidity.java ---------------------------------------------------------------------------= --- svn:eol-style =3D native Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRNo= deSourceValidity.java ---------------------------------------------------------------------------= --- svn:keywords =3D Id Added: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRSourceF= actory.java URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/java/org/apache/= cocoon/jcr/source/JCRSourceFactory.java?rev=3D164365&view=3Dauto =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D --- cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRSourceFact= ory.java (added) +++ cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRSourceFact= ory.java Sat Apr 23 00:56:42 2005 @@ -0,0 +1,452 @@ +/* + * Copyright 1999-2005 The Apache Software Foundation. + *=20 + * 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 + *=20 + * http://www.apache.org/licenses/LICENSE-2.0 + *=20 + * 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 + * ModifiableTraversableSource on top of a JCR (aka JSR-170) reposit= ory. + *

+ * Since JCR allows a repository to define its own node types, it is neces= sary + * 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 d= ata. + *

+ * A typical configuration for a naked Jackrabbit repository is as follows: + *=20 + *

+ *=20
+ *    <source-factories>
+ *      <component class=3D"org.apache.cocoon.jcr.source.JCRSource=
Factory" name=3D"jcr">
+ *        <folder-node type=3D"rep:root"  new-file=3D"nt=
:file" new-folder=3D"nt:folder"/>
+ *        <folder-node type=3D"nt:folder" new-file=3D"nt=
:file"/>
+ *        <file-node type=3D"nt:file" content-path=3D"jc=
r:content" content-type=3D"nt:resource"/>
+ *        <file-node type=3D"nt:linkedFile" content-ref=3D&qu=
ot;jcr:content"/>
+ *        <content-node type=3D"nt:resource"
+ *                      content-prop=3D"jcr:data"
+ *                      mimetype-prop=3D"jcr:mimeType"
+ *                      lastmodified-prop=3D"jcr:lastModified"
+ *                      validity-prop=3D"jcr:lastModified"/>
+ *      </component>
+ *    </source-factories>
+ * =20
+ * 
+ *=20 + * A <folder-node> defines a node type that is mapped t= o a + * non-terminal source (i.e. that can have children). The new-file + * and new-folder attributes respectively define what node ty= pes + * should be used to create a new terminal and non-terminal source. + *

+ * A <file-node> defines a note type that is mapped to a + * terminal source (i.e. that can have some content). The + * content-path attribute defines the path to the node's child + * that actually holds the content, and content-type defines = the + * type of this content node. + *

+ * The content-ref attribute is used to comply with JCR's + * nt:linkedFile 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. + *

+ * A <content-node> defines a node type that actually h= olds + * the content of a file-node. The content-prop + * attribute must be present and gives the name of the node's binary prope= rty + * that will hold the actual content. Other attributes are optional: + *

    + *
  • mimetype-prop defines a string property holding the + * content's MIME type,
  • + *
  • lastmodified-prop defines a date property holding the + * node's last modification date. It is automatically updated when content= is + * written to the content-node.
  • + *
  • validity-prop defines a property that gives the validi= ty + * of the content, used by Cocoon's cache. If not specified, + * lastmodified-prop is used, if present. Otherwise the source + * has no validity and won't be cacheable.
  • + *
+ *

+ * 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. + *=20 + * @version $Id$ + */ +public class JCRSourceFactory implements SourceFactory, Configurable, Serv= iceable { + + 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 configurati= on + */ + private Map typeInfos; + + private ServiceManager manager; + + public void service(ServiceManager manager) throws ServiceException { + this.manager =3D manager; + this.repo =3D (Repository) this.manager.lookup(Repository.class.ge= tName()); + } + + public void configure(Configuration config) throws ConfigurationExcept= ion { + this.typeInfos =3D new HashMap(); + + Configuration[] children =3D config.getChildren(); + + for (int i =3D 0; i < children.length; i++) { + Configuration child =3D children[i]; + String name =3D child.getName(); + + if ("folder-node".equals(name)) { + FolderTypeInfo info =3D new FolderTypeInfo(); + String type =3D child.getAttribute("type"); + info.newFileType =3D child.getAttribute("new-file"); + info.newFolderType =3D child.getAttribute("new-folder", ty= pe); + + this.typeInfos.put(type, info); + + } else if ("file-node".equals(name)) { + FileTypeInfo info =3D new FileTypeInfo(); + info.contentPath =3D child.getAttribute("content-path", nu= ll); + info.contentType =3D child.getAttribute("content-type", nu= ll); + info.contentRef =3D child.getAttribute("content-ref", null= ); + if (info.contentPath =3D=3D null && info.contentRef =3D=3D= null) { + throw new ConfigurationException("One of content-path = or content-ref is required at " + child.getLocation()); + } + if (info.contentPath !=3D null && info.contentType =3D=3D = 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 =3D new ContentTypeInfo(); + info.contentProp =3D child.getAttribute("content-prop"); + info.lastModifiedProp =3D child.getAttribute("lastmodified= -prop", null); + info.mimeTypeProp =3D child.getAttribute("mimetype-prop", = null); + info.validityProp =3D 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) + *=20 + * @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 =3D=3D null) { + this.scheme =3D SourceUtil.getScheme(uri); + } + + Session session; + try { + // TODO: accept a different workspace? + session =3D 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 =3D SourceUtil.getSpecificPart(uri); + if (!path.startsWith("//")) { + throw new MalformedURLException("Expecting " + this.scheme + "= ://path and got " + uri); + } + // Remove first '/' + path =3D path.substring(1); + int pathLen =3D path.length(); + if (pathLen > 1) { + // Not root: ensure there's no trailing '/' + if (path.charAt(pathLen - 1) =3D=3D '/') { + path =3D path.substring(0, pathLen - 1); + } + } + + return new JCRNodeSource(this, session, path); + } + + /* + * (non-Javadoc) + *=20 + * @see org.apache.excalibur.source.SourceFactory#release(org.apache.e= xcalibur.source.Source) + */ + public void release(Source source) { + // nothing + } + + String getScheme() { + return this.scheme; + } + + /** + * Get the type info for a node. + *=20 + * @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 =3D node.getPrimaryNodeType().getName(); + NodeTypeInfo result =3D (NodeTypeInfo) this.typeInfos.get(typeName= ); + if (result =3D=3D null) { + // TODO: build a NodeTypeInfo using introspection + throw new RepositoryException("No type info found for node typ= e '" + 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 =3D (NodeTypeInfo) this.typeInfos.get(typeName= ); + if (result =3D=3D null) { + // TODO: build a NodeTypeInfo using introspection + throw new RepositoryException("No type info found for node typ= e '" + typeName + "'"); + } + + return result; + } + + /** + * Get the content node for a given node + *=20 + * @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 n= ode isn't a file node or a content node + */ + Node getContentNode(Node node) throws RepositoryException { + NodeTypeInfo info =3D getTypeInfo(node); + + if (info instanceof ContentTypeInfo) { + return node; + + } else if (info instanceof FileTypeInfo) { + FileTypeInfo finfo =3D (FileTypeInfo) info; + if (".".equals(finfo.contentPath)) { + return node; + } else if (finfo.contentPath !=3D null) { + return node.getNode(finfo.contentPath); + } else { + Property ref =3D node.getProperty(finfo.contentRef); + return getContentNode(ref.getNode()); + } + } else { + // A folder + throw new RepositoryException("Can't get content node for fold= er node at " + node.getPath()); + } + } + + /** + * Create a child file node in a folder node. + *=20 + * @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 RepositoryExc= eption { + NodeTypeInfo info =3D getTypeInfo(folderNode); + if (!(info instanceof FolderTypeInfo)) { + throw new RepositoryException("Node type " + folderNode.getPri= maryNodeType().getName() + " is not a folder type"); + } + + FolderTypeInfo folderInfo =3D (FolderTypeInfo) info; + return folderNode.addNode(name, folderInfo.newFileType); + } + + /** + * Create the content node for a file node. + *=20 + * @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 =3D getTypeInfo(fileNode); + if (!(info instanceof FileTypeInfo)) { + throw new RepositoryException("Node type " + fileNode.getPrima= ryNodeType().getName() + " is not a file type"); + } + + FileTypeInfo fileInfo =3D (FileTypeInfo) info; + Node contentNode =3D fileNode.addNode(fileInfo.contentPath, fileIn= fo.contentType); + + return contentNode; + } + + /** + * Get the content property for a given node + *=20 + * @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 =3D getContentNode(node); + ContentTypeInfo info =3D (ContentTypeInfo) getTypeInfo(contentNode= ); + return contentNode.getProperty(info.contentProp); + } + + /** + * Get the mime-type property for a given node + *=20 + * @param node a file or content node + * @return the mime-type property, or null if no such pro= perty exists + * @throws RepositoryException if some error occurs + */ + Property getMimeTypeProperty(Node node) throws RepositoryException { + Node contentNode =3D getContentNode(node); + ContentTypeInfo info =3D (ContentTypeInfo) getTypeInfo(contentNode= ); + + String propName =3D info.mimeTypeProp; + if (propName !=3D null && contentNode.hasProperty(propName)) { + return contentNode.getProperty(propName); + } else { + return null; + } + } + + /** + * Get the lastmodified property for a given node + *=20 + * @param node a file or content node + * @return the lastmodified property, or null if no such = property exists + * @throws RepositoryException if some error occurs + */ + Property getLastModifiedDateProperty(Node node) throws RepositoryExcep= tion { + Node contentNode =3D getContentNode(node); + ContentTypeInfo info =3D (ContentTypeInfo) getTypeInfo(contentNode= ); + + String propName =3D info.lastModifiedProp; + if (propName !=3D null && contentNode.hasProperty(propName)) { + return contentNode.getProperty(propName); + } else { + return null; + } + } + + /** + * Get the validity property for a given node + *=20 + * @param node a file or content node + * @return the validity property, or null if no such prop= erty exists + * @throws RepositoryException if some error occurs + */ + Property getValidityProperty(Node node) throws RepositoryException { + Node contentNode =3D getContentNode(node); + ContentTypeInfo info =3D (ContentTypeInfo) getTypeInfo(contentNode= ); + + String propName =3D info.validityProp; + if (propName !=3D null && contentNode.hasProperty(propName)) { + return contentNode.getProperty(propName); + } else { + return null; + } + } + + /** + * Does a node represent a collection (i.e. folder-node)? + *=20 + * @param node the node + * @return true 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. + *=20 + * @param folderNode + * @return the child folder node type + * @throws RepositoryException if some error occurs + */ + String getFolderNodeType(Node folderNode) throws RepositoryException { + FolderTypeInfo info =3D (FolderTypeInfo) getTypeInfo(folderNode); + return info.newFolderType; + } +} Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRSo= urceFactory.java ---------------------------------------------------------------------------= --- svn:eol-style =3D native Propchange: cocoon/blocks/jcr/trunk/java/org/apache/cocoon/jcr/source/JCRSo= urceFactory.java ---------------------------------------------------------------------------= --- svn:keywords =3D 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=3D164365&view=3Dauto =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D --- cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/repository.xml (adde= d) +++ cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/repository.xml Sat A= pr 23 00:56:42 2005 @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + =20 + + + + =20 + + + + + Propchange: cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/repository.x= ml ---------------------------------------------------------------------------= --- svn:eol-style =3D native Propchange: cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/repository.x= ml ---------------------------------------------------------------------------= --- svn:keywords =3D Id Added: cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceT= estCase.java URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/test/org/apache/= cocoon/jcr/source/JCRSourceTestCase.java?rev=3D164365&view=3Dauto =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D --- cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTest= Case.java (added) +++ cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTest= Case.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 { + =20 + private SourceResolver resolver; + =20 + private File tempDir; + =20 + protected void addContext(DefaultContext context) { + super.addContext(context); + // Create a temp file + try { + tempDir =3D 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(); + =20 + // Setup context root as the temp dir so that relative URI used in= the=20 + // repository configuration go there + context.put("context-root", tempDir); + =20 + // Make VariableResolver used in repository configuration happy + context.put("object-model", Collections.EMPTY_MAP); + } + + protected void setUp() throws Exception { + super.setUp(); + resolver =3D (SourceResolver)getManager().lookup(SourceResolver.RO= LE); + } + =20 + private void write(ModifiableSource src, String text) throws Exception= { + byte[] data =3D text.getBytes("ISO-8859-1"); + OutputStream os =3D src.getOutputStream(); + os.write(data); + os.close(); + } + =20 + private String read(Source src) throws Exception { + byte[] data =3D new byte[(int)src.getContentLength()]; + InputStream is =3D src.getInputStream(); + assertEquals(data.length, is.read(data)); + is.close(); + return new String(data, "ISO-8859-1"); + } + =20 + protected void deleteFile(File file) { + File[] children =3D file.listFiles(); + if (children !=3D null) { + for (int i =3D 0; i < children.length; i++) { + deleteFile(children[i]); + } + } + file.delete(); + } + =20 + protected void tearDown() throws Exception { + super.tearDown(); + deleteFile(tempDir); + } + =20 + public void testJCRSourceInitialization() throws Exception { + ServiceSelector selector =3D (ServiceSelector)getManager().lookup(= SourceFactory.ROLE + "Selector"); + Object jcrSourceFactory =3D selector.select("jcr"); + + assertEquals("Wrong class name for jcr protocol", jcrSourceFactory= .getClass(), JCRSourceFactory.class); + } + =20 + public void testGetRootNode() throws Exception { + =20 + JCRNodeSource source =3D (JCRNodeSource)resolver.resolveURI("jcr:/= /"); + + assertTrue("Root node should exist", source.exists()); + System.err.println("Root node type =3D " + source.getNode().getPri= maryNodeType().getName()); + assertTrue("Root node should be a collection", source.isCollection= ()); + } + =20 + public void testCreateFirstLevelFile() throws Exception { + =20 + String someText =3D "Some text"; + + JCRNodeSource root =3D (JCRNodeSource)resolver.resolveURI("jcr://"= ); + =20 + JCRNodeSource firstChild =3D (JCRNodeSource)root.getChild("child1"= ); + =20 + assertFalse(firstChild.exists()); + assertEquals(firstChild.getURI(), "jcr://child1"); + =20 + write(firstChild, someText); + =20 + assertTrue(firstChild.exists()); + =20 + // Check content + Source child1 =3D resolver.resolveURI("jcr://child1"); + assertTrue(child1.exists()); + =20 + int len =3D (int)child1.getContentLength(); + assertEquals(someText.length(), len); + assertEquals(someText, read(child1)); + =20 + } + =20 + public void testCreateDeepFile() throws Exception { + String anotherText =3D "another text"; + =20 + JCRNodeSource source =3D (JCRNodeSource)resolver.resolveURI("jcr:/= /some/deep/path/to/file"); + assertFalse(source.exists()); + =20 + write(source, anotherText); + =20 + // Lookup again, using the parent, doing some traversal + TraversableSource dir =3D (TraversableSource)resolver.resolveURI("= jcr://some/deep"); + assertTrue(dir.isCollection()); + dir =3D (TraversableSource)dir.getChild("path"); + assertTrue(dir.isCollection()); + dir =3D (TraversableSource)dir.getChild("to"); + assertTrue(dir.isCollection()); + =20 + source =3D (JCRNodeSource)dir.getChild("file"); + assertTrue(source.exists()); + =20 + assertEquals(anotherText, read(source)); + } + =20 + public void testDeleteFile() throws Exception { + String text =3D "Yeah! Some content!"; + ModifiableSource source =3D (ModifiableSource)resolver.resolveURI(= "jcr://yet/another/deep/file"); + =20 + assertFalse(source.exists()); + write(source, text); + =20 + // Lookup a fresh source + source =3D (ModifiableSource)resolver.resolveURI("jcr://yet/anothe= r/deep/file"); + assertTrue(source.exists()); + source.delete(); + assertFalse(source.exists()); + =20 + // Lookup again to check it was really deleted + source =3D (ModifiableSource)resolver.resolveURI("jcr://yet/anothe= r/deep/file"); + assertFalse(source.exists()); + } + =20 + public void testDeleteDir() throws Exception { + String text =3D "Wow, a lot of data going there"; + ModifiableTraversableSource source =3D (ModifiableTraversableSourc= e)resolver.resolveURI("jcr://and/again/a/deep/node"); + =20 + assertFalse(source.exists()); + write(source, text); + =20 + // Lookup 'a' node + source =3D (ModifiableTraversableSource)resolver.resolveURI("jcr:/= /and/again/a/"); + assertTrue(source.isCollection()); + source.delete(); + assertFalse(source.exists()); + =20 + // Double check with a fresh source + source =3D (ModifiableTraversableSource)resolver.resolveURI("jcr:/= /and/again/a/"); + assertFalse(source.exists()); + =20 + // Check on children + source =3D (ModifiableTraversableSource)resolver.resolveURI("jcr:/= /and/again/a/deep/node"); + assertFalse(source.exists()); + } + =20 + public void testTraverseDir() throws Exception { + String text =3D "Look Ma, more data!"; + =20 + ModifiableTraversableSource dir =3D (ModifiableTraversableSource)r= esolver.resolveURI("jcr://path/to/dir"); + dir.makeCollection(); + =20 + for (int i =3D 0; i < 10; i++) { + ModifiableTraversableSource src =3D (ModifiableTraversableSour= ce)dir.getChild("file" + i); + write(src, text + i); + } + =20 + // Lookup dir again, and inspect children + dir =3D (ModifiableTraversableSource)resolver.resolveURI("jcr://pa= th/to/dir"); + Collection children =3D dir.getChildren(); + =20 + assertEquals(10, children.size()); + =20 + for (int i =3D 0; i < 10; i++) { + Source src =3D dir.getChild("file" + i); + assertTrue(src.exists()); + assertEquals(text + i, read(src)); + } + } + =20 + public void testCrawlUp() throws Exception { + String text =3D "Look Pa, some more!"; + =20 + ModifiableTraversableSource src =3D (ModifiableTraversableSource)r= esolver.resolveURI("jcr://path/to/very/deep/content"); + write(src, text); + =20 + // Do a fresh lookup + src =3D (ModifiableTraversableSource)resolver.resolveURI("jcr://pa= th/to/very/deep/content"); + =20 + ModifiableTraversableSource parent =3D (ModifiableTraversableSourc= e)src.getParent(); + assertTrue(parent.exists()); + assertEquals("jcr://path/to/very/deep", parent.getURI()); + + parent =3D (ModifiableTraversableSource)parent.getParent(); + assertTrue(parent.exists()); + assertEquals("jcr://path/to/very", parent.getURI()); + + parent =3D (ModifiableTraversableSource)parent.getParent(); + assertTrue(parent.exists()); + assertEquals("jcr://path/to", parent.getURI()); + + parent =3D (ModifiableTraversableSource)parent.getParent(); + assertTrue(parent.exists()); + assertEquals("jcr://path", parent.getURI()); + =20 + parent =3D (ModifiableTraversableSource)parent.getParent(); + assertTrue(parent.exists()); =20 + assertEquals("jcr://", parent.getURI()); + =20 + // Root node has no parent + parent =3D (ModifiableTraversableSource)parent.getParent(); + assertNull(parent); + } +} Propchange: cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSo= urceTestCase.java ---------------------------------------------------------------------------= --- svn:eol-style =3D native Propchange: cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSo= urceTestCase.java ---------------------------------------------------------------------------= --- svn:keywords =3D Id Added: cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceT= estCase.xtest URL: http://svn.apache.org/viewcvs/cocoon/blocks/jcr/trunk/test/org/apache/= cocoon/jcr/source/JCRSourceTestCase.xtest?rev=3D164365&view=3Dauto =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D --- cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTest= Case.xtest (added) +++ cocoon/blocks/jcr/trunk/test/org/apache/cocoon/jcr/source/JCRSourceTest= Case.xtest Sat Apr 23 00:56:42 2005 @@ -0,0 +1,45 @@ + + + + =20 + + + =20 + + + + + + + + + + + + + =20 + + + + + + + + =20 + + + + + + + \ No newline at end of file