001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.components.treeprocessor;
018:
019: import java.io.IOException;
020: import java.net.MalformedURLException;
021: import java.util.Collections;
022: import java.util.HashMap;
023: import java.util.List;
024: import java.util.Map;
025:
026: import org.apache.avalon.framework.activity.Disposable;
027: import org.apache.avalon.framework.component.ComponentManager;
028: import org.apache.avalon.framework.configuration.Configuration;
029: import org.apache.avalon.framework.logger.AbstractLogEnabled;
030: import org.apache.avalon.framework.logger.Logger;
031: import org.apache.cocoon.ProcessingException;
032: import org.apache.cocoon.Processor;
033: import org.apache.cocoon.components.ChainedConfiguration;
034: import org.apache.cocoon.components.CocoonComponentManager;
035: import org.apache.cocoon.components.pipeline.ProcessingPipeline;
036: import org.apache.cocoon.environment.Environment;
037: import org.apache.cocoon.environment.ForwardRedirector;
038: import org.apache.cocoon.environment.Redirector;
039: import org.apache.cocoon.environment.wrapper.EnvironmentWrapper;
040: import org.apache.cocoon.environment.wrapper.MutableEnvironmentFacade;
041:
042: /**
043: * The concrete implementation of {@link Processor}, containing the evaluation tree and associated
044: * data such as component manager.
045: *
046: * @version $Id: ConcreteTreeProcessor.java 602257 2007-12-07 22:43:53Z anathaniel $
047: */
048: public class ConcreteTreeProcessor extends AbstractLogEnabled implements
049: Processor, Disposable {
050:
051: /** The processor that wraps us */
052: private TreeProcessor wrappingProcessor;
053:
054: /** Component manager defined by the <map:components> of this sitemap */
055: ComponentManager sitemapComponentManager;
056:
057: /** Processing nodes that need to be disposed with this processor */
058: private List disposableNodes;
059:
060: /** Root node of the processing tree */
061: private ProcessingNode rootNode;
062:
063: private Map sitemapComponentConfigurations;
064:
065: private Configuration componentConfigurations;
066:
067: /** Number of simultaneous uses of this processor (either by concurrent request or by internal requests) */
068: private int requestCount;
069:
070: /** Builds a concrete processig, given the wrapping processor */
071: public ConcreteTreeProcessor(TreeProcessor wrappingProcessor) {
072: this .wrappingProcessor = wrappingProcessor;
073: }
074:
075: /** Set the processor data, result of the treebuilder job */
076: public void setProcessorData(ComponentManager manager,
077: ProcessingNode rootNode, List disposableNodes) {
078: if (this .sitemapComponentManager != null) {
079: throw new IllegalStateException(
080: "setProcessorData() can only be called once");
081: }
082:
083: this .sitemapComponentManager = manager;
084: this .rootNode = rootNode;
085: this .disposableNodes = disposableNodes;
086: }
087:
088: /** Set the sitemap component configurations (called as part of the tree building process) */
089: public void setComponentConfigurations(
090: Configuration componentConfigurations) {
091: this .componentConfigurations = componentConfigurations;
092: this .sitemapComponentConfigurations = null;
093: }
094:
095: /**
096: * Get the sitemap component configurations
097: * @since 2.1
098: */
099: public Map getComponentConfigurations() {
100: // do we have the sitemap configurations prepared for this processor?
101: if (null == this .sitemapComponentConfigurations) {
102:
103: synchronized (this ) {
104:
105: if (this .sitemapComponentConfigurations == null) {
106: // do we have configurations?
107: final Configuration[] childs = (this .componentConfigurations == null ? null
108: : this .componentConfigurations
109: .getChildren());
110:
111: if (null != childs) {
112:
113: if (null == this .wrappingProcessor.parent) {
114: this .sitemapComponentConfigurations = new HashMap(
115: 12);
116: } else {
117: // copy all configurations from parent
118: this .sitemapComponentConfigurations = new HashMap(
119: this .wrappingProcessor.parent
120: .getComponentConfigurations());
121: }
122:
123: // and now check for new configurations
124: for (int m = 0; m < childs.length; m++) {
125:
126: final String r = this .wrappingProcessor.roleManager
127: .getRoleForName(childs[m].getName());
128: this .sitemapComponentConfigurations
129: .put(
130: r,
131: new ChainedConfiguration(
132: childs[m],
133: (ChainedConfiguration) this .sitemapComponentConfigurations
134: .get(r)));
135: }
136: } else {
137: // we don't have configurations
138: if (null == this .wrappingProcessor.parent) {
139: this .sitemapComponentConfigurations = Collections.EMPTY_MAP;
140: } else {
141: // use configuration from parent
142: this .sitemapComponentConfigurations = this .wrappingProcessor.parent
143: .getComponentConfigurations();
144: }
145: }
146: }
147: }
148: }
149: return this .sitemapComponentConfigurations;
150: }
151:
152: /**
153: * Mark this processor as needing to be disposed. Actual call to {@link #dispose()} will occur when
154: * all request processings on this processor will be terminated.
155: */
156: public void markForDisposal() {
157: // Decrement the request count (negative number means dispose)
158: synchronized (this ) {
159: this .requestCount--;
160: }
161:
162: if (this .requestCount < 0) {
163: // No more users : dispose right now
164: dispose();
165: }
166: }
167:
168: public TreeProcessor getWrappingProcessor() {
169: return this .wrappingProcessor;
170: }
171:
172: public Processor getRootProcessor() {
173: return this .wrappingProcessor.getRootProcessor();
174: }
175:
176: /**
177: * Process the given <code>Environment</code> producing the output.
178: * @return If the processing is successfull <code>true</code> is returned.
179: * If not match is found in the sitemap <code>false</code>
180: * is returned.
181: * @throws org.apache.cocoon.ResourceNotFoundException If a sitemap component tries
182: * to access a resource which can not
183: * be found, e.g. the generator
184: * ConnectionResetException If the connection was reset
185: */
186: public boolean process(Environment environment) throws Exception {
187: InvokeContext context = new InvokeContext();
188: context.enableLogging(getLogger());
189: try {
190: return process(environment, context);
191: } finally {
192: context.dispose();
193: }
194: }
195:
196: /**
197: * Process the given <code>Environment</code> to assemble
198: * a <code>ProcessingPipeline</code>.
199: * @since 2.1
200: */
201: public ProcessingPipeline buildPipeline(Environment environment)
202: throws Exception {
203: InvokeContext context = new InvokeContext(true);
204: context.enableLogging(getLogger());
205: try {
206: if (process(environment, context)) {
207: return context.getProcessingPipeline();
208: } else {
209: return null;
210: }
211: } finally {
212: context.dispose();
213: }
214: }
215:
216: /**
217: * Do the actual processing, be it producing the response or just building the pipeline
218: * @param environment
219: * @param context
220: * @return true if the pipeline was successfully built, false otherwise.
221: * @throws Exception
222: */
223: protected boolean process(Environment environment,
224: InvokeContext context) throws Exception {
225:
226: // Increment the concurrent requests count
227: synchronized (this ) {
228: requestCount++;
229: }
230:
231: try {
232: // and now process
233: CocoonComponentManager.enterEnvironment(environment,
234: this .sitemapComponentManager, this );
235:
236: Map objectModel = environment.getObjectModel();
237:
238: Object oldResolver = objectModel
239: .get(ProcessingNode.OBJECT_SOURCE_RESOLVER);
240: final Redirector oldRedirector = context.getRedirector();
241:
242: // Build a redirector
243: TreeProcessorRedirector redirector = new TreeProcessorRedirector(
244: environment, context);
245: setupLogger(redirector);
246: context.setRedirector(redirector);
247:
248: objectModel.put(ProcessingNode.OBJECT_SOURCE_RESOLVER,
249: environment);
250: boolean success = false;
251: try {
252: success = this .rootNode.invoke(environment, context);
253:
254: return success;
255:
256: } finally {
257: CocoonComponentManager.leaveEnvironment(success);
258: // Restore old redirector and resolver
259: context.setRedirector(oldRedirector);
260: objectModel.put(ProcessingNode.OBJECT_SOURCE_RESOLVER,
261: oldResolver);
262: }
263:
264: } finally {
265:
266: // Decrement the concurrent request count
267: synchronized (this ) {
268: requestCount--;
269: }
270:
271: if (requestCount < 0) {
272: // Marked for disposal and no more concurrent requests.
273: dispose();
274: }
275: }
276: }
277:
278: private boolean handleCocoonRedirect(String uri,
279: Environment environment, InvokeContext context)
280: throws Exception {
281:
282: // Build an environment wrapper
283: // If the current env is a facade, change the delegate and continue processing the facade, since
284: // we may have other redirects that will in turn also change the facade delegate
285:
286: MutableEnvironmentFacade facade = environment instanceof MutableEnvironmentFacade ? ((MutableEnvironmentFacade) environment)
287: : null;
288:
289: if (facade != null) {
290: // Consider the facade delegate (the real environment)
291: environment = facade.getDelegate();
292: }
293:
294: // test if this is a call from flow
295: boolean isRedirect = (environment.getObjectModel().remove(
296: "cocoon:forward") == null);
297: Environment newEnv = new ForwardEnvironmentWrapper(environment,
298: this .sitemapComponentManager, uri, getLogger());
299: if (isRedirect) {
300: ((ForwardEnvironmentWrapper) newEnv)
301: .setInternalRedirect(true);
302: }
303:
304: if (facade != null) {
305: // Change the facade delegate
306: facade.setDelegate((EnvironmentWrapper) newEnv);
307: newEnv = facade;
308: }
309:
310: // Get the processor that should process this request
311: // (see https://issues.apache.org/jira/browse/COCOON-1990).
312: ConcreteTreeProcessor processor = this ;
313: if (uri.startsWith("cocoon://"))
314: processor = ((TreeProcessor) getRootProcessor()).concreteProcessor;
315:
316: // Process the redirect
317: // No more reset since with TreeProcessorRedirector, we need to pop values from the redirect location
318: // context.reset();
319: // The following is a fix for bug #26854 and #26571
320: final boolean result = processor.process(newEnv, context);
321: if (facade != null) {
322: newEnv = facade.getDelegate();
323: }
324: if (((ForwardEnvironmentWrapper) newEnv).getRedirectURL() != null) {
325: environment.redirect(false,
326: ((ForwardEnvironmentWrapper) newEnv)
327: .getRedirectURL());
328: }
329: return result;
330: }
331:
332: /* (non-Javadoc)
333: * @see org.apache.avalon.framework.activity.Disposable#dispose()
334: */
335: public void dispose() {
336: if (this .disposableNodes != null) {
337: // we must dispose the nodes in reverse order
338: // otherwise selector nodes are freed before the components node
339: for (int i = this .disposableNodes.size() - 1; i > -1; i--) {
340: ((Disposable) disposableNodes.get(i)).dispose();
341: }
342: this .disposableNodes = null;
343: }
344:
345: // Ensure it won't be used anymore
346: this .rootNode = null;
347: }
348:
349: public String toString() {
350: return "ConcreteTreeProcessor - "
351: + wrappingProcessor.source.getURI();
352: }
353:
354: private class TreeProcessorRedirector extends ForwardRedirector {
355:
356: private InvokeContext context;
357:
358: public TreeProcessorRedirector(Environment env,
359: InvokeContext context) {
360: super (env);
361: this .context = context;
362: }
363:
364: protected void cocoonRedirect(String uri) throws IOException,
365: ProcessingException {
366: try {
367: ConcreteTreeProcessor.this .handleCocoonRedirect(uri,
368: this .env, this .context);
369: } catch (IOException ioe) {
370: throw ioe;
371: } catch (ProcessingException pe) {
372: throw pe;
373: } catch (RuntimeException re) {
374: throw re;
375: } catch (Exception ex) {
376: throw new ProcessingException(ex);
377: }
378: }
379: }
380:
381: /**
382: * Local extension of EnvironmentWrapper to propagate otherwise blocked
383: * methods to the actual environment.
384: */
385: private static final class ForwardEnvironmentWrapper extends
386: EnvironmentWrapper {
387:
388: public ForwardEnvironmentWrapper(Environment env,
389: ComponentManager manager, String uri, Logger logger)
390: throws MalformedURLException {
391: super (env, manager, uri, logger, false);
392: }
393:
394: public void setStatus(int statusCode) {
395: environment.setStatus(statusCode);
396: }
397:
398: public void setContentLength(int length) {
399: environment.setContentLength(length);
400: }
401:
402: public void setContentType(String contentType) {
403: environment.setContentType(contentType);
404: }
405:
406: public String getContentType() {
407: return environment.getContentType();
408: }
409:
410: public boolean isResponseModified(long lastModified) {
411: return environment.isResponseModified(lastModified);
412: }
413:
414: public void setResponseIsNotModified() {
415: environment.setResponseIsNotModified();
416: }
417: }
418: }
|