- add moved class to core-cocoon

git-svn-id: https://svn.apache.org/repos/asf/lenya/trunk@1034327 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.lenya.core.cocoon/pom.xml b/org.apache.lenya.core.cocoon/pom.xml
index 844ce91..0b3fb15 100644
--- a/org.apache.lenya.core.cocoon/pom.xml
+++ b/org.apache.lenya.core.cocoon/pom.xml
@@ -16,7 +16,14 @@
   <description>Contains core cocoon components defined for Lenya</description>
   
   <dependencies>
-  
+  <dependency>
+      <groupId>org.apache.lenya</groupId>
+      <artifactId>lenya-core-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.lenya</groupId>
+      <artifactId>lenya-core-impl</artifactId>
+    </dependency>
   <!-- TODO : see if all theses dependencies are required -->
     <dependency>
       <groupId>org.apache.cocoon</groupId>
diff --git a/org.apache.lenya.core.cocoon/rcl.properties b/org.apache.lenya.core.cocoon/rcl.properties
index 711c321..8fe3e22 100644
--- a/org.apache.lenya.core.cocoon/rcl.properties
+++ b/org.apache.lenya.core.cocoon/rcl.properties
@@ -14,4 +14,4 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-org.apache.lenya.lenya-core-cocoon-components.service%classes-dir=./target/classes
\ No newline at end of file
+org.apache.lenya.core.cocoon.block%classes-dir=./target/classes
\ No newline at end of file
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/DiscoverCheckoutAction.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/DiscoverCheckoutAction.java
new file mode 100644
index 0000000..5996053
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/DiscoverCheckoutAction.java
@@ -0,0 +1,53 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one or more

+ *  contributor license agreements.  See the NOTICE file distributed with

+ *  this work for additional information regarding copyright ownership.

+ *  The ASF licenses this file to You under the Apache License, Version 2.0

+ *  (the "License"); you may not use this file except in compliance with

+ *  the License.  You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ *  Unless required by applicable law or agreed to in writing, software

+ *  distributed under the License is distributed on an "AS IS" BASIS,

+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ *  See the License for the specific language governing permissions and

+ *  limitations under the License.

+ *

+ */

+

+package org.apache.lenya.cms.cocoon.acting;

+

+import java.util.HashMap;

+import java.util.Map;

+

+import org.apache.avalon.framework.parameters.Parameters;

+import org.apache.cocoon.environment.Redirector;

+import org.apache.cocoon.environment.SourceResolver;

+import org.apache.lenya.cms.publication.Document;

+

+

+/**

+ * An action that tests if a document is already checked out by a given user.

+ * If it isn't, a check out will be tried.

+ */

+public class DiscoverCheckoutAction extends RevisionControllerAction {

+

+    /**

+	 * @see org.apache.cocoon.acting.Action#act(org.apache.cocoon.environment.Redirector, org.apache.cocoon.environment.SourceResolver, java.util.Map, java.lang.String, org.apache.avalon.framework.parameters.Parameters)

+	 */

+	public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String src,

+        Parameters parameters) throws Exception {

+        super.act(redirector, resolver, objectModel, src, parameters);

+

+        HashMap actionMap = new HashMap();

+        

+        Document document = getDocument();

+        if (document.isCheckedOut()) {

+            actionMap.put("filename", document.getSourceURI());

+            actionMap.put("user", document.getCheckoutUserId());

+            return actionMap;

+        }

+        return null;

+    }

+}

diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/LanguageExistsAction.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/LanguageExistsAction.java
new file mode 100644
index 0000000..b927992
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/LanguageExistsAction.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.cms.cocoon.acting;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.cocoon.acting.AbstractAction;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.SourceResolver;
+import org.apache.lenya.cms.publication.Repository;
+import org.apache.lenya.cms.publication.Session;
+import org.apache.lenya.util.ServletHelper;
+
+/**
+ * Action that checks if the current URL represents an existing document.
+ */
+public class LanguageExistsAction extends AbstractAction {
+    
+    private Repository repository;
+
+    /**
+     * Check if the current URL represents an existing document.
+     * @return an empty <code>Map</code> if there is a version of this document for the current
+     *         language, <code>null</code> otherwise.
+     * @throws Exception if an error occurs
+     */
+    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source,
+            Parameters parameters) throws Exception {
+
+        Request request = ObjectModelHelper.getRequest(objectModel);
+        Session session = this.repository.getSession(request);
+
+        String url = ServletHelper.getWebappURI(request);
+        if (session.getUriHandler().isDocument(url)) {
+            return Collections.unmodifiableMap(Collections.EMPTY_MAP);
+        }
+        else {
+            return null;
+        }
+    }
+
+    public void setRepository(Repository repository) {
+        this.repository = repository;
+    }
+
+    public Repository getRepository() {
+        return repository;
+    }
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/ReservedCheckinAction.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/ReservedCheckinAction.java
new file mode 100644
index 0000000..d776919
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/ReservedCheckinAction.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.cms.cocoon.acting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.SourceResolver;
+import org.apache.lenya.ac.Identity;
+import org.apache.lenya.cms.publication.Document;
+import org.apache.lenya.cms.publication.Repository;
+import org.apache.lenya.cms.publication.Session;
+import org.apache.lenya.util.ServletHelper;
+
+/**
+ * Checkin document
+ */
+public class ReservedCheckinAction extends RevisionControllerAction {
+    
+    protected Repository repository;
+    
+    /**
+     * Checkin document
+     * @return HashMap with checkin parameters
+     * @see org.apache.cocoon.acting.Action#act(org.apache.cocoon.environment.Redirector,
+     *      org.apache.cocoon.environment.SourceResolver, java.util.Map, java.lang.String,
+     *      org.apache.avalon.framework.parameters.Parameters)
+     */
+    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String src,
+            Parameters parameters) throws Exception {
+        super.act(redirector, resolver, objectModel, src, parameters);
+
+        try {
+            Request request = ObjectModelHelper.getRequest(objectModel);
+            Identity identity = (Identity) request.getSession().getAttribute(Identity.class.getName());
+            Session session = this.repository.startSession(identity, true);
+            
+            String url = ServletHelper.getWebappURI(request);
+            if (session.getUriHandler().isDocument(url)) {
+                Document document = session.getUriHandler().getDocument(url);
+                if (document.isCheckedOutBySession(session.getId(), session.getIdentity().getUser().getId())) {
+                    document.checkin();
+                }
+            }
+            else {
+                throw new RuntimeException("The URL [" + url + "] doesn't represent a document.");
+            }
+            
+        } catch (final Exception e) {
+            getLogger().error("Could not check in node: ", e);
+            Map actionMap = new HashMap();
+            actionMap.put("exception", "genericException");
+            actionMap.put("filename", getDocument().getSourceURI());
+            actionMap.put("message", e.getMessage());
+            return actionMap;
+        }
+
+        return null;
+    }
+
+    public void setRepository(Repository repository) {
+        this.repository = repository;
+    }
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/ReservedCheckoutAction.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/ReservedCheckoutAction.java
new file mode 100644
index 0000000..b252a48
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/ReservedCheckoutAction.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.cms.cocoon.acting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.environment.SourceResolver;
+import org.apache.lenya.cms.publication.Document;
+import org.apache.lenya.cms.publication.RepositoryException;
+import org.apache.lenya.cms.publication.Session;
+
+/**
+ * Action doing reserved checkout
+ */
+public class ReservedCheckoutAction extends RevisionControllerAction {
+
+    /**
+     * @see org.apache.cocoon.acting.Action#act(org.apache.cocoon.environment.Redirector, org.apache.cocoon.environment.SourceResolver, java.util.Map, java.lang.String, org.apache.avalon.framework.parameters.Parameters)
+     */
+    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String src,
+            Parameters parameters) throws Exception {
+        super.act(redirector, resolver, objectModel, src, parameters);
+
+        HashMap actionMap = new HashMap();
+        Document doc = getDocument();
+
+        //check out
+        try {
+            
+            String username = getUsername();
+            
+            assert doc != null;
+            assert username != null;
+
+            Session session = doc.getSession();
+            if (!doc.isCheckedOutBySession(session.getId(), session.getIdentity().getUser().getId())) {
+                doc.checkout();
+            }
+        } catch (RepositoryException e) {
+            actionMap.put("exception", "genericException");
+            actionMap.put("filename", doc.getSourceURI());
+            actionMap.put("message", "" + e.getMessage());
+            getLogger().error("The document " + doc.getSourceURI() + " couldn't be checked out: ", e);
+
+            return actionMap;
+        }
+
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/ReservedCheckoutTestAction.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/ReservedCheckoutTestAction.java
new file mode 100644
index 0000000..11e0f55
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/ReservedCheckoutTestAction.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.cms.cocoon.acting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.environment.SourceResolver;
+import org.apache.lenya.cms.publication.Document;
+import org.apache.lenya.cms.publication.RepositoryException;
+
+/**
+ * An action that tests if a document is already checked out by a given user. If it isn't, a check
+ * out will be tried.
+ */
+
+public class ReservedCheckoutTestAction extends RevisionControllerAction {
+
+    /**
+     * @see org.apache.cocoon.acting.Action#act(org.apache.cocoon.environment.Redirector,
+     *      org.apache.cocoon.environment.SourceResolver, java.util.Map, java.lang.String,
+     *      org.apache.avalon.framework.parameters.Parameters)
+     */
+    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String src,
+            Parameters parameters) throws Exception {
+        super.act(redirector, resolver, objectModel, src, parameters);
+
+        HashMap actionMap = new HashMap();
+
+        Document doc = getDocument();
+        try {
+
+            if (!doc.isCheckedOut() || !doc.getCheckoutUserId().equals(getUsername())) {
+                // check out
+                doc.checkout();
+            }
+        } catch (RepositoryException e) {
+            actionMap.put("exception", "RepositoryException");
+            actionMap.put("filename", doc.getSourceURI());
+
+            return actionMap;
+        } catch (Exception e) {
+            actionMap.put("exception", "genericException");
+            actionMap.put("filename", doc.getSourceURI());
+            actionMap.put("message", e.getMessage());
+            getLogger().error(
+                    ".act(): The document " + doc.getSourceURI() + " couldn't be checked out: ",
+                    e);
+
+            return actionMap;
+        }
+        return null;
+    }
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/ResourceExistsAction.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/ResourceExistsAction.java
new file mode 100644
index 0000000..24fe72d
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/ResourceExistsAction.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.cms.cocoon.acting;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.avalon.framework.thread.ThreadSafe;
+import org.apache.cocoon.acting.AbstractAction;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.environment.SourceResolver;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.TraversableSource;
+
+
+/**
+ * This action simply checks to see if a given resource exists. It checks
+ * whether the specified in the src attribute source exists or not.
+ * The action returns empty <code>Map</code> if it exists, null otherwise.
+ * <p>Instead of src attribute, source can be specified using
+ * parameter named 'url' (this is old syntax).
+ * <p>In order to differentiate between files and directories, the type can be specified
+ * using the parameter 'type' (&lt;map:parameter name="type" value="file"/&gt; or
+ * &lt;map:parameter name="type" value="directory"/&gt;). The parameter 'type' is optional.
+ * <p>
+ * <strong>Note:</strong> {@link org.apache.cocoon.selection.ResourceExistsSelector}
+ * should be preferred to this component, as the semantics of a Selector better
+ * match the supplied functionality.
+ */
+public class ResourceExistsAction extends AbstractAction implements ThreadSafe {
+    /**
+     * @see org.apache.cocoon.acting.Action#act(org.apache.cocoon.environment.Redirector, org.apache.cocoon.environment.SourceResolver, java.util.Map, java.lang.String, org.apache.avalon.framework.parameters.Parameters)
+     */
+    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source,
+        Parameters parameters) throws Exception {
+        String url = parameters.getParameter("url", source);
+        String type = parameters.getParameter("type", "resource");
+        Source src = null;
+
+        try {
+            src = resolver.resolveURI(url);
+            
+            if (src.exists()) {
+                
+                boolean isCollection = false;
+                if (src instanceof TraversableSource) {
+                    TraversableSource traversableSource = (TraversableSource) src;
+                    isCollection = traversableSource.isCollection();
+                }
+                
+                boolean exists = type.equals("resource")
+                    || type.equals("file") && !isCollection
+                    || type.equals("directory") && isCollection;
+                
+                if (exists) {
+                    getLogger().debug(type + " exists: " + src.getURI());
+                    return Collections.EMPTY_MAP;
+                }
+            }
+            getLogger().debug(".act(): Resource " + source + " as type \"" + type +
+                "\" does not exist");
+        } finally {
+            if (src != null) {
+                resolver.release(src);
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/RevisionControllerAction.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/RevisionControllerAction.java
new file mode 100644
index 0000000..4528325
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/acting/RevisionControllerAction.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.lenya.cms.cocoon.acting;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.cocoon.acting.ServiceableAction;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.SourceResolver;
+import org.apache.lenya.ac.Identity;
+import org.apache.lenya.ac.User;
+import org.apache.lenya.cms.publication.Document;
+import org.apache.lenya.cms.publication.PageEnvelope;
+import org.apache.lenya.cms.publication.PageEnvelopeFactory;
+import org.apache.lenya.cms.publication.Publication;
+import org.apache.lenya.cms.publication.Repository;
+import org.apache.lenya.cms.publication.Session;
+import org.apache.lenya.cms.publication.URLInformation;
+import org.apache.lenya.util.ServletHelper;
+
+/**
+ * Revision controller action.
+ * 
+ * @version $Id$
+ */
+public class RevisionControllerAction extends ServiceableAction {
+
+    private String username = null;
+    private Repository repository;
+    private Document document;
+
+    /**
+     * @see org.apache.cocoon.acting.Action#act(org.apache.cocoon.environment.Redirector,
+     *      org.apache.cocoon.environment.SourceResolver, java.util.Map, java.lang.String,
+     *      org.apache.avalon.framework.parameters.Parameters)
+     */
+    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String src,
+            Parameters parameters) throws Exception {
+        // Get request object
+        Request request = ObjectModelHelper.getRequest(objectModel);
+
+        if (request == null) {
+            getLogger().error(".act(): No request object");
+
+            return null;
+        }
+
+        Session repoSession = this.repository.getSession(request);
+
+        PageEnvelope envelope = null;
+        String id = new URLInformation(ServletHelper.getWebappURI(request)).getPublicationId();
+        Publication publication = repoSession.getPublication(id);
+
+        Document doc = null;
+
+        try {
+            envelope = PageEnvelopeFactory.getInstance().getPageEnvelope(objectModel, publication);
+            doc = envelope.getDocument();
+        } catch (Exception e) {
+            getLogger().error("Resolving page envelope failed: ", e);
+            throw e;
+        }
+
+        // Get session
+        HttpSession session = request.getSession(false);
+
+        if (session == null) {
+            getLogger().error(".act(): No session object");
+
+            return null;
+        }
+
+        Identity identity = (Identity) session.getAttribute(Identity.class.getName());
+        getLogger().debug(".act(): Identity: " + identity);
+
+        // FIXME: hack because of the uri for the editor bitflux. The filename
+        // cannot be get from
+        // the page-envelope
+
+        String path = doc.getPath();
+        int bx = path.lastIndexOf("-bxe");
+
+        if (bx > 0) {
+            String language = doc.getLanguage();
+
+            int l = path.length();
+            int bxLength = "-bxe".length();
+            int lang = path.lastIndexOf("_", bx);
+            int langLength = bx - lang;
+
+            if (bx > 0 && bx + bxLength <= l) {
+                path = path.substring(0, bx) + path.substring(bx + bxLength, l);
+
+                if (lang > 0 && langLength + lang < l) {
+                    language = path.substring(lang + 1, lang + langLength);
+                    path = path.substring(0, lang)
+                            + path.substring(lang + langLength, l - bxLength);
+                }
+            }
+
+            this.document = doc.area().getSite().getNode(path).getLink(language).getDocument();
+
+        } else {
+            this.document = doc;
+        }
+
+        this.username = null;
+
+        if (identity != null) {
+            User user = identity.getUser();
+            if (user != null) {
+                this.username = user.getId();
+            }
+        } else {
+            getLogger().error(".act(): No identity yet");
+        }
+
+        getLogger().debug(".act(): Username: " + this.username);
+
+        return null;
+    }
+
+    /**
+     * Get the node.
+     * @return the node
+     */
+    protected Document getDocument() {
+        return this.document;
+    }
+
+    /**
+     * Get the user name.
+     * @return the user name
+     */
+    protected String getUsername() {
+        return this.username;
+    }
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/context/ContextUtility.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/context/ContextUtility.java
new file mode 100644
index 0000000..8dcb0cb
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/context/ContextUtility.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lenya.cms.cocoon.components.context;
+
+import java.util.Map;
+
+import org.apache.avalon.framework.component.Component;
+import org.apache.avalon.framework.context.Context;
+import org.apache.avalon.framework.context.ContextException;
+import org.apache.avalon.framework.context.Contextualizable;
+import org.apache.cocoon.components.ContextHelper;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.Response;
+import org.apache.cocoon.util.AbstractLogEnabled;
+
+/**
+ * Utility class for getting the context, request, response and
+ * object model of the current request.
+ * @deprecated
+ */
+public class ContextUtility extends AbstractLogEnabled implements
+        Component, Contextualizable {
+    /**
+     * The component's role.
+     */
+    public static final String ROLE = ContextUtility.class.getName();
+
+    protected Context context;
+
+    
+    /**
+     * @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context)
+     */
+    public void contextualize(Context context) throws ContextException {
+        this.context = context;
+    }
+
+    /**
+     * Get the context object of the current request.
+     * @return The context object of the current request.
+     */
+    public Context getContext() {
+        return context;
+    }
+    
+    /**
+     * Get the request object of the current request.
+     * @return The request object of the current request.
+     */
+    public Request getRequest() {
+        return ContextHelper.getRequest(context);
+    }
+    
+    /**
+     * Get the response object of the current request.
+     * @return The response object of the current request.
+     */
+    public Response getResponse() {
+        return ContextHelper.getResponse(context);
+    }
+    
+    /**
+     * Get the object model of the current request.
+     * @return The object model of the current request.
+     */
+    public Map getObjectModel() {
+        return ContextHelper.getObjectModel(context);
+    }
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/AbstractPageEnvelopeModule.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/AbstractPageEnvelopeModule.java
new file mode 100644
index 0000000..9a6c6a0
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/AbstractPageEnvelopeModule.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.cms.cocoon.components.modules.input;
+
+import java.util.Map;
+
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Request;
+import org.apache.lenya.cms.publication.PageEnvelope;
+import org.apache.lenya.cms.publication.PageEnvelopeFactory;
+import org.apache.lenya.cms.publication.Publication;
+import org.apache.lenya.cms.publication.Session;
+import org.apache.lenya.util.ServletHelper;
+import org.apache.lenya.utils.URLInformation;
+/**
+ * Abstract superclass for classes which need access to the page envelope.
+ * 
+ * The web application URL can be provided in the attribute name, separated by a colon (":").
+ */
+public abstract class AbstractPageEnvelopeModule extends OperationModule {
+
+    /**
+     * Get the the page envelope for the given objectModel.
+     * @param objectModel the objectModel for which the page enevelope is requested.
+     * @param name The attribute name.
+     * @return a <code>PageEnvelope</code>
+     * @throws ConfigurationException if the page envelope could not be instantiated.
+     */
+    protected PageEnvelope getEnvelope(Map objectModel, String name) throws ConfigurationException {
+
+        String webappUrl = null;
+        Request request = ObjectModelHelper.getRequest(objectModel);
+        URLInformation urlInfo = new URLInformation();
+        
+        PageEnvelope envelope = (PageEnvelope) request.getAttribute(PageEnvelope.class.getName());
+        if (envelope == null) {
+
+            String[] snippets = name.split(":");
+            if (snippets.length > 1) {
+                webappUrl = snippets[1];
+            } else {
+            	webappUrl = urlInfo.getWebappUrl();
+            }
+
+            if (getLogger().isDebugEnabled()) {
+                getLogger().debug("Resolving page envelope for URL [" + webappUrl + "]");
+            }
+
+            String contextPath = request.getContextPath();
+
+            try {
+                Session session = getSession();
+                Publication pub = null;
+                String pubId = urlInfo.getPublicationId();
+                if (pubId != null && session.existsPublication(pubId)) {
+                    pub = session.getPublication(pubId);
+                }
+                envelope = PageEnvelopeFactory.getInstance().getPageEnvelope(
+                        contextPath,
+                        webappUrl,
+                        pub);
+            } catch (Exception e) {
+                throw new ConfigurationException("Resolving page envelope failed: ", e);
+            }
+            request.setAttribute(PageEnvelope.class.getName(), envelope);
+        }
+        return envelope;
+    }
+
+    /**
+     * @param name The original attribute name.
+     * @return The attribute name without URL attachment.
+     */
+    protected String getAttributeName(String name) {
+        final String[] snippets = name.split(":");
+        return snippets[0];
+    }
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/DateConverterModule.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/DateConverterModule.java
new file mode 100644
index 0000000..583cf0c
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/DateConverterModule.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lenya.cms.cocoon.components.modules.input;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.thread.ThreadSafe;
+import org.apache.cocoon.components.modules.input.AbstractInputModule;
+
+/**
+ * The DateConverterModule converts a date string from one format into 
+ * another format. 
+ * The conversion is defined by the nested elements &lt;src-pattern/&gt; and 
+ * &lt;pattern/&gt; of the module declaration.
+ *
+ */
+public class DateConverterModule extends AbstractInputModule implements ThreadSafe {
+
+    public Object getAttribute(String name, Configuration modeConf, Map objectModel) throws ConfigurationException {
+        
+        String srcPattern = (String) this.settings.get("src-pattern");
+        String pattern = (String) this.settings.get("pattern");
+        
+        if (modeConf != null) {
+            srcPattern = modeConf.getChild("src-pattern").getValue(srcPattern);
+            pattern = modeConf.getChild("pattern").getValue(pattern);
+        }
+
+        if (srcPattern==null) {
+            throw new ConfigurationException("Source date pattern not specified.");
+        }
+        if (pattern==null) {
+            throw new ConfigurationException("Date pattern not specified.");
+        }
+        
+        try {
+            SimpleDateFormat srcFormat = new SimpleDateFormat(srcPattern); 
+            SimpleDateFormat format = new SimpleDateFormat(pattern);
+            Date date = srcFormat.parse(name);
+            return format.format(date);
+        } catch (Exception e) {
+            throw new ConfigurationException("Could not convert date: "+name, e);
+        }
+    }
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/DocumentInfoModule.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/DocumentInfoModule.java
new file mode 100644
index 0000000..04c5444
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/DocumentInfoModule.java
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id:$  */
+
+package org.apache.lenya.cms.cocoon.components.modules.input;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.cocoon.components.modules.input.AbstractInputModule;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Request;
+import org.apache.lenya.cms.publication.Document;
+import org.apache.lenya.cms.publication.DocumentException;
+import org.apache.lenya.cms.publication.Publication;
+import org.apache.lenya.cms.publication.Repository;
+import org.apache.lenya.cms.publication.Session;
+
+/**
+ * Input module to get document information.
+ * {doc-info:{publication-id}:{area}:{uuid}:{document-language}:{property}} where {property} may be:
+ * <ul>
+ * <li><strong><code>contentLength</code></strong> - the content length (number of bytes).</li>
+ * <li><strong><code>expires</code></strong> - the expiration date in ISO 8601 format.</li>
+ * <li><strong><code>lastModified</code></strong> - the last modification date in ISO 8601
+ * format.</li>
+ * <li><strong><code>mimeType</code></strong> - the MIME type</li>
+ * <li><strong><code>nodeName</code></strong> - the name of the node in the site structure</li>
+ * <li><strong><code>path</code></strong> - the path in the site structure (starting with a
+ * slash) or an empty string if the document is not referenced in the site structure.</li>
+ * <li><strong><code>resourceType</code></strong> - the name of the resource type</li>
+ * <li><strong><code>sourceExtension</code></strong> - the source extension</li>
+ * <li><strong><code>visibleInNav</code></strong> - <code>true</code> if the document's node
+ * is visible in the navigation, <code>false</code> otherwise.</li>
+ * <li><strong><code>webappUrl</code></strong> - the web application URL of the document or
+ * an empty string if the document is not referenced in the site structure.</li>
+ * </ul>
+ */
+public class DocumentInfoModule extends AbstractInputModule {
+    
+    // Input module parameters:
+    protected final static String PARAM_PUBLICATION_ID = "publication-id";
+    protected final static String PARAM_AREA = "area";
+    protected final static String PARAM_UUID = "uuid";
+    protected final static String PARAM_DOCUMENT_LANGUAGE = "document-language";
+    protected final static String PARAM_PROPERTY = "property";
+    protected final static String PARAM_REVISION = "revision";
+    protected final static int MIN_MANDATORY_PARAMS = 5;
+
+    protected final static String UUID = "uuid";
+    protected final static String LANGUAGE = "language";
+    protected final static String PATH = "path";
+    protected final static String NODE_NAME = "nodeName";
+    protected final static String WEBAPP_URL = "webappUrl";
+    protected final static String DOCUMENT_URL = "documentUrl";
+    protected final static String RESOURCE_TYPE = "resourceType";
+    protected final static String LAST_MODIFIED = "lastModified";
+    protected final static String MIME_TYPE = "mimeType";
+    protected final static String CONTENT_LENGTH = "contentLength";
+    protected final static String SOURCE_EXTENSION = "sourceExtension";
+    protected final static String EXPIRES = "expires";
+    protected final static String VISIBLE_IN_NAVIGATION = "visibleInNav";
+
+    protected final static String[] PARAMS = { PARAM_PUBLICATION_ID, PARAM_AREA, PARAM_UUID,
+            PARAM_DOCUMENT_LANGUAGE, PARAM_PROPERTY, PARAM_REVISION };
+
+    protected final static String META_RESOURCE_TYPE = "resourceType";
+    protected final static String META_EXPIRES = "expires";
+
+    protected SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
+    
+    private Repository repository;
+    
+    /**
+     * Parse the parameters and return a document.
+     * @param publicationId The publication ID.
+     * @param area The area.
+     * @param uuid The document UUID.
+     * @param language The document language.
+     * @param revision The revision.
+     * @param objectModel The object model.
+     * @return The document object created.
+     * @throws ConfigurationException
+     */
+    protected Document getDocument(String publicationId, String area, String uuid, String language,
+            int revision, Map objectModel) throws ConfigurationException {
+        Document document = null;
+
+        Request request = ObjectModelHelper.getRequest(objectModel);
+
+        try {
+            Session session = this.repository.getSession(request);
+            Publication pub = session.getPublication(publicationId);
+            document = pub.getArea(area).getDocument(uuid, language, revision);
+        } catch (Exception e) {
+            throw new ConfigurationException("Error getting document [" + publicationId + ":"
+                    + area + ":" + uuid + ":" + language + "]: " + e.getMessage(), e);
+        }
+        return document;
+    }
+
+    /**
+     * @see org.apache.cocoon.components.modules.input.InputModule#getAttribute(java.lang.String,
+     *      org.apache.avalon.framework.configuration.Configuration, java.util.Map)
+     */
+    public Object getAttribute(String name, Configuration modeConf, Map objectModel)
+            throws ConfigurationException {
+        Object value = null;
+
+        InputModuleParameters params = new InputModuleParameters(name, PARAMS, MIN_MANDATORY_PARAMS);
+        
+        try {
+            int rev = -1;
+            if (params.isParameter(PARAM_REVISION)) {
+                String revision = params.getParameter(PARAM_REVISION);
+                if (!revision.equals("")) {
+                    rev = Integer.valueOf(revision).intValue();
+                }
+            }
+
+            Document document = getDocument(params.getParameter(PARAM_PUBLICATION_ID), params
+                    .getParameter(PARAM_AREA), params.getParameter(PARAM_UUID), params
+                    .getParameter(PARAM_DOCUMENT_LANGUAGE), rev, objectModel);
+            
+            String attribute = params.getParameter(PARAM_PROPERTY);
+
+            if (attribute.equals(RESOURCE_TYPE)) {
+                value = document.getResourceType().getName();
+            } else if (attribute.equals(LAST_MODIFIED)) {
+                value = this.dateFormat.format(new Date(document.getLastModified()));
+            } else if (attribute.equals(MIME_TYPE)) {
+                value = document.getMimeType();
+            } else if (attribute.equals(CONTENT_LENGTH)) {
+                value = Long.toString(document.getContentLength());
+            } else if (attribute.equals(SOURCE_EXTENSION)) {
+                value = document.getSourceExtension();
+            } else if (attribute.equals(LANGUAGE)) {
+                value = document.getLanguage();
+            } else if (attribute.equals(PATH)) {
+                value = document.getPath();
+            } else if (attribute.equals(NODE_NAME)) {
+                value = document.getName();
+            } else if (attribute.equals(UUID)) {
+                value = document.getUUID();
+            } else if (attribute.equals(WEBAPP_URL)) {
+                value = document.getCanonicalWebappURL();
+            } else if (attribute.equals(DOCUMENT_URL)) {
+                value = document.getCanonicalDocumentURL();
+            } else if (attribute.equals(EXPIRES)) {
+                try {
+                    Date expires = document.getExpires();
+                    value = this.dateFormat.format(expires);
+                } catch (DocumentException e) {
+                    throw new ConfigurationException("Error getting expires date from document.", e);
+                }
+            } else if (attribute.equals(VISIBLE_IN_NAVIGATION)) {
+                value = Boolean.toString(isVisibleInNavigation(document));
+            } else {
+                throw new ConfigurationException("Attribute '" + attribute + "' not supported ["
+                        + name + "]");
+            }
+        } catch (ConfigurationException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ConfigurationException("Error getting input module parameters.", e);
+        }
+
+        return value;
+    }
+
+    protected boolean isVisibleInNavigation(Document document) throws ConfigurationException {
+        try {
+            return document.getLink().getNode().isVisible();
+        } catch (DocumentException e) {
+            throw new ConfigurationException("Obtaining navigation visibility failed [" + document
+                    + "]: " + e.getMessage(), e);
+        }
+
+    }
+
+    public void setRepository(Repository repository) {
+        this.repository = repository;
+    }
+
+    public Repository getRepository() {
+        return repository;
+    }
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/DocumentURLModule.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/DocumentURLModule.java
new file mode 100644
index 0000000..610e170
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/DocumentURLModule.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.lenya.cms.cocoon.components.modules.input;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.lenya.cms.publication.util.DocumentHelper;
+
+/**
+ * <p>
+ * This module constructs the document url taking into account difference in the language .version
+ * being created and used.
+ * </p>
+ * <p>
+ * Example:
+ * <code>{document-url:{page-envelope:area}:{page-envelope:document-uuid}:{page-envelope:document-language}}</code>
+ * </p>
+ * @version $Id$
+ */
+public class DocumentURLModule extends AbstractPageEnvelopeModule {
+
+    /**
+     * @see org.apache.cocoon.components.modules.input.InputModule#getAttribute(java.lang.String,
+     *      org.apache.avalon.framework.configuration.Configuration, java.util.Map)
+     */
+    public Object getAttribute(String name, Configuration modeConf, Map objectModel)
+            throws ConfigurationException {
+
+        String url;
+
+        final String[] attributes = name.split(":");
+
+        if (attributes.length < 3) {
+            throw new ConfigurationException("Invalid number of parameters: " + attributes.length
+                    + ". Expected 3 (area, document-uuid, language)");
+        }
+
+        final String area = attributes[0];
+        final String uuid = attributes[1];
+        final String language = attributes[2];
+
+        try {
+            DocumentHelper helper = new DocumentHelper(objectModel);
+            url = helper.getDocumentUrl(uuid, area, language);
+        } catch (Exception e) {
+            throw new ConfigurationException("Resolving attribute [" + name + "] failed: ", e);
+        }
+
+        return url;
+    }
+
+    /**
+     * @see org.apache.cocoon.components.modules.input.InputModule#getAttributeNames(org.apache.avalon.framework.configuration.Configuration,
+     *      java.util.Map)
+     */
+    public Iterator getAttributeNames(Configuration modeConf, Map objectModel)
+            throws ConfigurationException {
+        return Collections.EMPTY_SET.iterator();
+    }
+
+    /**
+     * @see org.apache.cocoon.components.modules.input.InputModule#getAttributeValues(java.lang.String,
+     *      org.apache.avalon.framework.configuration.Configuration, java.util.Map)
+     */
+    public Object[] getAttributeValues(String name, Configuration modeConf, Map objectModel)
+            throws ConfigurationException {
+        Object[] objects = { getAttribute(name, modeConf, objectModel) };
+        return objects;
+    }
+
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/FallbackModule.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/FallbackModule.java
new file mode 100644
index 0000000..d09133f
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/FallbackModule.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.lenya.cms.cocoon.components.modules.input;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.cocoon.components.modules.input.AbstractInputModule;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Request;
+import org.apache.commons.lang.Validate;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.excalibur.store.impl.MRUMemoryStore;
+import org.apache.lenya.cms.cocoon.source.FallbackSourceFactory;
+import org.apache.lenya.cms.publication.Repository;
+import org.apache.lenya.cms.publication.Session;
+import org.apache.lenya.cms.publication.URLInformation;
+import org.apache.lenya.util.ServletHelper;
+
+/**
+ * <p>
+ * This module returns the actual source URI of a fallback:// source. The protocol (fallback,
+ * template-fallback, ...) is configurable via the <em>protocol</em> parameter.
+ * </p>
+ */
+public class FallbackModule extends AbstractInputModule {
+
+    private String protocol;
+    protected MRUMemoryStore store;
+    private Repository repository;
+    private SourceResolver resolver;
+    private static Boolean useCache = null;
+
+    public void setRepository(Repository repo) {
+        this.repository = repo;
+    }
+
+    protected boolean useCache() {
+        return this.store != null;
+    }
+
+    public void setStore(MRUMemoryStore store) {
+        Validate.notNull(store);
+        this.store = store;
+    }
+
+    protected MRUMemoryStore getStore() {
+        return this.store;
+    }
+
+    protected String getPublicationId(Map objectModel) {
+        Request request = ObjectModelHelper.getRequest(objectModel);
+        String webappUri = ServletHelper.getWebappURI(request);
+        URLInformation info = new URLInformation(webappUri);
+        String pubId = null;
+        try {
+            Session session = this.repository.getSession(request);
+            String pubIdCandidate = info.getPublicationId();
+            if (pubIdCandidate != null && session.existsPublication(pubIdCandidate)) {
+                pubId = pubIdCandidate;
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return pubId;
+    }
+
+    public Object getAttribute(String name, Configuration modeConf, Map objectModel)
+            throws ConfigurationException {
+        String uri;
+        String fallbackUri = getFallbackUri(name);
+        if (useCache()) {
+            final String pubId = getPublicationId(objectModel);
+            String cacheKey = FallbackSourceFactory.getCacheKey(pubId, fallbackUri);
+            MRUMemoryStore store = getStore();
+            if (store.containsKey(cacheKey)) {
+                uri = (String) store.get(cacheKey);
+            } else {
+                uri = resolveSourceUri(name);
+            }
+        } else {
+            uri = resolveSourceUri(name);
+        }
+        return uri;
+    }
+
+    protected String resolveSourceUri(String name) throws ConfigurationException {
+        Source source = null;
+        try {
+            source = this.resolver.resolveURI(getFallbackUri(name));
+            return source.getURI();
+        } catch (Exception e) {
+            throw new ConfigurationException("Resolving fallback source [" + name + "] failed: ", e);
+        } finally {
+            if (source != null) {
+                this.resolver.release(source);
+            }
+        }
+    }
+
+    protected String getFallbackUri(String name) {
+        return this.protocol + "://" + name;
+    }
+
+    public Iterator getAttributeNames(Configuration modeConf, Map objectModel)
+            throws ConfigurationException {
+        return Collections.EMPTY_SET.iterator();
+    }
+
+    public Object[] getAttributeValues(String name, Configuration modeConf, Map objectModel)
+            throws ConfigurationException {
+        Object[] objects = { getAttribute(name, modeConf, objectModel) };
+        return objects;
+    }
+
+    public void setProtocol(String protocol) {
+        Validate.notNull(protocol);
+        this.protocol = protocol;
+    }
+    
+    public void setSourceResolver(SourceResolver resolver) {
+        this.resolver = resolver;
+    }
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/InputModuleParameters.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/InputModuleParameters.java
new file mode 100644
index 0000000..2ab073d
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/InputModuleParameters.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id:$  */
+
+package org.apache.lenya.cms.cocoon.components.modules.input;
+
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.parameters.ParameterException;
+import org.apache.avalon.framework.parameters.Parameters;
+
+/**
+ * Input module parameter class.
+ * Parses input module parameters and offers accessors for the parameters.
+ * The parameters are expected to be separated by ":".
+ */
+public class InputModuleParameters {
+    
+    protected final static char PARAM_SEPARATOR = ':';
+    
+    protected Parameters params;
+    protected final String[] paramList;
+    
+    /**
+     * Parse input module parameters. 
+     * @param parameters Parameter to be parsed.
+     * @param paramList List of expected parameters.
+     * @param minParams Minimum number of parameters expected.
+     * @throws ConfigurationException if there is an error parsing the parameters.
+     */
+    public InputModuleParameters(String parameters, final String[] paramList, int minParams)
+    throws ConfigurationException
+    {
+        params = new Parameters();
+        this.paramList = (String[])paramList.clone();
+        parseParameters(parameters, minParams);
+    }
+    
+    /**
+     * Parse parameters according to the parameter list passed.
+     * @param parameters
+     * @param minParams Minimum number of parameters.
+     * @return Parameters object initialized with parsed parameters.
+     * @throws ConfigurationException
+     */
+    protected Parameters parseParameters(String parameters, int minParams)
+    throws ConfigurationException
+    {
+        // Parse parameters
+        int start = 0;
+        int end = parameters.indexOf(PARAM_SEPARATOR);
+        for (int i=0; i<paramList.length; i++) {
+            if (end != -1) {
+                String paramToken = parameters.substring(start, end);
+                params.setParameter(paramList[i], paramToken);
+                start = end+1;
+                end = parameters.indexOf(PARAM_SEPARATOR, start+1);
+            } else {
+                if ((i+1) < minParams) {
+                    // A mandatory parameter is missing.
+                    throw new ConfigurationException("Error parsing parameters: mandatory parameter '"
+                            + paramList[i] + "' not found [" + parameters + "]");
+                } else if (i == 0) {
+                    // Zero or one parameter passed.
+                    if (parameters.length() != 0) {
+                        params.setParameter(paramList[i], parameters);
+                    }
+                    break;
+                } else {
+                    // All parameters parsed except the last one.
+                    String paramToken = parameters.substring(start);
+                    if (paramToken.length() != 0) {
+                        params.setParameter(paramList[i], paramToken);
+                    }
+                    break;
+                }
+            }
+        }
+        return params;
+    }
+    
+    /**
+     * Get a parameter.
+     * @param param Name of requested parameter.
+     * @return Requested parameter.
+     * @throws ParameterException if the specified parameter cannot be found
+     */
+    public String getParameter(String param) throws ParameterException
+    {
+        return params.getParameter(param);
+    }
+    
+    /**
+     * Does a parameter with given name exists?
+     * @param param Parameter name.
+     * @return True if parameters exists, otherwise false.
+     */
+    public boolean isParameter(String param) {
+        return params.isParameter(param);
+    }
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/OperationModule.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/OperationModule.java
new file mode 100644
index 0000000..b90d45a
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/OperationModule.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cocoon.components.modules.input;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.cocoon.components.modules.input.AbstractInputModule;
+import org.apache.cocoon.processing.ProcessInfoProvider;
+import org.apache.cocoon.spring.configurator.WebAppContextUtils;
+import org.apache.lenya.cms.publication.Repository;
+import org.apache.lenya.cms.publication.Session;
+
+/**
+ * Super class for operation-based input modules.
+ * 
+ * @version $Id$
+ */
+public class OperationModule extends AbstractInputModule {
+
+    private Repository repository;
+    private org.apache.lenya.cms.publication.Session session;
+
+    protected Session getSession() {
+        if (this.session == null) {
+            ProcessInfoProvider processInfo = (ProcessInfoProvider) WebAppContextUtils
+                    .getCurrentWebApplicationContext().getBean(ProcessInfoProvider.ROLE);
+            HttpServletRequest request = processInfo.getRequest();
+            this.session = this.getRepository().getSession(request);
+        }
+        return this.session;
+    }
+
+    public void setRepository(Repository repository) {
+        this.repository = repository;
+    }
+
+    public Repository getRepository() {
+        return repository;
+    }
+
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/PageEnvelopeModule.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/PageEnvelopeModule.java
new file mode 100644
index 0000000..407a1b6
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/PageEnvelopeModule.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.cms.cocoon.components.modules.input;
+
+import java.net.MalformedURLException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.commons.lang.StringUtils;
+import org.apache.lenya.cms.publication.Document;
+import org.apache.lenya.cms.publication.DocumentLocator;
+import org.apache.lenya.cms.publication.PageEnvelope;
+import org.apache.lenya.cms.publication.Publication;
+import org.apache.lenya.cms.publication.ResourceType;
+import org.apache.lenya.cms.site.SiteException;
+import org.apache.lenya.utils.URLInformation;
+
+/**
+ * Input module wrapping the page envelope. This module provides publication
+ * related information such as document-uuid, area, publication-id.
+ * 
+ * @see org.apache.lenya.cms.publication.PageEnvelope
+ * @deprecated use DocumentInfoModule instead.
+ */
+public class PageEnvelopeModule extends AbstractPageEnvelopeModule {
+
+    protected static final String URI_PARAMETER_DOCTYPE = "doctype";
+    
+    /**
+     * @see org.apache.cocoon.components.modules.input.InputModule#getAttribute(java.lang.String,
+     *      org.apache.avalon.framework.configuration.Configuration,
+     *      java.util.Map)
+     */
+    public Object getAttribute(final String attributeName, Configuration modeConf, Map objectModel)
+            throws ConfigurationException {
+
+        final String name = getAttributeName(attributeName);
+
+        if (!Arrays.asList(PageEnvelope.PARAMETER_NAMES).contains(name)) {
+            throw new ConfigurationException("The attribute [" + name + "] is not supported!");
+        }
+
+        PageEnvelope envelope = getEnvelope(objectModel, attributeName);
+        Object value = null;
+
+        try {
+            if (name.equals(PageEnvelope.AREA)) {
+                value = envelope.getArea();
+            } else if (name.equals(PageEnvelope.CONTEXT)) {
+                value = envelope.getContext();
+            } else if (name.equals(PageEnvelope.IS_PUBLICATION)) {
+                value = Boolean.toString(envelope.getPublication() != null);
+            } else if (name.equals(PageEnvelope.PUBLICATION_ID)) {
+                Publication pub = envelope.getPublication();
+                value = pub != null ? pub.getId() : "";
+            } else if (name.equals(PageEnvelope.PUBLICATION)) {
+                value = envelope.getPublication();
+            } else if (name.equals(PageEnvelope.PUBLICATION_LANGUAGES_CSV)) {
+                value = StringUtils.join(envelope.getPublication().getLanguages(), ',');
+            } else if (name.equals(PageEnvelope.DEFAULT_LANGUAGE)) {
+                value = envelope.getPublication().getDefaultLanguage();
+            } else if (name.equals(PageEnvelope.LANGUAGE)) {
+                value = envelope.getLanguage();
+            } else if (name.equals(PageEnvelope.BREADCRUMB_PREFIX)) {
+                value = envelope.getPublication().getBreadcrumbPrefix();
+            } else if (name.equals(PageEnvelope.DOCUMENT_PATH)) {
+                value = getPath(envelope, objectModel);
+            } else {
+                Document document = envelope.getDocument();
+                if (document != null) {
+                    if (name.equals(PageEnvelope.DOCUMENT)) {
+                        value = document;
+                    } else if (name.equals(PageEnvelope.DOCUMENT_ID)) {
+                        getLogger().warn(
+                                "This attribute [ " + name + " ] is deprecated."
+                                        + " Use document-path or document-uuid instead!");
+                        value = document.getUUID();
+                    } else if (name.equals(PageEnvelope.DOCUMENT_PARENT)) {
+                        value = document.getLocator().getParent().getPath();
+                    } else if (name.equals(PageEnvelope.DOCUMENT_NAME)) {
+                        value = document.getName();
+                    } else if (name.equals(PageEnvelope.DOCUMENT_LABEL)) {
+                        value = document.getLink().getLabel();
+                    } else if (name.equals(PageEnvelope.DOCUMENT_URL)) {
+                        value = document.getCanonicalDocumentURL();
+                    } else if (name.equals(PageEnvelope.DOCUMENT_URL_WITHOUT_LANGUAGE)) {
+                        value = document.getCanonicalWebappURL();
+                    } else if (name.equals(PageEnvelope.DOCUMENT_EXTENSION)) {
+                        value = document.getExtension();
+                    } else if (name.equals(PageEnvelope.DOCUMENT_SOURCE_EXTENSION)) {
+                        value = document.getSourceExtension();
+                    } else if (name.equals(PageEnvelope.DOCUMENT_UUID)) {
+                        value = document.getUUID();
+                    } else if (name.equals(PageEnvelope.DOCUMENT_LANGUAGE)) {
+                        value = document.getLanguage();
+                    } else if (name.equals(PageEnvelope.DOCUMENT_LANGUAGES)) {
+                        value = document.getLanguages();
+                    } else if (name.equals(PageEnvelope.DOCUMENT_LANGUAGES_CSV)) {
+                        value = StringUtils.join(document.getLanguages(), ',');
+                    } else if (name.equals(PageEnvelope.DOCUMENT_LASTMODIFIED)) {
+                        Date date = new Date(document.getLastModified());
+                        value = new SimpleDateFormat(DATE_FORMAT).format(date);
+                    } else if (name.equals(PageEnvelope.DOCUMENT_MIME_TYPE)) {
+                        value = document.getMimeType();
+                    } else if (name.equals(PageEnvelope.DOCUMENT_TYPE)) {
+                        ResourceType resourceType = document.getResourceType();
+                        if (resourceType == null) {
+                            value = null;
+                        } else {
+                            value = resourceType.getName();
+                        }
+                    }
+                }
+            }
+        } catch (final Exception e) {
+            throw new ConfigurationException("Getting attribute for name [" + name + "] failed: ",
+                    e);
+        }
+
+        if (getLogger().isDebugEnabled()) {
+            getLogger().debug("Returning [" + name + "] = [" + value + "]");
+        }
+
+        return value;
+    }
+
+    protected String getPath(PageEnvelope envelope, Map objectModel) throws SiteException {
+        String path;
+        Document doc = envelope.getDocument();
+        if (doc == null) {
+            Publication pub = envelope.getPublication();
+            URLInformation info = new URLInformation();
+            String url = info.getWebappUrl();
+            DocumentLocator loc;
+            try {
+                loc = pub.getDocumentBuilder().getLocator(pub.getSession(), url);
+            } catch (MalformedURLException e) {
+                throw new SiteException(e);
+            }
+            path = loc.getPath();
+        } else {
+            path = doc.getLocator().getPath();
+        }
+        return path;
+    }
+
+    /**
+     * <code>DATE_FORMAT</code> The date format
+     */
+    public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss Z";
+
+    /**
+     * @see org.apache.cocoon.components.modules.input.InputModule#getAttributeNames(org.apache.avalon.framework.configuration.Configuration,
+     *      java.util.Map)
+     */
+    public Iterator getAttributeNames(Configuration modeConf, Map objectModel)
+            throws ConfigurationException {
+        return Arrays.asList(PageEnvelope.PARAMETER_NAMES).iterator();
+    }
+
+    /**
+     * @see org.apache.cocoon.components.modules.input.InputModule#getAttributeValues(java.lang.String,
+     *      org.apache.avalon.framework.configuration.Configuration,
+     *      java.util.Map)
+     */
+    public Object[] getAttributeValues(String name, Configuration modeConf, Map objectModel)
+            throws ConfigurationException {
+        Object[] objects = { getAttribute(name, modeConf, objectModel) };
+
+        return objects;
+    }
+
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/ResourceExistsModule.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/ResourceExistsModule.java
new file mode 100644
index 0000000..c40ab46
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/ResourceExistsModule.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cocoon.components.modules.input;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.avalon.framework.activity.Disposable;
+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.cocoon.components.modules.input.AbstractInputModule;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceNotFoundException;
+import org.apache.excalibur.source.SourceResolver;
+
+/**
+ * Checks if a certain resource exists and returns either the string "true" or "false".
+ * @version $Id$
+ */
+public class ResourceExistsModule extends AbstractInputModule implements Serviceable, Disposable {
+
+    /**
+     * @see org.apache.cocoon.components.modules.input.InputModule#getAttribute(java.lang.String,
+     *      org.apache.avalon.framework.configuration.Configuration, java.util.Map)
+     */
+    public Object getAttribute(String name, Configuration modeConf, Map objectModel)
+            throws ConfigurationException {
+
+        String resourceURI = name;
+
+        Source source = null;
+        boolean exists = false;
+        try {
+            source = this.resolver.resolveURI(resourceURI);
+            exists = source.exists();
+        } catch (SourceNotFoundException e) {
+            exists = false;
+        } catch (Exception e) {
+            getLogger().warn("Exception resolving resource [" + resourceURI + "]", e);
+            exists = false;
+        } finally {
+            if (source != null) {
+                this.resolver.release(source);
+            }
+        }
+
+        return Boolean.toString(exists);
+    }
+
+    /**
+     * @see org.apache.cocoon.components.modules.input.InputModule#getAttributeNames(org.apache.avalon.framework.configuration.Configuration,
+     *      java.util.Map)
+     */
+    public Iterator getAttributeNames(Configuration modeConf, Map objectModel)
+            throws ConfigurationException {
+        return Collections.EMPTY_SET.iterator();
+    }
+
+    private ServiceManager manager;
+    private SourceResolver resolver;
+
+    /**
+     * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
+     */
+    public void service(ServiceManager _manager) throws ServiceException {
+        this.manager = _manager;
+        this.resolver = (SourceResolver) _manager.lookup(SourceResolver.ROLE);
+    }
+
+    /**
+     * @see org.apache.avalon.framework.activity.Disposable#dispose()
+     */
+    public void dispose() {
+        super.dispose();
+        this.manager.release(this.resolver);
+        this.resolver = null;
+        this.manager = null;
+    }
+
+    /**
+     * @see org.apache.cocoon.components.modules.input.InputModule#getAttributeValues(java.lang.String,
+     *      org.apache.avalon.framework.configuration.Configuration, java.util.Map)
+     */
+    public Object[] getAttributeValues(String name, Configuration modeConf, Map objectModel)
+            throws ConfigurationException {
+        Object result = this.getAttribute(name, modeConf, objectModel);
+        return (result == null ? null : new Object[] { result });
+    }
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/ResourceTypeModule.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/ResourceTypeModule.java
new file mode 100644
index 0000000..c80abdf
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/ResourceTypeModule.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cocoon.components.modules.input;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.cocoon.components.modules.input.AbstractInputModule;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Request;
+import org.apache.commons.lang.StringUtils;
+import org.apache.lenya.cms.publication.Document;
+import org.apache.lenya.cms.publication.Publication;
+import org.apache.lenya.cms.publication.Repository;
+import org.apache.lenya.cms.publication.ResourceType;
+import org.apache.lenya.cms.publication.ResourceTypeResolver;
+import org.apache.lenya.cms.publication.Session;
+import org.apache.lenya.util.ServletHelper;
+
+/**
+ * <p>
+ * Resource type module.
+ * </p>
+ * <p>
+ * The syntax is either <code>{resource-type:&lt;attribute&gt;}</code> (which uses the resource type
+ * of the currenlty requested document) or
+ * <code>{resource-type:&lt;name&gt;:&lt;attribute&gt;}</code> (which allows to access an arbitrary
+ * resource type).
+ * </p>
+ * <p>
+ * Attributes:
+ * </p>
+ * <ul>
+ * <li><strong><code>expires</code></strong> - the expiration date in RFC 822/1123 format, see
+ * {@link org.apache.lenya.cms.publication.ResourceType#getExpires()}</li>
+ * <li><strong><code>schemaUri</code></strong> - see {@link org.apache.lenya.xml.Schema#getURI()}</li>
+ * <li><strong><code>httpSchemaUri</code></strong> - the URI to request the schema over HTTP,
+ * without Proxy and context (use {proxy:} around it).</li>
+ * <li><strong><code>supportsFormat:{format}</code></strong> - true if the resource type supports
+ * this format, false otherwise</li>
+ * </ul>
+ */
+public class ResourceTypeModule extends AbstractInputModule {
+
+    protected static final String SCHEMA_URI = "schemaUri";
+    protected static final String HTTP_SCHEMA_URI = "httpSchemaUri";
+    protected static final String EXPIRES = "expires";
+    protected static final String SUPPORTS_FORMAT = "supportsFormat";
+
+    private Repository repository;
+    private ResourceTypeResolver resourceTypeResolver;
+
+    public Object getAttribute(String name, Configuration modeConf, Map objectModel)
+            throws ConfigurationException {
+        Object value = null;
+
+        try {
+            Request request = ObjectModelHelper.getRequest(objectModel);
+            Session session = this.repository.getSession(request);
+
+            ResourceType resourceType;
+            Publication pub = null;
+            String attribute;
+
+            String[] steps = name.split(":");
+            if (steps.length == 1) {
+                String webappUrl = ServletHelper.getWebappURI(request);
+                Document document = session.getUriHandler().getDocument(webappUrl);
+                pub = document.getPublication();
+
+                attribute = name;
+                resourceType = document.getResourceType();
+            } else {
+                attribute = steps[1];
+                String resourceTypeName = steps[0];
+                resourceType = getResourceTypeResolver().getResourceType(resourceTypeName);
+            }
+
+            if (attribute.startsWith("format-")) {
+                String[] formatSteps = name.split("-");
+                String format = formatSteps[1];
+                value = resourceType.getFormatURI(format);
+            } else if (attribute.equals(SCHEMA_URI)) {
+                value = resourceType.getSchema().getURI();
+            } else if (attribute.equals(HTTP_SCHEMA_URI)) {
+                String uri = resourceType.getSchema().getURI();
+                value = transformFallbackUriToHttp(pub.getId(), uri);
+            } else if (attribute.equals(EXPIRES)) {
+                Date expires = resourceType.getExpires();
+                SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy kk:mm:ss zzz");
+                value = sdf.format(expires);
+            } else if (attribute.equals(SUPPORTS_FORMAT)) {
+                String format = steps[steps.length - 1];
+                String[] formats = resourceType.getFormats();
+                return Boolean.toString(Arrays.asList(formats).contains(format));
+            } else {
+                throw new ConfigurationException("Attribute [" + name + "] not supported!");
+            }
+
+        } catch (Exception e) {
+            throw new ConfigurationException("Resolving attribute [" + name + "] failed: ", e);
+        }
+
+        return value;
+    }
+
+    /**
+     * Transforms a fallback URI for resources into a HTTP URL.
+     * 
+     * Currently only supports module urls:
+     * 
+     * fallback://lenya/modules/foo/resources/schemas/bar.rng ->
+     * prefix/pubid/modules/foo/schemas/bar.rng
+     * 
+     * FIXME: allow other kind of fallback URIs
+     * 
+     * @param pubid publication id of the current document
+     * @param prefix prefix which will be prepended to the resulting URL
+     * @param uri fallback uri, must start with fallback://
+     * @return A string.
+     * @throws ConfigurationException
+     */
+    protected String transformFallbackUriToHttp(String pubid, String uri)
+            throws ConfigurationException {
+        if (uri.startsWith("fallback://lenya/modules/")) {
+            String path = StringUtils.substringAfter(uri, "fallback://lenya/modules/");
+            String module = StringUtils.substringBefore(path, "/");
+            path = StringUtils.substringAfter(path, module + "/resources");
+            return "/" + pubid + "/modules/" + module + path;
+        } else {
+            throw new ConfigurationException("Don't know how to create HTTP URL from : " + uri);
+        }
+    }
+
+    public void setResourceTypeResolver(ResourceTypeResolver resourceTypeResolver) {
+        this.resourceTypeResolver = resourceTypeResolver;
+    }
+
+    public ResourceTypeResolver getResourceTypeResolver() {
+        return resourceTypeResolver;
+    }
+
+    public void setRepository(Repository repository) {
+        this.repository = repository;
+    }
+
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/flow/FlowHelper.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/flow/FlowHelper.java
new file mode 100644
index 0000000..ffb55fc
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/flow/FlowHelper.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cocoon.flow;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.cocoon.components.flow.javascript.fom.FOM_Cocoon;
+import org.apache.lenya.ac.AccessControlException;
+import org.apache.lenya.cms.publication.PageEnvelope;
+import org.apache.lenya.cms.publication.PageEnvelopeException;
+import org.apache.lenya.cms.publication.util.DocumentHelper;
+import org.apache.lenya.workflow.WorkflowException;
+
+/**
+ * Flowscript utility class.
+ * @version $Id$
+ */
+public interface FlowHelper {
+
+    /**
+     * The Avalon Role.
+     */
+    String ROLE = FlowHelper.class.getName();
+
+    /**
+     * Returns the current page envelope.
+     * @param cocoon The FOM_Cocoon object.
+     * @return A page envelope.
+     * @throws PageEnvelopeException when something went wrong.
+     */
+    PageEnvelope getPageEnvelope(FOM_Cocoon cocoon) throws PageEnvelopeException;
+
+    /**
+     * Returns the request URI of the current request.
+     * @param cocoon The FOM_Cocoon object.
+     * @return A string.
+     */
+    String getRequestURI(FOM_Cocoon cocoon);
+
+    /**
+     * Returns the request object of the current request.
+     * @param cocoon The FOM_Cocoon object.
+     * @return A request object.
+     */
+    HttpServletRequest getRequest(FOM_Cocoon cocoon);
+
+    /**
+     * Returns the Cocoon Object Model
+     * @param cocoon The Flow Object Model of Cocoon
+     * @return The object model
+     */
+    Map getObjectModel(FOM_Cocoon cocoon);
+
+    /**
+     * Returns a DocumentHelper instance.
+     * @param cocoon The Flow Object Model of Cocoon
+     * @return The document helper
+     * @see DocumentHelper
+     */
+    DocumentHelper getDocumentHelper(FOM_Cocoon cocoon);
+
+    /**
+     * Resolves the request parameter value for a specific name. The parameter names are encoded as
+     * <code>{name}:{value}.{axis}</code>. This is a workaround for the &lt;input type="image"/&gt;
+     * bug in Internet Explorer.
+     * @param cocoon The FOM_Cocoon object.
+     * @param parameterName The request parameter name.
+     * @return A string.
+     */
+    String getImageParameterValue(FOM_Cocoon cocoon, String parameterName);
+
+    /**
+     * Trigger a workflow event for the document associated with the current PageEnvelope.
+     * @param cocoon The Cocoon Flow Object Model
+     * @param event The name of the workflow event to trigger.
+     * @throws WorkflowException If an workflow error occurs
+     * @throws PageEnvelopeException Page envelope can not operate properly.
+     * @throws AccessControlException If an access control violation occurs.
+     */
+    void triggerWorkflow(FOM_Cocoon cocoon, String event) throws WorkflowException,
+            PageEnvelopeException, AccessControlException;
+
+    /**
+     * Checkis in the current document from the PageEnvelope context.
+     * @param cocoon The Cocoon Flow Object Model
+     * @param backup Wether a new revision should be created.
+     * @throws Exception
+     */
+    void reservedCheckIn(FOM_Cocoon cocoon, boolean backup) throws Exception;
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/flow/FlowHelperImpl.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/flow/FlowHelperImpl.java
new file mode 100644
index 0000000..3cea2a9
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/flow/FlowHelperImpl.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/* $Id$  */
+
+package org.apache.lenya.cms.cocoon.flow;
+
+import java.util.Enumeration;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.cocoon.components.flow.javascript.fom.FOM_Cocoon;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.util.AbstractLogEnabled;
+import org.apache.lenya.ac.AccessControlException;
+import org.apache.lenya.cms.publication.Document;
+import org.apache.lenya.cms.publication.PageEnvelope;
+import org.apache.lenya.cms.publication.PageEnvelopeException;
+import org.apache.lenya.cms.publication.PageEnvelopeFactory;
+import org.apache.lenya.cms.publication.Publication;
+import org.apache.lenya.cms.publication.Repository;
+import org.apache.lenya.cms.publication.Session;
+import org.apache.lenya.cms.publication.URLInformation;
+import org.apache.lenya.cms.publication.util.DocumentHelper;
+import org.apache.lenya.cms.workflow.WorkflowUtil;
+import org.apache.lenya.util.ServletHelper;
+import org.apache.lenya.workflow.WorkflowException;
+
+/**
+ * Flowscript utility class. The FOM_Cocoon object is not passed in the constructor to avoid errors.
+ * This way, not the initial, but the current FOM_Cocoon object is used by the methods.
+ */
+public class FlowHelperImpl extends AbstractLogEnabled implements FlowHelper {
+
+    private Repository repository;
+
+    public void setRepository(Repository repository) {
+        this.repository = repository;
+    }
+
+    /**
+     * @see org.apache.lenya.cms.cocoon.flow.FlowHelper#getPageEnvelope(org.apache.cocoon.components.flow.javascript.fom.FOM_Cocoon)
+     */
+    public PageEnvelope getPageEnvelope(FOM_Cocoon cocoon) throws PageEnvelopeException {
+        HttpServletRequest request = getRequest(cocoon);
+        try {
+            Session session = this.repository.getSession(request);
+            PageEnvelopeFactory factory = PageEnvelopeFactory.getInstance();
+            URLInformation info = new URLInformation(ServletHelper.getWebappURI(request));
+            Publication publication = session.getPublication(info.getPublicationId());
+            return factory.getPageEnvelope(cocoon.getObjectModel(), publication);
+        } catch (Exception e) {
+            throw new PageEnvelopeException(e);
+        }
+    }
+
+    /**
+     * @see org.apache.lenya.cms.cocoon.flow.FlowHelper#getRequestURI(org.apache.cocoon.components.flow.javascript.fom.FOM_Cocoon)
+     */
+    public String getRequestURI(FOM_Cocoon cocoon) {
+        return cocoon.getRequest().getRequestURI();
+    }
+
+    /**
+     * @see org.apache.lenya.cms.cocoon.flow.FlowHelper#getRequest(org.apache.cocoon.components.flow.javascript.fom.FOM_Cocoon)
+     */
+    public HttpServletRequest getRequest(FOM_Cocoon cocoon) {
+        return cocoon.getRequest();
+    }
+
+    /**
+     * @see org.apache.lenya.cms.cocoon.flow.FlowHelper#getObjectModel(org.apache.cocoon.components.flow.javascript.fom.FOM_Cocoon)
+     */
+    public Map getObjectModel(FOM_Cocoon cocoon) {
+        return cocoon.getObjectModel();
+    }
+
+    /**
+     * @see org.apache.lenya.cms.cocoon.flow.FlowHelper#getDocumentHelper(org.apache.cocoon.components.flow.javascript.fom.FOM_Cocoon)
+     */
+    public DocumentHelper getDocumentHelper(FOM_Cocoon cocoon) {
+        return new DocumentHelper(cocoon.getObjectModel());
+    }
+
+    /**
+     * <code>SEPARATOR</code> The separator
+     */
+    public static final String SEPARATOR = ":";
+
+    /**
+     * @see org.apache.lenya.cms.cocoon.flow.FlowHelper#getImageParameterValue(org.apache.cocoon.components.flow.javascript.fom.FOM_Cocoon,
+     *      java.lang.String)
+     */
+    public String getImageParameterValue(FOM_Cocoon cocoon, String parameterName) {
+
+        getLogger().debug("Resolving parameter value for name [" + parameterName + "]");
+
+        Request request = cocoon.getRequest();
+        String value = request.getParameter(parameterName);
+
+        if (value == null) {
+            String prefix = parameterName + SEPARATOR;
+            Enumeration e = request.getParameterNames();
+            while (e.hasMoreElements() && value == null) {
+                String name = (String) e.nextElement();
+                if (name.startsWith(prefix)) {
+                    getLogger().debug("Complete parameter name: [" + name + "]");
+                    value = name.substring(prefix.length(), name.length() - 2);
+                    getLogger().debug("Resolved value: [" + value + "]");
+                }
+            }
+        }
+
+        return value;
+    }
+
+    /**
+     * @see org.apache.lenya.cms.cocoon.flow.FlowHelper#triggerWorkflow(org.apache.cocoon.components.flow.javascript.fom.FOM_Cocoon,
+     *      java.lang.String)
+     */
+    public void triggerWorkflow(FOM_Cocoon cocoon, String event) throws WorkflowException,
+            PageEnvelopeException, AccessControlException {
+        Document document = getPageEnvelope(cocoon).getDocument();
+        WorkflowUtil.invoke(document, event);
+    }
+
+    /**
+     * @see org.apache.lenya.cms.cocoon.flow.FlowHelper#reservedCheckIn(org.apache.cocoon.components.flow.javascript.fom.FOM_Cocoon,
+     *      boolean)
+     */
+    public void reservedCheckIn(FOM_Cocoon cocoon, boolean backup) throws Exception {
+        final PageEnvelope pageEnvelope = getPageEnvelope(cocoon);
+        pageEnvelope.getDocument().checkin();
+    }
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/selection/LastModSourceSelector.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/selection/LastModSourceSelector.java
new file mode 100644
index 0000000..fcafe85
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/selection/LastModSourceSelector.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lenya.cms.cocoon.selection;
+
+import org.apache.cocoon.selection.Selector;
+import org.apache.cocoon.util.AbstractLogEnabled;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceNotFoundException;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.avalon.framework.activity.Disposable;
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.avalon.framework.thread.ThreadSafe;
+
+import java.util.Map;
+
+/**
+ * <p>
+ * Last Modified Source Selector.
+ * </p>
+ * 
+ * <pre>
+ *  &lt;map:selector name="last-mod" src="org.apache.lenya.cms.cocoon.selection.LastModSourceSelector"/&gt;
+ *
+ *   &lt;map:select type="last-mod"&gt;
+ *      &lt;map:parameter name="compare-to" value="{sourceToCompareTo}"/&gt;
+ *
+ *      &lt;map:when test="cachedsource"&gt;
+ *         &lt;!-- executes iff cachedsource last-modified  &gt; courceToCompareTo last-modified --&gt;
+ *         &lt;map:read src="{cachedsource}" mime-type="text/xml; charset=utf-8"/&gt;
+ *      &lt;/map:when&gt;
+ *      &lt;map:otherwise&gt;
+ *         &lt;map:read src="{sourceToCompareTo}" mime-type="text/xml; charset=utf-8"/&gt;
+ *      &lt;/map:otherwise&gt;
+ *   &lt;/map:select&gt;
+ * </pre>
+ */
+
+public class LastModSourceSelector extends AbstractLogEnabled
+                 implements ThreadSafe, Serviceable, Disposable, Selector {
+
+    private ServiceManager manager;
+    private SourceResolver resolver;
+    private Source source = null;
+    private Source compare = null;
+
+    public void service(ServiceManager manager) throws ServiceException {
+        this.manager = manager;
+        this.resolver = (SourceResolver)manager.lookup(SourceResolver.ROLE);
+    }
+
+    public void dispose() {
+        if (null != this.source) {
+            resolver.release(this.source);
+            this.source = null;
+        }
+        if (null != this.compare) {
+            resolver.release(this.compare);
+            this.compare = null;
+        }
+        this.manager.release(this.resolver);
+        this.resolver = null;
+        this.manager = null;
+    }
+
+    public boolean select(String expression, Map objectModel, Parameters parameters) {
+        String sourceToCompare = parameters.getParameter("compare-to",null);
+        String compareToSource = expression;
+        long sourceModDate = 0;
+        long compareModDate = 0;
+        try {
+            source = resolver.resolveURI(sourceToCompare);
+            sourceModDate = source.getLastModified();
+            compare = resolver.resolveURI(compareToSource);
+            compareModDate = compare.getLastModified();
+        } catch (SourceNotFoundException e) {
+            return false;
+        } catch (Exception e) {
+            getLogger().warn("Exception resolving resource ", e);
+            return false;
+        }
+        boolean isNewer = (compareModDate > sourceModDate);        
+
+        return (sourceToCompare != null && isNewer);
+        
+    }
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/serialization/LinkSerializer.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/serialization/LinkSerializer.java
new file mode 100644
index 0000000..9a41a81
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/serialization/LinkSerializer.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lenya.cms.cocoon.serialization;
+
+import org.apache.cocoon.serialization.Serializer;
+import org.apache.cocoon.Constants;
+import org.apache.cocoon.xml.xlink.ExtendedXLinkPipe;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * Link serializer.
+ * TODO: add meaningful javadocs
+ */
+public class LinkSerializer extends ExtendedXLinkPipe implements Serializer {
+
+    private PrintStream out;
+
+    /**
+     * Set the {@link OutputStream} where the requested resource should be serialized.
+     */
+    public void setOutputStream(OutputStream out) throws IOException {
+        this.out = new PrintStream(out);
+    }
+
+    /**
+     * Get the mime-type of the output of this <code>Component</code>.
+     */
+    public String getMimeType() {
+        return Constants.LINK_CONTENT_TYPE;
+    }
+
+    public void simpleLink(String href, String role, String arcrole, String title, String show,
+            String actuate, String uri, String name, String raw, Attributes attr)
+            throws SAXException {
+        print(href);
+        super.simpleLink(href, role, arcrole, title, show, actuate, uri, name, raw, attr);
+    }
+
+    public void startLocator(String href, String role, String title, String label, String uri,
+            String name, String raw, Attributes attr) throws SAXException {
+        if (traversable(href)) {
+            print(href);
+        }
+        super.startLocator(href, role, title, label, uri, name, raw, attr);
+    }
+
+    private boolean traversable(String href) {
+        if (href.length() == 0)
+            return false;
+        if (href.charAt(0) == '#')
+            return false;
+        if (href.indexOf("://") != -1)
+            return false;
+        if (href.startsWith("mailto:"))
+            return false;
+        if (href.startsWith("news:"))
+            return false;
+        if (href.startsWith("javascript:"))
+            return false;
+        return true;
+    }
+
+    private void print(String href) {
+        int ankerPos = href.indexOf('#');
+        if (ankerPos == -1) {
+            // TODO: Xalan encodes international characters into URL encoding
+            out.println(href);
+        } else {
+            out.println(href.substring(0, ankerPos));
+        }
+    }
+
+    /**
+     * Test if the component wants to set the content length
+     */
+    public boolean shouldSetContentLength() {
+        return false;
+    }
+
+    /**
+     * Recyclable
+     */
+    public void recycle() {
+        super.recycle();
+        this.out = null;
+    }
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/AggregatingFallbackSourceFactory.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/AggregatingFallbackSourceFactory.java
new file mode 100644
index 0000000..f08be25
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/AggregatingFallbackSourceFactory.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cocoon.source;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.cocoon.processing.ProcessInfoProvider;
+import org.apache.cocoon.spring.configurator.WebAppContextUtils;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.store.impl.MRUMemoryStore;
+import org.apache.lenya.cms.module.Module;
+import org.apache.lenya.cms.publication.Publication;
+import org.apache.lenya.cms.publication.Session;
+import org.apache.lenya.cms.publication.URLInformation;
+import org.apache.lenya.cms.publication.templating.AllExistingSourceResolver;
+import org.apache.lenya.util.ServletHelper;
+
+/**
+ * <p>
+ * Aggregate all existing fallback sources by adding their XML content under the document element of
+ * the first encountered source. The document element of all subsequent sources is stripped.
+ * </p>
+ * <p>
+ * The fallback sources are resolved in bottom-up order, i.e.
+ * </p>
+ * <ul>
+ * <li>current publication</li>
+ * <li>template of the current publication</li>
+ * <li>template of the template publication</li>
+ * <li>...</li>
+ * <li>core</li>
+ * </ul>
+ * <p>
+ * If one of the fallback sources is not a well-formed XML document, a RuntimeException is thrown.
+ * </p>
+ */
+public class AggregatingFallbackSourceFactory extends FallbackSourceFactory {
+
+    /**
+     * @see org.apache.excalibur.source.SourceFactory#getSource(java.lang.String, java.util.Map)
+     */
+    public Source getSource(final String location, Map parameters) throws IOException,
+            MalformedURLException {
+
+        String[] uris;
+
+        if (useCache()) {
+            MRUMemoryStore store = getStore();
+            final String cacheKey = getCacheKey(getPublicationId(), location);
+            final String[] cachedUris = (String[]) store.get(cacheKey);
+            if (cachedUris == null) {
+                uris = findUris(location, parameters);
+                store.hold(cacheKey, uris);
+                if (getLogger().isDebugEnabled()) {
+                    getLogger()
+                            .debug(
+                                    "No cached source URI for key " + cacheKey
+                                            + ", caching resolved URIs.");
+                }
+            } else {
+                uris = cachedUris;
+                if (getLogger().isDebugEnabled()) {
+                    getLogger().debug("Using cached source URIs for key " + cacheKey);
+                }
+            }
+        } else {
+            uris = findUris(location, parameters);
+        }
+        return new AggregatingSource(location, uris, getSourceResolver());
+    }
+
+    protected String[] findUris(final String location, Map parameters) throws IOException,
+            MalformedURLException {
+
+        FallbackUri uri = new FallbackUri(location);
+        String pubId = uri.getPubId();
+        String path = uri.getPath();
+
+        try {
+
+            final ProcessInfoProvider processInfo = (ProcessInfoProvider) WebAppContextUtils
+                    .getCurrentWebApplicationContext().getBean(ProcessInfoProvider.ROLE);
+            HttpServletRequest request = processInfo.getRequest();
+
+            if (pubId == null) {
+                String webappUrl = ServletHelper.getWebappURI(request);
+                URLInformation info = new URLInformation(webappUrl);
+                pubId = info.getPublicationId();
+            }
+
+            Session session = getRepository().getSession(request);
+
+            String[] uris;
+
+            if (session.existsPublication(pubId)) {
+                Publication pub = session.getPublication(pubId);
+                AllExistingSourceResolver resolver = new AllExistingSourceResolver();
+                getTemplateManager().visit(pub, path, resolver);
+                uris = resolver.getUris();
+            } else {
+                uris = new String[0];
+            }
+
+            List allUris = new ArrayList();
+            allUris.addAll(Arrays.asList(uris));
+
+            String contextSourceUri = null;
+            if (path.startsWith("lenya/modules/")) {
+                final String moduleShortcut = path.split("/")[2];
+                Module module = (Module) getModules().get(moduleShortcut);
+                final String modulePath = path.substring(("lenya/modules/" + moduleShortcut)
+                        .length());
+                contextSourceUri = module.getBaseUri() + modulePath;
+            } else {
+                contextSourceUri = "context://" + path;
+            }
+            if (org.apache.lenya.cms.cocoon.source.SourceUtil.exists(contextSourceUri,
+                    getSourceResolver())) {
+                allUris.add(contextSourceUri);
+            }
+
+            return (String[]) allUris.toArray(new String[allUris.size()]);
+
+        } catch (Exception e) {
+            throw new RuntimeException("Resolving path [" + location + "] failed: ", e);
+        }
+    }
+
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/AggregatingSource.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/AggregatingSource.java
new file mode 100644
index 0000000..5d81eb7
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/AggregatingSource.java
@@ -0,0 +1,179 @@
+package org.apache.lenya.cms.cocoon.source;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.cocoon.components.source.impl.MultiSourceValidity;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceNotFoundException;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.excalibur.source.SourceValidity;
+import org.apache.lenya.xml.DocumentHelper;
+import org.apache.lenya.xml.NamespaceHelper;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * @see AggregatingFallbackSourceFactory
+ */
+public class AggregatingSource implements Source {
+
+    private String uri;
+    private String[] sourceUris;
+    private SourceResolver resolver;
+
+    /**
+     * @param uri
+     * @param uris
+     */
+    public AggregatingSource(String uri, String[] uris, SourceResolver resolver) {
+        this.sourceUris = (String[]) uris.clone();
+        this.uri = uri;
+        this.resolver = resolver;
+    }
+
+    public String toString() {
+        return getURI();
+    }
+
+    protected void loadDom() {
+        try {
+            for (int i = 0; i < sourceUris.length; i++) {
+                
+                Document sourceDom = SourceUtil.readDOM(sourceUris[i], this.resolver);
+
+                if (sourceDom == null) {
+                    throw new RuntimeException("The source [" + sourceUris[i]
+                            + "] doesn't contain XML.");
+                }
+
+                Element docElement = sourceDom.getDocumentElement();
+                if (this.dom == null) {
+                    String namespaceUri = docElement.getNamespaceURI();
+                    String prefix = docElement.getPrefix();
+                    String localName = docElement.getLocalName();
+
+                    if (namespaceUri == null) {
+                        this.dom = DocumentHelper.createDocument(null, localName, null);
+                    } else {
+                        NamespaceHelper helper = new NamespaceHelper(namespaceUri, prefix,
+                                localName);
+                        this.dom = helper.getDocument();
+                    }
+                }
+
+                Element[] elements = DocumentHelper.getChildren(docElement);
+                for (int e = 0; e < elements.length; e++) {
+                    Element clone = (Element) this.dom.importNode(elements[e], true);
+                    this.dom.getDocumentElement().appendChild(clone);
+                }
+            }
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Document dom;
+    private byte[] data;
+
+    protected Document getDom() {
+        if (this.dom == null) {
+            loadDom();
+        }
+        return this.dom;
+    }
+
+    protected byte[] getData() {
+        if (this.data == null) {
+            Document dom = getDom();
+            if (dom != null) {
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                try {
+                    DocumentHelper.writeDocument(dom, out);
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+                this.data = out.toByteArray();
+            }
+        }
+        return this.data;
+    }
+
+    public boolean exists() {
+        return this.sourceUris.length > 0;
+    }
+
+    public long getContentLength() {
+        return getData().length;
+    }
+
+    public InputStream getInputStream() throws IOException, SourceNotFoundException {
+        if (!exists()) {
+            throw new SourceNotFoundException(this + " does not exist!");
+        }
+        return new ByteArrayInputStream(getData());
+    }
+
+    public long getLastModified() {
+        long lastModified = 0;
+        for (int i = 0; i < this.sourceUris.length; i++) {
+            try {
+                lastModified = Math.max(lastModified, SourceUtil.getLastModified(sourceUris[i],
+                        this.resolver));
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return lastModified;
+    }
+
+    public String getMimeType() {
+        return "application/xml";
+    }
+
+    public String getScheme() {
+        return "aggregate-template";
+    }
+
+    public String getURI() {
+        return this.uri;
+    }
+
+    private SourceValidity validity;
+
+    public SourceValidity getValidity() {
+        if (this.validity == null) {
+            try {
+                MultiSourceValidity aggregatedValidity = new MultiSourceValidity(resolver,
+                        MultiSourceValidity.CHECK_ALWAYS);
+                for (int i = 0; i < this.sourceUris.length; i++) {
+                    Source source = null;
+                    try {
+                        source = resolver.resolveURI(this.sourceUris[i]);
+                        aggregatedValidity.addSource(source);
+                    } finally {
+                        if (source != null) {
+                            resolver.release(source);
+                        }
+                    }
+                }
+                aggregatedValidity.close();
+                this.validity = aggregatedValidity;
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return this.validity;
+    }
+
+    public void refresh() {
+        this.dom = null;
+        this.data = null;
+        this.validity = null;
+    }
+
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/DocumentSource.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/DocumentSource.java
new file mode 100644
index 0000000..095aabf
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/DocumentSource.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cocoon.source;
+
+import org.apache.excalibur.source.ModifiableSource;
+import org.apache.lenya.cms.publication.Document;
+
+public interface DocumentSource extends ModifiableSource {
+    
+    Document getDocument();
+
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/FallbackSourceFactory.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/FallbackSourceFactory.java
new file mode 100644
index 0000000..d44da8b
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/FallbackSourceFactory.java
@@ -0,0 +1,268 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cocoon.source;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.cocoon.processing.ProcessInfoProvider;
+import org.apache.cocoon.spring.configurator.WebAppContextUtils;
+import org.apache.cocoon.util.AbstractLogEnabled;
+import org.apache.commons.lang.Validate;
+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.URIAbsolutizer;
+import org.apache.excalibur.store.impl.MRUMemoryStore;
+import org.apache.lenya.cms.module.Module;
+import org.apache.lenya.cms.publication.Publication;
+import org.apache.lenya.cms.publication.Repository;
+import org.apache.lenya.cms.publication.Session;
+import org.apache.lenya.cms.publication.URLInformation;
+import org.apache.lenya.cms.publication.templating.ExistingSourceResolver;
+import org.apache.lenya.cms.publication.templating.PublicationTemplateManager;
+import org.apache.lenya.cms.publication.templating.VisitingSourceResolver;
+import org.apache.lenya.util.ServletHelper;
+
+/**
+ * <p>
+ * Source factory following the fallback principle.
+ * </p>
+ * <p>
+ * The ID of the current publication can be passed in the URL (
+ * <code>fallback:pub://path</code),
+ * this is necessary as a workaround for bug 40564.
+ * </p>
+ * 
+ * @version $Id$
+ */
+public class FallbackSourceFactory extends AbstractLogEnabled implements SourceFactory,
+        URIAbsolutizer {
+
+    protected MRUMemoryStore store;
+    private SourceResolver resolver;
+    private Repository repository;
+    private PublicationTemplateManager templateManager;
+    private Map modules;
+
+    /**
+     * Configure the spring bean accordingly if you want to use a store.
+     * @param store The store.
+     */
+    public void setStore(MRUMemoryStore store) {
+        Validate.notNull(store);
+        this.store = store;
+    }
+
+    protected boolean useCache() {
+        return this.store != null;
+    }
+
+    protected MRUMemoryStore getStore() {
+        return this.store;
+    }
+
+    public void setSourceResolver(SourceResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    protected SourceResolver getSourceResolver() {
+        return this.resolver;
+    }
+
+    public void setTemplateManager(PublicationTemplateManager mgr) {
+        this.templateManager = mgr;
+    }
+
+    protected PublicationTemplateManager getTemplateManager() {
+        return this.templateManager;
+    }
+
+    public void setModules(Map modules) {
+        this.modules = modules;
+    }
+
+    protected Map getModules() {
+        return this.modules;
+    }
+
+    /**
+     * @see org.apache.excalibur.source.SourceFactory#getSource(java.lang.String, java.util.Map)
+     */
+    public Source getSource(final String location, Map parameters) throws IOException,
+            MalformedURLException {
+
+        Source source;
+
+        if (useCache()) {
+            MRUMemoryStore store = getStore();
+            final String pubId = getPublicationId();
+            final String cacheKey = getCacheKey(pubId, location);
+            final String cachedSourceUri = (String) store.get(cacheKey);
+
+            if (cachedSourceUri == null) {
+                source = findSource(location, parameters);
+                final String resolvedSourceUri = source.getURI();
+                store.hold(cacheKey, resolvedSourceUri);
+                if (getLogger().isDebugEnabled()) {
+                    getLogger().debug(
+                            "No cached source URI for key " + cacheKey + ", caching URI "
+                                    + resolvedSourceUri);
+                }
+            } else {
+                source = this.resolver.resolveURI(cachedSourceUri);
+                if (getLogger().isDebugEnabled()) {
+                    getLogger().debug(
+                            "Using cached source URI " + cachedSourceUri + " for key " + cacheKey);
+                }
+            }
+
+        } else {
+            source = findSource(location, parameters);
+        }
+
+        return source;
+    }
+
+    /**
+     * @param pubId The publication ID.
+     * @param fallbackUri The fallback:// (or template-fallback:// etc.) URI.
+     * @return A string.
+     */
+    public static String getCacheKey(final String pubId, final String fallbackUri) {
+        String cacheKey = pubId == null ? fallbackUri : pubId + ":" + fallbackUri;
+        return cacheKey;
+    }
+
+    protected String getPublicationId() {
+        final ProcessInfoProvider processInfo = (ProcessInfoProvider) WebAppContextUtils
+                .getCurrentWebApplicationContext().getBean(ProcessInfoProvider.ROLE);
+        HttpServletRequest request = processInfo.getRequest();
+        String webappUri = ServletHelper.getWebappURI(request);
+        URLInformation info = new URLInformation(webappUri);
+        String pubId = null;
+        try {
+            Session session = this.repository.getSession(request);
+            String pubIdCandidate = info.getPublicationId();
+            if (pubIdCandidate != null && session.existsPublication(pubIdCandidate)) {
+                pubId = pubIdCandidate;
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return pubId;
+    }
+
+    protected Source findSource(final String location, Map parameters) throws IOException,
+            MalformedURLException {
+
+        FallbackUri uri = new FallbackUri(location);
+
+        String pubId = uri.getPubId();
+        String path = uri.getPath();
+
+        Source source = null;
+        try {
+            final ProcessInfoProvider processInfo = (ProcessInfoProvider) WebAppContextUtils
+                    .getCurrentWebApplicationContext().getBean(ProcessInfoProvider.ROLE);
+            HttpServletRequest request = processInfo.getRequest();
+
+            if (pubId == null) {
+                String webappUrl = request.getRequestURI().substring(
+                        request.getContextPath().length());
+
+                URLInformation info = new URLInformation(webappUrl);
+                pubId = info.getPublicationId();
+            }
+
+            Session session = this.repository.getSession(request);
+            if (session.existsPublication(pubId)) {
+                Publication pub = session.getPublication(pubId);
+                VisitingSourceResolver resolver = getSourceVisitor();
+                this.templateManager.visit(pub, path, resolver);
+                source = resolver.getSource();
+            }
+
+            if (source == null) {
+                if (path.startsWith("lenya/modules/")) {
+                    final String moduleShortcut = path.split("/")[2];
+                    if (!this.modules.containsKey(moduleShortcut)) {
+                        throw new RuntimeException("The module '" + moduleShortcut + "' is not registered.");
+                    }
+                    Module module = (Module) this.modules.get(moduleShortcut);
+                    String baseUri = module.getBaseUri();
+                    final String modulePath = path.substring(("lenya/modules/" + moduleShortcut)
+                            .length());
+                    source = this.resolver.resolveURI(baseUri + modulePath);
+                } else {
+                    String contextUri = "context://" + path;
+                    source = this.resolver.resolveURI(contextUri);
+                }
+            }
+
+            if (getLogger().isDebugEnabled()) {
+                getLogger().debug("Resolved source:  [" + source.getURI() + "]");
+            }
+
+        } catch (Exception e) {
+            throw new RuntimeException("Resolving path [" + location + "] failed: ", e);
+        }
+
+        return source;
+    }
+
+    protected VisitingSourceResolver getSourceVisitor() {
+        return new ExistingSourceResolver();
+    }
+
+    /**
+     * @see org.apache.excalibur.source.SourceFactory#release(org.apache.excalibur.source.Source)
+     */
+    public void release(Source source) {
+        // In fact, this method should never be called as this factory
+        // returns a source object from a different factory. So that
+        // factory should release the source
+        if (null != source) {
+            if (this.getLogger().isDebugEnabled()) {
+                this.getLogger().debug("Releasing source " + source.getURI());
+            }
+            this.resolver.release(source);
+        }
+    }
+
+    /**
+     * @see org.apache.excalibur.source.URIAbsolutizer#absolutize(java.lang.String,
+     *      java.lang.String)
+     */
+    public String absolutize(String baseURI, String location) {
+        return SourceUtil.absolutize(baseURI, location, true);
+    }
+
+    public void setRepository(Repository repository) {
+        this.repository = repository;
+    }
+
+    public Repository getRepository() {
+        return repository;
+    }
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/FallbackUri.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/FallbackUri.java
new file mode 100644
index 0000000..e1725e4
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/FallbackUri.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cocoon.source;
+
+import java.net.MalformedURLException;
+import java.util.StringTokenizer;
+
+public class FallbackUri {
+    
+    private String uri;
+    private String pubId;
+    private String path;
+    private String queryString;
+    
+    public FallbackUri(String uri) throws MalformedURLException {
+        
+        this.uri = uri;
+        
+        // Remove the protocol and the first '//'
+        int pos = uri.indexOf("://");
+
+        if (pos == -1) {
+            throw new MalformedURLException("The URI [" + uri
+                    + "] does not contain the string '://'");
+        }
+
+        String path = uri.substring(pos + 3);
+
+        // extract publication ID
+        String prefix = uri.substring(0, pos);
+        StringTokenizer tokens = new StringTokenizer(prefix, ":");
+        if (tokens.countTokens() > 1) {
+            tokens.nextToken();
+            this.pubId = tokens.nextToken();
+        }
+
+        // remove query string
+        int questionMarkIndex = path.indexOf("?");
+        if (questionMarkIndex > -1) {
+            this.queryString = path.substring(questionMarkIndex);
+            path = path.substring(0, questionMarkIndex);
+        }
+
+        if (path.length() == 0) {
+            throw new MalformedURLException("The path after the protocol must not be empty!");
+        }
+        
+        this.path = path;
+    }
+    
+    public String getPubId() {
+        return this.pubId;
+    }
+    
+    public String getPath() {
+        return this.path;
+    }
+    
+    public String getQeryString() {
+        return this.queryString;
+    }
+    
+    public String getUri() {
+        return this.uri;
+    }
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/LenyaDocSourceFactory.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/LenyaDocSourceFactory.java
new file mode 100644
index 0000000..9c6936c
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/LenyaDocSourceFactory.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cocoon.source;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.cocoon.processing.ProcessInfoProvider;
+import org.apache.cocoon.spring.configurator.WebAppContextUtils;
+import org.apache.cocoon.util.AbstractLogEnabled;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceException;
+import org.apache.excalibur.source.SourceFactory;
+import org.apache.lenya.cms.publication.Document;
+import org.apache.lenya.cms.publication.Publication;
+import org.apache.lenya.cms.publication.Repository;
+import org.apache.lenya.cms.publication.ResourceNotFoundException;
+import org.apache.lenya.cms.publication.Session;
+import org.apache.lenya.cms.publication.URLInformation;
+import org.apache.lenya.cms.repository.NodeFactory;
+import org.apache.lenya.util.ServletHelper;
+
+/**
+ * A factory for the "lenyadoc" scheme (virtual protocol), which is used to resolve any
+ * src="lenyadoc:<...>" attributes in sitemaps.
+ * 
+ * <code>lenyadoc://<publication>/<area>/<language>/<uuid></code>
+ * <code>lenyadoc:/<language>/<uuid></code>
+ * 
+ * If we want to request the meta data for a document
+ * instead of the document itself, we need to use
+ * 
+ * <code>lenyadoc:meta:/<language>/<uuid></code>
+ * <code>lenyadoc:meta://<publication>/<area>/<language>/<uuid></code>
+ * 
+ * @version $Id:$
+ * @deprecated Use <code>lenya-document</code> instead (see {@link org.apache.lenya.cms.cocoon.source.DocumentSourceFactory}.
+ */
+public class LenyaDocSourceFactory extends AbstractLogEnabled implements SourceFactory {
+
+    protected static final String SCHEME = "lenyadoc";
+    
+    private Repository repository;
+    private NodeFactory nodeFactory;
+
+    /**
+     * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
+     */
+    public void configure(Configuration configuration) throws ConfigurationException {
+    }
+
+    /**
+     * @see org.apache.excalibur.source.SourceFactory#getSource(java.lang.String, java.util.Map)
+     */
+    public Source getSource(String location, Map parameters) throws MalformedURLException,
+            IOException, SourceException {
+        String scheme = null;
+        String area = null;
+        String language = null;
+        String uuid = null;
+        Publication pub;
+
+        // Parse the url
+        int start = 0;
+        int end;
+
+        // Scheme
+        end = location.indexOf(':', start);
+        if (end == -1) {
+            throw new MalformedURLException("Malformed lenyadoc: URI: can not find scheme part ["
+                    + location + "]");
+        }
+        scheme = location.substring(start, end);
+        if (!SCHEME.equals(scheme)) {
+            throw new MalformedURLException("Malformed lenyadoc: URI: unknown scheme [" + location
+                    + "]");
+        }
+
+        ProcessInfoProvider processInfoProvider = (ProcessInfoProvider) WebAppContextUtils
+                .getCurrentWebApplicationContext().getBean(ProcessInfoProvider.ROLE);
+        HttpServletRequest request = processInfoProvider.getRequest();
+        Session session = this.repository.getSession(request);
+        start = end + 1;
+        
+        // Absolute vs. relative
+        if (location.startsWith("//", start)) {
+            // Absolute: get publication id
+            start += 2;
+            end = location.indexOf('/', start);
+            if (end == -1) {
+                throw new MalformedURLException("Malformed lenyadoc: URI: publication part not found ["
+                        + location + "]");
+            }
+            String publicationId = location.substring(start, end);
+            try {
+                pub = session.getPublication(publicationId);
+            } catch (ResourceNotFoundException e) {
+                throw new MalformedURLException("Malformed lenyadoc: Publication [" + publicationId
+                        + "] does not exist or could not be initialized");
+            }
+            if (pub == null || !pub.exists()) {
+                throw new SourceException("The publication [" + publicationId + "] does not exist!");
+            }
+
+            // Area
+            start = end + 1;
+            end = location.indexOf('/', start);
+            if (end == -1) {
+                throw new MalformedURLException("Malformed lenyadoc: URI: cannot find area ["
+                        + location + "]");
+            }
+            area = location.substring(start, end);
+
+        } else if (location.startsWith("/", start)) {
+            end += 1;
+            // Relative: get publication id and area from page envelope
+            try {
+                String id = new URLInformation(ServletHelper.getWebappURI(request)).getPublicationId();
+                pub = session.getPublication(id);
+            } catch (ResourceNotFoundException e) {
+                throw new SourceException("Error getting publication id / area from page envelope ["
+                        + location + "]");
+            }
+            if (pub != null && pub.exists()) {
+                String url = ServletHelper.getWebappURI(request);
+                area = new URLInformation(url).getArea();
+            } else {
+                throw new SourceException("Error getting publication id / area from page envelope ["
+                        + location + "]");
+            }
+        } else {
+            throw new MalformedURLException("Malformed lenyadoc: URI [" + location + "]");
+        }
+
+        // Language
+        start = end + 1;
+        end = location.indexOf('/', start);
+        if (end == -1) {
+            throw new MalformedURLException("Malformed lenyadoc: URI: cannot find language ["
+                    + location + "]");
+        }
+        language = location.substring(start, end);
+
+        // UUID
+        start = end + 1;
+        uuid = location.substring(start);
+
+        if (getLogger().isDebugEnabled()) {
+            getLogger().debug("Creating repository source for URI [" + location + "]");
+        }
+        Document document;
+        try {
+            document = pub.getArea(area).getDocument(uuid, language);
+        } catch (ResourceNotFoundException e) {
+            throw new MalformedURLException("Malformed lenyadoc: Document [" + uuid + ":"
+                    + language + "] could not be created.");
+        }
+
+        String lenyaURL = document.getSourceURI();
+
+        if (getLogger().isDebugEnabled()) {
+            getLogger().debug("Mapping 'lenyadoc:' URL [" + location + "] to 'lenya:' URL ["
+                    + lenyaURL + "]");
+            getLogger().debug("Creating repository source for URI [" + lenyaURL + "]");
+        }
+
+        return new RepositorySource(getNodeFactory(), lenyaURL, session, getLogger());
+    }
+
+    /**
+     * @see org.apache.excalibur.source.SourceFactory#release(org.apache.excalibur.source.Source)
+     */
+    public void release(Source source) {
+        // Source will be released by delegated source factory.
+    }
+
+    public void setRepository(Repository repository) {
+        this.repository = repository;
+    }
+
+    public void setNodeFactory(NodeFactory nodeFactory) {
+        this.nodeFactory = nodeFactory;
+    }
+
+    public NodeFactory getNodeFactory() {
+        return nodeFactory;
+    }
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/LenyaSourceFactory.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/LenyaSourceFactory.java
new file mode 100644
index 0000000..e665683
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/LenyaSourceFactory.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lenya.cms.cocoon.source;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.DefaultConfiguration;
+import org.apache.cocoon.components.flow.FlowHelper;
+import org.apache.cocoon.components.modules.input.JXPathHelper;
+import org.apache.cocoon.components.modules.input.JXPathHelperConfiguration;
+import org.apache.cocoon.processing.ProcessInfoProvider;
+import org.apache.cocoon.spring.configurator.WebAppContextUtils;
+import org.apache.cocoon.util.AbstractLogEnabled;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceException;
+import org.apache.excalibur.source.SourceFactory;
+import org.apache.lenya.cms.publication.Publication;
+import org.apache.lenya.cms.publication.Repository;
+import org.apache.lenya.cms.publication.Session;
+import org.apache.lenya.cms.repository.NodeFactory;
+import org.apache.lenya.cms.repository.RepositoryException;
+import org.apache.lenya.util.Query;
+
+/**
+ * A factory for the "lenya" scheme (virtual protocol), which is used to resolve any src="lenya:..."
+ * attributes in sitemaps. This implementation constructs the path to the source document from the
+ * page envelope and delegates any further resolving to the "context" source resolver of Cocoon.
+ * 
+ * @version $Id$
+ */
+public class LenyaSourceFactory extends AbstractLogEnabled implements SourceFactory {
+
+    protected static final String SCHEME = "lenya:";
+
+    /** fallback if no configuration is available */
+    protected static final String DEFAULT_DELEGATION_SCHEME = "context:";
+    protected static final String DEFAULT_DELEGATION_PREFIX = "/"
+            + Publication.PUBLICATION_PREFIX_URI;
+    
+    private NodeFactory nodeFactory;
+    private Repository repository;
+
+    /**
+     * @see org.apache.excalibur.source.SourceFactory#getSource(java.lang.String, java.util.Map)
+     */
+    public Source getSource(final String location, final Map parameters)
+            throws MalformedURLException, IOException, SourceException {
+
+        String sessionName = null;
+
+        String[] uriAndQuery = location.split("\\?");
+        if (uriAndQuery.length > 1) {
+            Query query = new Query(uriAndQuery[1]);
+            sessionName = query.getValue("session");
+        }
+
+        Session session;
+        try {
+            session = getSession(sessionName);
+        } catch (RepositoryException e) {
+            throw new RuntimeException(e);
+        }
+
+        if (getLogger().isDebugEnabled()) {
+            getLogger().debug("Creating repository source for URI [" + location + "]");
+        }
+
+        return new RepositorySource(this.nodeFactory, location, session, getLogger());
+
+    }
+
+    protected Session getSession(String sessionName) throws RepositoryException {
+        Session session;
+        ProcessInfoProvider process = (ProcessInfoProvider) WebAppContextUtils
+        .getCurrentWebApplicationContext().getBean(ProcessInfoProvider.ROLE);
+        if (sessionName == null) {
+            HttpServletRequest request = process.getRequest();
+            session = this.repository.getSession(request);
+        } else if (sessionName.equals("usecase")) {
+            session = getUsecaseSession(process.getObjectModel());
+        } else {
+            throw new RepositoryException("Invalid session: [" + sessionName + "]");
+        }
+
+        return session;
+    }
+
+    protected Session getUsecaseSession(Map objectModel) throws RepositoryException {
+        try {
+            Configuration config = new DefaultConfiguration("foo");
+            JXPathHelperConfiguration helperConfig = JXPathHelper.setup(config);
+            Object contextObject = FlowHelper.getContextObject(objectModel);
+            return (Session) JXPathHelper.getAttribute("usecase/session", config, helperConfig,
+                    contextObject);
+        } catch (Exception e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    /**
+     * Does nothing because the delegated factory does this.
+     * @see org.apache.excalibur.source.SourceFactory#release(org.apache.excalibur.source.Source)
+     */
+    public void release(Source source) {
+        // do nothing
+    }
+
+    public void setNodeFactory(NodeFactory nodeFactory) {
+        this.nodeFactory = nodeFactory;
+    }
+
+    public void setRepository(Repository repository) {
+        this.repository = repository;
+    }
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/PublicationSourceFactory.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/PublicationSourceFactory.java
new file mode 100644
index 0000000..70c5273
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/PublicationSourceFactory.java
@@ -0,0 +1,70 @@
+package org.apache.lenya.cms.cocoon.source;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.cocoon.processing.ProcessInfoProvider;
+import org.apache.cocoon.spring.configurator.WebAppContextUtils;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceFactory;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.lenya.cms.publication.Publication;
+import org.apache.lenya.cms.publication.Repository;
+import org.apache.lenya.cms.publication.ResourceNotFoundException;
+import org.apache.lenya.cms.publication.Session;
+
+/**
+ * Syntax: pub:{pubId}:/path/to/file
+ */
+public class PublicationSourceFactory implements SourceFactory {
+    
+    protected static final String PROTOCOL = "pub";
+
+    private Repository repository;
+    private SourceResolver sourceResolver;
+
+    public Source getSource(final String location, final Map parameters) throws IOException,
+            MalformedURLException {
+        
+        final String pathInfo = location.substring(PROTOCOL.length() + 1);
+        
+        final int colonIndex = pathInfo.indexOf(":");
+        if (colonIndex < 0) {
+            throw new MalformedURLException("The URI " + location
+                    + " must contain the publication ID.");
+        }
+
+        final String pubId = pathInfo.substring(0, colonIndex);
+        ProcessInfoProvider process = (ProcessInfoProvider) WebAppContextUtils
+                .getCurrentWebApplicationContext().getBean(ProcessInfoProvider.ROLE);
+        HttpServletRequest request = process.getRequest();
+        Session session = this.repository.getSession(request);
+        if (session.existsPublication(pubId)) {
+            Publication pub = session.getPublication(pubId);
+            final String path = pathInfo.substring(colonIndex + 1);
+            final String uri = pub.getSourceUri() + path;
+            return this.sourceResolver.resolveURI(uri);
+        } else {
+            throw new ResourceNotFoundException("The publication " + pubId + " does not exist.");
+        }
+    }
+
+    /**
+     * Does nothing because the delegated factory does this.
+     * @see org.apache.excalibur.source.SourceFactory#release(org.apache.excalibur.source.Source)
+     */
+    public void release(Source source) {
+    }
+
+    public void setRepository(Repository repository) {
+        this.repository = repository;
+    }
+
+    public void setSourceResolver(SourceResolver sourceResolver) {
+        this.sourceResolver = sourceResolver;
+    }
+
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/RepositorySource.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/RepositorySource.java
new file mode 100644
index 0000000..311b513
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/RepositorySource.java
@@ -0,0 +1,398 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cocoon.source;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.net.MalformedURLException;
+import java.util.Collection;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.cocoon.processing.ProcessInfoProvider;
+import org.apache.cocoon.spring.configurator.WebAppContextUtils;
+import org.apache.commons.logging.Log;
+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.impl.AbstractSource;
+import org.apache.lenya.cms.publication.Document;
+import org.apache.lenya.cms.publication.Publication;
+import org.apache.lenya.cms.publication.Session;
+import org.apache.lenya.cms.publication.URLInformation;
+import org.apache.lenya.cms.repository.ContentHolder;
+import org.apache.lenya.cms.repository.Node;
+import org.apache.lenya.cms.repository.NodeFactory;
+import org.apache.lenya.cms.repository.RepositoryException;
+import org.apache.lenya.cms.repository.SessionHolder;
+import org.apache.lenya.util.Query;
+import org.apache.lenya.util.ServletHelper;
+
+/**
+ * Repository source.
+ * 
+ * @version $Id$
+ */
+public class RepositorySource extends AbstractSource implements ModifiableTraversableSource {
+
+    private ContentHolder content;
+    private Session session;
+    private Log logger;
+    protected static final String SCHEME = "lenya";
+
+    private NodeFactory nodeFactory;
+
+    /**
+     * @param nodeFactory The node factory.
+     * @param uri The source URI.
+     * @param session The repository session.
+     * @param logger The logger.
+     * @throws SourceException if an error occurs.
+     * @throws MalformedURLException if an error occurs.
+     */
+    public RepositorySource(NodeFactory nodeFactory, String uri, Session session, Log logger)
+            throws SourceException, MalformedURLException {
+        this.logger = logger;
+        this.nodeFactory = nodeFactory;
+
+        if (getLogger().isDebugEnabled())
+            getLogger().debug("Init RepositorySource: " + uri);
+
+        if (session == null) {
+            throw new IllegalArgumentException("The repository session must not be null!");
+        }
+        this.session = session;
+
+        if (uri == null) {
+            throw new MalformedURLException("The source URI must not be null!");
+        }
+
+        setSystemId(uri);
+
+        // Scheme
+        int start = 0;
+        int end = uri.indexOf(':');
+        if (end == -1)
+            throw new MalformedURLException(
+                    "Malformed uri for xmodule source (cannot find scheme) : " + uri);
+
+        String scheme = uri.substring(start, end);
+        if (!SCHEME.equals(scheme))
+            throw new MalformedURLException("Malformed uri for a xmodule source : " + uri);
+
+        setScheme(scheme);
+
+        try {
+
+            String sourceUri;
+            int revisionNumber = -1;
+
+            int questionMarkIndex = uri.indexOf("?");
+            if (questionMarkIndex > -1) {
+                sourceUri = uri.substring(0, questionMarkIndex);
+                Query query = new Query(uri.substring(questionMarkIndex + 1));
+                String revisionString = query.getValue("rev", null);
+                if (revisionString != null) {
+                    ProcessInfoProvider process = (ProcessInfoProvider) WebAppContextUtils
+                            .getCurrentWebApplicationContext().getBean(ProcessInfoProvider.ROLE);
+                    HttpServletRequest request = process.getRequest();
+                    String webappUrl = ServletHelper.getWebappURI(request);
+                    String pubId = new URLInformation(webappUrl).getPublicationId();
+                    Publication pub = this.session.getPublication(pubId);
+                    Document currentDoc = pub.getSession().getUriHandler().getDocument(webappUrl);
+                    if (currentDoc.getSourceURI().equals(sourceUri)) {
+                        revisionNumber = Integer.valueOf(revisionString).intValue();
+                    }
+                }
+            } else {
+                sourceUri = uri;
+            }
+
+            org.apache.lenya.cms.repository.Session repoSession = ((SessionHolder) session)
+                    .getRepositorySession();
+            if (revisionNumber == -1) {
+                this.content = (ContentHolder) repoSession
+                        .getRepositoryItem(nodeFactory, sourceUri);
+            } else {
+                Node node = (Node) repoSession.getRepositoryItem(nodeFactory, sourceUri);
+                this.content = node.getHistory().getRevision(revisionNumber);
+            }
+
+        } catch (Exception e) {
+            throw new SourceException("Creating repository node failed: ", e);
+        }
+    }
+
+    /**
+     * @return The repository node which is accessed by this source.
+     */
+    public Node getNode() {
+
+        if (!(this.content instanceof Node)) {
+            throw new RuntimeException(
+                    "This operation can only be invoked on nodes, not on revisions.");
+        }
+
+        return (Node) this.content;
+    }
+
+    protected Log getLogger() {
+        return this.logger;
+    }
+
+    /**
+     * @see org.apache.excalibur.source.ModifiableSource#getOutputStream()
+     */
+    public OutputStream getOutputStream() throws IOException {
+        try {
+            return getNode().getOutputStream();
+        } catch (RepositoryException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @see org.apache.excalibur.source.ModifiableSource#delete()
+     */
+    public void delete() {
+        try {
+            getNode().delete();
+        } catch (RepositoryException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @see org.apache.excalibur.source.ModifiableSource#canCancel(java.io.OutputStream)
+     */
+    public boolean canCancel(OutputStream arg0) {
+        return false;
+    }
+
+    /**
+     * @see org.apache.excalibur.source.ModifiableSource#cancel(java.io.OutputStream)
+     */
+    public void cancel(OutputStream arg0) throws IOException {
+    }
+
+    /**
+     * @see org.apache.excalibur.source.Source#exists()
+     */
+    public boolean exists() {
+        try {
+            if (getContent().exists()) {
+                return true;
+            } else {
+                return isCollection();
+            }
+        } catch (RepositoryException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @see org.apache.excalibur.source.Source#getInputStream()
+     */
+    public InputStream getInputStream() throws IOException, SourceNotFoundException {
+        if (getLogger().isDebugEnabled())
+            getLogger().debug("Get InputStream for " + getURI());
+        if (!exists()) {
+            throw new SourceNotFoundException("The source [" + getURI() + "] does not exist!");
+        }
+        try {
+            return getContent().getInputStream();
+        } catch (RepositoryException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected InputStream convert(org.w3c.dom.Document edoc) throws IOException {
+
+        final org.w3c.dom.Document doc = edoc;
+        final PipedOutputStream pos = new PipedOutputStream();
+        PipedInputStream pis = new PipedInputStream();
+        pis.connect(pos);
+
+        (new Thread(new Runnable() {
+
+            public void run() {
+                try {
+                    transform(doc, pos);
+                } catch (TransformerException e) {
+                    throw new RuntimeException(
+                            "Failed to tranform org.w3c.dom.Document to PipedOutputStream", e);
+                } finally {
+                    try {
+                        pos.close();
+                    } catch (Exception ignore) {
+                        ignore.printStackTrace();
+                    }
+                }
+            }
+        }, getClass().getName() + ".convert(org.w3c.dom.Document edoc)")).start();
+
+        return pis;
+    }
+
+    void transform(org.w3c.dom.Document edoc, PipedOutputStream pos) throws TransformerException {
+
+        TransformerFactory tFactory = TransformerFactory.newInstance();
+        Transformer transformer = tFactory.newTransformer();
+
+        transformer.setOutputProperty("encoding", "UTF-8");
+        transformer.setOutputProperty("indent", "yes");
+
+        transformer.transform(new DOMSource(edoc), new StreamResult(pos));
+
+    }
+
+    /**
+     * @return The content of this source.
+     */
+    public ContentHolder getContent() {
+        return this.content;
+    }
+
+    /**
+     * @see org.apache.excalibur.source.Source#getContentLength()
+     */
+    public long getContentLength() {
+        try {
+            return getContent().getContentLength();
+        } catch (RepositoryException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @see org.apache.excalibur.source.Source#getLastModified()
+     */
+    public long getLastModified() {
+        try {
+            return getContent().getLastModified();
+        } catch (RepositoryException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @return The source URI.
+     */
+    public String getSourceURI() {
+        return getContent().getSourceURI();
+    }
+
+    /**
+     * @see org.apache.excalibur.source.Source#getMimeType()
+     */
+    public String getMimeType() {
+        try {
+            return getContent().getMimeType();
+        } catch (RepositoryException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 
+     */
+    public Source getParent() {
+        getLogger().warn("getParent() not implemented yet!");
+        return null;
+    }
+
+    /**
+     * 
+     */
+    public void makeCollection() {
+        getLogger().warn("RepositorySource().makeCollection() not implemented yet!");
+    }
+
+    /**
+     * 
+     */
+    public String getName() {
+        // Quick and dirty
+        String name = new java.io.File(getURI()).getName();
+        if (getLogger().isDebugEnabled())
+            getLogger().debug("getName(): URI: " + name);
+        return name;
+    }
+
+    /**
+     * 
+     */
+    public Source getChild(String name) {
+        getLogger().warn("getChild() not implemented yet!");
+        return null;
+    }
+
+    /**
+     * 
+     */
+    public Collection getChildren() {
+        try {
+            Collection children = getNode().getChildren();
+            java.util.Iterator iterator = children.iterator();
+            java.util.Vector newChildren = new java.util.Vector();
+            while (iterator.hasNext()) {
+                Node child = (Node) iterator.next();
+                newChildren.add(new RepositorySource(getNodeFactory(), child.getSourceURI(),
+                        this.session, getLogger()));
+            }
+            return newChildren;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 
+     */
+    public boolean isCollection() {
+        try {
+            return getNode().isCollection();
+        } catch (RepositoryException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private SourceValidity validity;
+
+    public SourceValidity getValidity() {
+        if (this.validity == null) {
+            this.validity = new RepositorySourceValidity(this);
+        }
+        return this.validity;
+    }
+
+    public NodeFactory getNodeFactory() {
+        return nodeFactory;
+    }
+
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/RepositorySourceValidity.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/RepositorySourceValidity.java
new file mode 100644
index 0000000..6249618
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/RepositorySourceValidity.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cocoon.source;
+
+import org.apache.excalibur.source.SourceValidity;
+
+/**
+ * Validity for repository sources.
+ */
+public class RepositorySourceValidity implements SourceValidity {
+
+    private static final long serialVersionUID = 1L;
+
+    private String sourceUri;
+    private long lastModified;
+
+    /**
+     * @param source The source this validity is for.
+     */
+    public RepositorySourceValidity(RepositorySource source) {
+        this.sourceUri = source.getSourceURI();
+        this.lastModified = source.exists() ? source.getLastModified() : 0;
+    }
+
+    public int isValid() {
+        return SourceValidity.UNKNOWN;
+    }
+
+    public int isValid(SourceValidity validity) {
+        if (validity instanceof RepositorySourceValidity) {
+            RepositorySourceValidity repoValidity = (RepositorySourceValidity) validity;
+            String repoValidityUri = repoValidity.getSourceURI();
+
+            if (!repoValidityUri.equals(this.sourceUri)) {
+                throw new RuntimeException("Wrong source URI: [" + repoValidityUri
+                        + "] instead of [" + this.sourceUri + "]!");
+            }
+            if (this.lastModified >= repoValidity.getLastModified()) {
+                return SourceValidity.VALID;
+            } else {
+                return SourceValidity.INVALID;
+            }
+        } else {
+            return SourceValidity.INVALID;
+        }
+    }
+
+    protected long getLastModified() {
+        return this.lastModified;
+    }
+
+    protected String getSourceURI() {
+        return this.sourceUri;
+    }
+
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/SiteSourceFactory.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/SiteSourceFactory.java
new file mode 100644
index 0000000..068018a
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/SiteSourceFactory.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cocoon.source;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.cocoon.processing.ProcessInfoProvider;
+import org.apache.cocoon.spring.configurator.WebAppContextUtils;
+import org.apache.cocoon.util.AbstractLogEnabled;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceException;
+import org.apache.excalibur.source.SourceFactory;
+import org.apache.excalibur.source.SourceNotFoundException;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.lenya.cms.publication.Document;
+import org.apache.lenya.cms.publication.Repository;
+import org.apache.lenya.cms.publication.Session;
+import org.apache.lenya.cms.publication.URLInformation;
+import org.apache.lenya.cms.site.SiteStructure;
+import org.apache.lenya.util.ServletHelper;
+
+/**
+ * <p>
+ * This source factory allows to access documents based on their path in the site structure.
+ * </p>
+ * <p>
+ * Relative addressing refers to the current publication and area.
+ * </p>
+ * <p>
+ * Syntax:
+ * </p>
+ * <ul>
+ * <li>Absolute: <code>site://{pubId}/{area}/{language}{path}</code></li>
+ * <li>Relative: <code>site:/{language}{path}</code></li>
+ * </ul>
+ * <p>
+ * Usage examples:
+ * </p>
+ * <ul>
+ * <li><code>site://default/authoring/en/news/today</code></li>
+ * <li><code>site:/en/news/today</code></li>
+ * </ul>
+ */
+public class SiteSourceFactory extends AbstractLogEnabled implements SourceFactory {
+
+    private SourceResolver sourceResolver;
+    private Repository repository;
+
+    /**
+     * @see org.apache.excalibur.source.SourceFactory#getSource(java.lang.String, java.util.Map)
+     */
+    public Source getSource(String location, Map parameters) throws MalformedURLException,
+            IOException, SourceException {
+        ProcessInfoProvider process = (ProcessInfoProvider) WebAppContextUtils
+                .getCurrentWebApplicationContext().getBean(ProcessInfoProvider.ROLE);
+        HttpServletRequest request = process.getRequest();
+
+        String areaName = null;
+        String pubId;
+
+        StringTokenizer locationSteps = new StringTokenizer(location, "?");
+        String completePath = locationSteps.nextToken();
+
+        String relativePath;
+        try {
+
+            String scheme = completePath.substring(0, completePath.indexOf(":") + 1);
+            final String absolutePath = completePath.substring(scheme.length());
+            if (absolutePath.startsWith("//")) {
+                final String fullPath = absolutePath.substring(2);
+                StringTokenizer steps = new StringTokenizer(fullPath, "/");
+                pubId = steps.nextToken();
+                areaName = steps.nextToken();
+                String prefix = pubId + "/" + areaName;
+                relativePath = fullPath.substring(prefix.length());
+            } else if (absolutePath.startsWith("/")) {
+                String webappUrl = ServletHelper.getWebappURI(request);
+                URLInformation info = new URLInformation(webappUrl);
+                pubId = info.getPublicationId();
+                areaName = info.getArea();
+                relativePath = absolutePath;
+            } else {
+                throw new MalformedURLException("The path [" + absolutePath
+                        + "] must start with at least one slash.");
+            }
+
+            Session session = this.repository.getSession(request);
+            SiteStructure site = session.getPublication(pubId).getArea(areaName).getSite();
+
+            String[] steps = relativePath.substring(1).split("/");
+
+            String language = steps[0];
+            String prefix = "/" + language;
+            String path = relativePath.substring(prefix.length());
+
+            if (site.contains(path, language)) {
+                Document doc = site.getNode(path).getLink(language).getDocument();
+                String docUri = "lenya-document:" + doc.getUUID() + ",lang=" + doc.getLanguage()
+                        + ",area=" + doc.getArea() + ",pub=" + doc.getPublication().getId();
+
+                if (locationSteps.hasMoreTokens()) {
+                    String queryString = locationSteps.nextToken();
+                    docUri = docUri + "?" + queryString;
+                }
+                return this.sourceResolver.resolveURI(docUri);
+            } else {
+                throw new SourceNotFoundException("The source [" + location + "] doesn't exist.");
+            }
+
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * @see org.apache.excalibur.source.SourceFactory#release(org.apache.excalibur.source.Source)
+     */
+    public void release(Source source) {
+        this.sourceResolver.release(source);
+    }
+
+    public SourceResolver getSourceResolver() {
+        return sourceResolver;
+    }
+
+    public void setSourceResolver(SourceResolver sourceResolver) {
+        this.sourceResolver = sourceResolver;
+    }
+
+    public void setRepository(Repository repository) {
+        this.repository = repository;
+    }
+
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/TemplateFallbackSourceFactory.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/TemplateFallbackSourceFactory.java
new file mode 100644
index 0000000..efd3fc9
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/TemplateFallbackSourceFactory.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.cms.cocoon.source;
+
+import org.apache.lenya.cms.publication.templating.ExistingAncestorSourceResolver;
+import org.apache.lenya.cms.publication.templating.VisitingSourceResolver;
+
+/**
+ * Source factory following the fallback principle, resolving the existing ancestor of the existing resource.
+ * For more information about the URL syntax, see {@link FallbackSourceFactory}.
+ * 
+ * @version $Id: FallbackSourceFactory.java 264153 2005-08-29 15:11:14Z andreas $
+ */
+public class TemplateFallbackSourceFactory extends FallbackSourceFactory {
+
+    protected VisitingSourceResolver getSourceVisitor() {
+        return new ExistingAncestorSourceResolver();
+    }
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/ZipSource.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/ZipSource.java
new file mode 100644
index 0000000..7d57a59
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/ZipSource.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lenya.cms.cocoon.source;
+//package org.apache.cocoon.components.source.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.apache.cocoon.util.AbstractLogEnabled;
+import org.apache.cocoon.util.MIMEUtils;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceNotFoundException;
+import org.apache.excalibur.source.SourceValidity;
+
+/**
+  * @author <a href="http://apache.org/~reinhard">Reinhard Poetz</a>
+  * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
+  * @version CVS $Id: ZipSource.java 30932 2004-07-29 17:35:38Z vgritsenko $
+  * @since 2.1.4 
+  */
+public class ZipSource extends AbstractLogEnabled implements Source {
+
+    Source archive;
+    String documentName;
+
+    /**
+     * Ctor.
+     * @param archive The archive source.
+     * @param fileName The filename.
+     */
+    public ZipSource(Source archive, String fileName) {
+        this.archive = archive;
+        this.documentName = fileName;
+    }
+
+    public boolean exists() {
+        if(!this.archive.exists()) {
+            return false;
+        }
+        ZipInputStream zipStream = null;
+        ZipEntry document = null;
+        boolean found = false;
+        try {
+            zipStream = new ZipInputStream(this.archive.getInputStream());
+            do {
+                document = zipStream.getNextEntry();
+                if (document != null) {
+                    if (document.getName().equals(this.documentName)) {
+                        found = true;
+                    } else {
+                        zipStream.closeEntry();
+                    }
+                }
+            } while (document != null && found == false);
+        } catch(IOException ioe) {
+            return false;
+        } finally {
+            try {
+                zipStream.close();
+            } catch (IOException ioe) {
+                this.getLogger().error("Error while closing ZipInputStream: " + this.documentName);
+            }
+        } 
+        return found;
+    }
+    
+    public InputStream getInputStream()
+        throws IOException, SourceNotFoundException {
+
+        ZipInputStream zipStream =
+            new ZipInputStream(this.archive.getInputStream());
+        ZipEntry document = null;
+        boolean found = false;
+        do {
+            document = zipStream.getNextEntry();
+            if (document != null) {
+                if (document.getName().equals(this.documentName)) {
+                    found = true;
+                } else {
+                    // go to next entry
+                    zipStream.closeEntry();
+                }
+            }
+        } while (document != null && found == false);
+
+        if (document == null) {
+            throw new SourceNotFoundException(
+                "The document "
+                    + documentName
+                    + " is not in the archive "
+                    + this.archive.getURI());
+        }
+
+        // now we will extract the document and write it into a byte array
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] buffer = new byte[8192];
+        int length = -1;
+        while (zipStream.available() > 0) {
+            length = zipStream.read(buffer, 0, 8192);
+            if (length > 0) {
+                baos.write(buffer, 0, length);
+            }
+        }
+        zipStream.close();
+        baos.flush();
+
+        // return an input stream
+        return new ByteArrayInputStream(baos.toByteArray());
+    }
+
+    public String getURI() {
+        return this.archive.getURI() + "/" + this.documentName;
+    }
+
+    public String getScheme() {
+        return ZipSourceFactory.ZIP_SOURCE_SCHEME;
+    }
+
+    public SourceValidity getValidity() {
+        return this.archive.getValidity();
+    }
+
+    public void refresh() {
+    }
+
+    public String getMimeType() {
+        String ext = this.documentName.substring( this.documentName.lastIndexOf(".") );
+        return MIMEUtils.getMIMEType( ext );
+    }
+
+    public long getContentLength() {
+        return -1;
+    }
+
+    public long getLastModified() {
+        return this.archive.getLastModified();
+    }
+
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/ZipSourceFactory.java b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/ZipSourceFactory.java
new file mode 100644
index 0000000..e26596b
--- /dev/null
+++ b/org.apache.lenya.core.cocoon/src/main/java/org/apache/lenya/cms/cocoon/source/ZipSourceFactory.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//package org.apache.cocoon.components.source.impl;
+package org.apache.lenya.cms.cocoon.source;
+
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.Map;
+
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.avalon.framework.thread.ThreadSafe;
+import org.apache.cocoon.util.AbstractLogEnabled;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceException;
+import org.apache.excalibur.source.SourceFactory;
+import org.apache.excalibur.source.SourceResolver;
+
+
+/** Implementation of a {@link Source} that gets its content from
+  * and ZIP archive.
+  * 
+  * A ZIP source can be reached using the zip:// pseudo-protocol. The syntax is
+  * zip://myFile.xml@myZip.zip (zip://[file]@[archive])
+  * 
+  * @author <a href="http://apache.org/~reinhard">Reinhard Poetz</a>
+  * @version CVS $Id: ZipSourceFactory.java 30932 2004-07-29 17:35:38Z vgritsenko $
+  * @since 2.1.4
+  */ 
+public class ZipSourceFactory extends AbstractLogEnabled
+    implements SourceFactory, ThreadSafe, Serviceable {
+
+    protected ServiceManager manager;
+    /**
+     * The URL scheme, including the colon.
+     */
+    public static final String ZIP_SOURCE_SCHEME = "zip:";
+
+    public Source getSource(String location, Map parameters)
+        throws IOException, MalformedURLException {
+        
+        if ( this.getLogger().isDebugEnabled() ) {
+            this.getLogger().debug("Processing " + location);
+        }
+        
+        // syntax checks
+        int separatorPos = location.indexOf('@');
+        if (separatorPos == -1) {
+            throw new MalformedURLException("@ required in URI: " + location);
+        }
+        int protocolEnd = location.indexOf("://");
+        if (protocolEnd == -1) {
+            throw new MalformedURLException("URI does not contain '://' : " + location);
+        }
+
+        // get the source of the archive and return the ZipSource passing
+        // a source retrieved from the SourceResolver
+        String documentName = location.substring(protocolEnd + 3, separatorPos);
+        Source archive;
+        SourceResolver resolver = null;
+        try {
+            resolver = (SourceResolver)this.manager.lookup( SourceResolver.ROLE );
+            archive = resolver.resolveURI(location.substring(separatorPos + 1));            
+        } catch (ServiceException se) {
+            throw new SourceException("SourceResolver is not available.", se);
+        } finally {
+            this.manager.release(resolver);
+        }
+        return new ZipSource(archive, documentName);
+    }
+
+
+    public void release(Source source) {
+        // not necessary here
+    }
+
+    public void service(ServiceManager manager) throws ServiceException {
+        this.manager = manager;
+    }
+
+}
diff --git a/org.apache.lenya.core.cocoon/src/main/resources/META-INF/cocoon/spring/block-servlet-service.xml b/org.apache.lenya.core.cocoon/src/main/resources/META-INF/cocoon/spring/block-servlet-service.xml
index 8bff2f4..4a18f78 100644
--- a/org.apache.lenya.core.cocoon/src/main/resources/META-INF/cocoon/spring/block-servlet-service.xml
+++ b/org.apache.lenya.core.cocoon/src/main/resources/META-INF/cocoon/spring/block-servlet-service.xml
@@ -25,8 +25,8 @@
                            http://cocoon.apache.org/schema/servlet http://cocoon.apache.org/schema/servlet/cocoon-servlet-1.0.xsd">
 
   <!--  exemple from the archetype -->
-  <bean name="org.apache.lenya.lenya-core-cocoon-components.service" class="org.apache.cocoon.sitemap.SitemapServlet">
-    <servlet:context mount-path="/lenya-core-cocoon-components" context-path="blockcontext:/lenya-core-cocoon-components/"/>
+  <bean name="org.apache.lenya.core.cocoon.block" class="org.apache.cocoon.sitemap.SitemapServlet">
+    <servlet:context mount-path="/lenya.core.cocoon" context-path="blockcontext:/lenya.core.cocoon/"/>
   </bean>
 
 </beans>