Author: rgardler
Date: Thu Nov 23 16:32:14 2006
New Revision: 478728

URL: http://svn.apache.org/viewvc?view=rev&rev=478728
Log:
Handle required and optional resources.
This includes a completion of the refactoring of the way 
aggregated resources will be hanled. Instead of having multiple
internal documents that are processed at the output stage we
now have an AggregatedInputDocument, so the aggregation will
be done at the input stage instead. This means we have a 
single internal document.

Modified:
    
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/Controller.java
    
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/IController.java
    
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/locationMap/Location.java
    
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/locationMap/LocationMap.java
    
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/ChainedReader.java
    
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/FileReader.java
    
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/HTTPReader.java
    
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/IReader.java

Modified: 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/Controller.java
URL: 
http://svn.apache.org/viewvc/forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/Controller.java?view=diff&rev=478728&r1=478727&r2=478728
==============================================================================
--- 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/Controller.java
 (original)
+++ 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/Controller.java
 Thu Nov 23 16:32:14 2006
@@ -65,7 +65,7 @@
  * 
  */
 public class Controller implements IController {
-       
+
        Logger log = Logger.getLogger(Controller.class);
 
        private final String sourceURLExtension = ".forrestSource";
@@ -76,7 +76,7 @@
 
        private final Map<URI, AbstractSourceDocument> sourceDocsCache = new 
HashMap<URI, AbstractSourceDocument>();
 
-       private final Map<URI, List<InternalDocument>> internalDocsCache = new 
HashMap<URI, List<InternalDocument>>();
+       private final Map<URI, InternalDocument> internalDocsCache = new 
HashMap<URI, InternalDocument>();
 
        private final Map<URI, AbstractOutputDocument> outputDocCache = new 
HashMap<URI, AbstractOutputDocument>();
 
@@ -102,8 +102,7 @@
                final File file = new File(contextPath);
                if (file.exists()) {
                        log.info("Using Spring Context definition in " + 
contextPath);
-                       this.context = new FileSystemXmlApplicationContext(file
-                                       .getPath());
+                       this.context = new 
FileSystemXmlApplicationContext(file.getPath());
                } else {
                        log.info("Using default spring context definition");
                        this.context = new ClassPathXmlApplicationContext(
@@ -151,15 +150,16 @@
         */
        private AbstractOutputDocument processRequest(final URI requestURI)
                        throws IOException, LocationmapException, 
ProcessingException {
-               final List<Location> sourceLocs = 
this.resolveSourceLocations(requestURI);
+               final List<Location> sourceLocs = this
+                               .resolveSourceLocations(requestURI);
                this.sourceLocationsCache.put(requestURI, sourceLocs);
 
                final List<AbstractSourceDocument> sourceDocs = this
                                .loadAllSourceDocuments(requestURI, sourceLocs);
 
-               final List<InternalDocument> internalDocs = this
+               final InternalDocument internalDoc = this
                                .processInputPlugins(sourceDocs);
-               this.internalDocsCache.put(requestURI, internalDocs);
+               this.internalDocsCache.put(requestURI, internalDoc);
 
                final AbstractOutputDocument output = this
                                .processOutputPlugins(requestURI);
@@ -175,11 +175,10 @@
         * @throws IOException
         * @throws ProcessingException
         */
-       private List<InternalDocument> processInputPlugins(
+       private InternalDocument processInputPlugins(
                        final List<AbstractSourceDocument> sourceDocuments)
                        throws IOException, ProcessingException {
-               final List<InternalDocument> results = new 
ArrayList<InternalDocument>(
-                               sourceDocuments.size());
+               InternalDocument result = null;
                for (int i = 0; i < sourceDocuments.size(); i++) {
                        final AbstractSourceDocument doc = 
sourceDocuments.get(i);
                        if (doc == null) {
@@ -187,9 +186,9 @@
                                                "No source document is 
available.");
                        }
                        AbstractInputPlugin plugin = getInputPlugin(doc);
-                       results.add((InternalDocument) plugin.process(doc));
+                       result = (InternalDocument) plugin.process(doc);
                }
-               return results;
+               return result;
        }
 
        /*
@@ -218,18 +217,9 @@
         */
        private AbstractOutputDocument processOutputPlugins(final URI 
requestURI)
                        throws ProcessingException, IOException {
-               IDocument doc = null;
-               final List<InternalDocument> intDocs = this
-                               .getInternalDocuments(requestURI);
-               if (intDocs.size() > 1) {
-                       doc = new AggregateInteralDocument(intDocs);
-               } else {
-                       doc = intDocs.get(0);
-               }
-
+               final InternalDocument intDoc = 
this.getInternalDocument(requestURI);
                BaseOutputPlugin plugin = getOutputPlugin(requestURI);
-
-               return (AbstractOutputDocument) plugin.process(doc);
+               return (AbstractOutputDocument) plugin.process(intDoc);
        }
 
        /*
@@ -285,9 +275,14 @@
                        MalformedURLException {
                AbstractSourceDocument doc = sourceDocsCache.get(requestURI);
                if (doc == null) {
-                       IReader reader = getReader(location);
-                       doc = reader.read(this, requestURI, location);
-                       addToSourceDocCache(requestURI, doc);
+                       for (URI uri : location.getSourceURIs()) {
+                               IReader reader = getReader(uri);
+                               doc = reader.read(this, requestURI, location, 
uri);
+                               if (doc != null) {
+                                 addToSourceDocCache(requestURI, doc);
+                                 break;
+                               }
+                       }
                }
                return doc;
        }
@@ -308,14 +303,17 @@
         * 
         * @see 
org.apache.forrest.core.IController#getReader(org.apache.forrest.core.locationMap.Location)
         */
-       public IReader getReader(final Location location)
-                       throws ProcessingException {
+       public IReader getReader(final URI uri) throws ProcessingException {
                IReader reader;
+               String scheme = uri.getScheme();
+               if (scheme.equals("classpath")) {
+                       scheme = "file";
+               }
                try {
-                       reader = (IReader) 
this.context.getBean(location.getScheme());
+                       reader = (IReader) this.context.getBean(scheme);
                } catch (Exception e) {
-                       throw new ProcessingException("Unable to get a reader 
for : "
-                                       + location.getRequestPattern(), e);
+                       throw new ProcessingException(
+                                       "Unable to get a reader for : " + uri, 
e);
                }
                return reader;
        }
@@ -358,7 +356,9 @@
                                } else {
                                        if (loc.isRequired()) {
                                                isValid = false;
-                                               log.debug("Can't use this set 
of locations because one is required: " + loc.toString());
+                                               log
+                                                               .debug("Can't 
use this set of locations because one is required: "
+                                                                               
+ loc.toString());
                                        } else {
                                                log.debug("Can't find file for 
" + loc.toString());
                                        }
@@ -430,21 +430,20 @@
         * 
         * @see 
org.apache.forrest.core.IController#getInternalDocuments(java.net.URI)
         */
-       public List<InternalDocument> getInternalDocuments(final URI requestURI)
+       public InternalDocument getInternalDocument(final URI requestURI)
                        throws ProcessingException {
-               List<InternalDocument> internalDocs = this.internalDocsCache
-                               .get(requestURI);
-               if (internalDocs == null)
+               InternalDocument internalDoc = 
this.internalDocsCache.get(requestURI);
+               if (internalDoc == null)
                        try {
                                this.processRequest(requestURI);
-                               internalDocs = 
this.internalDocsCache.get(requestURI);
+                               internalDoc = 
this.internalDocsCache.get(requestURI);
                        } catch (final Exception e) {
                                throw new ProcessingException(
                                                "Unable to create the internal 
representation of the source documents for "
                                                                + 
requestURI.toString(), e);
                        }
 
-               return internalDocs;
+               return internalDoc;
        }
 
        /*
@@ -470,18 +469,14 @@
                                        content.toString());
                        return output;
                } else if 
(requestURI.getPath().endsWith(this.internalURLExtension)) {
-                       final List<InternalDocument> docs = this
-                                       .getInternalDocuments(requestURI);
+                       final InternalDocument doc = 
this.getInternalDocument(requestURI);
                        final StringBuffer content = new StringBuffer();
-                       for (final InternalDocument doc : docs) {
-                               try {
-                                       
content.append(doc.getContentAsString());
-                               } catch (final IOException e) {
-                                       content
-                                                       .append("<error>Unable 
to read source document for ");
-                                       content.append(requestURI);
-                                       content.append("</error>");
-                               }
+                       try {
+                               content.append(doc.getContentAsString());
+                       } catch (final IOException e) {
+                               content.append("<error>Unable to read source 
document for ");
+                               content.append(requestURI);
+                               content.append("</error>");
                        }
                        final DefaultOutputDocument output = new 
DefaultOutputDocument(
                                        content.toString());

Modified: 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/IController.java
URL: 
http://svn.apache.org/viewvc/forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/IController.java?view=diff&rev=478728&r1=478727&r2=478728
==============================================================================
--- 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/IController.java
 (original)
+++ 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/IController.java
 Thu Nov 23 16:32:14 2006
@@ -22,7 +22,15 @@
 
        public abstract BaseOutputPlugin getOutputPlugin(final URI requestURI);
 
-       public abstract IReader getReader(final Location location) throws 
ProcessingException;
+       /**
+        * Get a reader that can be used for retrieving a resource
+        * from a given URI.
+        * 
+        * @param sourceURI
+        * @return
+        * @throws ProcessingException
+        */
+       public abstract IReader getReader(final URI sourceURI) throws 
ProcessingException;
 
        /**
         * Get the source URLs for a given request URI.
@@ -58,7 +66,7 @@
         * @throws MalformedURLException
         * @throws IOException
         */
-       public abstract List<InternalDocument> getInternalDocuments(
+       public abstract InternalDocument getInternalDocument(
                        final URI requestURI) throws ProcessingException;
 
        /**

Modified: 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/locationMap/Location.java
URL: 
http://svn.apache.org/viewvc/forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/locationMap/Location.java?view=diff&rev=478728&r1=478727&r2=478728
==============================================================================
--- 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/locationMap/Location.java
 (original)
+++ 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/locationMap/Location.java
 Thu Nov 23 16:32:14 2006
@@ -21,6 +21,8 @@
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
 
 import org.apache.forrest.core.exception.ProcessingException;
 import org.apache.log4j.Logger;
@@ -38,18 +40,20 @@
  * 
  */
 public class Location {
-       
+
        Logger log = Logger.getLogger(Location.class);
-       
+
        private String requestPattern;
 
        private boolean isRequired;
 
-       private URI sourceURI;
+       private List<URI> sourceURIs;
 
        public Location(final String pattern, final URL sourceURL,
                        final boolean isRequired) throws URISyntaxException {
-               this.init(pattern, sourceURL.toURI(), isRequired);
+               List<URI> uris = new ArrayList<URI>();
+               uris.add(sourceURL.toURI());
+               this.init(pattern, uris, isRequired);
        }
 
        /**
@@ -62,35 +66,39 @@
        public Location(final Node element) throws URISyntaxException, 
IOException {
                String pattern = null;
                String url = null;
-               boolean isOptional = false;
+               List<URI> uris = new ArrayList<URI>();
+               boolean isRequired = false;
 
                final NamedNodeMap atts = element.getAttributes();
                pattern = atts.getNamedItem("pattern").getNodeValue();
                final NodeList children = element.getChildNodes();
                for (int i = 0; i < children.getLength(); i++) {
                        final Node child = children.item(i);
-                       if (child.getNodeName().equals("source")) {
+                       String nodeName = child.getNodeName();
+                       if (nodeName != null && nodeName.equals("source")) {
                                url = 
child.getAttributes().getNamedItem("href").getNodeValue();
                                final Node required = 
child.getAttributes().getNamedItem(
                                                "required");
                                if (required != null) {
-                                       isOptional = 
required.getNodeValue().equals("false");
+                                       isRequired = 
required.getNodeValue().equals("true");
                                }
+                               uris.add(new URI(url));
                        }
                }
-               this.init(pattern, new URI(url), isOptional);
+               this.init(pattern, uris, isRequired);
        }
 
-       private void init(final String pattern, final URI uri,
-                       final boolean isOptional) throws URISyntaxException {
+       private void init(final String pattern, final List<URI> uris,
+                       final boolean isRequired) throws URISyntaxException {
                if (pattern == null)
                        throw new IllegalArgumentException(
                                        "requestURIPattern cannot be null");
-               if (uri == null)
-                       throw new IllegalArgumentException("sourceURI cannot be 
null");
+               if (uris == null || uris.size() == 0)
+                       throw new IllegalArgumentException(
+                                       "There must be at least one postential 
source uri");
                this.setRequestPattern(pattern);
-               this.setSourceURI(uri);
-               this.setRequired(this.isRequired);
+               this.setSourceURIs(uris);
+               this.setRequired(isRequired);
        }
 
        public boolean isRequired() {
@@ -110,114 +118,51 @@
        }
 
        /**
-        * Get the source URL, that is the one to use when reading the source
-        * document. The source URL is the sourceURI modified appropriately for 
the
-        * given request.
+        * Get the source URLs, that may be used to read the source document. A
+        * source URL is the sourceURI modified appropriately for the given 
request.
+        * Note that the resulting list of URLs have not been verified with 
respect
+        * to the existence of a document, it is only a potential location.
         * 
         * @return
         * @throws MalformedURLException
         * @throws ProcessingException
         */
-       public URL getResolvedSourceURL(URI requestURI)
+       public List<URL> getResolvedSourceURL(URI requestURI)
                        throws MalformedURLException, ProcessingException {
-
-               URL url;
-               try {
-                       url = requestURI.toURL();
-               } catch (final IllegalArgumentException e) {
-                       // we'll assume that this is not an absolute URL and 
therefore
-                       // refers to a file
-                       url = new URL("file://" + requestURI);
+               List<URL> resolvedUrls = new ArrayList<URL>();
+               for (URI sourceURI : getSourceURIs()) {
+                       resolvedUrls.add(resolveURL(requestURI, sourceURI));
                }
-               final String urlString = url.toExternalForm();
-
-               RE r;
-               String sourcePath = this.getSourceURI().getPath();
-               try {
-                       r = new RE(getRequestPattern());
-               } catch (RESyntaxException re) {
-                       throw new ProcessingException(
-                                       "Unable to extract variable values from 
request: "
-                                                       + re.getMessage(), re);
-               }
-
-               if (r.match(urlString)) {
-                       String variable;
-                       String value;
-                       for (int i = 0; i < r.getParenCount(); i++) {
-                               variable = "$(" + i + ")";
-                               value = r.getParen(i);
-                               sourcePath = sourcePath.replace(variable, 
value);
-                       }
-                       log.debug("After variable substitution the source path 
is " + sourcePath);
-               } else {
-                       throw new ProcessingException(
-                                       "Unable to extract variable values from 
requestURI");
-               }
-
-               URI uri = getSourceURI();
-               URL resourceURL;
-               if (uri.getScheme().equals("classpath")) {
-                       resourceURL = resolveClasspathURI(sourcePath);
-               } else {
-                       String strURI = uri.getSchemeSpecificPart();
-                       if (strURI.contains(":")) {
-                               String subProtocol = strURI.substring(0, strURI
-                                               .lastIndexOf(':'));
-                               sourcePath = 
strURI.substring(strURI.lastIndexOf(':') + 1);
-                               if (subProtocol.equals("classpath")) {
-                                       resourceURL = 
resolveClasspathURI(sourcePath);
-                               } else {
-                                       URI subURI;
-                                       try {
-                                               subURI = new URI(subProtocol, 
sourcePath, null);
-                                               resourceURL = subURI.toURL();
-                                       } catch (URISyntaxException e) {
-                                               throw new MalformedURLException(
-                                                               "Unable to work 
out sub protocol URI");
-                                       }
-                               }
-                       } else {
-                               resourceURL = uri.toURL();
-                       }
-               }
-               return resourceURL;
+               return resolvedUrls;
        }
 
+       /**
+        * 
+        * @param sourcePath
+        * @return
+        * @throws ProcessingException - if the path to the resource cannot be 
resolved
+        */
        private URL resolveClasspathURI(final String sourcePath)
-                       throws MalformedURLException {
+                       throws ProcessingException {
                URL resourceURL;
                resourceURL = this.getClass().getResource(sourcePath);
                if (resourceURL == null)
-                       throw new MalformedURLException(
+                       throw new ProcessingException(
                                        "Cannot find the classpath resource: " 
+ sourcePath);
                return resourceURL;
        }
 
-       public void setSourceURL(final URL sourceURL) throws URISyntaxException 
{
-               this.setSourceURI(sourceURL.toURI());
-       }
-
-       public URI getSourceURI() {
-               return this.sourceURI;
-       }
-
-       public void setSourceURI(final URI sourceURI) {
-               this.sourceURI = sourceURI;
+       public List<URI> getSourceURIs() {
+               return this.sourceURIs;
        }
 
        /**
-        * Get the scheme used for retrieving this resource. The scheme will be 
the
-        * first protocol in the source URI.
+        * Set the list of potential source URIs for this document.
         * 
-        * @return
+        * @param sourceURI
         */
-       public String getScheme() {
-               String scheme = getSourceURI().getScheme();
-               if (scheme.equals("classpath")) {
-                       scheme = "file";
-               }
-               return scheme;
+       public void setSourceURIs(final List<URI> sourceURIs) {
+               this.sourceURIs = sourceURIs;
        }
        
        public String toString() {
@@ -230,10 +175,90 @@
                sb.append("location: ");
                sb.append("Pattern: ");
                sb.append(this.getRequestPattern());
-               sb.append(" SourceURI: ");
-               sb.append(this.getSourceURI().toASCIIString());
-               
+               sb.append(" Potential sourceURIs: ");
+               for (URI uri : getSourceURIs()) {
+                 sb.append(uri.toASCIIString());
+                 sb.append(" ");
+               }
+
                return sb.toString();
+       }
+
+       /**
+        * Resolve the supplied URI and return a URL that can
+        * be used to attempt to retrieve the resource. A resolved
+        * uri has all variables substituted with their values.
+        * 
+        * @param sourceURI
+        * @return
+        * @throws MalformedURLException 
+        * @throws ProcessingException 
+        */
+       public URL resolveURL(URI requestURI, URI sourceURI) throws 
MalformedURLException, ProcessingException {
+               URL url;
+               RE r;
+
+               try {
+                       r = new RE(getRequestPattern());
+               } catch (RESyntaxException re) {
+                       throw new ProcessingException(
+                                       "Unable to extract variable values from 
request: "
+                                                       + re.getMessage(), re);
+               }
+
+               try {
+                       url = requestURI.toURL();
+               } catch (final IllegalArgumentException e) {
+                       // we'll assume that this is not an absolute URL and 
therefore
+                       // refers to a file
+                       url = new URL("file://" + requestURI);
+               }
+               final String urlString = url.toExternalForm();
+
+                       String sourceSSP = sourceURI.getSchemeSpecificPart();
+
+                       if (r.match(urlString)) {
+                               String variable;
+                               String value;
+                               for (int i = 0; i < r.getParenCount(); i++) {
+                                       variable = "$(" + i + ")";
+                                       value = r.getParen(i);
+                                       sourceSSP = sourceSSP.replace(variable, 
value);
+                               }
+                               log.debug("After variable substitution a 
potential source path is "
+                                               + sourceSSP);
+                       } else {
+                               throw new ProcessingException(
+                                               "Unable to extract variable 
values from requestURI");
+                       }
+
+                       URL resolvedURL;
+                       if (sourceURI.getScheme().equals("classpath")) {
+                               resolvedURL = resolveClasspathURI(sourceSSP);
+                       } else {
+                               String strURI = 
sourceURI.getSchemeSpecificPart();
+                               if (strURI.contains(":")) {
+                                       String subProtocol = 
strURI.substring(0, strURI
+                                                       .lastIndexOf(':'));
+                                       sourceSSP = 
strURI.substring(strURI.lastIndexOf(':') + 1);
+                                       if (subProtocol.equals("classpath")) {
+                                               resolvedURL = 
resolveClasspathURI(sourceSSP);
+                                       } else {
+                                               URI subURI;
+                                               try {
+                                                       subURI = new 
URI(subProtocol, sourceSSP, null);
+                                                       resolvedURL = 
subURI.toURL();
+                                               } catch (URISyntaxException e) {
+                                                       throw new 
MalformedURLException(
+                                                                       "Unable 
to work out sub protocol URI");
+                                               }
+                                       }
+                               } else {
+                                       resolvedURL = sourceURI.toURL();
+                               }
+                       }
+                       return resolvedURL;
+               
        }
 
 }

Modified: 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/locationMap/LocationMap.java
URL: 
http://svn.apache.org/viewvc/forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/locationMap/LocationMap.java?view=diff&rev=478728&r1=478727&r2=478728
==============================================================================
--- 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/locationMap/LocationMap.java
 (original)
+++ 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/locationMap/LocationMap.java
 Thu Nov 23 16:32:14 2006
@@ -98,7 +98,12 @@
        }
 
        /**
-        * Get the all matching sets of locations for the given URI.
+        * Get all matching sets of locations for the given URI.
+        * A matching location is one that provides a source
+        * location that <em>may</em> provide a source document
+        * for the request. That is, the existence of the source
+        * document is not checked before adding the location
+        * to the results.
         * 
         * @param requestURI
         * @return

Modified: 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/ChainedReader.java
URL: 
http://svn.apache.org/viewvc/forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/ChainedReader.java?view=diff&rev=478728&r1=478727&r2=478728
==============================================================================
--- 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/ChainedReader.java
 (original)
+++ 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/ChainedReader.java
 Thu Nov 23 16:32:14 2006
@@ -27,32 +27,25 @@
 import org.apache.forrest.core.locationMap.Location;
 
 /**
- * A chained reader implements a psuedo protocol.
- * It is commonly used when you need to retrieve a
- * document that whose type cannot be identified
- * from the raw source alone.
+ * A chained reader implements a psuedo protocol. It is commonly used when you
+ * need to retrieve a document that whose type cannot be identified from the 
raw
+ * source alone.
  * 
  * It is defined in forrestContext.xml as follows:
  * 
- * <bean id="fooProtocol"
- *   class="org.apache.forrest.core.reader.ChainedReader" >
- *   <property name="docType"
- *             value="org.foo.Bar" />
- * </bean>
+ * <bean id="fooProtocol" class="org.apache.forrest.core.reader.ChainedReader" 
>
+ * <property name="docType" value="org.foo.Bar" /> </bean>
  * 
  * We can then define a chain of readers like this:
- *
- * <location pattern="classpath/foo.*">
- *   <source href="fooProtocol:classpath:/xdocs/exampleFeed.xml"/>
- * </location>
- *
- * <location pattern="file/foo.*">
- *    <source href="fooProtocol:file:/xdocs/exampleFeed.xml"/>
- * </location>
- * 
- * <location pattern="http/foo.*">
- *   <source href="fooProtocol:http:/xdocs/exampleFeed.xml"/>
- * </location>
+ * 
+ * <location pattern="classpath/foo.*"> <source
+ * href="fooProtocol:classpath:/xdocs/exampleFeed.xml"/> </location>
+ * 
+ * <location pattern="file/foo.*"> <source
+ * href="fooProtocol:file:/xdocs/exampleFeed.xml"/> </location>
+ * 
+ * <location pattern="http/foo.*"> <source
+ * href="fooProtocol:http:/xdocs/exampleFeed.xml"/> </location>
  * 
  * etc.
  * 
@@ -60,29 +53,30 @@
 public class ChainedReader extends AbstractReader {
 
        private String docType;
-       
-       public AbstractSourceDocument read(IController controller,
-                       URI requestURI, final Location location) throws 
ProcessingException {
+
+       public AbstractSourceDocument read(IController controller, URI 
requestURI,
+                       final Location location, URI sourceURI) throws 
ProcessingException {
                DefaultSourceDocument doc = null;
-               final URI psudeoURI = location.getSourceURI();
-               final String ssp = psudeoURI.getSchemeSpecificPart();
-               URI uri;
-               try {
-                       uri = new URI(ssp);
-                       location.setSourceURI(uri);
-                       IReader reader;
-                       reader = (IReader) controller.getReader(location);
-                       doc = (DefaultSourceDocument) reader.read(controller, 
requestURI, location);
-                       if (doc != null) {
-                               doc
-                                               .setType(getDocType());
+               for (URI psuedoURI: location.getSourceURIs()) {
+                       final String ssp = psuedoURI.getSchemeSpecificPart();
+                       URI subSourceURI;
+                       try {
+                               subSourceURI = new URI(ssp);
+                               IReader reader;
+                               reader = (IReader) 
controller.getReader(subSourceURI);
+                               doc = (DefaultSourceDocument) 
reader.read(controller,
+                                               requestURI, location, 
subSourceURI);
+                               if (doc != null) {
+                                       doc.setType(getDocType());
+                                       break;
+                               }
+                       } catch (final URISyntaxException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       } catch (MalformedURLException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
                        }
-               } catch (final URISyntaxException e) {
-                       // TODO Auto-generated catch block
-                       e.printStackTrace();
-               } catch (MalformedURLException e) {
-                       // TODO Auto-generated catch block
-                       e.printStackTrace();
                }
                return doc;
        }
@@ -94,6 +88,5 @@
        public void setDocType(String docType) {
                this.docType = docType;
        }
-       
-       
+
 }

Modified: 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/FileReader.java
URL: 
http://svn.apache.org/viewvc/forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/FileReader.java?view=diff&rev=478728&r1=478727&r2=478728
==============================================================================
--- 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/FileReader.java
 (original)
+++ 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/FileReader.java
 Thu Nov 23 16:32:14 2006
@@ -20,6 +20,7 @@
 import java.io.FileInputStream;
 import java.io.InputStream;
 import java.net.URI;
+import java.net.URL;
 
 import org.apache.forrest.core.IController;
 import org.apache.forrest.core.document.AbstractSourceDocument;
@@ -28,7 +29,7 @@
 import org.apache.forrest.core.locationMap.Location;
 
 /**
- * An File reader reads a resource using the file protocol, i.e. it will read
+ * A File reader reads a resource using the file protocol, i.e. it will read
  * from local storage.
  * 
  */
@@ -39,11 +40,12 @@
         * 
         * @see 
org.apache.forrest.core.reader.IReader#read(org.apache.forrest.test.core.locationMap.Location)
         */
-       public AbstractSourceDocument read(IController controller, URI 
requestURI, final Location location) {
+       public AbstractSourceDocument read(IController controller, URI 
requestURI,
+                       final Location location, final URI sourceURI) {
                AbstractSourceDocument result = null;
                try {
-                       final InputStream is = new FileInputStream(new 
File(location
-                                       
.getResolvedSourceURL(requestURI).toURI()));
+                       URL resolvedURL = location.resolveURL(requestURI, 
sourceURI);
+                       final InputStream is = new FileInputStream(new 
File(resolvedURL.toURI()));
                        result = DocumentFactory.getSourceDocumentFor(is);
                } catch (final Exception e) {
                        if (location.isRequired())

Modified: 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/HTTPReader.java
URL: 
http://svn.apache.org/viewvc/forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/HTTPReader.java?view=diff&rev=478728&r1=478727&r2=478728
==============================================================================
--- 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/HTTPReader.java
 (original)
+++ 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/HTTPReader.java
 Thu Nov 23 16:32:14 2006
@@ -20,6 +20,7 @@
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URI;
+import java.net.URL;
 
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
@@ -55,13 +56,14 @@
         * 
         * @see 
org.apache.forrest.core.reader.IReader#read(org.apache.forrest.test.core.locationMap.Location)
         */
-       public AbstractSourceDocument read(IController controller, final URI 
requestURI, final Location location)
+       public AbstractSourceDocument read(IController controller,
+                       final URI requestURI, final Location location, URI 
sourceURI)
                        throws MalformedURLException, ProcessingException {
                InputStream is;
                DefaultSourceDocument result = null;
+               URL resolvedURL = location.resolveURL(requestURI, sourceURI);
                final ByteArrayOutputStream out = new ByteArrayOutputStream();
-               final GetMethod get = new 
GetMethod(location.getResolvedSourceURL(requestURI)
-                               .toExternalForm());
+               final GetMethod get = new 
GetMethod(resolvedURL.toExternalForm());
                get.setFollowRedirects(true);
                try {
                        this.client.executeMethod(get);
@@ -71,6 +73,7 @@
                        tidy.parseDOM(is, out);
                        result = new DefaultSourceDocument(out.toString());
                } catch (final Exception e) {
+                       result = null;
                        if (location.isRequired())
                                throw new SourceException("Source URL is 
invalid", e);
                } finally {

Modified: 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/IReader.java
URL: 
http://svn.apache.org/viewvc/forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/IReader.java?view=diff&rev=478728&r1=478727&r2=478728
==============================================================================
--- 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/IReader.java
 (original)
+++ 
forrest/trunk/whiteboard/forrest2/core/src/core/org/apache/forrest/core/reader/IReader.java
 Thu Nov 23 16:32:14 2006
@@ -34,20 +34,20 @@
        abstract void init();
 
        /**
-        * Read a resource from a given location. If the resource cannot be 
read,
+        * Read a resource from a given uri. If the resource cannot be read,
         * but it is an optional location then return null, if it cannot be 
read and
         * it is a required location throw SourceException.
         * @param context 
         * 
         * @param controller - the forrest controller in use
         * @param requestURI - the URI being requested
-        * @param location - the location we are to read the document from
+        * @param uri - the uri we are to read the document from
         * 
         * @return
         * @throws MalformedURLException
         * @throws ProcessingException 
         */
-       public abstract AbstractSourceDocument read(IController controller, URI 
requestURI, Location location)
+       public abstract AbstractSourceDocument read(IController controller, URI 
requestURI, Location location, URI sourceURI)
                        throws MalformedURLException, ProcessingException;
 
 }