View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    * 
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.    
18   */ 
19  
20  package org.apache.commons.pipeline.config;
21  
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.commons.beanutils.BeanUtils;
27  import org.apache.commons.digester.AbstractObjectCreationFactory;
28  import org.apache.commons.digester.CallMethodRule;
29  import org.apache.commons.digester.Digester;
30  import org.apache.commons.digester.ObjectCreationFactory;
31  import org.apache.commons.digester.Rule;
32  import org.apache.commons.digester.RuleSet;
33  import org.apache.commons.digester.RuleSetBase;
34  import org.apache.commons.pipeline.Pipeline;
35  import org.apache.commons.pipeline.Stage;
36  import org.apache.commons.pipeline.StageDriver;
37  import org.apache.commons.pipeline.StageDriverFactory;
38  import org.xml.sax.Attributes;
39  
40  /**
41   * This is a Digester RuleSet that provides rules for parsing a pipeline
42   * XML file.
43   *
44   * The rules defined by this object are used for parsing the following tags:
45   * <ul>
46   *     <li>
47   *         <B><code>&lt;pipeline&gt;&lt;/pipeline&gt;</code></B><br/>
48   *         The root element of the XML configuration file for a pipeline. This tag
49   *         supports two optional attributes that are for use only when configuring
50   *         branch pipelines, <code>key</code> and <code>configURI</code>. These
51   *         attributes are described more fully below in the <code>&lt;branch&gt;</code>
52   *         documentation.
53   *     </li>
54   *     <li>
55   *         <B><code>&lt;driverFactory className="<em>MyDriverFactory</em>" id="<em>my_id</em>" ... /&gt;</code></B><br/>
56   *         This tag is used to create and configure a {@link StageDriverFactory} that
57   *         will be used to create {@link StageDriver} instances for stages
58   *         in the parent pipeline. Each {@link StageDriverFactory} is identified by a unique
59   *         string identifier that is used by the <code>&lt;stage&gt;</code> tag
60   *         to identify the factory used to create the driver for the stage.
61   *         The class of the factory (which must supply a no-argument constructor)
62   *         is specified by the <code>className</code> attribute, and all other
63   *         additional attributes are used by Digester to configure the associated properties
64   *         (using standard Java bean naming conventions) of the driver instance created.
65   *     </li>
66   *     <li>
67   *         <B><code>&lt;stage className="<em>MyStageClass</em>" driverFactoryId="<i>name</i>" ... &gt;&lt;/stage&gt;</code></B><br/>
68   *         A single stage is created, configured, and added to the parent pipeline using
69   *         this tag. Stages created in this manner are added to the pipeline in the order
70   *         that they occur in the configuration file. The class of the stage (which must
71   *         provide a no-argument constructor) is specified by the <em>className</em> attribute.
72   *         Each stage should be associated with a previously declared driver factory
73   *         by use of the <code>driverFactoryId</code> attribute. All other attributes are
74   *         used by Digester to set bean properties on the newly created Stage object.
75   *     </li>
76   *     <li>
77   *         <B><code>&lt;feed/&gt;</code></B><br/>
78   *         Enqueue an object onto the first stage in the pipeline. Note that this
79   *         must come <em>after</em> the first stage declaration in the configuration file,
80   *         otherwise the queue for the first stage does not exist yet and the fed
81   *         values will be discarded.
82   *         <p/>
83   *         There are two types of usage available, provided by the following subtags:
84   *         <ul>
85   *             <li>
86   *                 <B><code>&lt;value&gt;my_value&lt;/value&gt;</code></B><br/>
87   *                 Feed the string value of the body of this tag to the first stage in the
88   *                 pipeline.
89   *             </li>
90   *             <li>
91   *                 <B><code>&lt;object className="MyClass" ... /&gt;</code></B><br/>
92   *                 This tag uses the standard Digester ObjectCreateRule to create an
93   *                 arbitrary object of the specified class (which must provide a
94   *                 no-argument constructor) to the first stage in the pipeline.
95   *                 All attributes other than <code>className</codee> are used by
96   *                 Digester to set bean properties on the newly created object.
97   *             </li>
98   *         </ul>
99   *     </li>
100  *     <li>
101  *         <B><code>&lt;branch/&gt;</code></B><br/>
102  *         Add a branch to a pipeline. The contents of this tag should
103  *         be one or more <code>&lt;pipeline/&gt;</code> declarations. Branch
104  *         pipelines added in this fashion must be configured with an attribute
105  *         named <code>key</code> that holds the name by which the branch pipeline
106  *         will be referred to.
107  *         <p/>
108  *         Branch pipelines may be configured either inline in the main 
109  *         configuration file or in a separate file referred to by the
110  *         <code>configURI</code> pipeline attribute.
111  *     </li>
112  * </ul>
113  */
114 public class PipelineRuleSet extends RuleSetBase {
115     private static Class[] addBranchTypes = { String.class, Pipeline.class };
116     private static Class[] setEnvTypes = { String.class, Object.class };
117     private List<RuleSet> nestedRuleSets;
118     
119     /**
120      * Creates a new instance of the rule set used by Digester to configure a pipeline.
121      */
122     public PipelineRuleSet() {
123     }
124     
125     /**
126      * Creates a new pipeline rule set with the specified collection of additional
127      * rule sets that may be used for recursive creation of branch pipelines.
128      * @param nestedRuleSets A list of other RuleSet instances that are being used in conjunction with the
129      * PipelineRuleSet. In the case that branch pipelines are referred to by URI, these
130      * rule sets will be added to a new Digester instance (along with a PipelineRuleSet
131      * instance) that is used to parse the branch configuration file.
132      */
133     public PipelineRuleSet(List<RuleSet> nestedRuleSets) {
134         this.nestedRuleSets = nestedRuleSets;
135     }
136     
137     /**
138      * Adds the rule instances for pipeline, stage, and enqueue
139      * tasks to the Digester instance supplied.
140      * @param digester The Digester instance to which the rules should be added.
141      */
142     public void addRuleInstances(Digester digester) {
143         ObjectCreationFactory pipelineFactory = new PipelineFactory();
144         ObjectCreationFactory driverFactoryFactory = new StageDriverFactoryFactory();
145         
146         //rules to create pipeline
147         digester.addFactoryCreate("pipeline", pipelineFactory);
148         digester.addSetProperties("pipeline");
149         digester.addRule("pipeline", new PipelineDriverFactoriesRule());
150         
151         //these rules are used to add branches to the main pipeline
152         digester.addFactoryCreate("*/branch/pipeline", pipelineFactory);
153         digester.addRule("*/branch/pipeline", new CallMethodRule(1, "addBranch", 2, addBranchTypes));
154         digester.addCallParam("*/branch/pipeline", 0, "key");
155         digester.addCallParam("*/branch/pipeline", 1, 0);
156         digester.addRule("*/branch/pipeline", new PipelineDriverFactoriesRule());
157         
158         //rules for adding values to the global pipeline environment
159         digester.addObjectCreate("*/pipeline/env/object", "java.lang.Object", "className");
160         digester.addSetProperties("*/pipeline/env/object");
161         digester.addRule("*/pipeline/env/object", new CallMethodRule(1, "setEnv", 2, setEnvTypes));
162         digester.addCallParam("*/pipeline/env/object", 0, "key");
163         digester.addCallParam("*/pipeline/env/object", 1, 0);
164         
165         digester.addRule("*/pipeline/env/value", new CallMethodRule(0, "setEnv", 2, setEnvTypes));
166         digester.addCallParam("*/pipeline/env/value", 0, "key");
167         digester.addCallParam("*/pipeline/env/value", 1);
168         
169         //this rule is intended to be used to add a StageEventListener to the pipeline.
170         digester.addObjectCreate("*/pipeline/listener", "org.apache.commons.pipeline.StageEventListener", "className");
171         digester.addSetProperties("*/pipeline/listener");
172         digester.addSetNext("*/pipeline/listener", "registerListener", "org.apache.commons.pipeline.StageEventListener");
173         
174         //this rule is intended to be used to add a StageDriverFactory to the pipeline.
175         digester.addFactoryCreate("*/pipeline/driverFactory", driverFactoryFactory);
176         digester.addSetProperties("*/pipeline/driverFactory");
177         
178         digester.addObjectCreate("*/driverFactory", "org.apache.commons.pipeline.StageDriverFactory", "className");
179         digester.addSetProperties("*/driverFactory");
180         digester.addSetNext("*/driverFactory", "setWrappedSDF", "org.apache.commons.pipeline.StageDriverFactory");
181         
182         //rules for adding lifecycle jobs
183         digester.addObjectCreate("*/pipeline/lifecycleJob", "org.apache.commons.pipeline.PipelineLifecycleJob", "className");
184         digester.addSetProperties("*/pipeline/lifecycleJob");
185         digester.addSetNext("*/pipeline/lifecycleJob", "addLifecycleJob", "org.apache.commons.pipeline.PipelineLifecycleJob");
186         
187         //rules for setting an object property on the next-to-top object on the stack
188         //similar to setNestedPropertiesRule
189         digester.addObjectCreate("*/property", "java.lang.Object", "className");
190         digester.addSetProperties("*/property");
191         digester.addRule("*/property", new SetNestedPropertyObjectRule());
192         
193         //this rule is intended to be used to add a stage to a pipeline
194         digester.addObjectCreate("*/pipeline/stage", "org.apache.commons.pipeline.BaseStage", "className");
195         digester.addSetProperties("*/pipeline/stage");
196         digester.addRule("*/pipeline/stage", new PipelineAddStageRule());
197         
198         //rule for feeding a string value to the source feeder
199         digester.addRule("*/pipeline/feed/value", new PipelineFeedValueRule());
200         
201         //rules for enqueueing an object
202         digester.addObjectCreate("*/pipeline/feed/object", "java.lang.Object", "className");
203         digester.addSetProperties("*/pipeline/feed/object");
204         digester.addRule("*/pipeline/feed/object", new PipelineFeedObjectRule());
205     }
206     
207     /**
208      * This factory is used to create a pipeline. If the "configURI" parameter
209      * is specified, the pipeline is created based upon the configuration file
210      * located at that URI.
211      */
212     private class PipelineFactory extends AbstractObjectCreationFactory {
213         public Object createObject(Attributes attributes) throws java.lang.Exception {
214             String configURI = attributes.getValue("configURI");
215             if (configURI == null) {
216                 return new Pipeline();
217             } else {
218                 Digester subDigester = new Digester();
219                 if (nestedRuleSets != null) {
220                     for (RuleSet ruleset : nestedRuleSets) {
221                         subDigester.addRuleSet(ruleset);
222                     }
223                     
224                     Pipeline pipeline = (Pipeline) subDigester.parse(configURI);
225                     return pipeline;
226                 } else {
227                     throw new IllegalStateException("Unable to parse branch configuration file: No parsing rules provided to PipelineRuleSet constructor.");
228                 }
229             }
230         }
231     }
232     
233     /**
234      * Configure the storage for the map of driver factories for the pipeline.
235      */
236     private class PipelineDriverFactoriesRule extends Rule {
237         public void begin(String namespace, String name, Attributes attributes) throws Exception {
238             digester.push("org.apache.commons.pipeline.config.DriverFactories", new HashMap<String, StageDriverFactory>());
239             super.begin(namespace, name, attributes);
240         }
241         
242         public void end(String namespace, String name) throws Exception {
243             super.end(namespace, name);
244             digester.pop("org.apache.commons.pipeline.config.DriverFactories");
245         }
246     }
247     
248     /**
249      * Configure the storage for the map of driver factories for the pipeline.
250      */
251     private class SetNestedPropertyObjectRule extends Rule {
252         String propName;
253         
254         public void begin(String namespace, String name, Attributes attributes) throws Exception {
255             propName = attributes.getValue("propName");
256             super.begin(namespace, name, attributes);
257         }
258         
259         public void end(String namespace, String name) throws Exception {
260             super.end(namespace, name);
261             BeanUtils.setProperty(digester.peek(1), propName, digester.peek());
262         }
263     }
264     
265     /**
266      * This ObjectCreationFactory creates a stage driver factory and stores
267      * it in the scope of the rule set so that it can be retrieved by the stage
268      * creation rule.
269      */
270     private class StageDriverFactoryFactory extends AbstractObjectCreationFactory {
271         public Object createObject(Attributes attributes) throws Exception {
272             Map<String, StageDriverFactory> driverFactories =
273                     (Map<String,StageDriverFactory>) digester.peek("org.apache.commons.pipeline.config.DriverFactories");
274             
275             String className = attributes.getValue("className");
276             String id = attributes.getValue("id");
277             Class clazz = Class.forName(className);
278             if (!StageDriverFactory.class.isAssignableFrom(clazz)) {
279                 throw new IllegalArgumentException("Class " + clazz + " does not implement StageDriverFactory.");
280             } else {
281                 StageDriverFactory factory = (StageDriverFactory) clazz.newInstance();
282                 driverFactories.put(id, factory);
283                 return factory;
284             }
285         }
286     }
287     
288     /**
289      * This Rule adds a stage to the pipeline using the factory specified
290      * by the driverFactoryId attribute.
291      */
292     private class PipelineAddStageRule extends Rule {
293         public void begin(String namespace, String name, Attributes attributes) throws Exception {
294             digester.push("org.apache.commons.pipeline.config.DriverFactoryIds", attributes.getValue("driverFactoryId"));
295             super.begin(namespace, name, attributes);
296         }
297         
298         public void end(String namespace, String name) throws Exception {
299             super.end(namespace, name);
300             String factoryId = (String) digester.pop("org.apache.commons.pipeline.config.DriverFactoryIds");
301             Map<String, StageDriverFactory> driverFactories =
302                     (Map<String,StageDriverFactory>) digester.peek("org.apache.commons.pipeline.config.DriverFactories");
303             StageDriverFactory factory = driverFactories.get(factoryId);
304             Stage stage = (Stage) digester.peek();
305             Pipeline pipeline = (Pipeline) digester.peek(1);
306             pipeline.addStage(stage, factory);
307         }
308     }
309     
310     /**
311      * This Rule allows an object to be fed to the pipeline.
312      */
313     private class PipelineFeedValueRule extends Rule {
314         public void body(String namespace, String name, String text) throws Exception {
315             Pipeline pipeline = (Pipeline) digester.peek();
316             pipeline.getSourceFeeder().feed(text);
317             super.body(namespace, name, text);
318         }
319     }
320     
321     /**
322      * This Rule allows an object to be fed to the pipeline.
323      */
324     private class PipelineFeedObjectRule extends Rule {
325         public void end(String namespace, String name) throws Exception {
326             super.end(namespace, name);
327             Pipeline pipeline = (Pipeline) digester.peek(1);
328             pipeline.getSourceFeeder().feed(digester.peek());
329         }
330     }
331 }