KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > components > treeprocessor > DefaultTreeBuilder


1 /*
2  * Copyright 1999-2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.components.treeprocessor;
17
18 import org.apache.avalon.excalibur.component.DefaultRoleManager;
19 import org.apache.avalon.excalibur.component.ExcaliburComponentSelector;
20 import org.apache.avalon.excalibur.component.RoleManageable;
21 import org.apache.avalon.excalibur.component.RoleManager;
22 import org.apache.avalon.excalibur.pool.Recyclable;
23 import org.apache.avalon.framework.activity.Disposable;
24 import org.apache.avalon.framework.activity.Initializable;
25 import org.apache.avalon.framework.component.ComponentException;
26 import org.apache.avalon.framework.component.ComponentManager;
27 import org.apache.avalon.framework.component.ComponentSelector;
28 import org.apache.avalon.framework.component.Recomposable;
29 import org.apache.avalon.framework.configuration.AbstractConfiguration;
30 import org.apache.avalon.framework.configuration.Configurable;
31 import org.apache.avalon.framework.configuration.Configuration;
32 import org.apache.avalon.framework.configuration.ConfigurationException;
33 import org.apache.avalon.framework.configuration.NamespacedSAXConfigurationHandler;
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.cocoon.ProcessingException;
39 import org.apache.cocoon.components.ExtendedComponentSelector;
40 import org.apache.cocoon.components.LifecycleHelper;
41 import org.apache.cocoon.components.source.SourceUtil;
42 import org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory;
43 import org.apache.cocoon.sitemap.PatternException;
44 import org.apache.cocoon.sitemap.SitemapParameters;
45 import org.apache.cocoon.util.location.Location;
46 import org.apache.cocoon.util.location.LocationImpl;
47 import org.apache.cocoon.util.location.LocationUtils;
48 import org.apache.excalibur.source.Source;
49
50 import java.util.ArrayList JavaDoc;
51 import java.util.HashMap JavaDoc;
52 import java.util.Iterator JavaDoc;
53 import java.util.List JavaDoc;
54 import java.util.Map JavaDoc;
55
56 /**
57  *
58  * @author <a HREF="mailto:sylvain@apache.org">Sylvain Wallez</a>
59  * @version CVS $Id: DefaultTreeBuilder.java 280628 2005-09-13 19:14:35Z sylvain $
60  */

