move implementation classes from core.api to core.impl

git-svn-id: https://svn.apache.org/repos/asf/lenya/trunk@1033147 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.lenya.core.impl/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/AbstractPageEnvelopeModule.java b/org.apache.lenya.core.impl/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.impl/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.impl/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/DateConverterModule.java b/org.apache.lenya.core.impl/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.impl/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.impl/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/DocumentInfoModule.java b/org.apache.lenya.core.impl/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.impl/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.impl/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/DocumentURLModule.java b/org.apache.lenya.core.impl/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.impl/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.impl/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/FallbackModule.java b/org.apache.lenya.core.impl/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.impl/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.impl/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/InputModuleParameters.java b/org.apache.lenya.core.impl/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.impl/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.impl/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/OperationModule.java b/org.apache.lenya.core.impl/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.impl/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.impl/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/PageEnvelopeModule.java b/org.apache.lenya.core.impl/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.impl/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.impl/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/ResourceExistsModule.java b/org.apache.lenya.core.impl/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.impl/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.impl/src/main/java/org/apache/lenya/cms/cocoon/components/modules/input/ResourceTypeModule.java b/org.apache.lenya.core.impl/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.impl/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.impl/src/main/java/org/apache/lenya/cms/publication/SessionImpl.java b/org.apache.lenya.core.impl/src/main/java/org/apache/lenya/cms/publication/SessionImpl.java
index bae3284..f7deacd 100644
--- a/org.apache.lenya.core.impl/src/main/java/org/apache/lenya/cms/publication/SessionImpl.java
+++ b/org.apache.lenya.core.impl/src/main/java/org/apache/lenya/cms/publication/SessionImpl.java
@@ -26,6 +26,9 @@
 import org.apache.lenya.cms.repository.SessionHolder;
 import org.apache.lenya.transaction.UnitOfWork;
 
+/**
+ * @deprecated solve the concurrency beetween lenya-core-repository/o.a.l.cms.repository.SessionImpl and lenya-core-impl/o.a.l.cms.publication.SEssionImpl
+ */
 public class SessionImpl implements Session, SessionHolder {
 
     private static final Log logger = LogFactory.getLog(SessionImpl.class);
@@ -79,16 +82,14 @@
         return ((IdentityWrapper) getRepositorySession().getIdentity()).getIdentity();
     }
 
-    private UnitOfWork unitOfWork;
-
     /**
      * @return The unit of work.
      */
     protected UnitOfWork getUnitOfWork() {
-        if (this.unitOfWork == null) {
-            throw new RuntimeException("This session [" + getId() + "] is not modifiable!");
-        }
-        return this.unitOfWork;
+      if (repositorySession == null){
+        throw new RuntimeException("This session [" + getId() + "] is not modifiable!");
+      }  
+      return repositorySession;
     }
 
     public String getId() {