1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.cocoon.components.treeprocessor;
18
19 import java.io.IOException;
20 import java.net.MalformedURLException;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25
26 import org.apache.avalon.framework.activity.Disposable;
27 import org.apache.avalon.framework.component.ComponentManager;
28 import org.apache.avalon.framework.configuration.Configuration;
29 import org.apache.avalon.framework.logger.AbstractLogEnabled;
30 import org.apache.avalon.framework.logger.Logger;
31 import org.apache.cocoon.ProcessingException;
32 import org.apache.cocoon.Processor;
33 import org.apache.cocoon.components.ChainedConfiguration;
34 import org.apache.cocoon.components.CocoonComponentManager;
35 import org.apache.cocoon.components.pipeline.ProcessingPipeline;
36 import org.apache.cocoon.environment.Environment;
37 import org.apache.cocoon.environment.ForwardRedirector;
38 import org.apache.cocoon.environment.Redirector;
39 import org.apache.cocoon.environment.wrapper.EnvironmentWrapper;
40 import org.apache.cocoon.environment.wrapper.MutableEnvironmentFacade;
41
42 /**
43 * The concrete implementation of {@link Processor}, containing the evaluation tree and associated
44 * data such as component manager.
45 *
46 * @version $Id: ConcreteTreeProcessor.java 602257 2007-12-07 22:43:53Z anathaniel $
47 */
48 public class ConcreteTreeProcessor extends AbstractLogEnabled
49 implements Processor, Disposable {
50
51 /** The processor that wraps us */
52 private TreeProcessor wrappingProcessor;
53
54 /** Component manager defined by the <map:components> of this sitemap */
55 ComponentManager sitemapComponentManager;
56
57 /** Processing nodes that need to be disposed with this processor */
58 private List disposableNodes;
59
60 /** Root node of the processing tree */
61 private ProcessingNode rootNode;
62
63 private Map sitemapComponentConfigurations;
64
65 private Configuration componentConfigurations;
66
67 /** Number of simultaneous uses of this processor (either by concurrent request or by internal requests) */
68 private int requestCount;
69
70 /** Builds a concrete processig, given the wrapping processor */
71 public ConcreteTreeProcessor(TreeProcessor wrappingProcessor) {
72 this.wrappingProcessor = wrappingProcessor;
73 }
74
75 /** Set the processor data, result of the treebuilder job */
76 public void setProcessorData(ComponentManager manager, ProcessingNode rootNode, List disposableNodes) {
77 if (this.sitemapComponentManager != null) {
78 throw new IllegalStateException("setProcessorData() can only be called once");
79 }
80
81 this.sitemapComponentManager = manager;
82 this.rootNode = rootNode;
83 this.disposableNodes = disposableNodes;
84 }
85
86 /** Set the sitemap component configurations (called as part of the tree building process) */
87 public void setComponentConfigurations(Configuration componentConfigurations) {
88 this.componentConfigurations = componentConfigurations;
89 this.sitemapComponentConfigurations = null;
90 }
91
92 /**
93 * Get the sitemap component configurations
94 * @since 2.1
95 */
96 public Map getComponentConfigurations() {
97 // do we have the sitemap configurations prepared for this processor?
98 if ( null == this.sitemapComponentConfigurations ) {
99
100 synchronized (this) {
101
102 if ( this.sitemapComponentConfigurations == null ) {
103 // do we have configurations?
104 final Configuration[] childs = (this.componentConfigurations == null
105 ? null
106 : this.componentConfigurations.getChildren());
107
108 if ( null != childs ) {
109
110 if ( null == this.wrappingProcessor.parent ) {
111 this.sitemapComponentConfigurations = new HashMap(12);
112 } else {
113 // copy all configurations from parent
114 this.sitemapComponentConfigurations = new HashMap(
115 this.wrappingProcessor.parent.getComponentConfigurations());
116 }
117
118 // and now check for new configurations
119 for(int m = 0; m < childs.length; m++) {
120
121 final String r = this.wrappingProcessor.roleManager.getRoleForName(childs[m].getName());
122 this.sitemapComponentConfigurations.put(r, new ChainedConfiguration(childs[m],
123 (ChainedConfiguration)this.sitemapComponentConfigurations.get(r)));
124 }
125 } else {
126 // we don't have configurations
127 if ( null == this.wrappingProcessor.parent ) {
128 this.sitemapComponentConfigurations = Collections.EMPTY_MAP;
129 } else {
130 // use configuration from parent
131 this.sitemapComponentConfigurations = this.wrappingProcessor.parent.getComponentConfigurations();
132 }
133 }
134 }
135 }
136 }
137 return this.sitemapComponentConfigurations; }
138
139 /**
140 * Mark this processor as needing to be disposed. Actual call to {@link #dispose()} will occur when
141 * all request processings on this processor will be terminated.
142 */
143 public void markForDisposal() {
144 // Decrement the request count (negative number means dispose)
145 synchronized(this) {
146 this.requestCount--;
147 }
148
149 if (this.requestCount < 0) {
150 // No more users : dispose right now
151 dispose();
152 }
153 }
154
155 public TreeProcessor getWrappingProcessor() {
156 return this.wrappingProcessor;
157 }
158
159 public Processor getRootProcessor() {
160 return this.wrappingProcessor.getRootProcessor();
161 }
162
163 /**
164 * Process the given <code>Environment</code> producing the output.
165 * @return If the processing is successfull <code>true</code> is returned.
166 * If not match is found in the sitemap <code>false</code>
167 * is returned.
168 * @throws org.apache.cocoon.ResourceNotFoundException If a sitemap component tries
169 * to access a resource which can not
170 * be found, e.g. the generator
171 * ConnectionResetException If the connection was reset
172 */
173 public boolean process(Environment environment) throws Exception {
174 InvokeContext context = new InvokeContext();
175 context.enableLogging(getLogger());
176 try {
177 return process(environment, context);
178 } finally {
179 context.dispose();
180 }
181 }
182
183 /**
184 * Process the given <code>Environment</code> to assemble
185 * a <code>ProcessingPipeline</code>.
186 * @since 2.1
187 */
188 public ProcessingPipeline buildPipeline(Environment environment)
189 throws Exception {
190 InvokeContext context = new InvokeContext(true);
191 context.enableLogging(getLogger());
192 try {
193 if (process(environment, context)) {
194 return context.getProcessingPipeline();
195 } else {
196 return null;
197 }
198 } finally {
199 context.dispose();
200 }
201 }
202
203 /**
204 * Do the actual processing, be it producing the response or just building the pipeline
205 * @param environment
206 * @param context
207 * @return true if the pipeline was successfully built, false otherwise.
208 * @throws Exception
209 */
210 protected boolean process(Environment environment, InvokeContext context)
211 throws Exception {
212
213 // Increment the concurrent requests count
214 synchronized(this) {
215 requestCount++;
216 }
217
218 try {
219 // and now process
220 CocoonComponentManager.enterEnvironment(environment, this.sitemapComponentManager, this);
221
222 Map objectModel = environment.getObjectModel();
223
224 Object oldResolver = objectModel.get(ProcessingNode.OBJECT_SOURCE_RESOLVER);
225 final Redirector oldRedirector = context.getRedirector();
226
227 // Build a redirector
228 TreeProcessorRedirector redirector = new TreeProcessorRedirector(environment, context);
229 setupLogger(redirector);
230 context.setRedirector(redirector);
231
232 objectModel.put(ProcessingNode.OBJECT_SOURCE_RESOLVER, environment);
233 boolean success = false;
234 try {
235 success = this.rootNode.invoke(environment, context);
236
237 return success;
238
239 } finally {
240 CocoonComponentManager.leaveEnvironment(success);
241 // Restore old redirector and resolver
242 context.setRedirector(oldRedirector);
243 objectModel.put(ProcessingNode.OBJECT_SOURCE_RESOLVER, oldResolver);
244 }
245
246 } finally {
247
248 // Decrement the concurrent request count
249 synchronized(this) {
250 requestCount--;
251 }
252
253 if(requestCount < 0) {
254 // Marked for disposal and no more concurrent requests.
255 dispose();
256 }
257 }
258 }
259
260 private boolean handleCocoonRedirect(String uri, Environment environment, InvokeContext context) throws Exception {
261
262 // Build an environment wrapper
263 // If the current env is a facade, change the delegate and continue processing the facade, since
264 // we may have other redirects that will in turn also change the facade delegate
265
266 MutableEnvironmentFacade facade = environment instanceof MutableEnvironmentFacade ?
267 ((MutableEnvironmentFacade)environment) : null;
268
269 if (facade != null) {
270 // Consider the facade delegate (the real environment)
271 environment = facade.getDelegate();
272 }
273
274 // test if this is a call from flow
275 boolean isRedirect = (environment.getObjectModel().remove("cocoon:forward") == null);
276 Environment newEnv = new ForwardEnvironmentWrapper(environment, this.sitemapComponentManager, uri, getLogger());
277 if ( isRedirect ) {
278 ((ForwardEnvironmentWrapper)newEnv).setInternalRedirect(true);
279 }
280
281 if (facade != null) {
282 // Change the facade delegate
283 facade.setDelegate((EnvironmentWrapper)newEnv);
284 newEnv = facade;
285 }
286
287 // Get the processor that should process this request
288 // (see https://issues.apache.org/jira/browse/COCOON-1990).
289 ConcreteTreeProcessor processor = this;
290 if (uri.startsWith("cocoon://"))
291 processor = ((TreeProcessor)getRootProcessor()).concreteProcessor;
292
293 // Process the redirect
294 // No more reset since with TreeProcessorRedirector, we need to pop values from the redirect location
295 // context.reset();
296 // The following is a fix for bug #26854 and #26571
297 final boolean result = processor.process(newEnv, context);
298 if ( facade != null ) {
299 newEnv = facade.getDelegate();
300 }
301 if ( ((ForwardEnvironmentWrapper)newEnv).getRedirectURL() != null ) {
302 environment.redirect( false, ((ForwardEnvironmentWrapper)newEnv).getRedirectURL() );
303 }
304 return result;
305 }
306
307 /* (non-Javadoc)
308 * @see org.apache.avalon.framework.activity.Disposable#dispose()
309 */
310 public void dispose() {
311 if (this.disposableNodes != null) {
312 // we must dispose the nodes in reverse order
313 // otherwise selector nodes are freed before the components node
314 for(int i=this.disposableNodes.size()-1; i>-1; i--) {
315 ((Disposable)disposableNodes.get(i)).dispose();
316 }
317 this.disposableNodes = null;
318 }
319
320 // Ensure it won't be used anymore
321 this.rootNode = null;
322 }
323
324 public String toString() {
325 return "ConcreteTreeProcessor - " + wrappingProcessor.source.getURI();
326 }
327
328 private class TreeProcessorRedirector extends ForwardRedirector {
329
330 private InvokeContext context;
331 public TreeProcessorRedirector(Environment env, InvokeContext context) {
332 super(env);
333 this.context = context;
334 }
335
336 protected void cocoonRedirect(String uri) throws IOException, ProcessingException {
337 try {
338 ConcreteTreeProcessor.this.handleCocoonRedirect(uri, this.env, this.context);
339 } catch(IOException ioe) {
340 throw ioe;
341 } catch(ProcessingException pe) {
342 throw pe;
343 } catch(RuntimeException re) {
344 throw re;
345 } catch(Exception ex) {
346 throw new ProcessingException(ex);
347 }
348 }
349 }
350
351 /**
352 * Local extension of EnvironmentWrapper to propagate otherwise blocked
353 * methods to the actual environment.
354 */
355 private static final class ForwardEnvironmentWrapper extends EnvironmentWrapper {
356
357 public ForwardEnvironmentWrapper(Environment env,
358 ComponentManager manager, String uri, Logger logger)
359 throws MalformedURLException {
360 super(env, manager, uri, logger, false);
361 }
362
363 public void setStatus(int statusCode) {
364 environment.setStatus(statusCode);
365 }
366
367 public void setContentLength(int length) {
368 environment.setContentLength(length);
369 }
370
371 public void setContentType(String contentType) {
372 environment.setContentType(contentType);
373 }
374
375 public String getContentType() {
376 return environment.getContentType();
377 }
378
379 public boolean isResponseModified(long lastModified) {
380 return environment.isResponseModified(lastModified);
381 }
382
383 public void setResponseIsNotModified() {
384 environment.setResponseIsNotModified();
385 }
386 }
387 }