| /* |
| * 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; |
| } |
| } |
| } |