61
62 public class DefaultTreeBuilder extends AbstractLogEnabled implements TreeBuilder,
63   Recomposable, Configurable, Contextualizable, RoleManageable, Recyclable, Disposable {
64
65     protected Map JavaDoc attributes = new HashMap JavaDoc();
66
67     /**
68      * The tree processor that we're building.
69      */

70     protected ConcreteTreeProcessor processor;
71
72     //----- lifecycle-related objects ------
73
protected Context context;
74
75     /**
76      * The parent component manager, set using <code>compose()</code> and <code>recompose()</code>
77      * (implementation of <code>Recomposable</code>).
78      */

79     protected ComponentManager parentManager;
80
81     /**
82      * The parent role manager, set using <code>setRoleManager</code> (implementation of
83      * <code>RoleManageable</code>).
84      */

85     protected RoleManager parentRoleManager;
86
87     protected Configuration configuration;
88     // -------------------------------------
89

90     /**
91      * Component manager created by {@link #createComponentManager(Configuration)}.
92      */

93     protected ComponentManager manager;
94
95     /**
96      * Role manager result created by {@link #createRoleManager()}.
97      */

98     protected RoleManager roleManager;
99
100     /** Selector for ProcessingNodeBuilders */
101     protected ComponentSelector builderSelector;
102
103     protected LifecycleHelper lifecycle;
104
105     protected String JavaDoc namespace;
106
107     protected String JavaDoc parameterElement;
108
109     protected String JavaDoc languageName;
110
111     protected String JavaDoc fileName;
112
113     /** Nodes gone through setupNode() that implement Initializable */
114     private List JavaDoc initializableNodes = new ArrayList JavaDoc();
115
116     /** Nodes gone through setupNode() that implement Disposable */
117     private List JavaDoc disposableNodes = new ArrayList JavaDoc();
118
119     /** NodeBuilders created by createNodeBuilder() that implement LinkedProcessingNodeBuilder */
120     private List JavaDoc linkedBuilders = new ArrayList JavaDoc();
121
122     /** Are we in a state that allows to get registered nodes ? */
123     private boolean canGetNode = false;
124
125     /** Nodes registered using registerNode() */
126     private Map JavaDoc registeredNodes = new HashMap JavaDoc();
127
128
129     public void contextualize(Context context) throws ContextException {
130         this.context = context;
131     }
132
133     public void compose(ComponentManager manager) throws ComponentException {
134         this.parentManager = manager;
135     }
136
137     public void recompose(ComponentManager manager) throws ComponentException {
138         this.parentManager = manager;
139     }
140
141     public void setRoleManager(RoleManager rm) {
142         this.parentRoleManager = rm;
143     }
144
145     /**
146      * Configurable
147      */

148     public void configure(Configuration config) throws ConfigurationException {
149         this.configuration = config;
150
151         this.languageName = config.getAttribute("name");
152         if (this.getLogger().isDebugEnabled()) {
153             getLogger().debug("Configuring Builder for language : " + this.languageName);
154         }
155
156         this.fileName = config.getChild("file").getAttribute("name");
157
158         this.namespace = config.getChild("namespace").getAttribute("uri", "");
159
160         this.parameterElement = config.getChild("parameter").getAttribute("element", "parameter");
161     }
162
163     public void setAttribute(String JavaDoc name, Object JavaDoc value) {
164         this.attributes.put(name, value);
165     }
166     
167     public Object JavaDoc getAttribute(String JavaDoc name) {
168         return this.attributes.get(name);
169     }
170
171     /**
172      * Create a role manager that will be used by all <code>RoleManageable</code>
173      * components. The default here is to create a role manager with the contents of
174      * the &lt;roles&gt; element of the configuration.
175      * <p>
176      * Subclasses can redefine this method to create roles from other sources than
177      * the one used here.
178      *
179      * @return the role manager
180      */

181     protected RoleManager createRoleManager() throws Exception JavaDoc
182     {
183         RoleManager roles = new DefaultRoleManager(this.parentRoleManager);
184
185         LifecycleHelper.setupComponent(roles,
186             getLogger(),
187             this.context,
188             this.manager,
189             this.parentRoleManager,
190             this.configuration.getChild("roles")
191         );
192
193         return roles;
194     }
195
196     /**
197      * Create a component manager that will be used for all <code>Composable</code>
198      * <code>ProcessingNodeBuilder</code>s and <code>ProcessingNode</code>s.
199      * <p>
200      * The default here is to simply return the manager set by <code>compose()</code>,
201      * i.e. the component manager set by the calling <code>TreeProcessor</code>.
202      * <p>
203      * Subclasses can redefine this method to create a component manager local to a tree,
204      * such as for sitemap's &lt;map:components&gt;.
205      *
206      * @return a component manager
207      */

208     protected ComponentManager createComponentManager(Configuration tree) throws Exception JavaDoc
209     {
210         return this.parentManager;
211     }
212
213     /**
214      * Create a <code>ComponentSelector</code> for <code>ProcessingNodeBuilder</code>s.
215      * It creates a selector with the contents of the "node" element of the configuration.
216      *
217      * @return a selector for node builders
218      */

219     protected ComponentSelector createBuilderSelector() throws Exception JavaDoc {
220
221         // Create the NodeBuilder selector.
222
ExcaliburComponentSelector selector = new ExtendedComponentSelector() {
223             protected String JavaDoc getComponentInstanceName() {
224                 return "node";
225             }
226
227             protected String JavaDoc getClassAttributeName() {
228                 return "builder";
229             }
230         };
231
232         // Automagically initialize the selector
233
LifecycleHelper.setupComponent(selector,
234             getLogger(),
235             this.context,
236             this.manager,
237             this.roleManager,
238             this.configuration.getChild("nodes")
239         );
240
241         return selector;
242     }
243
244     public void setProcessor(ConcreteTreeProcessor processor) {
245         this.processor = processor;
246     }
247
248     public ConcreteTreeProcessor getProcessor() {
249         return this.processor;
250     }
251
252     /**
253      * Returns the language that is being built (e.g. "sitemap").
254      */

255     public String JavaDoc getLanguage() {
256         return this.languageName;
257     }
258
259     /**
260      * Returns the name of the parameter element.
261      */

262     public String JavaDoc getParameterName() {
263         return this.parameterElement;
264     }
265
266     /**
267      * @see org.apache.cocoon.components.treeprocessor.TreeBuilder#registerNode(java.lang.String, org.apache.cocoon.components.treeprocessor.ProcessingNode)
268      */

269     public boolean registerNode(String JavaDoc name, ProcessingNode node) {
270         if ( this.registeredNodes.containsKey(name) ) {
271             return false;
272         }
273         this.registeredNodes.put(name, node);
274         return true;
275     }
276
277     public ProcessingNode getRegisteredNode(String JavaDoc name) {
278         if (this.canGetNode) {
279             return (ProcessingNode)this.registeredNodes.get(name);
280         } else {
281             throw new IllegalArgumentException JavaDoc("Categories are only available during buildNode()");
282         }
283     }
284
285     public ProcessingNodeBuilder createNodeBuilder(Configuration config) throws Exception JavaDoc {
286         //FIXME : check namespace
287
String JavaDoc nodeName = config.getName();
288
289         if (this.getLogger().isDebugEnabled()) {
290             getLogger().debug("Creating node builder for " + nodeName);
291         }
292
293         ProcessingNodeBuilder builder;
294         try {
295             builder = (ProcessingNodeBuilder)this.builderSelector.select(nodeName);
296
297         } catch(ComponentException ce) {
298             // Is it because this element is unknown ?
299
if (this.builderSelector.hasComponent(nodeName)) {
300                 // No : rethrow
301
throw ce;
302             } else {
303                 // Throw a more meaningful exception
304
String JavaDoc msg = "Unknown element '" + nodeName + "' at " + config.getLocation();
305                 throw new ConfigurationException(msg);
306             }
307         }
308
309         if (builder instanceof Recomposable) {
310             ((Recomposable)builder).recompose(this.manager);
311         }
312
313         builder.setBuilder(this);
314
315         if (builder instanceof LinkedProcessingNodeBuilder) {
316             this.linkedBuilders.add(builder);
317         }
318
319         return builder;
320     }
321
322     /**
323      * Create the tree once component manager and node builders have been set up.
324      * Can be overriden by subclasses to perform pre/post tree creation operations.
325      */

326     protected ProcessingNode createTree(Configuration tree) throws Exception JavaDoc {
327         // Create a node builder from the top-level element
328
ProcessingNodeBuilder rootBuilder = createNodeBuilder(tree);
329
330         // Build the whole tree (with an empty buildModel)
331
return rootBuilder.buildNode(tree);
332     }
333
334     /**
335      * Resolve links : call <code>linkNode()</code> on all
336      * <code>LinkedProcessingNodeBuilder</code>s.
337      * Can be overriden by subclasses to perform pre/post resolution operations.
338      */

339     protected void linkNodes() throws Exception JavaDoc {
340         // Resolve links
341
Iterator JavaDoc iter = this.linkedBuilders.iterator();
342         while(iter.hasNext()) {
343             ((LinkedProcessingNodeBuilder)iter.next()).linkNode();
344         }
345     }
346
347     /**
348      * Get the namespace URI that builders should use to find their nodes.
349      */

350     public String JavaDoc getNamespace() {
351         return this.namespace;
352     }
353
354     public ProcessingNode build(Source source)
355     throws Exception JavaDoc {
356
357         try {
358             // Build a namespace-aware configuration object
359
NamespacedSAXConfigurationHandler handler = new NamespacedSAXConfigurationHandler();
360             AnnotationsFilter annotationsFilter = new AnnotationsFilter(handler);
361             SourceUtil.toSAX( source, annotationsFilter );
362             Configuration treeConfig = handler.getConfiguration();
363
364             return build(treeConfig);
365         } catch (ProcessingException e) {
366             throw e;
367         } catch(Exception JavaDoc e) {
368             throw new ProcessingException("Failed to load " + this.languageName + " from " +
369                 source.getURI(), e);
370         }
371     }
372
373     public String JavaDoc getFileName() {
374         return this.fileName;
375     }
376
377     /**
378      * Build a processing tree from a <code>Configuration</code>.
379      */

380     public ProcessingNode build(Configuration tree) throws Exception JavaDoc {
381
382         this.roleManager = createRoleManager();
383
384         this.manager = createComponentManager(tree);
385
386         // Create a helper object to setup components
387
this.lifecycle = new LifecycleHelper(getLogger(),
388             this.context,
389             this.manager,
390             this.roleManager,
391             null // configuration
392
);
393
394         this.builderSelector = createBuilderSelector();
395
396         // Calls to getRegisteredNode() are forbidden
397
this.canGetNode = false;
398
399         // Collect all disposable variable resolvers
400
VariableResolverFactory.setDisposableCollector(this.disposableNodes);
401
402         ProcessingNode result = createTree(tree);
403
404         // Calls to getRegisteredNode() are now allowed
405
this.canGetNode = true;
406
407         linkNodes();
408
409         // Initialize all Initializable nodes
410
Iterator JavaDoc iter = this.initializableNodes.iterator();
411         while(iter.hasNext()) {
412             ((Initializable)iter.next()).initialize();
413         }
414
415         // And that's all !
416
return result;
417     }
418
419     /**
420      * Return the list of <code>ProcessingNodes</code> part of this tree that are
421      * <code>Disposable</code>. Care should be taken to properly dispose them before
422      * trashing the processing tree.
423      */

424     public List JavaDoc getDisposableNodes() {
425         return this.disposableNodes;
426     }
427
428     /**
429      * Return the sitemap component manager
430      */

431     public ComponentManager getSitemapComponentManager() {
432         return this.manager;
433     }
434     
435     /**
436      * Setup a <code>ProcessingNode</code> by setting its location, calling all
437      * the lifecycle interfaces it implements and giving it the parameter map if
438      * it's a <code>ParameterizableNode</code>.
439      * <p>
440      * As a convenience, the node is returned by this method to allow constructs
441      * like <code>return treeBuilder.setupNode(new MyNode(), config)</code>.
442      */

443     public ProcessingNode setupNode(ProcessingNode node, Configuration config)
444       throws Exception JavaDoc {
445         Location location = getLocation(config);
446         if (node instanceof AbstractProcessingNode) {
447             ((AbstractProcessingNode)node).setLocation(location);
448         }
449
450         this.lifecycle.setupComponent(node, false);
451
452         if (node instanceof ParameterizableProcessingNode) {
453             Map JavaDoc params = getParameters(config, location);
454             ((ParameterizableProcessingNode)node).setParameters(params);
455         }
456
457         if (node instanceof Initializable) {
458             this.initializableNodes.add(node);
459         }
460
461         if (node instanceof Disposable) {
462             this.disposableNodes.add(node);
463         }
464
465         return node;
466     }
467
468     protected LocationImpl getLocation(Configuration config) {
469         String JavaDoc prefix = "";
470
471         if (config instanceof AbstractConfiguration) {
472             //FIXME: AbstractConfiguration has a _protected_ getPrefix() method.
473
// So make some reasonable guess on the prefix until it becomes public
474
String JavaDoc namespace = null;
475             try {
476                 namespace = ((AbstractConfiguration)config).getNamespace();
477             } catch (ConfigurationException e) {
478                 // ignore
479
}
480             if ("http://apache.org/cocoon/sitemap/1.0".equals(namespace)) {
481                 prefix="map";
482             }
483         }
484         
485         StringBuffer JavaDoc desc = new StringBuffer JavaDoc().append('<');
486         if (prefix.length() > 0) {
487             desc.append(prefix).append(':').append(config.getName());
488         } else {
489             desc.append(config.getName());
490         }
491         String JavaDoc type = config.getAttribute("type", null);
492         if (type != null) {
493             desc.append(" type=\"").append(type).append('"');
494         }
495         desc.append('>');
496         
497         Location rawLoc = LocationUtils.getLocation(config, null);
498         return new LocationImpl(desc.toString(), rawLoc.getURI(), rawLoc.getLineNumber(), rawLoc.getColumnNumber());
499     }
500
501     /**
502      * Get &lt;xxx:parameter&gt; elements as a <code>Map</code> of </code>ListOfMapResolver</code>s,
503      * that can be turned into parameters using <code>ListOfMapResolver.buildParameters()</code>.
504      *
505      * @return the Map of ListOfMapResolver, or <code>null</code> if there are no parameters.
506      */

507     protected Map JavaDoc getParameters(Configuration config, Location location) throws ConfigurationException {
508
509         Configuration[] children = config.getChildren(this.parameterElement);
510         
511         if (children.length == 0) {
512             // Parameters are only the component's location
513
// TODO Optimize this
514
return new SitemapParameters.LocatedHashMap(location, 0);
515         }
516
517         Map JavaDoc params = new SitemapParameters.LocatedHashMap(location, children.length+1);
518         for (int i = 0; i < children.length; i++) {
519             Configuration child = children[i];
520             if (true) { // FIXME : check namespace
521
String JavaDoc name = child.getAttribute("name");
522                 String JavaDoc value = child.getAttribute("value");
523                 try {
524                     params.put(
525                         VariableResolverFactory.getResolver(name, this.manager),
526                         VariableResolverFactory.getResolver(value, this.manager));
527                 } catch(PatternException pe) {
528                     String JavaDoc msg = "Invalid pattern '" + value + "' at " + child.getLocation();
529                     throw new ConfigurationException(msg, pe);
530                 }
531             }
532         }
533
534         return params;
535     }
536
537     /**
538      * Get the type for a statement : it returns the 'type' attribute if present,
539      * and otherwhise the default hint of the <code>ExtendedSelector</code> designated by
540      * role <code>role</code>.
541      *
542      * @throws ConfigurationException if the default type could not be found.
543      */

544     public String JavaDoc getTypeForStatement(Configuration statement, String JavaDoc role) throws ConfigurationException {
545
546         String JavaDoc type = statement.getAttribute("type", null);
547
548         ComponentSelector selector = null;
549
550         try {
551             try {
552                 selector = (ComponentSelector)this.manager.lookup(role);
553             } catch(ComponentException ce) {
554                 String JavaDoc msg = "Cannot get component selector for '" + statement.getName() + "' at " +
555                     statement.getLocation();
556                 throw new ConfigurationException(msg, ce);
557             }
558
559             if (type == null && selector instanceof ExtendedComponentSelector) {
560                 type = ((ExtendedComponentSelector)selector).getDefaultHint();
561             }
562
563             if (type == null) {
564                 String JavaDoc msg = "No default type exists for '" + statement.getName() + "' at " +
565                     statement.getLocation();
566                 throw new ConfigurationException(msg);
567             }
568
569             if (!selector.hasComponent(type)) {
570                 String JavaDoc msg = "Type '" + type + "' is not defined for '" + statement.getName() + "' at " +
571                     statement.getLocation();
572                 throw new ConfigurationException(msg);
573             }
574         } finally {
575             this.manager.release(selector);
576         }
577         return type;
578     }
579
580     public void recycle() {
581         this.lifecycle = null; // Created in build()
582
this.initializableNodes.clear();
583         this.linkedBuilders.clear();
584         this.canGetNode = false;
585         this.registeredNodes.clear();
586
587         // Don't clear disposableNodes as they're used by the Processor
588
this.disposableNodes = new ArrayList JavaDoc();
589         VariableResolverFactory.setDisposableCollector(null);
590
591         this.processor = null;
592         this.manager = null;
593         this.roleManager = null;
594     }
595
596     public void dispose() {
597         LifecycleHelper.dispose(this.builderSelector);
598
599         // Don't dispose manager or roles : they are used by the built tree
600
// and thus must live longer than the builder.
601
}
602 }
603
Popular Tags