| /* |
| * 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.environment; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.lang.reflect.Method; |
| import java.net.MalformedURLException; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.apache.avalon.framework.CascadingRuntimeException; |
| import org.apache.avalon.framework.component.Component; |
| import org.apache.avalon.framework.component.ComponentException; |
| import org.apache.avalon.framework.component.ComponentManager; |
| import org.apache.avalon.framework.logger.AbstractLogEnabled; |
| import org.apache.cocoon.Constants; |
| import org.apache.cocoon.ProcessingException; |
| import org.apache.cocoon.components.CocoonComponentManager; |
| import org.apache.cocoon.components.source.SourceUtil; |
| import org.apache.cocoon.util.BufferedOutputStream; |
| import org.apache.cocoon.util.ClassUtils; |
| import org.apache.cocoon.util.Deprecation; |
| import org.apache.commons.collections.iterators.IteratorEnumeration; |
| import org.apache.excalibur.source.SourceException; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * Base class for any environment |
| * |
| * @author <a href="mailto:bluetkemeier@s-und-n.de">Björn Lütkemeier</a> |
| * @author <a href="mailto:Giacomo.Pati@pwr.ch">Giacomo Pati</a> |
| * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a> |
| * @version $Id$ |
| */ |
| public abstract class AbstractEnvironment extends AbstractLogEnabled implements Environment { |
| |
| /** The current uri in progress */ |
| protected String uris; |
| |
| /** The current prefix to strip off from the request uri */ |
| protected StringBuffer prefix = new StringBuffer(); |
| |
| /** The View requested */ |
| protected String view; |
| |
| /** The Action requested */ |
| protected String action; |
| |
| /** The Context path */ |
| protected String context; |
| |
| /** The context path stored temporarily between constructor and initComponents */ |
| private String tempInitContext; |
| |
| /** The root context path */ |
| protected String rootContext; |
| |
| /** The servlet object model */ |
| protected HashMap objectModel; |
| |
| /** The real source resolver */ |
| protected org.apache.excalibur.source.SourceResolver sourceResolver; |
| |
| /** The component manager */ |
| protected ComponentManager manager; |
| |
| /** The attributes */ |
| private Map attributes = new HashMap(); |
| |
| /** The secure Output Stream */ |
| protected BufferedOutputStream secureOutputStream; |
| |
| /** The real output stream */ |
| protected OutputStream outputStream; |
| |
| /** The AvalonToCocoonSourceWrapper (this is for the deprecated support) */ |
| static protected Method avalonToCocoonSourceWrapper; |
| |
| /** Do we have our components ? */ |
| protected boolean initializedComponents = false; |
| |
| /** |
| * Constructs the abstract environment |
| */ |
| public AbstractEnvironment(String uri, String view, File file) |
| throws MalformedURLException { |
| this(uri, view, file, null); |
| } |
| |
| /** |
| * Constructs the abstract environment |
| */ |
| public AbstractEnvironment(String uri, String view, File file, String action) |
| throws MalformedURLException { |
| this(uri, view, file.toURL().toExternalForm(), action); |
| } |
| |
| /** |
| * Constructs the abstract environment |
| */ |
| public AbstractEnvironment(String uri, String view, String context, String action) |
| throws MalformedURLException { |
| this.uris = uri; |
| this.view = view; |
| this.tempInitContext = context; |
| this.action = action; |
| this.objectModel = new HashMap(); |
| } |
| |
| /** |
| * Allow implementations to set view later than in super() constructor. |
| * View can be set only once, and should be set in implementation's constructor. |
| */ |
| protected void setView(String view) { |
| if (this.view != null) { |
| throw new IllegalStateException("View was already set on this environment"); |
| } |
| this.view = view; |
| } |
| |
| /** |
| * Allow implementations to set action later than in super() constructor |
| * Action can be set only once, and should be set in implementation's constructor. |
| */ |
| protected void setAction(String action) { |
| if (this.action != null) { |
| throw new IllegalStateException("Action was already set on this environment"); |
| } |
| this.action = action; |
| } |
| |
| /** |
| * Helper method to extract the view name from the request. |
| */ |
| protected static String extractView(Request request) { |
| return request.getParameter(Constants.VIEW_PARAM); |
| } |
| |
| /** |
| * Helper method to extract the action name from the request. |
| */ |
| protected static String extractAction(Request req) { |
| String action = req.getParameter(Constants.ACTION_PARAM); |
| if (action != null) { |
| /* TC: still support the deprecated syntax */ |
| return action; |
| } else { |
| for(Enumeration e = req.getParameterNames(); e.hasMoreElements(); ) { |
| String name = (String)e.nextElement(); |
| if (name.startsWith(Constants.ACTION_PARAM_PREFIX)) { |
| if (name.endsWith(".x") || name.endsWith(".y")) { |
| return name.substring(Constants.ACTION_PARAM_PREFIX.length(),name.length()-2); |
| } else { |
| return name.substring(Constants.ACTION_PARAM_PREFIX.length()); |
| } |
| } |
| } |
| return null; |
| } |
| } |
| |
| // Sitemap methods |
| |
| /** |
| * Returns the uri in progress. The prefix is stripped off |
| */ |
| public String getURI() { |
| return this.uris; |
| } |
| |
| /** |
| * Get the Root Context |
| */ |
| public String getRootContext() { |
| if ( !this.initializedComponents) { |
| this.initComponents(); |
| } |
| return this.rootContext; |
| } |
| |
| /** |
| * Get the current Context |
| */ |
| public String getContext() { |
| if (!this.initializedComponents) { |
| this.initComponents(); |
| } |
| return this.context; |
| } |
| |
| /** |
| * Get the prefix of the URI in progress |
| */ |
| public String getURIPrefix() { |
| return this.prefix.toString(); |
| } |
| |
| /** |
| * Set the prefix of the URI in progress |
| */ |
| protected void setURIPrefix(String prefix) { |
| if (this.getLogger().isDebugEnabled()) { |
| this.getLogger().debug("Set the URI Prefix (OLD=" + this.getURIPrefix() + ", NEW=" + prefix + ")"); |
| } |
| this.prefix = new StringBuffer(prefix); |
| } |
| |
| /** |
| * Set the context. |
| */ |
| protected void setContext(String context) { |
| this.context = context; |
| } |
| |
| /** |
| * Set the context. This is similar to changeContext() |
| * except that it is absolute. |
| */ |
| public void setContext(String prefix, String uri, String context) { |
| this.setContext(context); |
| this.setURIPrefix(prefix == null ? "" : prefix); |
| this.uris = uri; |
| if (this.getLogger().isDebugEnabled()) { |
| this.getLogger().debug("Reset context to " + this.context); |
| } |
| } |
| |
| /** |
| * Adds an prefix to the overall stripped off prefix from the request uri |
| */ |
| public void changeContext(String newPrefix, String newContext) |
| throws IOException { |
| if (!this.initializedComponents) { |
| this.initComponents(); |
| } |
| |
| if (this.getLogger().isDebugEnabled()) { |
| this.getLogger().debug("Changing Cocoon context"); |
| this.getLogger().debug(" from context(" + this.context + ") and prefix(" + this.prefix + ")"); |
| this.getLogger().debug(" to context(" + newContext + ") and prefix(" + newPrefix + ")"); |
| this.getLogger().debug(" at URI " + this.uris); |
| } |
| |
| int l = newPrefix.length(); |
| if (l >= 1) { |
| if (!this.uris.startsWith(newPrefix)) { |
| String message = "The current URI (" + this.uris + |
| ") doesn't start with given prefix (" + newPrefix + ")"; |
| this.getLogger().error(message); |
| throw new RuntimeException(message); |
| } |
| this.prefix.append(newPrefix); |
| this.uris = this.uris.substring(l); |
| |
| // check for a slash at the beginning to avoid problems with subsitemaps |
| if (this.uris.startsWith("/")) { |
| this.uris = this.uris.substring(1); |
| this.prefix.append('/'); |
| } |
| } |
| |
| if (this.context.startsWith("zip:")) { |
| // if the resource is zipped into a war file (e.g. Weblogic temp deployment) |
| // FIXME (VG): Is this still required? Better to unify both cases. |
| if (this.getLogger().isDebugEnabled()) { |
| this.getLogger().debug("Base context is zip: " + this.context); |
| } |
| |
| org.apache.excalibur.source.Source source = null; |
| try { |
| source = this.sourceResolver.resolveURI(this.context + newContext); |
| this.context = source.getURI(); |
| } finally { |
| this.sourceResolver.release(source); |
| } |
| } else if (newContext.length() > 0){ |
| String sContext; |
| // if we got a absolute context or one with a protocol resolve it |
| if (newContext.charAt(0) == '/') { |
| // context starts with the '/' - absolute file URL |
| sContext = "file:" + newContext; |
| } else if (newContext.indexOf(':') > 1) { |
| // context have ':' - absolute URL |
| sContext = newContext; |
| } else { |
| // context is relative to old one |
| sContext = this.context + '/' + newContext; |
| } |
| |
| // Cut the file name part from context (if present) |
| int i = sContext.lastIndexOf('/'); |
| if (i != -1 && i + 1 < sContext.length()) { |
| sContext = sContext.substring(0, i + 1); |
| } |
| |
| org.apache.excalibur.source.Source source = null; |
| try { |
| source = this.sourceResolver.resolveURI(sContext); |
| this.context = source.getURI(); |
| } finally { |
| this.sourceResolver.release(source); |
| } |
| } |
| |
| if (this.getLogger().isDebugEnabled()) { |
| this.getLogger().debug("New context is " + this.context); |
| } |
| } |
| |
| public void globalRedirect(boolean sessionmode, String newURL) throws IOException { |
| this.redirect(sessionmode, newURL); |
| } |
| |
| // Request methods |
| |
| /** |
| * Returns the request view |
| */ |
| public String getView() { |
| return this.view; |
| } |
| |
| /** |
| * Returns the request action |
| */ |
| public String getAction() { |
| return this.action; |
| } |
| |
| // Response methods |
| |
| /** |
| * Set a status code |
| */ |
| public void setStatus(int statusCode) { |
| } |
| |
| // Object model method |
| |
| /** |
| * Returns a Map containing environment specific objects |
| */ |
| public Map getObjectModel() { |
| return this.objectModel; |
| } |
| |
| /** |
| * Resolve an entity. |
| * @deprecated Use the resolveURI methods instead |
| */ |
| public Source resolve(String systemId) |
| throws ProcessingException, SAXException, IOException { |
| Deprecation.logger.warn("The method SourceResolver.resolve(String) is " |
| + "deprecated. Use resolveURI(String) instead."); |
| if (!this.initializedComponents) { |
| this.initComponents(); |
| } |
| |
| if (this.getLogger().isDebugEnabled()) { |
| this.getLogger().debug("Resolving '" + systemId + "' in context '" + this.context + "'"); |
| } |
| |
| if (systemId == null) { |
| throw new SAXException("Invalid System ID"); |
| } |
| |
| // get the wrapper class - we don't want to import the wrapper directly |
| // to avoid a direct dependency from the core to the deprecation package |
| Class clazz; |
| try { |
| clazz = ClassUtils.loadClass("org.apache.cocoon.components.source.impl.AvalonToCocoonSourceInvocationHandler"); |
| } catch (Exception e) { |
| throw new ProcessingException("The deprecated resolve() method of the environment was called." |
| +"Please either update your code to use the new resolveURI() method or" |
| +" install the deprecation support.", e); |
| } |
| |
| if (null == avalonToCocoonSourceWrapper) { |
| synchronized (this.getClass()) { |
| try { |
| avalonToCocoonSourceWrapper = clazz.getDeclaredMethod("createProxy", |
| new Class[] {ClassUtils.loadClass("org.apache.excalibur.source.Source"), |
| ClassUtils.loadClass("org.apache.excalibur.source.SourceResolver"), |
| ClassUtils.loadClass(Environment.class.getName()), |
| ClassUtils.loadClass(ComponentManager.class.getName())}); |
| } catch (Exception e) { |
| throw new ProcessingException("The deprecated resolve() method of the environment was called." |
| +"Please either update your code to use the new resolveURI() method or" |
| +" install the deprecation support.", e); |
| } |
| } |
| } |
| |
| try { |
| org.apache.excalibur.source.Source source = this.resolveURI(systemId); |
| Source wrappedSource = (Source)avalonToCocoonSourceWrapper.invoke( |
| clazz, |
| new Object[] {source, this.sourceResolver, this, this.manager}); |
| return wrappedSource; |
| } catch (SourceException se) { |
| throw SourceUtil.handle(se); |
| } catch (Exception e) { |
| throw new ProcessingException("Unable to create source wrapper.", e); |
| } |
| } |
| |
| /** |
| * Check if the response has been modified since the same |
| * "resource" was requested. |
| * The caller has to test if it is really the same "resource" |
| * which is requested. |
| * @return true if the response is modified or if the |
| * environment is not able to test it |
| */ |
| public boolean isResponseModified(long lastModified) { |
| return true; // always modified |
| } |
| |
| /** |
| * Mark the response as not modified. |
| */ |
| public void setResponseIsNotModified() { |
| // does nothing |
| } |
| |
| public Object getAttribute(String name) { |
| return this.attributes.get(name); |
| } |
| |
| public void setAttribute(String name, Object value) { |
| this.attributes.put(name, value); |
| } |
| |
| protected boolean hasAttribute(String name) { |
| return this.attributes.containsKey(name); |
| } |
| |
| public void removeAttribute(String name) { |
| this.attributes.remove(name); |
| } |
| |
| public Enumeration getAttributeNames() { |
| return new IteratorEnumeration(this.attributes.keySet().iterator()); |
| } |
| |
| /** |
| * Get the output stream where to write the generated resource. |
| * @deprecated Use {@link #getOutputStream(int)} instead. |
| */ |
| public OutputStream getOutputStream() throws IOException { |
| Deprecation.logger.warn("The method Environment.getOutputStream() " + |
| "is deprecated. Use getOutputStream(-1) instead."); |
| // by default we use the complete buffering output stream |
| return this.getOutputStream(-1); |
| } |
| |
| /** |
| * Get the output stream where to write the generated resource. |
| * The returned stream is buffered by the environment. If the |
| * buffer size is -1 then the complete output is buffered. |
| * If the buffer size is 0, no buffering takes place. |
| * |
| * <br>This method replaces {@link #getOutputStream()}. |
| */ |
| public OutputStream getOutputStream(int bufferSize) throws IOException { |
| // This method could be called several times during request processing |
| // with differing values of bufferSize and should handle this situation |
| // correctly. |
| // FIXME (JH): Question is what "correctly" means. The current behavior |
| // seems to be inconsistent: On a second call with bufferSize == 0 we |
| // discard whatever the first called set up. With a bufferSize != 0 the |
| // first call's setup is preserved. Why not always creating new |
| // BufferedOutputStream in the else block replacing a potentially |
| // existing one? |
| if (bufferSize == 0) { |
| // Discard secure output stream if it was created before. |
| if (this.secureOutputStream != null) { |
| this.secureOutputStream = null; |
| } |
| return this.outputStream; |
| } else { |
| if (this.secureOutputStream == null) { |
| this.secureOutputStream = new BufferedOutputStream(this.outputStream, bufferSize); |
| } |
| return this.secureOutputStream; |
| } |
| } |
| |
| /** |
| * Reset the response if possible. This allows error handlers to have |
| * a higher chance to produce clean output if the pipeline that raised |
| * the error has already output some data. |
| * |
| * @return true if the response was successfully reset |
| */ |
| public boolean tryResetResponse() |
| throws IOException { |
| if (this.secureOutputStream != null && this.secureOutputStream.isResettable()) { |
| this.secureOutputStream.reset(); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Commit the response |
| */ |
| public void commitResponse() |
| throws IOException { |
| if (this.secureOutputStream != null) { |
| if (this.secureOutputStream.isResettable()) { |
| this.setContentLength(this.secureOutputStream.getCount()); |
| } |
| this.secureOutputStream.flush(); |
| } else if (this.outputStream != null) { |
| this.outputStream.flush(); |
| } |
| } |
| |
| /** |
| * Get a <code>Source</code> object. |
| */ |
| public org.apache.excalibur.source.Source resolveURI(final String location) |
| throws MalformedURLException, IOException, SourceException { |
| return this.resolveURI(location, null, null); |
| } |
| |
| /** |
| * Get a <code>Source</code> object. |
| */ |
| public org.apache.excalibur.source.Source resolveURI(final String location, |
| String baseURI, |
| final Map parameters) |
| throws MalformedURLException, IOException, SourceException { |
| if (!this.initializedComponents) { |
| this.initComponents(); |
| } |
| return this.sourceResolver.resolveURI(location, baseURI, parameters); |
| } |
| |
| /** |
| * Releases a resolved resource |
| */ |
| public void release(final org.apache.excalibur.source.Source source) { |
| if (null != source) { |
| this.sourceResolver.release(source); |
| } |
| } |
| |
| /** |
| * Initialize the components for the environment |
| * This gets the source resolver and the xmlizer component |
| */ |
| protected void initComponents() { |
| this.initializedComponents = true; |
| try { |
| this.manager = CocoonComponentManager.getSitemapComponentManager(); |
| this.sourceResolver = (org.apache.excalibur.source.SourceResolver)this.manager.lookup(org.apache.excalibur.source.SourceResolver.ROLE); |
| if (this.tempInitContext != null) { |
| org.apache.excalibur.source.Source source = null; |
| try { |
| source = this.sourceResolver.resolveURI(this.tempInitContext); |
| this.context = source.getURI(); |
| |
| if (this.rootContext == null) // hack for EnvironmentWrapper |
| this.rootContext = this.context; |
| } finally { |
| this.sourceResolver.release(source); |
| } |
| this.tempInitContext = null; |
| } |
| } catch (ComponentException ce) { |
| // this should never happen! |
| throw new CascadingRuntimeException("Unable to lookup component.", ce); |
| } catch (IOException ie) { |
| throw new CascadingRuntimeException("Unable to resolve URI: "+this.tempInitContext, ie); |
| } |
| } |
| |
| /** |
| * Notify that the processing starts. |
| */ |
| public void startingProcessing() { |
| // do nothing here |
| } |
| |
| /** |
| * Notify that the processing is finished |
| * This can be used to cleanup the environment object |
| */ |
| public void finishingProcessing() { |
| if (null != this.manager) { |
| this.manager.release((Component)this.sourceResolver); |
| this.manager = null; |
| this.sourceResolver = null; |
| } |
| this.initializedComponents = false; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.cocoon.environment.Environment#isInternRedirect() |
| */ |
| public boolean isInternalRedirect() { |
| return false; |
| } |
| } |