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>&lt;location&gt;</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>&lt;locator&gt;</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 &lt;match&gt; 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>&lt;match&gt;</code>,
+ * <code>&lt;select&gt;</code> and <code>&lt;location&gt;</code>
+ * child statements.
+ * 
+ * <p>
+ * Match nodes can be parametrized using <code>&lt;parameter&gt;</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>