/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.cocoon.components.source.impl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.net.MalformedURLException; import java.util.Iterator; import java.util.Map; import org.apache.avalon.framework.component.Component; import org.apache.avalon.framework.component.ComponentManager; import org.apache.avalon.framework.logger.AbstractLogEnabled; import org.apache.avalon.framework.logger.Logger; import org.apache.cocoon.Constants; import org.apache.cocoon.Processor; import org.apache.cocoon.ResourceNotFoundException; import org.apache.cocoon.components.CocoonComponentManager; import org.apache.cocoon.components.pipeline.ProcessingPipeline; import org.apache.cocoon.components.source.SourceUtil; import org.apache.cocoon.environment.Environment; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.cocoon.environment.wrapper.EnvironmentWrapper; import org.apache.cocoon.environment.wrapper.MutableEnvironmentFacade; import org.apache.cocoon.xml.ContentHandlerWrapper; import org.apache.cocoon.xml.XMLConsumer; import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceException; import org.apache.excalibur.source.SourceNotFoundException; import org.apache.excalibur.source.SourceResolver; import org.apache.excalibur.source.SourceValidity; import org.apache.excalibur.xml.sax.XMLizable; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.ext.LexicalHandler; /** * Implementation of a {@link Source} that gets its content * by invoking a pipeline. * * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a> * @version $Id$ */ public final class SitemapSource extends AbstractLogEnabled implements Source, XMLizable { /** The internal event pipeline validities */ private SitemapSourceValidity validity; /** The system id */ private final String systemId; /** The system id used for caching */ private String systemIdForCaching; /** The current ComponentManager */ private final ComponentManager manager; /** The processor */ private final Processor processor; /** The pipeline processor */ private Processor pipelineProcessor; /** The environment */ private final MutableEnvironmentFacade environment; /** The <code>ProcessingPipeline</code> */ private ProcessingPipeline processingPipeline; /** The redirect <code>Source</code> */ private Source redirectSource; /** The <code>SAXException</code> if unable to get resource */ private SAXException exception; /** Do I need a refresh ? */ private boolean needsRefresh; /** The unique key for this processing */ private Object processKey; /** The used protocol */ private final String protocol; /** SourceResolver (for the redirect source) */ private SourceResolver sourceResolver; private String mimeType; /** * Construct a new object */ public SitemapSource(ComponentManager manager, String uri, Map parameters, Logger logger) throws MalformedURLException { Environment env = CocoonComponentManager.getCurrentEnvironment(); if ( env == null ) { throw new MalformedURLException("The cocoon protocol can not be used outside an environment."); } this.manager = manager; this.enableLogging(logger); boolean rawMode = false; // remove the protocol int position = uri.indexOf(':') + 1; if (position != 0) { this.protocol = uri.substring(0, position-1); // check for subprotocol if (uri.startsWith("raw:", position)) { position += 4; rawMode = true; } } else { throw new MalformedURLException("No protocol found for sitemap source in " + uri); } // does the uri point to this sitemap or to the root sitemap? String prefix; if (uri.startsWith("//", position)) { position += 2; this.processor = CocoonComponentManager.getActiveProcessor(env).getRootProcessor(); prefix = ""; // start at the root } else if (uri.startsWith("/", position)) { position ++; prefix = null; this.processor = CocoonComponentManager.getActiveProcessor(env); } else { throw new MalformedURLException("Malformed cocoon URI: " + uri); } // create the queryString (if available) String queryString = null; int queryStringPos = uri.indexOf('?', position); if (queryStringPos != -1) { queryString = uri.substring(queryStringPos + 1); uri = uri.substring(position, queryStringPos); } else if (position > 0) { uri = uri.substring(position); } // determine if the queryString specifies a cocoon-view String view = null; if (queryString != null) { int index = queryString.indexOf(Constants.VIEW_PARAM); if (index != -1 && (index == 0 || queryString.charAt(index-1) == '&') && queryString.length() > index + Constants.VIEW_PARAM.length() && queryString.charAt(index+Constants.VIEW_PARAM.length()) == '=') { String tmp = queryString.substring(index+Constants.VIEW_PARAM.length()+1); index = tmp.indexOf('&'); if (index != -1) { view = tmp.substring(0,index); } else { view = tmp; } } else { view = env.getView(); } } else { view = env.getView(); } // build the request uri which is relative to the context String requestURI = (prefix == null ? env.getURIPrefix() + uri : uri); // create system ID this.systemId = queryString == null ? this.protocol + "://" + requestURI : this.protocol + "://" + requestURI + "?" + queryString; // create environment... EnvironmentWrapper wrapper = new EnvironmentWrapper(env, requestURI, queryString, logger, manager, rawMode, view); wrapper.setURI(prefix, uri); // The environment is a facade whose delegate can be changed in case of internal redirects this.environment = new MutableEnvironmentFacade(wrapper); // ...and put information passed from the parent request to the internal request if ( null != parameters ) { this.environment.getObjectModel().put(ObjectModelHelper.PARENT_CONTEXT, parameters); } else { this.environment.getObjectModel().remove(ObjectModelHelper.PARENT_CONTEXT); } // create a new validity holder this.validity = new SitemapSourceValidity(); // initialize this.init(); } /** * Return the protocol identifier. */ public String getScheme() { return this.protocol; } /** * Get the content length of the source or -1 if it * is not possible to determine the length. */ public long getContentLength() { return -1; } /** * Get the last modification date. * @return The last modification in milliseconds since January 1, 1970 GMT * or 0 if it is unknown */ public long getLastModified() { return 0; } /** * Return an <code>InputStream</code> object to read from the source. */ public InputStream getInputStream() throws IOException, SourceException { if (this.needsRefresh) { this.refresh(); } // VG: Why exception is not thrown in constructor? if (this.exception != null) { throw new SourceException("Cannot get input stream for " + this.getURI(), this.exception); } if (this.redirectSource != null) { return this.redirectSource.getInputStream(); } try { ByteArrayOutputStream os = new ByteArrayOutputStream(); this.environment.setOutputStream(os); CocoonComponentManager.enterEnvironment(this.environment, this.manager, this.pipelineProcessor); try { this.processingPipeline.process(this.environment); } finally { CocoonComponentManager.leaveEnvironment(); } return new ByteArrayInputStream(os.toByteArray()); } catch (ResourceNotFoundException e) { throw new SourceNotFoundException("Exception during processing of " + this.systemId, e); } catch (Exception e) { throw new SourceException("Exception during processing of " + this.systemId, e); } finally { // Unhide wrapped environment output stream this.environment.setOutputStream(null); this.needsRefresh = true; } } /** * Returns the unique identifer for this source */ public String getURI() { return this.systemIdForCaching; } /** * Returns true always. * @see org.apache.excalibur.source.Source#exists() */ public boolean exists() { return true; } /** * Get the validity object. This wraps validity of the enclosed event * pipeline. If pipeline is not cacheable, <code>null</code> is returned. */ public SourceValidity getValidity() { return this.validity.getNestedValidity() == null? null: this.validity; } /** * The mime-type of the content described by this object. * If the source is not able to determine the mime-type by itself * this can be null. */ public String getMimeType() { return this.mimeType; } /** * Refresh this object and update the last modified date * and content length. */ public void refresh() { this.reset(); this.init(); } /** * Initialize */ protected void init() { this.systemIdForCaching = this.systemId; try { this.processKey = CocoonComponentManager.startProcessing(this.environment); this.processingPipeline = this.processor.buildPipeline(this.environment); this.pipelineProcessor = CocoonComponentManager.getActiveProcessor(this.environment); String redirectURL = this.environment.getRedirectURL(); if (redirectURL == null) { CocoonComponentManager.enterEnvironment(this.environment, this.manager, this.pipelineProcessor); try { this.processingPipeline.prepareInternal(this.environment); this.validity.set(this.processingPipeline.getValidityForEventPipeline()); this.mimeType = this.environment.getContentType(); final String eventPipelineKey = this.processingPipeline.getKeyForEventPipeline(); if (eventPipelineKey != null) { StringBuffer buffer = new StringBuffer(this.systemId); if (this.systemId.indexOf('?') == -1) { buffer.append('?'); } else { buffer.append('&'); } buffer.append("pipelinehash="); buffer.append(eventPipelineKey); this.systemIdForCaching = buffer.toString(); } else { this.systemIdForCaching = this.systemId; } } finally { CocoonComponentManager.leaveEnvironment(); } } else { if (redirectURL.indexOf(":") == -1) { redirectURL = this.protocol + ":/" + redirectURL; } if (this.sourceResolver == null) { this.sourceResolver = (SourceResolver) this.manager.lookup(SourceResolver.ROLE); } this.redirectSource = this.sourceResolver.resolveURI(redirectURL); this.validity.set(this.redirectSource.getValidity()); this.mimeType = this.redirectSource.getMimeType(); } } catch (SAXException e) { this.reset(); this.exception = e; } catch (Exception e) { this.reset(); this.exception = new SAXException("Could not get sitemap source " + this.systemId, e); } this.needsRefresh = false; } /** * Stream content to the content handler */ public void toSAX(ContentHandler contentHandler) throws SAXException { if (this.needsRefresh) { this.refresh(); } if (this.exception != null) { throw this.exception; } try { if (this.redirectSource != null) { SourceUtil.parse(this.manager, this.redirectSource, contentHandler); } else { XMLConsumer consumer; if (contentHandler instanceof XMLConsumer) { consumer = (XMLConsumer)contentHandler; } else if (contentHandler instanceof LexicalHandler) { consumer = new ContentHandlerWrapper(contentHandler, (LexicalHandler)contentHandler); } else { consumer = new ContentHandlerWrapper(contentHandler); } // We have to add an environment changer // for clean environment stack handling. CocoonComponentManager.enterEnvironment(this.environment, this.manager, this.pipelineProcessor); try { this.processingPipeline.process(this.environment, CocoonComponentManager.createEnvironmentAwareConsumer(consumer)); } finally { CocoonComponentManager.leaveEnvironment(); } } } catch (SAXException e) { // Preserve original exception throw e; } catch (Exception e) { throw new SAXException("Exception during processing of " + this.systemId, e); } finally { this.needsRefresh = true; } } /** * Reset everything */ private void reset() { if (this.processingPipeline != null) { this.processingPipeline.release(); this.processingPipeline = null; } if (this.processKey != null) { CocoonComponentManager.endProcessing(this.environment, this.processKey); this.processKey = null; } if (this.redirectSource != null) { this.sourceResolver.release(this.redirectSource); this.redirectSource = null; } this.validity.set(null); this.environment.reset(); this.exception = null; this.needsRefresh = true; this.pipelineProcessor = null; } /** * Recyclable */ public void recycle() { this.validity = new SitemapSourceValidity(); this.reset(); if (this.sourceResolver != null) { this.manager.release((Component)this.sourceResolver); this.sourceResolver = null; } } /** * Get the value of a parameter. * Using this it is possible to get custom information provided by the * source implementation, like an expires date, HTTP headers etc. */ public String getParameter(String name) { return null; } /** * Get the value of a parameter. * Using this it is possible to get custom information provided by the * source implementation, like an expires date, HTTP headers etc. */ public long getParameterAsLong(String name) { return 0; } /** * Get parameter names * Using this it is possible to get custom information provided by the * source implementation, like an expires date, HTTP headers etc. */ public Iterator getParameterNames() { return java.util.Collections.EMPTY_LIST.iterator(); } /** * A simple SourceValidity protecting callers from resets. */ public static final class SitemapSourceValidity implements SourceValidity, Serializable { private SourceValidity validity; private SitemapSourceValidity() { super(); } void set(SourceValidity validity) { this.validity = validity; } public int isValid() { return (this.validity != null ? this.validity.isValid() : SourceValidity.INVALID); } public int isValid(SourceValidity validity) { if (validity instanceof SitemapSourceValidity) { return (this.validity != null ? this.validity.isValid(((SitemapSourceValidity) validity).getNestedValidity()) : SourceValidity.INVALID); } return SourceValidity.INVALID; } public SourceValidity getNestedValidity() { return this.validity; } } }