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 org.apache.avalon.excalibur.component.DefaultRoleManager;
020: import org.apache.avalon.excalibur.component.ExcaliburComponentSelector;
021: import org.apache.avalon.excalibur.component.RoleManageable;
022: import org.apache.avalon.excalibur.component.RoleManager;
023: import org.apache.avalon.excalibur.pool.Recyclable;
024: import org.apache.avalon.framework.activity.Disposable;
025: import org.apache.avalon.framework.activity.Initializable;
026: import org.apache.avalon.framework.component.ComponentException;
027: import org.apache.avalon.framework.component.ComponentManager;
028: import org.apache.avalon.framework.component.ComponentSelector;
029: import org.apache.avalon.framework.component.Recomposable;
030: import org.apache.avalon.framework.configuration.AbstractConfiguration;
031: import org.apache.avalon.framework.configuration.Configurable;
032: import org.apache.avalon.framework.configuration.Configuration;
033: import org.apache.avalon.framework.configuration.ConfigurationException;
034: import org.apache.avalon.framework.configuration.NamespacedSAXConfigurationHandler;
035: import org.apache.avalon.framework.context.Context;
036: import org.apache.avalon.framework.context.ContextException;
037: import org.apache.avalon.framework.context.Contextualizable;
038: import org.apache.avalon.framework.logger.AbstractLogEnabled;
039: import org.apache.cocoon.ProcessingException;
040: import org.apache.cocoon.components.ExtendedComponentSelector;
041: import org.apache.cocoon.components.LifecycleHelper;
042: import org.apache.cocoon.components.PropertyAwareNamespacedSAXConfigurationHandler;
043: import org.apache.cocoon.components.source.SourceUtil;
044: import org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory;
045: import org.apache.cocoon.sitemap.PatternException;
046: import org.apache.cocoon.sitemap.SitemapParameters;
047: import org.apache.cocoon.util.location.Location;
048: import org.apache.cocoon.util.location.LocationImpl;
049: import org.apache.cocoon.util.location.LocationUtils;
050: import org.apache.cocoon.util.Settings;
051: import org.apache.cocoon.util.SettingsHelper;
052: import org.apache.excalibur.source.Source;
053:
054: import java.util.ArrayList;
055: import java.util.HashMap;
056: import java.util.Iterator;
057: import java.util.List;
058: import java.util.Map;
059:
060: /**
061: *
062: * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
063: * @version CVS $Id: DefaultTreeBuilder.java 446922 2006-09-16 19:24:36Z vgritsenko $
064: */
065:
066: public class DefaultTreeBuilder extends AbstractLogEnabled implements
067: TreeBuilder, Recomposable, Configurable, Contextualizable,
068: RoleManageable, Recyclable, Disposable {
069:
070: protected Map attributes = new HashMap();
071:
072: /**
073: * The tree processor that we're building.
074: */
075: protected ConcreteTreeProcessor processor;
076:
077: //----- lifecycle-related objects ------
078: protected Context context;
079:
080: /**
081: * The parent component manager, set using <code>compose()</code> and <code>recompose()</code>
082: * (implementation of <code>Recomposable</code>).
083: */
084: protected ComponentManager parentManager;
085:
086: /**
087: * The parent role manager, set using <code>setRoleManager</code> (implementation of
088: * <code>RoleManageable</code>).
089: */
090: protected RoleManager parentRoleManager;
091:
092: protected Configuration configuration;
093: // -------------------------------------
094:
095: /**
096: * Component manager created by {@link #createComponentManager(Configuration)}.
097: */
098: protected ComponentManager manager;
099:
100: /**
101: * Role manager result created by {@link #createRoleManager()}.
102: */
103: protected RoleManager roleManager;
104:
105: /** Selector for ProcessingNodeBuilders */
106: protected ComponentSelector builderSelector;
107:
108: protected LifecycleHelper lifecycle;
109:
110: protected String namespace;
111:
112: protected String parameterElement;
113:
114: protected String languageName;
115:
116: protected String fileName;
117:
118: /** Nodes gone through setupNode() that implement Initializable */
119: private List initializableNodes = new ArrayList();
120:
121: /** Nodes gone through setupNode() that implement Disposable */
122: private List disposableNodes = new ArrayList();
123:
124: /** NodeBuilders created by createNodeBuilder() that implement LinkedProcessingNodeBuilder */
125: private List linkedBuilders = new ArrayList();
126:
127: /** Are we in a state that allows to get registered nodes ? */
128: private boolean canGetNode = false;
129:
130: /** Nodes registered using registerNode() */
131: private Map registeredNodes = new HashMap();
132:
133: public void contextualize(Context context) throws ContextException {
134: this .context = context;
135: }
136:
137: public void compose(ComponentManager manager)
138: throws ComponentException {
139: this .parentManager = manager;
140: }
141:
142: public void recompose(ComponentManager manager)
143: throws ComponentException {
144: this .parentManager = manager;
145: }
146:
147: public void setRoleManager(RoleManager rm) {
148: this .parentRoleManager = rm;
149: }
150:
151: /**
152: * Configurable
153: */
154: public void configure(Configuration config)
155: throws ConfigurationException {
156: this .configuration = config;
157:
158: this .languageName = config.getAttribute("name");
159: if (this .getLogger().isDebugEnabled()) {
160: getLogger().debug(
161: "Configuring Builder for language : "
162: + this .languageName);
163: }
164:
165: this .fileName = config.getChild("file").getAttribute("name");
166:
167: this .namespace = config.getChild("namespace").getAttribute(
168: "uri", "");
169:
170: this .parameterElement = config.getChild("parameter")
171: .getAttribute("element", "parameter");
172: }
173:
174: public void setAttribute(String name, Object value) {
175: this .attributes.put(name, value);
176: }
177:
178: public Object getAttribute(String name) {
179: return this .attributes.get(name);
180: }
181:
182: /**
183: * Create a role manager that will be used by all <code>RoleManageable</code>
184: * components. The default here is to create a role manager with the contents of
185: * the <roles> element of the configuration.
186: * <p>
187: * Subclasses can redefine this method to create roles from other sources than
188: * the one used here.
189: *
190: * @return the role manager
191: */
192: protected RoleManager createRoleManager() throws Exception {
193: RoleManager roles = new DefaultRoleManager(
194: this .parentRoleManager);
195:
196: LifecycleHelper.setupComponent(roles, getLogger(),
197: this .context, this .manager, this .parentRoleManager,
198: this .configuration.getChild("roles"));
199:
200: return roles;
201: }
202:
203: /**
204: * Create a component manager that will be used for all <code>Composable</code>
205: * <code>ProcessingNodeBuilder</code>s and <code>ProcessingNode</code>s.
206: * <p>
207: * The default here is to simply return the manager set by <code>compose()</code>,
208: * i.e. the component manager set by the calling <code>TreeProcessor</code>.
209: * <p>
210: * Subclasses can redefine this method to create a component manager local to a tree,
211: * such as for sitemap's <map:components>.
212: *
213: * @return a component manager
214: */
215: protected ComponentManager createComponentManager(Configuration tree)
216: throws Exception {
217: return this .parentManager;
218: }
219:
220: /**
221: * Create a <code>ComponentSelector</code> for <code>ProcessingNodeBuilder</code>s.
222: * It creates a selector with the contents of the "node" element of the configuration.
223: *
224: * @return a selector for node builders
225: */
226: protected ComponentSelector createBuilderSelector()
227: throws Exception {
228:
229: // Create the NodeBuilder selector.
230: ExcaliburComponentSelector selector = new ExtendedComponentSelector() {
231: protected String getComponentInstanceName() {
232: return "node";
233: }
234:
235: protected String getClassAttributeName() {
236: return "builder";
237: }
238: };
239:
240: // Automagically initialize the selector
241: LifecycleHelper.setupComponent(selector, getLogger(),
242: this .context, this .manager, this .roleManager,
243: this .configuration.getChild("nodes"));
244:
245: return selector;
246: }
247:
248: public void setProcessor(ConcreteTreeProcessor processor) {
249: this .processor = processor;
250: }
251:
252: public ConcreteTreeProcessor getProcessor() {
253: return this .processor;
254: }
255:
256: /**
257: * Returns the language that is being built (e.g. "sitemap").
258: */
259: public String getLanguage() {
260: return this .languageName;
261: }
262:
263: /**
264: * Returns the name of the parameter element.
265: */
266: public String getParameterName() {
267: return this .parameterElement;
268: }
269:
270: /**
271: * @see org.apache.cocoon.components.treeprocessor.TreeBuilder#registerNode(java.lang.String, org.apache.cocoon.components.treeprocessor.ProcessingNode)
272: */
273: public boolean registerNode(String name, ProcessingNode node) {
274: if (this .registeredNodes.containsKey(name)) {
275: return false;
276: }
277: this .registeredNodes.put(name, node);
278: return true;
279: }
280:
281: public ProcessingNode getRegisteredNode(String name) {
282: if (this .canGetNode) {
283: return (ProcessingNode) this .registeredNodes.get(name);
284: } else {
285: throw new IllegalArgumentException(
286: "Categories are only available during buildNode()");
287: }
288: }
289:
290: public ProcessingNodeBuilder createNodeBuilder(Configuration config)
291: throws Exception {
292: //FIXME : check namespace
293: String nodeName = config.getName();
294:
295: if (this .getLogger().isDebugEnabled()) {
296: getLogger().debug("Creating node builder for " + nodeName);
297: }
298:
299: ProcessingNodeBuilder builder;
300: try {
301: builder = (ProcessingNodeBuilder) this .builderSelector
302: .select(nodeName);
303:
304: } catch (ComponentException ce) {
305: // Is it because this element is unknown ?
306: if (this .builderSelector.hasComponent(nodeName)) {
307: // No : rethrow
308: throw ce;
309: } else {
310: // Throw a more meaningful exception
311: String msg = "Unknown element '" + nodeName + "' at "
312: + config.getLocation();
313: throw new ConfigurationException(msg);
314: }
315: }
316:
317: if (builder instanceof Recomposable) {
318: ((Recomposable) builder).recompose(this .manager);
319: }
320:
321: builder.setBuilder(this );
322:
323: if (builder instanceof LinkedProcessingNodeBuilder) {
324: this .linkedBuilders.add(builder);
325: }
326:
327: return builder;
328: }
329:
330: /**
331: * Create the tree once component manager and node builders have been set up.
332: * Can be overriden by subclasses to perform pre/post tree creation operations.
333: */
334: protected ProcessingNode createTree(Configuration tree)
335: throws Exception {
336: // Create a node builder from the top-level element
337: ProcessingNodeBuilder rootBuilder = createNodeBuilder(tree);
338:
339: // Build the whole tree (with an empty buildModel)
340: return rootBuilder.buildNode(tree);
341: }
342:
343: /**
344: * Resolve links : call <code>linkNode()</code> on all
345: * <code>LinkedProcessingNodeBuilder</code>s.
346: * Can be overriden by subclasses to perform pre/post resolution operations.
347: */
348: protected void linkNodes() throws Exception {
349: // Resolve links
350: Iterator iter = this .linkedBuilders.iterator();
351: while (iter.hasNext()) {
352: ((LinkedProcessingNodeBuilder) iter.next()).linkNode();
353: }
354: }
355:
356: /**
357: * Get the namespace URI that builders should use to find their nodes.
358: */
359: public String getNamespace() {
360: return this .namespace;
361: }
362:
363: public ProcessingNode build(Source source) throws Exception {
364:
365: try {
366: // Build a namespace-aware configuration object
367: Settings settings = SettingsHelper
368: .getSettings(this .context);
369: NamespacedSAXConfigurationHandler handler = new PropertyAwareNamespacedSAXConfigurationHandler(
370: settings, getLogger());
371: AnnotationsFilter annotationsFilter = new AnnotationsFilter(
372: handler);
373: SourceUtil.toSAX(source, annotationsFilter);
374: Configuration treeConfig = handler.getConfiguration();
375:
376: return build(treeConfig);
377: } catch (ProcessingException e) {
378: throw e;
379: } catch (Exception e) {
380: throw new ProcessingException("Failed to load "
381: + this .languageName + " from " + source.getURI(), e);
382: }
383: }
384:
385: public String getFileName() {
386: return this .fileName;
387: }
388:
389: /**
390: * Build a processing tree from a <code>Configuration</code>.
391: */
392: public ProcessingNode build(Configuration tree) throws Exception {
393:
394: this .roleManager = createRoleManager();
395:
396: this .manager = createComponentManager(tree);
397:
398: // Create a helper object to setup components
399: this .lifecycle = new LifecycleHelper(getLogger(), this .context,
400: this .manager, this .roleManager, null // configuration
401: );
402:
403: this .builderSelector = createBuilderSelector();
404:
405: // Calls to getRegisteredNode() are forbidden
406: this .canGetNode = false;
407:
408: // Collect all disposable variable resolvers
409: VariableResolverFactory
410: .setDisposableCollector(this .disposableNodes);
411:
412: ProcessingNode result = createTree(tree);
413:
414: // Calls to getRegisteredNode() are now allowed
415: this .canGetNode = true;
416:
417: linkNodes();
418:
419: // Initialize all Initializable nodes
420: Iterator iter = this .initializableNodes.iterator();
421: while (iter.hasNext()) {
422: ((Initializable) iter.next()).initialize();
423: }
424:
425: // And that's all !
426: return result;
427: }
428:
429: /**
430: * Return the list of <code>ProcessingNodes</code> part of this tree that are
431: * <code>Disposable</code>. Care should be taken to properly dispose them before
432: * trashing the processing tree.
433: */
434: public List getDisposableNodes() {
435: return this .disposableNodes;
436: }
437:
438: /**
439: * Return the sitemap component manager
440: */
441: public ComponentManager getSitemapComponentManager() {
442: return this .manager;
443: }
444:
445: /**
446: * Setup a <code>ProcessingNode</code> by setting its location, calling all
447: * the lifecycle interfaces it implements and giving it the parameter map if
448: * it's a <code>ParameterizableNode</code>.
449: * <p>
450: * As a convenience, the node is returned by this method to allow constructs
451: * like <code>return treeBuilder.setupNode(new MyNode(), config)</code>.
452: */
453: public ProcessingNode setupNode(ProcessingNode node,
454: Configuration config) throws Exception {
455: Location location = getLocation(config);
456: if (node instanceof AbstractProcessingNode) {
457: ((AbstractProcessingNode) node).setLocation(location);
458: }
459:
460: this .lifecycle.setupComponent(node, false);
461:
462: if (node instanceof ParameterizableProcessingNode) {
463: Map params = getParameters(config, location);
464: ((ParameterizableProcessingNode) node)
465: .setParameters(params);
466: }
467:
468: if (node instanceof Initializable) {
469: this .initializableNodes.add(node);
470: }
471:
472: if (node instanceof Disposable) {
473: this .disposableNodes.add(node);
474: }
475:
476: return node;
477: }
478:
479: protected LocationImpl getLocation(Configuration config) {
480: String prefix = "";
481:
482: if (config instanceof AbstractConfiguration) {
483: //FIXME: AbstractConfiguration has a _protected_ getPrefix() method.
484: // So make some reasonable guess on the prefix until it becomes public
485: String namespace = null;
486: try {
487: namespace = config.getNamespace();
488: } catch (ConfigurationException e) {
489: // ignore
490: }
491: if ("http://apache.org/cocoon/sitemap/1.0"
492: .equals(namespace)) {
493: prefix = "map";
494: }
495: }
496:
497: StringBuffer desc = new StringBuffer().append('<');
498: if (prefix.length() > 0) {
499: desc.append(prefix).append(':').append(config.getName());
500: } else {
501: desc.append(config.getName());
502: }
503: String type = config.getAttribute("type", null);
504: if (type != null) {
505: desc.append(" type=\"").append(type).append('"');
506: }
507: desc.append('>');
508:
509: Location rawLoc = LocationUtils.getLocation(config, null);
510: return new LocationImpl(desc.toString(), rawLoc.getURI(),
511: rawLoc.getLineNumber(), rawLoc.getColumnNumber());
512: }
513:
514: /**
515: * Get <xxx:parameter> elements as a <code>Map</code> of </code>ListOfMapResolver</code>s,
516: * that can be turned into parameters using <code>ListOfMapResolver.buildParameters()</code>.
517: *
518: * @return the Map of ListOfMapResolver, or <code>null</code> if there are no parameters.
519: */
520: protected Map getParameters(Configuration config, Location location)
521: throws ConfigurationException {
522:
523: Configuration[] children = config
524: .getChildren(this .parameterElement);
525:
526: if (children.length == 0) {
527: // Parameters are only the component's location
528: // TODO Optimize this
529: return new SitemapParameters.LocatedHashMap(location, 0);
530: }
531:
532: Map params = new SitemapParameters.LocatedHashMap(location,
533: children.length + 1);
534: for (int i = 0; i < children.length; i++) {
535: Configuration child = children[i];
536: if (true) { // FIXME : check namespace
537: String name = child.getAttribute("name");
538: String value = child.getAttribute("value");
539: try {
540: params.put(VariableResolverFactory.getResolver(
541: name, this .manager),
542: VariableResolverFactory.getResolver(value,
543: this .manager));
544: } catch (PatternException pe) {
545: String msg = "Invalid pattern '" + value + "' at "
546: + child.getLocation();
547: throw new ConfigurationException(msg, pe);
548: }
549: }
550: }
551:
552: return params;
553: }
554:
555: /**
556: * Get the type for a statement : it returns the 'type' attribute if present,
557: * and otherwhise the default hint of the <code>ExtendedSelector</code> designated by
558: * role <code>role</code>.
559: *
560: * @throws ConfigurationException if the default type could not be found.
561: */
562: public String getTypeForStatement(Configuration statement,
563: String role) throws ConfigurationException {
564:
565: String type = statement.getAttribute("type", null);
566:
567: ComponentSelector selector = null;
568:
569: try {
570: try {
571: selector = (ComponentSelector) this .manager
572: .lookup(role);
573: } catch (ComponentException ce) {
574: String msg = "Cannot get component selector for '"
575: + statement.getName() + "' at "
576: + statement.getLocation();
577: throw new ConfigurationException(msg, ce);
578: }
579:
580: if (type == null
581: && selector instanceof ExtendedComponentSelector) {
582: type = ((ExtendedComponentSelector) selector)
583: .getDefaultHint();
584: }
585:
586: if (type == null) {
587: String msg = "No default type exists for '"
588: + statement.getName() + "' at "
589: + statement.getLocation();
590: throw new ConfigurationException(msg);
591: }
592:
593: if (!selector.hasComponent(type)) {
594: String msg = "Type '" + type + "' is not defined for '"
595: + statement.getName() + "' at "
596: + statement.getLocation();
597: throw new ConfigurationException(msg);
598: }
599: } finally {
600: this .manager.release(selector);
601: }
602: return type;
603: }
604:
605: public void recycle() {
606: this .lifecycle = null; // Created in build()
607: this .initializableNodes.clear();
608: this .linkedBuilders.clear();
609: this .canGetNode = false;
610: this .registeredNodes.clear();
611:
612: // Don't clear disposableNodes as they're used by the Processor
613: this .disposableNodes = new ArrayList();
614: VariableResolverFactory.setDisposableCollector(null);
615:
616: this .processor = null;
617: this .manager = null;
618: this .roleManager = null;
619: }
620:
621: public void dispose() {
622: LifecycleHelper.dispose(this .builderSelector);
623:
624: // Don't dispose manager or roles : they are used by the built tree
625: // and thus must live longer than the builder.
626: }
627: }
|