FOR-1057 Porting of the locationmap to a cocoon2.2 block. Work in progress since the cocoon:// protocol is deprecated and it will not work as we used it till now
git-svn-id: https://svn.apache.org/repos/asf/forrest/trunk@592080 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/pom.xml b/whiteboard/cocoon-2.2-blocks/locationmap/pom.xml
new file mode 100644
index 0000000..94930b2
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/pom.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<!--+
+ | @version $Id: pom.xml 579823 2007-09-26 22:09:12Z reinhard $
+ +-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>jar</packaging>
+
+ <name>locationmap</name>
+ <groupId>org.apache.forrest</groupId>
+ <artifactId>locationmap</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.cocoon</groupId>
+ <artifactId>cocoon-core</artifactId>
+ <version>2.2.0-RC2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cocoon</groupId>
+ <artifactId>cocoon-servlet-service-components</artifactId>
+ <version>1.0.0-RC1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cocoon</groupId>
+ <artifactId>cocoon-template-impl</artifactId>
+ <version>1.0.0-RC2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cocoon</groupId>
+ <artifactId>cocoon-flowscript-impl</artifactId>
+ <version>1.0.0-RC2</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.4</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.cocoon</groupId>
+ <artifactId>cocoon-maven-plugin</artifactId>
+ <version>1.0.0-M1</version>
+ <executions>
+ <execution>
+ <id>rcl</id>
+ <phase>compile</phase>
+ <goals>
+ <goal>rcl</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>maven-jetty-plugin</artifactId>
+ <version>6.1.5</version>
+ <configuration>
+ <connectors>
+ <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
+ <port>8888</port>
+ <maxIdleTime>30000</maxIdleTime>
+ </connector>
+ </connectors>
+ <webAppSourceDirectory>${project.build.directory}/rcl/webapp</webAppSourceDirectory>
+ <contextPath>/</contextPath>
+ <systemProperties>
+ <systemProperty>
+ <name>org.apache.cocoon.mode</name>
+ <value>dev</value>
+ </systemProperty>
+ </systemProperties>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.1</version>
+ <configuration>
+ <archive>
+ <manifestEntries>
+ <Cocoon-Block-Name>${pom.artifactId}</Cocoon-Block-Name>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-eclipse-plugin</artifactId>
+ <version>2.4</version>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/rcl.properties b/whiteboard/cocoon-2.2-blocks/locationmap/rcl.properties
new file mode 100644
index 0000000..9ee8856
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/rcl.properties
@@ -0,0 +1,17 @@
+#
+# 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.
+#
+org.apache.forrest.locationmap.service%classes-dir=./target/classes
\ No newline at end of file
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/AbstractWrappingModule.java b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/AbstractWrappingModule.java
new file mode 100644
index 0000000..b2c8227
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/AbstractWrappingModule.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.forrest.locationmap;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.avalon.framework.activity.Disposable;
+import org.apache.avalon.framework.configuration.Configurable;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.logger.LogEnabled;
+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.InputModule;
+
+public abstract class AbstractWrappingModule extends AbstractLogEnabled
+ implements InputModule, Configurable, Serviceable, Disposable {
+
+ InputModule child;
+ ServiceManager manager;
+
+ public void service(ServiceManager manager) throws ServiceException {
+ this.manager = manager;
+ }
+
+ public void configure(Configuration config) throws ConfigurationException {
+
+ Configuration childConf = config.getChild("component-instance");
+ String childClassName = childConf.getAttribute("class");
+ getLogger().debug("Loading wrapped class:"+childClassName);
+
+ try{
+ child = (InputModule) Class.forName(childClassName).newInstance();
+ getLogger().debug("Wrapped class instantiated:"+child);
+
+ if(child instanceof LogEnabled){
+ ((LogEnabled)child).enableLogging( getLogger() );
+ getLogger().debug("Wrapped class LogEnabled");
+ }
+
+ if(child instanceof Serviceable){
+ ((Serviceable)child).service( manager );
+ getLogger().debug("Wrapped class Serviced");
+ }
+
+ if(child instanceof Configurable){
+ ((Configurable)child).configure(config.getChild("component-instance"));
+ getLogger().debug("Wrapped class Configured");
+ }
+
+ }catch(Exception e){
+ throw new ConfigurationException
+ ("Cannot instatiate the wrapped Module of class:"+childClassName, e);
+ }
+ }
+
+ public Object getAttribute(String name, Configuration modeConf, Map objectModel)
+ throws ConfigurationException {
+ return child.getAttribute(name, modeConf, objectModel);
+ }
+
+ public Iterator getAttributeNames(Configuration modeConf, Map objectModel)
+ throws ConfigurationException {
+ return child.getAttributeNames(modeConf, objectModel);
+ }
+
+ public Object[] getAttributeValues(String name, Configuration modeConf, Map objectModel)
+ throws ConfigurationException {
+ return child.getAttributeValues(name, modeConf, objectModel);
+ }
+
+ public void dispose() {
+ if(child instanceof Disposable){
+ ((Disposable)child).dispose();
+ }
+ }
+
+}
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/LocationMapModule.java b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/LocationMapModule.java
new file mode 100644
index 0000000..c74ad25
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/LocationMapModule.java
@@ -0,0 +1,260 @@
+/*
+ * 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.forrest.locationmap;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.HashMap;
+
+import org.apache.forrest.locationmap.lm.LocationMap;
+import org.apache.avalon.framework.activity.Disposable;
+import org.apache.avalon.framework.configuration.Configurable;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.configuration.NamespacedSAXConfigurationHandler;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+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.components.modules.input.InputModule;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.excalibur.source.SourceValidity;
+import org.apache.excalibur.xml.sax.SAXParser;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * Resolves a request against a LocationMap.
+ */
+public class LocationMapModule extends AbstractLogEnabled
+ implements InputModule, Serviceable, Configurable, Disposable, ThreadSafe {
+
+ private static final Iterator ATTNAMES = Collections.EMPTY_LIST.iterator();
+
+ private ServiceManager m_manager;
+ private SourceResolver m_resolver;
+ private String m_src;
+ private SourceValidity m_srcVal;
+ private LocationMap m_lm;
+ private boolean m_cacheAll;
+ private int m_cacheTtl;
+ private Date m_cacheLastLoaded;
+ private Map m_locationsCache;
+
+ // ---------------------------------------------------- lifecycle
+
+ public LocationMapModule() {
+ }
+
+ public void service(ServiceManager manager) throws ServiceException {
+ m_manager = manager;
+ m_resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
+ }
+
+ public void configure(Configuration configuration) throws ConfigurationException {
+ m_src = configuration.getChild("file").getAttribute("src");
+ m_cacheAll = configuration.getChild("cacheable").getValueAsBoolean(true);
+ m_cacheTtl = configuration.getChild("cache-lifespan").getValueAsInteger();
+
+ debug("LM Configured cache? " + m_cacheAll);
+ debug("LM Configured cache-lifespan: " + m_cacheTtl);
+
+ if (m_cacheAll == true) {
+ m_locationsCache = new HashMap();
+ m_cacheLastLoaded = new Date();
+ }
+ }
+
+ public void dispose() {
+ m_lm.dispose();
+ m_locationsCache = null;
+ }
+
+ private LocationMap getLocationMap() throws Exception {
+ Source source = null;
+ try {
+ source = m_resolver.resolveURI(m_src);
+ if (m_lm == null) {
+ synchronized (this) {
+ if (m_lm == null) {
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug("loading location map at " + m_src);
+ }
+ m_srcVal = source.getValidity();
+ m_lm = new LocationMap(m_manager);
+ m_lm.enableLogging(getLogger());
+ m_lm.build(loadConfiguration(source));
+ }
+ }
+ }
+ else {
+ SourceValidity valid = source.getValidity();
+ if (m_srcVal != null && m_srcVal.isValid(valid) != 1) {
+ synchronized (this) {
+ if (m_srcVal != null && m_srcVal.isValid(valid) != 1) {
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug("reloading location map at " + m_src);
+ }
+ m_srcVal = valid;
+ m_lm.dispose();
+ m_lm = new LocationMap(m_manager);
+ m_lm.enableLogging(getLogger());
+ m_lm.build(loadConfiguration(source));
+ }
+ }
+ }
+ }
+ }
+ finally {
+ if (source != null) {
+ m_resolver.release(source);
+ }
+ }
+ m_cacheLastLoaded = new Date();
+
+ return m_lm;
+ }
+
+ private Configuration loadConfiguration(Source source) throws ConfigurationException {
+ Configuration configuration = null;
+ SAXParser parser = null;
+ try {
+ parser = (SAXParser) m_manager.lookup(SAXParser.ROLE);
+ NamespacedSAXConfigurationHandler handler =
+ new NamespacedSAXConfigurationHandler();
+ parser.parse(new InputSource(source.getInputStream()),handler);
+ configuration = handler.getConfiguration();
+ }
+ catch (IOException e) {
+ throw new ConfigurationException("Unable to build LocationMap.",e);
+ }
+ catch (SAXException e) {
+ throw new ConfigurationException("Unable to build LocationMap.",e);
+ }
+ catch (ServiceException e) {
+ throw new ConfigurationException("Unable to build LocationMap.",e);
+ }
+ finally {
+ if (parser != null) {
+ m_manager.release(parser);
+ }
+ }
+ return configuration;
+ }
+
+ // ---------------------------------------------------- Module implementation
+
+ /**
+ * Execute the current request against the locationmap returning the
+ * resulting string.
+ */
+ public Object getAttribute(
+ final String name,
+ final Configuration modeConf,
+ final Map objectModel)
+ throws ConfigurationException {
+
+ Object result = null;
+ boolean hasBeenCached = false;
+
+ try {
+ if (this.m_cacheAll == true) {
+ Date now = new Date();
+ long cacheAge = now.getTime() - m_cacheLastLoaded.getTime();
+ debug("LM Cache current age is: " + cacheAge + "ms");
+
+ if (cacheAge > m_cacheTtl) {
+ debug("LM Cache has expiring - contains " + m_locationsCache.size() + " objects.");
+ synchronized (this) {
+ m_locationsCache.clear();
+ debug("LM Cache has expired - now contains " + m_locationsCache.size() + " objects.");
+ }
+ }
+
+ hasBeenCached = m_locationsCache.containsKey(name);
+ if (hasBeenCached == true) {
+ result = m_locationsCache.get(name);
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug("Locationmap cached location returned for hint: " + name + " value: " + result);
+ }
+ }
+ }
+
+ if (hasBeenCached == false) {
+ result = getLocationMap().locate(name,objectModel);
+
+ if (m_cacheAll == true) {
+ m_locationsCache.put(name,result);
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug("Locationmap caching hint: " + name + " value: " + result);
+ }
+ }
+ }
+
+ if (result == null) {
+ String msg = "Locationmap did not return a location for hint " + name;
+ getLogger().debug(msg);
+ }
+
+ return result;
+ }
+ catch (ConfigurationException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ getLogger().error("Failure processing LocationMap.",e);
+ }
+ return null;
+ }
+
+ /**
+ * The possibilities are endless. No way to enumerate them all.
+ * Therefore returns null.
+ */
+ public Iterator getAttributeNames(Configuration modeConf, Map objectModel)
+ throws ConfigurationException {
+
+ return null;
+ }
+
+ /**
+ * Always returns only one value. Use getAttribute() instead.
+ */
+ public Object[] getAttributeValues(
+ String name,
+ Configuration modeConf,
+ Map objectModel)
+ throws ConfigurationException {
+
+ return new Object[] {getAttribute(name,modeConf,objectModel)};
+ }
+
+ /**
+ * If debugging is turned on then log a debug message.
+ * @param debugString - the debug message
+ */
+ private final void debug(String debugString) {
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug(debugString);
+ }
+ }
+}
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/RegexpLocationMapMatcher.java b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/RegexpLocationMapMatcher.java
new file mode 100644
index 0000000..4aecdbf
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/RegexpLocationMapMatcher.java
@@ -0,0 +1,62 @@
+/*
+ * 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.forrest.locationmap;
+
+import java.util.Map;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.avalon.framework.thread.ThreadSafe;
+import org.apache.forrest.locationmap.lm.LocationMap;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.matching.AbstractRegexpMatcher;
+
+
+/**
+ * Match a LocationMap URI against a regular expression pattern.
+ *
+ * <p>
+ * The LocationMap URI is a string that is composed of
+ * the input module hint the LocationMap was called with
+ * concatenated with the request URI of the current Request.
+ * </p>
+ *
+ * <a href="mailto:unico@hippo.nl">Unico Hommes</a>
+ */
+public class RegexpLocationMapMatcher extends AbstractRegexpMatcher implements ThreadSafe {
+
+ /**
+ * Return the LocationMap hint concatenated with the request URI.
+ */
+ protected String getMatchString(Map objectModel, Parameters parameters) {
+
+ String hint = parameters.getParameter(LocationMap.HINT_PARAM,"");
+ String uri = ObjectModelHelper.getRequest(objectModel).getSitemapURI();
+
+ if (uri.charAt(0) != '/') {
+ uri = "/" + uri;
+ }
+ if (hint.charAt(hint.length()-1) == '/') {
+ hint = hint.substring(0,hint.length()-1);
+ }
+ if (hint.charAt(0) == '/') {
+ hint = hint.substring(1);
+ }
+
+ return hint + uri;
+ }
+
+}
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/WildcardLocationMapHintMatcher.java b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/WildcardLocationMapHintMatcher.java
new file mode 100644
index 0000000..8f23439
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/WildcardLocationMapHintMatcher.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.forrest.locationmap;
+
+import java.util.Map;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.forrest.locationmap.lm.LocationMap;
+import org.apache.cocoon.matching.AbstractWildcardMatcher;
+
+/**
+ * Match a LocationMap hint - the part after the module name (module_name:<b>hint</b>) -
+ * against a wildcard pattern.
+ */
+public class WildcardLocationMapHintMatcher extends AbstractWildcardMatcher {
+
+ protected String getMatchString(Map objectModel, Parameters parameters) {
+ return parameters.getParameter(LocationMap.HINT_PARAM,"");
+ }
+
+}
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/WildcardLocationMapMatcher.java b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/WildcardLocationMapMatcher.java
new file mode 100644
index 0000000..af36004
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/WildcardLocationMapMatcher.java
@@ -0,0 +1,63 @@
+/*
+ * 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.forrest.locationmap;
+
+import java.util.Map;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.avalon.framework.thread.ThreadSafe;
+import org.apache.forrest.locationmap.lm.LocationMap;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.matching.AbstractWildcardMatcher;
+
+
+/**
+ * Match a LocationMap URI against a wildcard pattern.
+ *
+ * <p>
+ * The LocationMap URI is a string that is composed of
+ * the input module hint the LocationMap was called with
+ * concatenated with the request URI of the current Request.
+ * </p>
+ *
+ * <a href="mailto:unico@hippo.nl">Unico Hommes</a>
+ */
+public class WildcardLocationMapMatcher extends AbstractWildcardMatcher
+ implements ThreadSafe {
+
+ /**
+ * Return the LocationMap hint concatenated with the request URI.
+ */
+ protected String getMatchString(Map objectModel, Parameters parameters) {
+
+ String hint = parameters.getParameter(LocationMap.HINT_PARAM,"");
+ String uri = ObjectModelHelper.getRequest(objectModel).getSitemapURI();
+
+ if (uri.charAt(0) != '/') {
+ uri = "/" + uri;
+ }
+ if (hint.charAt(hint.length()-1) == '/') {
+ hint = hint.substring(0,hint.length()-1);
+ }
+ if (hint.charAt(0) == '/') {
+ hint = hint.substring(1);
+ }
+
+ return hint + uri;
+ }
+
+}
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/AbstractNode.java b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/AbstractNode.java
new file mode 100644
index 0000000..8aa42e7
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/AbstractNode.java
@@ -0,0 +1,121 @@
+/*
+ * 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.forrest.locationmap.lm;
+
+import java.util.HashMap;
+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.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.components.treeprocessor.InvokeContext;
+import org.apache.cocoon.components.treeprocessor.variables.VariableResolver;
+import org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory;
+import org.apache.cocoon.sitemap.PatternException;
+
+/**
+ * Base class for LocationMap nodes. Defines the contract between the
+ * LocationMap and its nodes.
+ */
+public abstract class AbstractNode extends AbstractLogEnabled {
+
+
+ protected final ServiceManager m_manager;
+
+ // optional parameters defined by the node's configuration
+ private Map m_parameters;
+
+
+ public AbstractNode(final ServiceManager manager) {
+ m_manager = manager;
+ }
+
+ public void build(final Configuration configuration) throws ConfigurationException {
+ m_parameters = getParameters(configuration);
+ }
+
+ /**
+ * Create a Map of resolvable parameters.
+ *
+ * @param configuration the configuration to build parameters from.
+ * @return a Map of parameters wrapped in VariableResolver objects,
+ * <code>null</code> if the configuration contained no parameters.
+ * @throws ConfigurationException
+ */
+ private final Map getParameters(final Configuration configuration)
+ throws ConfigurationException {
+
+ final Configuration[] children = configuration.getChildren("parameter");
+ if (children.length == 0) {
+ return null;
+ }
+ final Map parameters = new HashMap();
+ for (int i = 0; i < children.length; i++) {
+ final String name = children[i].getAttribute("name");
+ final String value = children[i].getAttribute("value");
+ try {
+ parameters.put(
+ VariableResolverFactory.getResolver(name, m_manager),
+ VariableResolverFactory.getResolver(value, m_manager));
+ } catch(PatternException pe) {
+ String msg = "Invalid pattern '" + value + "' at "
+ + children[i].getLocation();
+ throw new ConfigurationException(msg, pe);
+ }
+ }
+
+ return parameters;
+ }
+
+ /**
+ * Resolve the parameters. Also passes the LocationMap special
+ * variables into the Parameters object.
+ *
+ * @param context InvokeContext used during resolution.
+ * @param om object model used during resolution.
+ * @return the resolved parameters or null if this node contains no parameters.
+ * @throws PatternException
+ */
+ protected final Parameters resolveParameters(
+ final InvokeContext context,
+ final Map om) throws PatternException {
+
+ Parameters parameters = null;
+ if (m_parameters != null) {
+ parameters = VariableResolver.buildParameters(m_parameters,context,om);
+ }
+ else {
+ parameters = new Parameters();
+ }
+ // also pass the anchor map as parameters directly into the components
+ Map anchorMap = context.getMapByAnchor(LocationMap.ANCHOR_NAME);
+ Iterator entries = anchorMap.entrySet().iterator();
+ while (entries.hasNext()) {
+ Map.Entry entry = (Map.Entry) entries.next();
+ parameters.setParameter(
+ "#"+LocationMap.ANCHOR_NAME+":"+entry.getKey(),
+ entry.getValue().toString());
+ }
+ return parameters;
+ }
+
+ public abstract String locate(Map objectModel, InvokeContext context) throws Exception;
+
+}
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/ActNode.java b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/ActNode.java
new file mode 100644
index 0000000..495a125
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/ActNode.java
@@ -0,0 +1,173 @@
+/*
+ * 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.forrest.locationmap.lm;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+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.ServiceSelector;
+import org.apache.cocoon.acting.Action;
+import org.apache.cocoon.components.treeprocessor.InvokeContext;
+import org.apache.cocoon.components.treeprocessor.variables.VariableResolver;
+import org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.sitemap.PatternException;
+import org.apache.cocoon.environment.SourceResolver;
+
+
+/**
+ * Locationmap select statement.
+ */
+public final class ActNode extends AbstractNode {
+
+ // the containing LocatorNode
+ private final LocatorNode m_ln;
+
+ // the action that does the work
+ private Action m_action;
+
+ // the type of action for this node
+ private String m_type;
+
+ // the src of action for this node
+ private String m_src;
+
+ private VariableResolver m_varResolver;
+
+ // the locations to test against
+ private AbstractNode[] m_nodes;
+
+ private SourceResolver resolver;
+
+
+ public ActNode(LocatorNode ln, ServiceManager manager) {
+ super(manager);
+ m_ln = ln;
+ }
+
+ public void build(Configuration configuration) throws ConfigurationException {
+
+ super.build(configuration);
+
+ // get the selector
+ m_type = configuration.getAttribute("type",m_ln.getDefaultAction());
+ String src=configuration.getAttribute("src","");
+ try
+ {
+ m_varResolver = VariableResolverFactory.getResolver(
+ src, super.m_manager);
+ }
+ catch (PatternException e) {
+ throw new ConfigurationException("unable to resolve action 'src' attribute");
+ }
+// try {
+// m_src = VariableResolverFactory.getResolver(
+// configuration.getAttribute("src"), super.m_manager);
+// } catch (PatternException e) {
+// final String message = "Illegal pattern syntax at for location attribute 'src'" +
+// " at " + configuration.getLocation();
+// throw new ConfigurationException(message,e);
+// }
+ try {
+ final ServiceSelector selectors = (ServiceSelector) super.m_manager.lookup(Action.ROLE + "Selector");
+ m_action = (Action) selectors.select(m_type);
+ } catch (ServiceException e) {
+ final String message = "Unable to get Selector of type " + m_type;
+ throw new ConfigurationException(message,e);
+ }
+
+ // build the child nodes
+ final Configuration[] children = configuration.getChildren();
+ final List nodes = new ArrayList(children.length);
+ for (int i = 0; i < children.length; i++) {
+ AbstractNode node = null;
+ String name = children[i].getName();
+ if (name.equals("location")) {
+ node = new LocationNode(m_ln, super.m_manager);
+ }
+ else if (name.equals("match")) {
+ node = new MatchNode(m_ln, super.m_manager);
+ }
+ else if (name.equals("select")) {
+ node = new SelectNode(m_ln, super.m_manager);
+ }
+ else if (name.equals("act")) {
+ node = new ActNode(m_ln, super.m_manager);
+ }
+ else if (!name.equals("parameter")) {
+ final String message = "Unknown select node child:" + name;
+ throw new ConfigurationException(message);
+ }
+ if (node != null) {
+ node.enableLogging(getLogger());
+ node.build(children[i]);
+ nodes.add(node);
+ }
+ }
+ m_nodes = (AbstractNode[]) nodes.toArray(new AbstractNode[nodes.size()]);
+ }
+ public void service(ServiceManager manager) throws ServiceException {
+ //FIXME: Determine whether this is really necessary anymore.
+ this.resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
+ }
+ /* (non-Javadoc)
+ * @see org.apache.forrest.locationmap.lm.AbstractNode#locate(java.util.Map, org.apache.cocoon.components.treeprocessor.InvokeContext)
+ */
+ public String locate(Map objectModel, InvokeContext context) throws Exception {
+ this.resolver = (SourceResolver)m_manager.lookup(SourceResolver.ROLE);
+ Parameters parameters = resolveParameters(context,objectModel);
+ Redirector redirector = context.getRedirector();
+ m_src = m_varResolver.resolve(context,objectModel);
+
+ Map substitutions = m_action.act(redirector, resolver, objectModel, m_src, parameters);
+ if (substitutions != null) {
+ if (getLogger().isDebugEnabled()) {
+ // getLogger().debug("matched: " + m_pattern);
+ }
+ context.pushMap(null,substitutions);
+ for (int i = 0; i < m_nodes.length; i++) {
+ String location = m_nodes[i].locate(objectModel,context);
+ if (location != null) {
+ return location;
+ }
+ }
+ context.popMap();
+ }
+ return null;
+ }
+
+// public String locate(Map om, InvokeContext context) throws Exception {
+//
+// Parameters parameters = resolveParameters(context,om);
+// for (int i = 0; i < m_nodes.length; i++) {
+// String location = m_nodes[i].locate(om,context);
+// if (m_action.act().select(location,om,parameters)) {
+// if (getLogger().isDebugEnabled()) {
+// getLogger().debug("selected: " + location);
+// }
+// return location;
+// }
+// }
+// return null;
+// }
+}
\ No newline at end of file
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/LocationMap.java b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/LocationMap.java
new file mode 100644
index 0000000..bd806cd
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/LocationMap.java
@@ -0,0 +1,328 @@
+/*
+ * 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.forrest.locationmap.lm;
+
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.avalon.framework.component.WrapperComponentManager;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.container.ContainerUtil;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.logger.Logger;
+import org.apache.avalon.framework.service.DefaultServiceManager;
+import org.apache.avalon.framework.service.DefaultServiceSelector;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.components.treeprocessor.InvokeContext;
+import org.apache.cocoon.matching.Matcher;
+import org.apache.cocoon.selection.Selector;
+import org.apache.cocoon.acting.*;
+
+/**
+ * A LocationMap defines a mapping from requests to locations.
+ * <p>
+ * The syntax of the locationmap is similar to the way a sitemap
+ * maps requests to pipelines. In the locationmap's case
+ * Matchers and Selectors are not used to identify pipelines but
+ * location strings.
+ * </p>
+ * <p>
+ * The locationmap was conceived to:
+ * <ul>
+ * <li>
+ * Provide a tool for more powerful virtual linking.
+ * </li>
+ * <li>
+ * Enable Forrest with a standard configuration override mechanism.
+ * </li>
+ * </ul>
+ * </p>
+ */
+public final class LocationMap extends AbstractLogEnabled {
+
+ /**
+ * The locationmap namespace:
+ * <code>http://apache.org/forrest/locationmap/1.0</code>
+ */
+ public static final String URI = "http://apache.org/forrest/locationmap/1.0";
+
+ /**
+ * Name of the special anchor map passed into the VariableContext.
+ * The value is <code>lm</code> .
+ * <p>
+ * This makes it possible to use special locationmap variables
+ * inside the locationmap definition.
+ * </p>
+ * <p>
+ * Special locationmap parameters are available through this anchor map.
+ * For instance the hint parameter defined below can be accessed in
+ * the locationmap definition as follows: <code>{#lm:hint}</code>
+ * </p>
+ * All anchor map parameters are also passed implicitly into each selector
+ * and matcher at run-time.
+ */
+ public static final String ANCHOR_NAME = "lm";
+
+ /**
+ * Special anchor map key holding the 'hint' or 'argument' the LocationMap was
+ * called with.
+ *
+ * <p>
+ * The value is <code>hint</code>.
+ * </p>
+ */
+ public static final String HINT_KEY = "hint";
+
+ /**
+ * The hint's parameter name.
+ */
+ public static final String HINT_PARAM = "#" + ANCHOR_NAME + ":" + HINT_KEY;
+
+ /** Component manager containing the locationmap components */
+ private LocationMapServiceManager m_manager;
+
+ /** default matcher */
+ private String m_defaultMatcher;
+
+ /** default action */
+ private String m_defaultAction;
+
+ /** default selector */
+ private String m_defaultSelector;
+
+ /** list of LocatorNodes */
+ private LocatorNode[] m_locatorNodes;
+
+ /** list of MountNodes */
+ private MountNode[] m_mountNodes;
+
+ public LocationMap(ServiceManager manager) {
+ m_manager = new LocationMapServiceManager(manager);
+ }
+
+ /**
+ * Build the LocationMap by creating the components and recursively building
+ * the LocatorNodes.
+ */
+ public void build(final Configuration configuration) throws ConfigurationException {
+
+ // components
+ final Configuration components = configuration.getChild("components");
+
+ // mount
+ final Configuration[] mountChildren = configuration.getChildren("mount");
+ m_mountNodes = new MountNode[mountChildren.length];
+
+ for (int i = 0; i < mountChildren.length; i++) {
+ m_mountNodes[i] = new MountNode(m_manager);
+ m_mountNodes[i].enableLogging(getLogger());
+ m_mountNodes[i].build(mountChildren[i]);
+ }
+
+ // matchers
+ Configuration child = components.getChild("matchers",false);
+ if (child != null) {
+ final DefaultServiceSelector matcherSelector = new DefaultServiceSelector();
+ m_defaultMatcher = child.getAttribute("default");
+ final Configuration[] matchers = child.getChildren("matcher");
+ for (int i = 0; i < matchers.length; i++) {
+ String name = matchers[i].getAttribute("name");
+ String src = matchers[i].getAttribute("src");
+ Matcher matcher = (Matcher) createComponent(src, matchers[i]);
+ matcherSelector.put(name, matcher);
+ }
+ matcherSelector.makeReadOnly();
+ if (!matcherSelector.isSelectable(m_defaultMatcher)) {
+ throw new ConfigurationException("Default matcher is not defined.");
+ }
+ m_manager.put(Matcher.ROLE+"Selector", matcherSelector);
+ }
+
+ // selectors
+ child = components.getChild("selectors",false);
+ if (child != null) {
+ final DefaultServiceSelector selectorSelector = new DefaultServiceSelector();
+ m_defaultSelector = child.getAttribute("default");
+ final Configuration[] selectors = child.getChildren("selector");
+ for (int i = 0; i < selectors.length; i++) {
+ String name = selectors[i].getAttribute("name");
+ String src = selectors[i].getAttribute("src");
+ Selector selector = (Selector) createComponent(src,selectors[i]);
+ selectorSelector.put(name,selector);
+ }
+ selectorSelector.makeReadOnly();
+ if (!selectorSelector.isSelectable(m_defaultSelector)) {
+ throw new ConfigurationException("Default selector is not defined.");
+ }
+ m_manager.put(Selector.ROLE+"Selector",selectorSelector);
+ }
+
+ // actions
+ child = components.getChild("actions",false);
+ if (child != null) {
+ final DefaultServiceSelector actionSelector = new DefaultServiceSelector();
+ m_defaultAction = child.getAttribute("default");
+ final Configuration[] actions = child.getChildren("action");
+ for (int i = 0; i < actions.length; i++) {
+ String name = actions[i].getAttribute("name");
+ String src = actions[i].getAttribute("src");
+ Action action = (Action) createComponent(src,actions[i]);
+ actionSelector.put(name,action);
+ }
+ actionSelector.makeReadOnly();
+ if (!actionSelector.isSelectable(m_defaultAction)) {
+ throw new ConfigurationException("Default action is not defined.");
+ }
+ m_manager.put(Action.ROLE+"Selector",actionSelector);
+ }
+
+ m_manager.makeReadOnly();
+
+ // locators
+ final Configuration[] children = configuration.getChildren("locator");
+ m_locatorNodes = new LocatorNode[children.length];
+ for (int i = 0; i < children.length; i++) {
+ m_locatorNodes[i] = new LocatorNode(this, m_manager);
+ m_locatorNodes[i].enableLogging(getLogger());
+ m_locatorNodes[i].build(children[i]);
+ }
+ }
+
+ /**
+ * Creates a LocationMap component.
+ * <p>
+ * supported component creation lifecycles that are:
+ * - LogEnabled
+ * - Serviceable
+ * - Configurable
+ * - Initializable
+ * </p>
+ */
+ private Object createComponent(String src, Configuration config) throws ConfigurationException {
+ Object component = null;
+ try {
+ component = Class.forName(src).newInstance();
+ ContainerUtil.enableLogging(component,getLogger());
+ ContainerUtil.service(component, m_manager);
+ if (config != null) {
+ ContainerUtil.configure(component, config);
+ }
+ ContainerUtil.initialize(component);
+ } catch (Exception e) {
+ throw new ConfigurationException("Couldn't create object of type " + src,e);
+ }
+ return component;
+ }
+
+ public void dispose() {
+ final Iterator components = m_manager.getObjects();
+ while(components.hasNext()) {
+ ContainerUtil.dispose(components.next());
+ }
+ m_manager = null;
+ m_locatorNodes = null;
+ }
+
+ /**
+ * Loop through the list of locator nodes invoking
+ * the <code>locate()</code> method on each and return
+ * the first non-null result.
+ */
+ public String locate(String hint, Map om) throws Exception {
+
+ String location = null;
+
+ final InvokeContext context = new InvokeContext();
+ final Logger contextLogger = getLogger().getChildLogger("ctx");
+
+ ContainerUtil.enableLogging(context, contextLogger);
+ ContainerUtil.compose(context, new WrapperComponentManager(m_manager));
+ ContainerUtil.service(context, m_manager);
+
+ final Map anchorMap = new HashMap(2);
+ anchorMap.put(HINT_KEY,hint);
+ context.pushMap(ANCHOR_NAME,anchorMap);
+
+ //mounted locations first
+ if (m_mountNodes != null) {
+ for (int i = 0; i < m_mountNodes.length; i++) {
+ location = m_mountNodes[i].locate(om,context);
+ if (location !=null) {
+ ContainerUtil.dispose(context);
+ return location;
+ }
+ }
+ }
+
+ if (m_locatorNodes != null) {
+ for (int i = 0; i < m_locatorNodes.length; i++) {
+ location = m_locatorNodes[i].locate(om,context);
+ if (location != null) {
+ break;
+ }
+ }
+ }
+
+ ContainerUtil.dispose(context);
+
+ if (location != null && location.startsWith("http:")) {
+ location.replaceAll(" ", "%20");
+ }
+
+ return location;
+ }
+
+ /**
+ * Expose the default Matcher to LocatorNodes
+ */
+ String getDefaultMatcher() {
+ return m_defaultMatcher;
+ }
+
+ /**
+ * Expose the default Selector to LocatorNodes
+ */
+ String getDefaultSelector() {
+ return m_defaultSelector;
+ }
+
+ /**
+ * Overide DefaultComponentManager to access the list of all
+ * components.
+ */
+ private static class LocationMapServiceManager extends DefaultServiceManager {
+
+ LocationMapServiceManager(ServiceManager parent) {
+ super(parent);
+ }
+
+ Iterator getObjects() {
+ return super.getObjectMap().values().iterator();
+ }
+
+ }
+
+ /**
+ * Expose the default Action to LocatorNodes
+ */
+ String getDefaultAction() {
+ return m_defaultAction;
+ }
+}
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/LocationNode.java b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/LocationNode.java
new file mode 100644
index 0000000..df3d6f7
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/LocationNode.java
@@ -0,0 +1,68 @@
+/*
+ * 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.forrest.locationmap.lm;
+
+import java.util.Map;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.components.treeprocessor.InvokeContext;
+import org.apache.cocoon.components.treeprocessor.variables.VariableResolver;
+import org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory;
+import org.apache.cocoon.sitemap.PatternException;
+
+/**
+ * locationmap leaf statement identifying a location.
+ *
+ * <p>
+ * The <code><location></code> element has one
+ * required attribute <code>src</code> that contains the
+ * location string.
+ * </p>
+ */
+public class LocationNode extends AbstractNode {
+
+ private final LocatorNode m_ln;
+
+ // the resolvable location source
+ private VariableResolver m_src;
+
+ public LocationNode(final LocatorNode ln, final ServiceManager manager) {
+ super(manager);
+ m_ln = ln;
+ }
+
+ public void build(final Configuration configuration) throws ConfigurationException {
+ try {
+ m_src = VariableResolverFactory.getResolver(
+ configuration.getAttribute("src"), super.m_manager);
+ } catch (PatternException e) {
+ final String message = "Illegal pattern syntax at for location attribute 'src'" +
+ " at " + configuration.getLocation();
+ throw new ConfigurationException(message,e);
+ }
+ }
+
+ /**
+ * Resolve the location string against the InvokeContext.
+ */
+ public String locate(Map om, InvokeContext context) throws Exception {
+ return m_src.resolve(context, om);
+ }
+
+}
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/LocatorNode.java b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/LocatorNode.java
new file mode 100644
index 0000000..4afcf44
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/LocatorNode.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.forrest.locationmap.lm;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.components.treeprocessor.InvokeContext;
+import org.apache.cocoon.components.treeprocessor.variables.VariableResolver;
+import org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory;
+import org.apache.cocoon.sitemap.PatternException;
+import org.apache.excalibur.source.SourceUtil;
+
+
+/**
+ * Top level locate statement containing
+ * <code>MatchNode</code>s and <code>SelectNode</code>s.
+ *
+ * <p>
+ * A locator defines a common location context for its
+ * contained location statements. Location strings resolved within
+ * the context of a given locator will be prefixed with the
+ * locator's base location. This base location is specified
+ * using the <code>base</code> attribute on the <code><locator></code>
+ * element and defaults to <code>.</code>.
+ * </p>
+ */
+public final class LocatorNode extends AbstractNode {
+
+ // the containing LocationMap
+ private final LocationMap m_lm;
+
+ // location base resolver
+ private VariableResolver m_baseLocation;
+
+ // the contained Match- and SelectNodes
+ private AbstractNode[] m_nodes;
+
+ public LocatorNode(final LocationMap lm, final ServiceManager manager) {
+ super(manager);
+ m_lm = lm;
+ }
+
+ public void build(final Configuration configuration) throws ConfigurationException {
+
+ super.build(configuration);
+
+ // get the base attribute
+ String base = configuration.getAttribute("base", null);
+ if (base != null) {
+ try {
+ m_baseLocation = VariableResolverFactory.getResolver(base, super.m_manager);
+ } catch (PatternException e) {
+ final String message = "Illegal pattern syntax for locator attribute 'base'" +
+ " at " + configuration.getLocation();
+ throw new ConfigurationException(message);
+ }
+ }
+
+ // get the child nodes
+ final Configuration[] children = configuration.getChildren();
+ final List nodes = new ArrayList(children.length);
+ for (int i = 0; i < children.length; i++) {
+ AbstractNode node = null;
+ if (children[i].getName().equals("match")) {
+ node = new MatchNode(this, super.m_manager);
+ }
+ else if (children[i].getName().equals("select")) {
+ node = new SelectNode(this, super.m_manager);
+ }
+ else if (children[i].getName().equals("act")) {
+ node = new ActNode(this, super.m_manager);
+ }
+ else {
+ final String message = "Illegal locator node child: "
+ + children[i].getName();
+ throw new ConfigurationException(message);
+ }
+ node.enableLogging(getLogger());
+ node.build(children[i]);
+ nodes.add(node);
+ }
+ m_nodes = (AbstractNode[]) nodes.toArray(new AbstractNode[nodes.size()]);
+ }
+
+ /**
+ * Loop over the list of match and select nodes and call their
+ * respective <code>locate()</code> methods returning the first
+ * non-null result.
+ */
+ public String locate(Map om, InvokeContext context) throws Exception {
+
+ // resolve the base location
+ String base = null;
+ if (m_baseLocation != null) {
+ base = m_baseLocation.resolve(context, om);
+ if (base != null && base.charAt(base.length()-1) != '/') {
+ base = base + "/";
+ }
+ }
+
+ if (m_nodes != null) {
+ for (int i = 0; i < m_nodes.length; i++) {
+ String location = m_nodes[i].locate(om, context);
+ if (location != null) {
+ if (base != null && base.length() != 0) {
+ if (location.charAt(0) != '/' && SourceUtil.indexOfSchemeColon(location) == -1) {
+ location = base + location;
+ }
+ }
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug("located: " + location);
+ }
+ return location;
+ }
+ }
+ }
+ return null;
+ }
+
+ String getDefaultMatcher() {
+ return m_lm.getDefaultMatcher();
+ }
+
+ String getDefaultSelector() {
+ return m_lm.getDefaultSelector();
+ }
+
+ String getDefaultAction() {
+ return m_lm.getDefaultAction();
+ }
+
+}
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/MatchNode.java b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/MatchNode.java
new file mode 100644
index 0000000..78c7ed1
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/MatchNode.java
@@ -0,0 +1,148 @@
+/*
+ * 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.forrest.locationmap.lm;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+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.ServiceSelector;
+import org.apache.cocoon.components.treeprocessor.InvokeContext;
+import org.apache.cocoon.matching.Matcher;
+
+/**
+ * Locationmap match statement.
+ *
+ * <p>
+ * The <match> element has one required <code>pattern</code> attribute
+ * which identifies the pattern the associated Matcher should match
+ * against and one optional <code>type</code> attribute that identifies
+ * the Matcher that is to do the matching.
+ * </p>
+ *
+ * Match statements can contain <code><match></code>,
+ * <code><select></code> and <code><location></code>
+ * child statements.
+ *
+ * <p>
+ * Match nodes can be parametrized using <code><parameter></code> child elements.
+ * </p>
+ */
+public final class MatchNode extends AbstractNode {
+
+ // the containing LocatorNode
+ private final LocatorNode m_ln;
+
+ // the Matcher that does the work
+ private Matcher m_matcher;
+
+ // the type of Matcher for this node
+ private String m_type;
+
+ // the pattern to match
+ private String m_pattern;
+
+ // the child nodes
+ private AbstractNode[] m_nodes;
+
+ public MatchNode(final LocatorNode ln, final ServiceManager manager) {
+ super(manager);
+ m_ln = ln;
+ }
+
+ public void build(final Configuration configuration) throws ConfigurationException {
+
+ super.build(configuration);
+
+ // get the matcher
+ m_type = configuration.getAttribute("type",m_ln.getDefaultMatcher());
+ try {
+ ServiceSelector matchers = (ServiceSelector) super.m_manager.lookup(Matcher.ROLE + "Selector");
+ m_matcher = (Matcher) matchers.select(m_type);
+ } catch (ServiceException e) {
+ final String message = "Unable to get Matcher of type " + m_type;
+ throw new ConfigurationException(message,e);
+ }
+
+ // get the matcher pattern
+ m_pattern = configuration.getAttribute("pattern");
+
+ // get the child nodes
+ final Configuration[] children = configuration.getChildren();
+ final List nodes = new ArrayList(children.length);
+ for (int i = 0; i < children.length; i++) {
+ AbstractNode node = null;
+ String name = children[i].getName();
+ if (name.equals("location")) {
+ node = new LocationNode(m_ln, super.m_manager);
+ }
+ else if (name.equals("match")) {
+ node = new MatchNode(m_ln,super.m_manager);
+ }
+ else if (name.equals("select")) {
+ node = new SelectNode(m_ln, super.m_manager);
+ }
+ else if (name.equals("act")) {
+ node = new ActNode(m_ln, super.m_manager);
+ }
+ else if (!name.equals("parameter")) {
+ final String message =
+ "Unknown match node child: " + name;
+ throw new ConfigurationException(message);
+ }
+ if (node != null) {
+ node.enableLogging(getLogger());
+ node.build(children[i]);
+ nodes.add(node);
+ }
+ }
+ m_nodes = (AbstractNode[]) nodes.toArray(new AbstractNode[nodes.size()]);
+ }
+
+ public String locate(Map om, InvokeContext context) throws Exception {
+
+ Parameters parameters = resolveParameters(context,om);
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug("Examining match: " + m_pattern);
+ }
+ Map substitutions = m_matcher.match(m_pattern,om,parameters);
+ if (substitutions != null) {
+ context.pushMap(null,substitutions);
+ for (int i = 0; i < m_nodes.length; i++) {
+ String location = m_nodes[i].locate(om,context);
+ if (location != null) {
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug("got location: " + location);
+ }
+ return location;
+ }
+ }
+ context.popMap();
+ }
+
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug("no appropriate location, continue processing matches");
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/MountNode.java b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/MountNode.java
new file mode 100644
index 0000000..5dcd5dc
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/MountNode.java
@@ -0,0 +1,169 @@
+/*
+ * 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.forrest.locationmap.lm;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.configuration.NamespacedSAXConfigurationHandler;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.components.treeprocessor.InvokeContext;
+import org.apache.cocoon.components.treeprocessor.variables.VariableResolver;
+import org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory;
+import org.apache.cocoon.sitemap.PatternException;
+import org.apache.excalibur.source.SourceValidity;
+import org.apache.excalibur.source.SourceUtil;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.xml.sax.SAXParser;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * locationmap mount statement.
+ */
+public class MountNode extends AbstractNode {
+
+ // the resolvable location source
+ private SourceResolver m_srcResolver;
+ private VariableResolver m_varResolver;
+ private String m_src;
+ private SourceValidity m_srcVal;
+ private LocationMap m_lm;
+
+
+ public MountNode(final ServiceManager manager) {
+ super(manager);
+ }
+
+ public void build(final Configuration configuration) throws ConfigurationException {
+ try
+ {
+ m_varResolver = VariableResolverFactory.getResolver(
+ configuration.getAttribute("src"), super.m_manager);
+ }
+ catch (PatternException e) {
+ throw new ConfigurationException("unable to resolve mounts 'src' attribute");
+ }
+ }
+
+ //TODO: Look at re-use with locationmapmodule code.
+ private LocationMap getLocationMap() throws Exception {
+ Source source = null;
+ try {
+ try {
+ m_srcResolver = (SourceResolver) m_manager.lookup(SourceResolver.ROLE);
+ }
+ catch (ServiceException e) {
+ throw new ConfigurationException("building mount node");
+ }
+ source = m_srcResolver.resolveURI(m_src);
+ if (m_lm == null) {
+ synchronized (this) {
+ if (m_lm == null) {
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug("loading mounted location map at " + m_src);
+ }
+ m_srcVal = source.getValidity();
+ m_lm = new LocationMap(m_manager);
+ m_lm.enableLogging(getLogger());
+ m_lm.build(loadConfiguration(source));
+ }
+ }
+ }
+ else {
+ SourceValidity valid = source.getValidity();
+ if (m_srcVal != null && m_srcVal.isValid(valid) != 1) {
+ synchronized (this) {
+ if (m_srcVal != null && m_srcVal.isValid(valid) != 1) {
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug("reloading mounted location map at " + m_src);
+ }
+ m_srcVal = valid;
+ m_lm.dispose();
+ m_lm = new LocationMap(m_manager);
+ m_lm.enableLogging(getLogger());
+ m_lm.build(loadConfiguration(source));
+ }
+ }
+ }
+ }
+ }
+ finally {
+ if (source != null) {
+ m_srcResolver.release(source);
+ }
+ }
+ return m_lm;
+ }
+
+ //TODO: Look at re-use with locationmapmodule code.
+ private Configuration loadConfiguration(Source source) throws ConfigurationException {
+ Configuration configuration = null;
+ SAXParser parser = null;
+ try {
+ parser = (SAXParser) m_manager.lookup(SAXParser.ROLE);
+ NamespacedSAXConfigurationHandler handler =
+ new NamespacedSAXConfigurationHandler();
+ parser.parse(new InputSource(source.getInputStream()),handler);
+ configuration = handler.getConfiguration();
+ }
+ catch (IOException e) {
+ throw new ConfigurationException("Unable to build LocationMap.",e);
+ }
+ catch (SAXException e) {
+ throw new ConfigurationException("Unable to build LocationMap.",e);
+ }
+ catch (ServiceException e) {
+ throw new ConfigurationException("Unable to build LocationMap.",e);
+ }
+ finally {
+ if (parser != null) {
+ m_manager.release(parser);
+ }
+ }
+ return configuration;
+ }
+
+ /**
+ * use mounted locationmap to locate hint
+ */
+ public String locate(Map om, InvokeContext context) throws Exception {
+ try {
+ Map anchor = context.getMapByAnchor(LocationMap.ANCHOR_NAME );
+ String hint = (String)anchor.get(LocationMap.HINT_KEY);
+
+ //TODO: Put this in a better place, closer to instantiation;
+ // m_src needs to be properly set prior to getLocMap call
+ m_src = m_varResolver.resolve(context,om);
+
+ return getLocationMap().locate(hint,om);
+ }
+ catch (ConfigurationException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ getLogger().error("Failure processing LocationMap.",e);
+ }
+ return null;
+
+ }
+
+}
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/SelectNode.java b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/SelectNode.java
new file mode 100644
index 0000000..f12f0f0
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/lm/SelectNode.java
@@ -0,0 +1,136 @@
+/*
+ * 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.forrest.locationmap.lm;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+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.ServiceSelector;
+import org.apache.cocoon.components.treeprocessor.InvokeContext;
+import org.apache.cocoon.selection.Selector;
+
+
+/**
+ * Locationmap select statement.
+ */
+public final class SelectNode extends AbstractNode {
+
+ // the containing LocatorNode
+ private final LocatorNode m_ln;
+
+ // the selector that does the work
+ private Selector m_selector;
+
+ // the type of selector for this node
+ private String m_type;
+
+ // the locations to test against
+ private AbstractNode[] m_nodes;
+
+
+ public SelectNode(LocatorNode ln, ServiceManager manager) {
+ super(manager);
+ m_ln = ln;
+ }
+
+ public void build(Configuration configuration) throws ConfigurationException {
+
+ super.build(configuration);
+
+ // get the selector
+ m_type = configuration.getAttribute("type",m_ln.getDefaultSelector());
+ try {
+ final ServiceSelector selectors = (ServiceSelector) super.m_manager.lookup(Selector.ROLE + "Selector");
+ m_selector = (Selector) selectors.select(m_type);
+ } catch (ServiceException e) {
+ final String message = "Unable to get Selector of type " + m_type;
+ throw new ConfigurationException(message,e);
+ }
+
+ // build the child nodes
+ final Configuration[] children = configuration.getChildren();
+ final List nodes = new ArrayList(children.length);
+ for (int i = 0; i < children.length; i++) {
+ AbstractNode node = null;
+ String name = children[i].getName();
+ if (name.equals("location")) {
+ node = new LocationNode(m_ln, super.m_manager);
+ }
+ else if (name.equals("match")) {
+ node = new MatchNode(m_ln, super.m_manager);
+ }
+ else if (name.equals("select")) {
+ node = new SelectNode(m_ln, super.m_manager);
+ }
+ else if (name.equals("mount")) {
+ node = new MountNode(super.m_manager);
+ }
+ else if (name.equals("act")) {
+ node = new ActNode(m_ln, super.m_manager);
+ }
+ else if (!name.equals("parameter")) {
+ final String message = "Unknown select node child:" + name;
+ throw new ConfigurationException(message);
+ }
+ if (node != null) {
+ node.enableLogging(getLogger());
+ node.build(children[i]);
+ nodes.add(node);
+ }
+ }
+ m_nodes = (AbstractNode[]) nodes.toArray(new AbstractNode[nodes.size()]);
+ }
+
+ public String locate(Map om, InvokeContext context) throws Exception {
+
+ Parameters parameters = resolveParameters(context,om);
+ String location;
+ for (int i = 0; i < m_nodes.length; i++) {
+ try {
+ location = m_nodes[i].locate(om,context);
+ if (m_selector.select(location,om,parameters)) {
+ debug("Selected: " + location);
+ return location;
+ } else {
+ debug("Not selected: " + location);
+ }
+ } catch (ConfigurationException e) {
+ getLogger().error("Ignoring locationmap config exception: " + e.getMessage(), e);
+ }
+ }
+
+ return null;
+ }
+
+
+
+ /**
+ * If debugging is turned on then log a debug message.
+ * @param debugString - the debug message
+ */
+ private final void debug(String debugString) {
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug(debugString);
+ }
+ }
+}
\ No newline at end of file
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/source/impl/LocationmapSourceFactory.java b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/source/impl/LocationmapSourceFactory.java
new file mode 100644
index 0000000..a22ae98
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/java/org/apache/forrest/locationmap/source/impl/LocationmapSourceFactory.java
@@ -0,0 +1,102 @@
+/*
+ * 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.forrest.locationmap.source.impl;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.Map;
+
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.context.Context;
+import org.apache.avalon.framework.context.ContextException;
+import org.apache.avalon.framework.context.Contextualizable;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.ServiceSelector;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.avalon.framework.thread.ThreadSafe;
+import org.apache.cocoon.components.ContextHelper;
+import org.apache.cocoon.components.modules.input.InputModule;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceException;
+import org.apache.excalibur.source.SourceFactory;
+
+public class LocationmapSourceFactory extends AbstractLogEnabled implements
+ Serviceable, SourceFactory, ThreadSafe, Contextualizable {
+
+ protected ServiceManager m_manager;
+ private Context context;
+ public static final String LM_PREFIX = "lm";
+ public static final String LM_SOURCE_SCHEME =LM_PREFIX+ ":";
+
+ public Source getSource(String location, Map parameters)
+ throws IOException, MalformedURLException {
+ Map objectModel = ContextHelper.getObjectModel( this.context );
+ if (this.getLogger().isDebugEnabled()) {
+ this.getLogger().debug("Processing " + location);
+ }
+
+ int protocolEnd = location.indexOf("://");
+ if (protocolEnd == -1) {
+ throw new MalformedURLException("URI does not contain '://' : "
+ + location);
+ }
+ String documentName = location.substring(protocolEnd + 3, location
+ .length());
+ String lmLocation = "";
+ Source lmSource = null;
+ SourceResolver resolver = null;
+ ServiceSelector selector = null;
+ InputModule inputModule = null;
+ try {
+ selector = (ServiceSelector) m_manager.lookup(InputModule.ROLE
+ + "Selector");
+ inputModule = (InputModule) selector.select(LM_PREFIX);
+ resolver = (SourceResolver) m_manager.lookup(SourceResolver.ROLE);
+ lmLocation = (String) inputModule.getAttribute(documentName, null,
+ objectModel);
+ if (lmLocation==null)
+ throw new SourceException("Could not resolve locationmap location.");
+ lmSource = resolver.resolveURI(lmLocation);
+ } catch (ServiceException se) {
+ throw new SourceException("InputModule is not available.", se);
+ } catch (ConfigurationException e) {
+ throw new SourceException("SourceResolver is not available.", e);
+ } finally {
+ if (inputModule != null)
+ selector.release(inputModule);
+ m_manager.release(resolver);
+ }
+ return lmSource;
+ }
+ /**
+ * Contextualizable, get the object model
+ */
+ public void contextualize( Context context ) throws ContextException {
+ this.context = context;
+ }
+ public void release(Source source) {
+ // not necessary here
+ }
+
+ public void service(ServiceManager manager) throws ServiceException {
+ this.m_manager = manager;
+ }
+
+}
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/locationmap.xml b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/locationmap.xml
new file mode 100644
index 0000000..b6133ad
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/locationmap.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<!-- Default Forrest locationmap -->
+<locationmap xmlns="http://apache.org/forrest/locationmap/1.0">
+ <components>
+ <matchers default="lm">
+ <matcher
+ name="lm"
+ src="org.apache.forrest.locationmap.WildcardLocationMapHintMatcher"/>
+ </matchers>
+ </components>
+ <locator>
+ <match pattern="demo">
+ <location src="test.xml" />
+ </match>
+ </locator>
+</locationmap>
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/resource/external/README.txt b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/resource/external/README.txt
new file mode 100644
index 0000000..9ba2b20
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/resource/external/README.txt
@@ -0,0 +1,7 @@
+This is a dummy file provided to avoid Maven archetype plugin's limitation.
+
+Archetype plugin does not create empty directory structure:
+"At this point one can only specify individual files to be created but not empty directories."
+(http://maven.apache.org/plugins/maven-archetype-plugin/examples/archetype.html)
+
+You can safely remove this file at any time.
\ No newline at end of file
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/resource/internal/README.txt b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/resource/internal/README.txt
new file mode 100644
index 0000000..9ba2b20
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/resource/internal/README.txt
@@ -0,0 +1,7 @@
+This is a dummy file provided to avoid Maven archetype plugin's limitation.
+
+Archetype plugin does not create empty directory structure:
+"At this point one can only specify individual files to be created but not empty directories."
+(http://maven.apache.org/plugins/maven-archetype-plugin/examples/archetype.html)
+
+You can safely remove this file at any time.
\ No newline at end of file
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/sitemap.xmap b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/sitemap.xmap
new file mode 100644
index 0000000..4317f11
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/sitemap.xmap
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<map:sitemap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://apache.org/cocoon/sitemap/1.0 http://cocoon.apache.org/schema/sitemap/cocoon-sitemap-1.0.xsd"
+ xmlns:map="http://apache.org/cocoon/sitemap/1.0">
+
+ <map:pipelines>
+ <map:pipeline id="lm">
+ <map:match pattern="locationmap.xml">
+ <map:generate src="locationmap.xml"/>
+ <map:serialize type="xml"/>
+ </map:match>
+ <map:match pattern="test">
+ <map:generate src="{lm:demo}"/>
+ <map:serialize type="xml"/>
+ </map:match>
+ <map:match pattern="test2">
+ <map:generate src="lm://demo"/>
+ <map:serialize type="xml"/>
+ </map:match>
+ </map:pipeline>
+
+ <map:pipeline id="service">
+ <!-- Put your servlet service matchers here.
+ More details: http://cocoon.zones.apache.org/daisy/cdocs-site-main/g2/1345.html -->
+ </map:pipeline>
+ </map:pipelines>
+
+</map:sitemap>
\ No newline at end of file
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/test.xml b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/test.xml
new file mode 100644
index 0000000..b8cddca
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/COB-INF/test.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<test>
+ <justTesting/>
+</test>
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/META-INF/cocoon/avalon/forrest-locationmap.xconf b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/META-INF/cocoon/avalon/forrest-locationmap.xconf
new file mode 100644
index 0000000..057ab69
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/META-INF/cocoon/avalon/forrest-locationmap.xconf
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<!-- SVN $Id: cocoon-core-additional-sample-modules-input.xconf 588433 2007-10-26 00:12:56Z vgritsenko $ -->
+<components>
+
+ <input-modules>
+
+ <!-- LocationMap is used to map one URL to another, allowing content to be stored anywhere -->
+ <component-instance class="org.apache.forrest.locationmap.LocationMapModule"
+ logger="core.modules.mapper.lm" name="lm">
+ <file src="cocoon://locationmap.xml"/>
+ <cacheable>true</cacheable>
+ <cache-lifespan>100000</cache-lifespan>
+ </component-instance>
+ </input-modules>
+ <source-factories>
+ <component-instance
+ class="org.apache.forrest.locationmap.source.impl.LocationmapSourceFactory"
+ name="lm"/>
+ </source-factories>
+
+</components>
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/META-INF/cocoon/spring/demo-application-context.xml b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/META-INF/cocoon/spring/demo-application-context.xml
new file mode 100644
index 0000000..6046f35
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/META-INF/cocoon/spring/demo-application-context.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
+ "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
+<!--
+ 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.
+-->
+<beans/>
diff --git a/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/META-INF/cocoon/spring/servlet-service.xml b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/META-INF/cocoon/spring/servlet-service.xml
new file mode 100644
index 0000000..55471ae
--- /dev/null
+++ b/whiteboard/cocoon-2.2-blocks/locationmap/src/main/resources/META-INF/cocoon/spring/servlet-service.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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
+ und
+-->
+<!--+
+ | @version $Id: servlet-service.xml 578865 2007-09-24 16:09:46Z reinhard $
+ +-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:servlet="http://cocoon.apache.org/schema/servlet"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
+ http://cocoon.apache.org/schema/servlet http://cocoon.apache.org/schema/servlet/cocoon-servlet-1.0.xsd">
+
+ <bean id="org.apache.forrest.locationmap.service" class="org.apache.cocoon.sitemap.SitemapServlet">
+ <servlet:context mount-path="/locationmap" context-path="blockcontext:/locationmap/"/>
+ </bean>
+
+</beans>