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.util.Map;
020:
021: import org.apache.avalon.excalibur.component.RoleManageable;
022: import org.apache.avalon.excalibur.component.RoleManager;
023: import org.apache.avalon.framework.activity.Disposable;
024: import org.apache.avalon.framework.component.Component;
025: import org.apache.avalon.framework.component.ComponentException;
026: import org.apache.avalon.framework.component.ComponentManager;
027: import org.apache.avalon.framework.component.Composable;
028: import org.apache.avalon.framework.component.Recomposable;
029: import org.apache.avalon.framework.configuration.Configurable;
030: import org.apache.avalon.framework.configuration.Configuration;
031: import org.apache.avalon.framework.configuration.ConfigurationException;
032: import org.apache.avalon.framework.configuration.SAXConfigurationHandler;
033: import org.apache.avalon.framework.container.ContainerUtil;
034: import org.apache.avalon.framework.context.Context;
035: import org.apache.avalon.framework.context.ContextException;
036: import org.apache.avalon.framework.context.Contextualizable;
037: import org.apache.avalon.framework.logger.AbstractLogEnabled;
038: import org.apache.avalon.framework.thread.ThreadSafe;
039: import org.apache.cocoon.Processor;
040: import org.apache.cocoon.util.Settings;
041: import org.apache.cocoon.util.SettingsHelper;
042: import org.apache.cocoon.components.CocoonComponentManager;
043: import org.apache.cocoon.components.ExtendedComponentSelector;
044: import org.apache.cocoon.components.LifecycleHelper;
045: import org.apache.cocoon.components.PropertyAwareSAXConfigurationHandler;
046: import org.apache.cocoon.components.pipeline.ProcessingPipeline;
047: import org.apache.cocoon.components.source.SourceUtil;
048: import org.apache.cocoon.components.source.impl.DelayedRefreshSourceWrapper;
049: import org.apache.cocoon.environment.Environment;
050: import org.apache.excalibur.source.Source;
051: import org.apache.excalibur.source.SourceResolver;
052:
053: /**
054: * Interpreted tree-traversal implementation of a pipeline assembly language.
055: *
056: * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
057: * @version CVS $Id: TreeProcessor.java 540711 2007-05-22 19:36:07Z cziegeler $
058: */
059:
060: public class TreeProcessor extends AbstractLogEnabled implements
061: ThreadSafe, Processor, Composable, Configurable,
062: RoleManageable, Contextualizable, Disposable {
063:
064: public static final String COCOON_REDIRECT_ATTR = "sitemap:cocoon-redirect";
065:
066: private static final String XCONF_URL = "resource://org/apache/cocoon/components/treeprocessor/treeprocessor-builtins.xml";
067:
068: /** The parent TreeProcessor, if any */
069: protected TreeProcessor parent;
070:
071: /** The context */
072: protected Context context;
073:
074: /** The component manager */
075: protected ComponentManager manager;
076:
077: /** The role manager */
078: protected RoleManager roleManager;
079:
080: /** Selector of TreeBuilders, the hint is the language name */
081: protected ExtendedComponentSelector builderSelector;
082:
083: /** Last modification time */
084: protected long lastModified = 0;
085:
086: /** The source of the tree definition */
087: protected DelayedRefreshSourceWrapper source;
088:
089: /** Delay for <code>sourceLastModified</code>. */
090: protected long lastModifiedDelay;
091:
092: /** The current language configuration */
093: protected Configuration currentLanguage;
094:
095: /** Check for reload? */
096: protected boolean checkReload;
097:
098: /** The source resolver */
099: protected SourceResolver resolver;
100:
101: /** The actual processor (package-private as needs to be accessed by ConcreteTreeProcessor) */
102: ConcreteTreeProcessor concreteProcessor;
103:
104: /**
105: * Create a TreeProcessor.
106: */
107: public TreeProcessor() {
108: this .checkReload = true;
109: this .lastModifiedDelay = 1000;
110: }
111:
112: /**
113: * Create a child processor for a given language
114: */
115: protected TreeProcessor(TreeProcessor parent,
116: ComponentManager manager) {
117: this .parent = parent;
118:
119: // Copy all that can be copied from the parent
120: this .enableLogging(parent.getLogger());
121: this .context = parent.context;
122: this .roleManager = parent.roleManager;
123: this .builderSelector = parent.builderSelector;
124: this .checkReload = parent.checkReload;
125: this .lastModifiedDelay = parent.lastModifiedDelay;
126:
127: // We have our own CM
128: this .manager = manager;
129: }
130:
131: /**
132: * Create a new child of this processor (used for mounting submaps).
133: *
134: * @param manager the component manager to be used by the child processor.
135: * @return a new child processor.
136: */
137: public TreeProcessor createChildProcessor(ComponentManager manager,
138: String actualSource, boolean checkReload) throws Exception {
139:
140: // Note: lifecycle methods aren't called, since this constructors copies all
141: // that can be copied from the parent (see above)
142: TreeProcessor child = new TreeProcessor(this , manager);
143: child.checkReload = checkReload;
144: child.resolver = (SourceResolver) manager
145: .lookup(SourceResolver.ROLE);
146: child.source = new DelayedRefreshSourceWrapper(child.resolver
147: .resolveURI(actualSource), this .lastModifiedDelay);
148:
149: return child;
150: }
151:
152: /* (non-Javadoc)
153: * @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context)
154: */
155: public void contextualize(Context context) throws ContextException {
156: this .context = context;
157: }
158:
159: /* (non-Javadoc)
160: * @see org.apache.avalon.framework.component.Composable#compose(org.apache.avalon.framework.component.ComponentManager)
161: */
162: public void compose(ComponentManager manager)
163: throws ComponentException {
164: this .manager = manager;
165: this .resolver = (SourceResolver) this .manager
166: .lookup(SourceResolver.ROLE);
167: }
168:
169: /* (non-Javadoc)
170: * @see org.apache.avalon.excalibur.component.RoleManageable#setRoleManager(org.apache.avalon.excalibur.component.RoleManager)
171: */
172: public void setRoleManager(RoleManager rm) {
173: this .roleManager = rm;
174: }
175:
176: /*
177: <processor>
178: <reload delay="10"/>
179: <language>...</language>
180: </processor>
181: */
182: public void configure(Configuration config)
183: throws ConfigurationException {
184:
185: this .checkReload = config.getAttributeAsBoolean("check-reload",
186: true);
187:
188: // Obtain the configuration file, or use the XCONF_URL if none
189: // is defined
190: String xconfURL = config.getAttribute("config", XCONF_URL);
191:
192: // Reload check delay. Default is 1 second.
193: this .lastModifiedDelay = config.getChild("reload")
194: .getAttributeAsLong("delay", 1000L);
195:
196: String fileName = config.getAttribute("file", "sitemap.xmap");
197:
198: try {
199: this .source = new DelayedRefreshSourceWrapper(this .resolver
200: .resolveURI(fileName), this .lastModifiedDelay);
201: } catch (Exception e) {
202: throw new ConfigurationException("Cannot resolve "
203: + fileName, e);
204: }
205:
206: // Read the builtin languages definition file
207: Configuration builtin;
208: try {
209: Source source = this .resolver.resolveURI(xconfURL);
210: try {
211: Settings settings = SettingsHelper
212: .getSettings(this .context);
213: SAXConfigurationHandler handler = new PropertyAwareSAXConfigurationHandler(
214: settings, this .getLogger());
215: SourceUtil.toSAX(this .manager, source, null, handler);
216: builtin = handler.getConfiguration();
217: } finally {
218: this .resolver.release(source);
219: }
220: } catch (Exception e) {
221: String msg = "Error while reading " + xconfURL + ": "
222: + e.getMessage();
223: throw new ConfigurationException(msg, e);
224: }
225:
226: // Create a selector for tree builders of all languages
227: this .builderSelector = new ExtendedComponentSelector(Thread
228: .currentThread().getContextClassLoader());
229: try {
230: LifecycleHelper.setupComponent(this .builderSelector, this
231: .getLogger(), this .context, this .manager,
232: this .roleManager, builtin);
233: } catch (ConfigurationException e) {
234: throw e;
235: } catch (Exception e) {
236: throw new ConfigurationException(
237: "Could not setup builder selector", e);
238: }
239: }
240:
241: /**
242: * Process the given <code>Environment</code> producing the output.
243: * @return If the processing is successfull <code>true</code> is returned.
244: * If not match is found in the sitemap <code>false</code>
245: * is returned.
246: * @throws org.apache.cocoon.ResourceNotFoundException If a sitemap component tries
247: * to access a resource which can not
248: * be found, e.g. the generator
249: * ConnectionResetException If the connection was reset
250: */
251: public boolean process(Environment environment) throws Exception {
252:
253: this .setupConcreteProcessor(environment);
254:
255: return this .concreteProcessor.process(environment);
256: }
257:
258: /**
259: * Process the given <code>Environment</code> to assemble
260: * a <code>ProcessingPipeline</code>.
261: * @since 2.1
262: */
263: public ProcessingPipeline buildPipeline(Environment environment)
264: throws Exception {
265:
266: this .setupConcreteProcessor(environment);
267:
268: return this .concreteProcessor.buildPipeline(environment);
269: }
270:
271: /* (non-Javadoc)
272: * @see org.apache.cocoon.Processor#getRootProcessor()
273: */
274: public Processor getRootProcessor() {
275: TreeProcessor result = this ;
276: while (result.parent != null) {
277: result = result.parent;
278: }
279:
280: return result;
281: }
282:
283: /**
284: * Set the sitemap component configurations
285: */
286: public void setComponentConfigurations(
287: Configuration componentConfigurations) {
288: this .concreteProcessor
289: .setComponentConfigurations(componentConfigurations);
290: }
291:
292: /* (non-Javadoc)
293: * @see org.apache.cocoon.Processor#getComponentConfigurations()
294: */
295: public Map getComponentConfigurations() {
296: return this .concreteProcessor.getComponentConfigurations();
297: }
298:
299: private void setupConcreteProcessor(Environment env)
300: throws Exception {
301:
302: if (this .parent == null) {
303: // Ensure root sitemap uses the correct context, even if not located in the webapp context
304: env.changeContext("", this .source.getURI());
305: }
306:
307: // check for sitemap changes
308: if (this .concreteProcessor == null
309: || (this .checkReload && this .source.getLastModified() != this .lastModified)) {
310: this .buildConcreteProcessor(env);
311: }
312: }
313:
314: private synchronized void buildConcreteProcessor(Environment env)
315: throws Exception {
316:
317: // Now that we entered the synchronized area, recheck what's already
318: // been checked in process().
319: if (this .concreteProcessor != null
320: && this .source.getLastModified() == this .lastModified) {
321: // Nothing changed
322: return;
323: }
324:
325: long startTime = System.currentTimeMillis();
326:
327: // Dispose the old processor, if any
328: if (this .concreteProcessor != null) {
329: this .concreteProcessor.markForDisposal();
330: }
331:
332: // Get a builder
333: TreeBuilder builder = (TreeBuilder) this .builderSelector
334: .select("sitemap");
335: ConcreteTreeProcessor newProcessor = new ConcreteTreeProcessor(
336: this );
337: long newLastModified;
338: this .setupLogger(newProcessor);
339: //FIXME (SW): why do we need to enterProcessor here?
340: CocoonComponentManager
341: .enterEnvironment(env, this .manager, this );
342: try {
343: if (builder instanceof Recomposable) {
344: ((Recomposable) builder).recompose(this .manager);
345: }
346: builder.setProcessor(newProcessor);
347:
348: newLastModified = this .source.getLastModified();
349:
350: ProcessingNode root = builder.build(this .source);
351:
352: newProcessor.setProcessorData(builder
353: .getSitemapComponentManager(), root, builder
354: .getDisposableNodes());
355: } finally {
356: CocoonComponentManager.leaveEnvironment();
357: this .builderSelector.release(builder);
358: }
359:
360: if (this .getLogger().isDebugEnabled()) {
361: double time = (this .lastModified - startTime) / 1000.0;
362: this .getLogger().debug(
363: "TreeProcessor built in " + time + " secs from "
364: + this .source.getURI());
365: }
366:
367: // Switch to the new processor (ensure it's never temporarily null)
368: this .concreteProcessor = newProcessor;
369: this .lastModified = newLastModified;
370: }
371:
372: /* (non-Javadoc)
373: * @see org.apache.avalon.framework.activity.Disposable#dispose()
374: */
375: public void dispose() {
376: // Dispose the concrete processor. No need to check for existing requests, as there
377: // are none when a TreeProcessor is disposed.
378: ContainerUtil.dispose(this .concreteProcessor);
379: this .concreteProcessor = null;
380:
381: if (this .manager != null) {
382: if (this .source != null) {
383: this .resolver.release(this .source.getSource());
384: this .source = null;
385: }
386:
387: if (this .parent == null) {
388: // root processor : dispose the builder selector
389: this .builderSelector.dispose();
390: this .builderSelector = null;
391: }
392:
393: // Release resolver looked up in compose()
394: this .manager.release((Component) this .resolver);
395: this .resolver = null;
396:
397: this .manager = null;
398: }
399: }
400:
401: public String toString() {
402: return "TreeProcessor - "
403: + (this .source == null ? "[unknown location]"
404: : this.source.getURI());
405: }
406: }
|