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.util.Map;
20
21 import org.apache.avalon.excalibur.component.RoleManageable;
22 import org.apache.avalon.excalibur.component.RoleManager;
23 import org.apache.avalon.framework.activity.Disposable;
24 import org.apache.avalon.framework.component.Component;
25 import org.apache.avalon.framework.component.ComponentException;
26 import org.apache.avalon.framework.component.ComponentManager;
27 import org.apache.avalon.framework.component.Composable;
28 import org.apache.avalon.framework.component.Recomposable;
29 import org.apache.avalon.framework.configuration.Configurable;
30 import org.apache.avalon.framework.configuration.Configuration;
31 import org.apache.avalon.framework.configuration.ConfigurationException;
32 import org.apache.avalon.framework.configuration.SAXConfigurationHandler;
33 import org.apache.avalon.framework.container.ContainerUtil;
34 import org.apache.avalon.framework.context.Context;
35 import org.apache.avalon.framework.context.ContextException;
36 import org.apache.avalon.framework.context.Contextualizable;
37 import org.apache.avalon.framework.logger.AbstractLogEnabled;
38 import org.apache.avalon.framework.thread.ThreadSafe;
39 import org.apache.cocoon.Processor;
40 import org.apache.cocoon.util.Settings;
41 import org.apache.cocoon.util.SettingsHelper;
42 import org.apache.cocoon.components.CocoonComponentManager;
43 import org.apache.cocoon.components.ExtendedComponentSelector;
44 import org.apache.cocoon.components.LifecycleHelper;
45 import org.apache.cocoon.components.PropertyAwareSAXConfigurationHandler;
46 import org.apache.cocoon.components.pipeline.ProcessingPipeline;
47 import org.apache.cocoon.components.source.SourceUtil;
48 import org.apache.cocoon.components.source.impl.DelayedRefreshSourceWrapper;
49 import org.apache.cocoon.environment.Environment;
50 import org.apache.excalibur.source.Source;
51 import org.apache.excalibur.source.SourceResolver;
52
53 /**
54 * Interpreted tree-traversal implementation of a pipeline assembly language.
55 *
56 * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
57 * @version CVS $Id: TreeProcessor.java 540711 2007-05-22 19:36:07Z cziegeler $
58 */
59
60 public class TreeProcessor
61 extends AbstractLogEnabled
62 implements ThreadSafe,
63 Processor,
64 Composable,
65 Configurable,
66 RoleManageable,
67 Contextualizable,
68 Disposable {
69
70 public static final String COCOON_REDIRECT_ATTR = "sitemap:cocoon-redirect";
71
72 private static final String XCONF_URL =
73 "resource://org/apache/cocoon/components/treeprocessor/treeprocessor-builtins.xml";
74
75 /** The parent TreeProcessor, if any */
76 protected TreeProcessor parent;
77
78 /** The context */
79 protected Context context;
80
81 /** The component manager */
82 protected ComponentManager manager;
83
84 /** The role manager */
85 protected RoleManager roleManager;
86
87 /** Selector of TreeBuilders, the hint is the language name */
88 protected ExtendedComponentSelector builderSelector;
89
90 /** Last modification time */
91 protected long lastModified = 0;
92
93 /** The source of the tree definition */
94 protected DelayedRefreshSourceWrapper source;
95
96 /** Delay for <code>sourceLastModified</code>. */
97 protected long lastModifiedDelay;
98
99 /** The current language configuration */
100 protected Configuration currentLanguage;
101
102 /** Check for reload? */
103 protected boolean checkReload;
104
105 /** The source resolver */
106 protected SourceResolver resolver;
107
108 /** The actual processor (package-private as needs to be accessed by ConcreteTreeProcessor) */
109 ConcreteTreeProcessor concreteProcessor;
110
111 /**
112 * Create a TreeProcessor.
113 */
114 public TreeProcessor() {
115 this.checkReload = true;
116 this.lastModifiedDelay = 1000;
117 }
118
119 /**
120 * Create a child processor for a given language
121 */
122 protected TreeProcessor(TreeProcessor parent, ComponentManager manager) {
123 this.parent = parent;
124
125 // Copy all that can be copied from the parent
126 this.enableLogging(parent.getLogger());
127 this.context = parent.context;
128 this.roleManager = parent.roleManager;
129 this.builderSelector = parent.builderSelector;
130 this.checkReload = parent.checkReload;
131 this.lastModifiedDelay = parent.lastModifiedDelay;
132
133 // We have our own CM
134 this.manager = manager;
135 }
136
137 /**
138 * Create a new child of this processor (used for mounting submaps).
139 *
140 * @param manager the component manager to be used by the child processor.
141 * @return a new child processor.
142 */
143 public TreeProcessor createChildProcessor(ComponentManager manager,
144 String actualSource,
145 boolean checkReload)
146 throws Exception {
147
148 // Note: lifecycle methods aren't called, since this constructors copies all
149 // that can be copied from the parent (see above)
150 TreeProcessor child = new TreeProcessor(this, manager);
151 child.checkReload = checkReload;
152 child.resolver = (SourceResolver)manager.lookup(SourceResolver.ROLE);
153 child.source = new DelayedRefreshSourceWrapper(child.resolver.resolveURI(actualSource), this.lastModifiedDelay);
154
155 return child;
156 }
157
158 /* (non-Javadoc)
159 * @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context)
160 */
161 public void contextualize(Context context) throws ContextException {
162 this.context = context;
163 }
164
165 /* (non-Javadoc)
166 * @see org.apache.avalon.framework.component.Composable#compose(org.apache.avalon.framework.component.ComponentManager)
167 */
168 public void compose(ComponentManager manager) throws ComponentException {
169 this.manager = manager;
170 this.resolver = (SourceResolver)this.manager.lookup(SourceResolver.ROLE);
171 }
172
173 /* (non-Javadoc)
174 * @see org.apache.avalon.excalibur.component.RoleManageable#setRoleManager(org.apache.avalon.excalibur.component.RoleManager)
175 */
176 public void setRoleManager(RoleManager rm) {
177 this.roleManager = rm;
178 }
179
180
181 /*
182 <processor>
183 <reload delay="10"/>
184 <language>...</language>
185 </processor>
186 */
187 public void configure(Configuration config)
188 throws ConfigurationException {
189
190 this.checkReload = config.getAttributeAsBoolean("check-reload", true);
191
192 // Obtain the configuration file, or use the XCONF_URL if none
193 // is defined
194 String xconfURL = config.getAttribute("config", XCONF_URL);
195
196 // Reload check delay. Default is 1 second.
197 this.lastModifiedDelay = config.getChild("reload").getAttributeAsLong("delay", 1000L);
198
199 String fileName = config.getAttribute("file", "sitemap.xmap");
200
201 try {
202 this.source = new DelayedRefreshSourceWrapper(this.resolver.resolveURI(fileName), this.lastModifiedDelay);
203 } catch (Exception e) {
204 throw new ConfigurationException("Cannot resolve " + fileName, e);
205 }
206
207 // Read the builtin languages definition file
208 Configuration builtin;
209 try {
210 Source source = this.resolver.resolveURI(xconfURL);
211 try {
212 Settings settings = SettingsHelper.getSettings(this.context);
213 SAXConfigurationHandler handler = new PropertyAwareSAXConfigurationHandler(settings, this.getLogger());
214 SourceUtil.toSAX( this.manager, source, null, handler);
215 builtin = handler.getConfiguration();
216 } finally {
217 this.resolver.release(source);
218 }
219 } catch(Exception e) {
220 String msg = "Error while reading " + xconfURL + ": " + e.getMessage();
221 throw new ConfigurationException(msg, e);
222 }
223
224 // Create a selector for tree builders of all languages
225 this.builderSelector = new ExtendedComponentSelector(Thread.currentThread().getContextClassLoader());
226 try {
227 LifecycleHelper.setupComponent(this.builderSelector,
228 this.getLogger(),
229 this.context,
230 this.manager,
231 this.roleManager,
232 builtin);
233 } catch (ConfigurationException e) {
234 throw e;
235 } catch (Exception e) {
236 throw new ConfigurationException("Could not setup builder selector", e);
237 }
238 }
239
240 /**
241 * Process the given <code>Environment</code> producing the output.
242 * @return If the processing is successfull <code>true</code> is returned.
243 * If not match is found in the sitemap <code>false</code>
244 * is returned.
245 * @throws org.apache.cocoon.ResourceNotFoundException If a sitemap component tries
246 * to access a resource which can not
247 * be found, e.g. the generator
248 * ConnectionResetException If the connection was reset
249 */
250 public boolean process(Environment environment) throws Exception {
251
252 this.setupConcreteProcessor(environment);
253
254 return this.concreteProcessor.process(environment);
255 }
256
257 /**
258 * Process the given <code>Environment</code> to assemble
259 * a <code>ProcessingPipeline</code>.
260 * @since 2.1
261 */
262 public ProcessingPipeline buildPipeline(Environment environment)
263 throws Exception {
264
265 this.setupConcreteProcessor(environment);
266
267 return this.concreteProcessor.buildPipeline(environment);
268 }
269
270 /* (non-Javadoc)
271 * @see org.apache.cocoon.Processor#getRootProcessor()
272 */
273 public Processor getRootProcessor() {
274 TreeProcessor result = this;
275 while(result.parent != null) {
276 result = result.parent;
277 }
278
279 return result;
280 }
281
282 /**
283 * Set the sitemap component configurations
284 */
285 public void setComponentConfigurations(Configuration componentConfigurations) {
286 this.concreteProcessor.setComponentConfigurations(componentConfigurations);
287 }
288
289 /* (non-Javadoc)
290 * @see org.apache.cocoon.Processor#getComponentConfigurations()
291 */
292 public Map getComponentConfigurations() {
293 return this.concreteProcessor.getComponentConfigurations();
294 }
295
296 private void setupConcreteProcessor(Environment env) throws Exception {
297
298 if (this.parent == null) {
299 // Ensure root sitemap uses the correct context, even if not located in the webapp context
300 env.changeContext("", this.source.getURI());
301 }
302
303 // check for sitemap changes
304 if (this.concreteProcessor == null ||
305 (this.checkReload && this.source.getLastModified() != this.lastModified)) {
306 this.buildConcreteProcessor(env);
307 }
308 }
309
310 private synchronized void buildConcreteProcessor(Environment env) throws Exception {
311
312 // Now that we entered the synchronized area, recheck what's already
313 // been checked in process().
314 if (this.concreteProcessor != null && this.source.getLastModified() == this.lastModified) {
315 // Nothing changed
316 return;
317 }
318
319 long startTime = System.currentTimeMillis();
320
321 // Dispose the old processor, if any
322 if (this.concreteProcessor != null) {
323 this.concreteProcessor.markForDisposal();
324 }
325
326 // Get a builder
327 TreeBuilder builder = (TreeBuilder)this.builderSelector.select("sitemap");
328 ConcreteTreeProcessor newProcessor = new ConcreteTreeProcessor(this);
329 long newLastModified;
330 this.setupLogger(newProcessor);
331 //FIXME (SW): why do we need to enterProcessor here?
332 CocoonComponentManager.enterEnvironment(env, this.manager, this);
333 try {
334 if (builder instanceof Recomposable) {
335 ((Recomposable)builder).recompose(this.manager);
336 }
337 builder.setProcessor(newProcessor);
338
339 newLastModified = this.source.getLastModified();
340
341 ProcessingNode root = builder.build(this.source);
342
343 newProcessor.setProcessorData(builder.getSitemapComponentManager(), root, builder.getDisposableNodes());
344 } finally {
345 CocoonComponentManager.leaveEnvironment();
346 this.builderSelector.release(builder);
347 }
348
349 if (this.getLogger().isDebugEnabled()) {
350 double time = (this.lastModified - startTime) / 1000.0;
351 this.getLogger().debug("TreeProcessor built in " + time + " secs from " + this.source.getURI());
352 }
353
354 // Switch to the new processor (ensure it's never temporarily null)
355 this.concreteProcessor = newProcessor;
356 this.lastModified = newLastModified;
357 }
358
359 /* (non-Javadoc)
360 * @see org.apache.avalon.framework.activity.Disposable#dispose()
361 */
362 public void dispose() {
363 // Dispose the concrete processor. No need to check for existing requests, as there
364 // are none when a TreeProcessor is disposed.
365 ContainerUtil.dispose(this.concreteProcessor);
366 this.concreteProcessor = null;
367
368 if (this.manager != null) {
369 if (this.source != null) {
370 this.resolver.release(this.source.getSource());
371 this.source = null;
372 }
373
374 if (this.parent == null) {
375 // root processor : dispose the builder selector
376 this.builderSelector.dispose();
377 this.builderSelector = null;
378 }
379
380 // Release resolver looked up in compose()
381 this.manager.release((Component)this.resolver);
382 this.resolver = null;
383
384 this.manager = null;
385 }
386 }
387
388 public String toString() {
389 return "TreeProcessor - " + (this.source == null ? "[unknown location]" : this.source.getURI());
390 }
391 }