| /* |
| * Copyright 1999-2005 The Apache Software Foundation. |
| * |
| * Licensed 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.internal; |
| |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.Map; |
| |
| import org.apache.avalon.framework.activity.Disposable; |
| import org.apache.avalon.framework.logger.AbstractLogEnabled; |
| import org.apache.avalon.framework.logger.Logger; |
| import org.apache.avalon.framework.service.ServiceException; |
| import org.apache.avalon.framework.service.ServiceManager; |
| import org.apache.avalon.framework.service.Serviceable; |
| import org.apache.cocoon.ProcessingException; |
| import org.apache.cocoon.Processor; |
| import org.apache.cocoon.components.source.SourceUtil; |
| import org.apache.cocoon.environment.Environment; |
| import org.apache.cocoon.environment.SourceResolver; |
| import org.apache.cocoon.xml.XMLConsumer; |
| import org.apache.excalibur.source.Source; |
| |
| /** |
| * Helper class for maintaining the environment stack. |
| * |
| * This is an internal class, and it might change in an incompatible way over time. |
| * For developing your own components/applications based on Cocoon, you shouldn't |
| * really need it. |
| * |
| * @version $Id$ |
| * @since 2.2 |
| */ |
| public class EnvironmentHelper |
| extends AbstractLogEnabled |
| implements SourceResolver, Serviceable, Disposable { |
| |
| /** The environment information */ |
| static protected final ThreadLocal environmentStack = new ThreadLocal(); |
| |
| /** The real source resolver */ |
| protected org.apache.excalibur.source.SourceResolver resolver; |
| |
| /** The service manager */ |
| protected ServiceManager manager; |
| |
| /** The complete prefix */ |
| protected String prefix; |
| |
| /** The Context path */ |
| protected String context; |
| |
| /** The last prefix, which is stripped off from the request uri */ |
| protected String lastPrefix; |
| |
| /** |
| * Constructor |
| * |
| */ |
| public EnvironmentHelper(URL context) { |
| if ( context != null ) { |
| this.context = context.toExternalForm(); |
| } |
| } |
| |
| /** |
| * Constructor |
| * |
| */ |
| public EnvironmentHelper(EnvironmentHelper parent) { |
| this.context = parent.context; |
| this.lastPrefix = parent.lastPrefix; |
| this.prefix = parent.prefix; |
| } |
| |
| /** |
| * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager) |
| */ |
| public void service(ServiceManager manager) throws ServiceException { |
| this.manager = manager; |
| this.resolver = (org.apache.excalibur.source.SourceResolver) |
| this.manager.lookup(org.apache.excalibur.source.SourceResolver.ROLE); |
| Source source = null; |
| try { |
| source = this.resolver.resolveURI(this.context); |
| this.context = source.getURI(); |
| |
| } catch (IOException ioe) { |
| throw new ServiceException("EnvironmentHelper", "Unable to resolve environment context. ", ioe); |
| } finally { |
| this.resolver.release(source); |
| } |
| } |
| |
| /** |
| * @see org.apache.avalon.framework.activity.Disposable#dispose() |
| */ |
| public void dispose() { |
| if ( this.manager != null ) { |
| this.manager.release( this.resolver ); |
| this.resolver = null; |
| this.manager = null; |
| } |
| } |
| |
| /** |
| * @see org.apache.excalibur.source.SourceResolver#release(org.apache.excalibur.source.Source) |
| */ |
| public void release(Source source) { |
| this.resolver.release(source); |
| } |
| |
| /** |
| * @see org.apache.excalibur.source.SourceResolver#resolveURI(java.lang.String, java.lang.String, java.util.Map) |
| */ |
| public Source resolveURI(final String location, |
| String baseURI, |
| final Map parameters) |
| throws MalformedURLException, IOException { |
| return this.resolver.resolveURI(location, |
| (baseURI == null ? this.context : baseURI), |
| parameters); |
| } |
| |
| /** |
| * @see org.apache.excalibur.source.SourceResolver#resolveURI(java.lang.String) |
| */ |
| public Source resolveURI(final String location) |
| throws MalformedURLException, IOException { |
| return this.resolveURI(location, null, null); |
| } |
| |
| /** |
| * Return the current context URI |
| */ |
| public String getContext() { |
| return this.context; |
| } |
| |
| /** |
| * Return the prefix |
| */ |
| public String getPrefix() { |
| return this.prefix; |
| } |
| |
| /** |
| * Change the context of the environment. |
| * @param env The environment to change |
| * @throws ProcessingException |
| */ |
| public void changeContext(Environment env) |
| throws ProcessingException { |
| if ( this.lastPrefix != null ) { |
| final String uris = env.getURI(); |
| if (!uris.startsWith(this.lastPrefix)) { |
| final String message = "The current URI (" + uris + |
| ") doesn't start with given prefix (" + this.lastPrefix + ")"; |
| throw new ProcessingException(message); |
| } |
| // we don't need to check for slash at the beginning |
| // of uris - the prefix always ends with a slash! |
| final int l = this.lastPrefix.length(); |
| env.setURI(this.prefix, uris.substring(l)); |
| } |
| } |
| |
| /** |
| * Adds an prefix to the overall stripped off prefix from the request uri |
| */ |
| public void changeContext(Source newSource, String newPrefix) |
| throws IOException { |
| final String newContext = newSource.getURI(); |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug("Changing Cocoon context"); |
| getLogger().debug(" from context(" + this.context + ") and prefix(" + this.prefix + ")"); |
| getLogger().debug(" to context(" + newContext + ") and prefix(" + newPrefix + ")"); |
| } |
| int l = newPrefix.length(); |
| if (l >= 1) { |
| this.lastPrefix = newPrefix; |
| if ( this.prefix == null ) { |
| this.prefix = ""; |
| } |
| final StringBuffer buffer = new StringBuffer(this.prefix); |
| buffer.append(newPrefix); |
| // check for a slash at the beginning to avoid problems with subsitemaps |
| if ( buffer.charAt(buffer.length()-1) != '/') { |
| buffer.append('/'); |
| this.lastPrefix = this.lastPrefix + '/'; |
| } |
| this.prefix = buffer.toString(); |
| } else { |
| this.lastPrefix = null; |
| } |
| |
| if (SourceUtil.getScheme(this.context).equals("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 (getLogger().isDebugEnabled()) { |
| getLogger().debug("Base context is zip: " + this.context); |
| } |
| |
| org.apache.excalibur.source.Source source = null; |
| try { |
| source = this.resolver.resolveURI(this.context + newContext); |
| this.context = source.getURI(); |
| } finally { |
| this.resolver.release(source); |
| } |
| } else { |
| 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); |
| } |
| |
| Source source = null; |
| try { |
| source = this.resolver.resolveURI(sContext); |
| this.context = source.getURI(); |
| } finally { |
| this.resolver.release(source); |
| } |
| } |
| |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug("New context is " + this.context); |
| } |
| } |
| |
| /** |
| * This hook must be called by the sitemap each time a sitemap is entered. |
| * |
| * <p>This method should never raise an exception, except when the |
| * parameters are not set!</p> |
| * |
| * @throws ProcessingException if processor is null |
| */ |
| public static void enterProcessor(Processor processor, |
| ServiceManager manager, |
| Environment env) |
| throws ProcessingException { |
| if (null == processor) { |
| throw new ProcessingException("Processor is not set."); |
| } |
| |
| EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); |
| if (stack == null) { |
| stack = new EnvironmentStack(); |
| environmentStack.set(stack); |
| } |
| stack.pushInfo(new EnvironmentInfo(processor, stack.getOffset(), manager, env)); |
| stack.setOffset(stack.size() - 1); |
| } |
| |
| /** |
| * This hook must be called by the sitemap each time a sitemap is left. |
| * |
| * <p>It's the counterpart to the {@link #enterProcessor(Processor, ServiceManager, Environment)} |
| * method.</p> |
| */ |
| public static void leaveProcessor() { |
| final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); |
| final EnvironmentInfo info = (EnvironmentInfo)stack.pop(); |
| stack.setOffset(info.oldStackCount); |
| } |
| |
| /** |
| * This method is used for entering a new environment. |
| * |
| * @throws ProcessingException if there is no current processing environment |
| */ |
| public static void enterEnvironment(Environment env) |
| throws ProcessingException { |
| final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); |
| EnvironmentInfo info = null; |
| if ( stack != null && !stack.isEmpty()) { |
| info = stack.getCurrentInfo(); |
| } else { |
| throw new ProcessingException("There must be a current processing environment."); |
| } |
| |
| stack.pushInfo(new EnvironmentInfo(info.processor, stack.getOffset(), info.manager, env)); |
| stack.setOffset(stack.size() - 1); |
| } |
| |
| /** |
| * This method is used for leaving the current environment. |
| * |
| * <p>It's the counterpart to the {@link #enterEnvironment(Environment)} method.</p> |
| */ |
| public static Environment leaveEnvironment() { |
| final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); |
| final EnvironmentInfo info = (EnvironmentInfo)stack.pop(); |
| stack.setOffset(info.oldStackCount); |
| |
| return info.environment; |
| } |
| |
| /** |
| * INTERNAL METHOD. Do not use, can be removed without warning or deprecation cycle. |
| */ |
| public static int markEnvironment() { |
| // TODO (CZ): This is only for testing - remove it later on. See also Cocoon.java. |
| final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); |
| if (stack != null) { |
| return stack.size(); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * INTERNAL METHOD. Do not use, can be removed without warning or deprecation cycle. |
| */ |
| public static void checkEnvironment(int depth, Logger logger) |
| throws Exception { |
| // TODO (CZ): This is only for testing - remove it later on. See also Cocoon.java. |
| final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); |
| int currentDepth = stack != null? stack.size() : 0; |
| if (currentDepth != depth) { |
| logger.error("ENVIRONMENT STACK HAS NOT BEEN CLEANED PROPERLY!"); |
| throw new ProcessingException("Environment stack has not been cleaned up properly. " + |
| "Please report this (and if possible, together with a test case) " + |
| "to the Cocoon developers."); |
| } |
| } |
| |
| /** |
| * Return the environment |
| */ |
| public static Environment getCurrentEnvironment() { |
| final EnvironmentStack stack = (EnvironmentStack) environmentStack.get(); |
| if ( stack != null && !stack.empty() ) { |
| final EnvironmentInfo info = stack.getCurrentInfo(); |
| return info.environment; |
| } |
| return null; |
| } |
| |
| /** |
| * Return the current processor |
| */ |
| public static Processor getCurrentProcessor() { |
| final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); |
| if ( stack != null && !stack.isEmpty()) { |
| final EnvironmentInfo info = stack.getCurrentInfo(); |
| return info.processor; |
| } |
| return null; |
| } |
| |
| /** |
| * Get the current sitemap component manager. |
| * This method return the current sitemap component manager. This |
| * is the manager that holds all the components of the currently |
| * processed (sub)sitemap. |
| */ |
| static public ServiceManager getSitemapServiceManager() { |
| final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); |
| if ( stack != null && !stack.isEmpty()) { |
| final EnvironmentInfo info = stack.getCurrentInfo(); |
| return info.manager; |
| } |
| return null; |
| } |
| |
| /** |
| * Create an environment aware xml consumer for the cocoon |
| * protocol |
| */ |
| public static XMLConsumer createEnvironmentAwareConsumer(XMLConsumer consumer) { |
| final EnvironmentStack stack = (EnvironmentStack)environmentStack.get(); |
| final EnvironmentInfo info = stack.getCurrentInfo(); |
| return stack.getEnvironmentAwareConsumerWrapper(consumer, info.oldStackCount); |
| } |
| |
| /** |
| * Create an environment aware xml consumer that push an |
| * environment before calling the consumer. |
| */ |
| public static XMLConsumer createPushEnvironmentConsumer(XMLConsumer consumer, Environment environment) { |
| return new PushEnvironmentChanger(consumer, environment); |
| } |
| |
| /** |
| * Create an environment aware xml consumer that pop and save the |
| * current environment before calling the consumer. |
| */ |
| public static XMLConsumer createPopEnvironmentConsumer(XMLConsumer consumer) { |
| return new PopEnvironmentChanger(consumer); |
| } |
| |
| /** |
| * A runnable wrapper that inherits the environment stack of the thread it is |
| * created in. |
| * <p> |
| * It's defined as an abstract class here to use some internals of EnvironmentHelper, and |
| * should only be used through its public counterpart, {@link org.apache.cocoon.environment.CocoonRunnable} |
| */ |
| public static abstract class AbstractCocoonRunnable implements Runnable { |
| private Object parentStack = null; |
| |
| public AbstractCocoonRunnable() { |
| // Clone the environment stack of the calling thread. |
| // We'll use it in run() below |
| Object stack = EnvironmentHelper.environmentStack.get(); |
| if (stack != null) { |
| this.parentStack = ((EnvironmentStack)stack).clone(); |
| } |
| } |
| |
| /** |
| * Calls {@link #doRun()} within the environment context of the creating thread. |
| */ |
| public final void run() { |
| // Install the stack from the parent thread and run the Runnable |
| Object oldStack = environmentStack.get(); |
| EnvironmentHelper.environmentStack.set(this.parentStack); |
| try { |
| doRun(); |
| } finally { |
| // Restore the previous stack |
| EnvironmentHelper.environmentStack.set(oldStack); |
| } |
| // FIXME: Check the lifetime of this run compared to the parent thread. |
| // A CocoonThread is meant to start and die within the execution period of the parent request, |
| // and it is an error if it lives longer as the parent environment is no more valid. |
| } |
| |
| abstract protected void doRun(); |
| } |
| } |