blob: df75bd38084880bc8a1c7cabc7f4cee9c1b30abf [file] [log] [blame]
/*
* 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&ouml;rn L&uuml;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;
}
}