001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     * 
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     * 
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.    
018     */ 
019    
020    package org.apache.commons.pipeline.config;
021    
022    import java.util.HashMap;
023    import java.util.List;
024    import java.util.Map;
025    
026    import org.apache.commons.beanutils.BeanUtils;
027    import org.apache.commons.digester.AbstractObjectCreationFactory;
028    import org.apache.commons.digester.CallMethodRule;
029    import org.apache.commons.digester.Digester;
030    import org.apache.commons.digester.ObjectCreationFactory;
031    import org.apache.commons.digester.Rule;
032    import org.apache.commons.digester.RuleSet;
033    import org.apache.commons.digester.RuleSetBase;
034    import org.apache.commons.pipeline.Pipeline;
035    import org.apache.commons.pipeline.Stage;
036    import org.apache.commons.pipeline.StageDriver;
037    import org.apache.commons.pipeline.StageDriverFactory;
038    import org.xml.sax.Attributes;
039    
040    /**
041     * This is a Digester RuleSet that provides rules for parsing a pipeline
042     * XML file.
043     *
044     * The rules defined by this object are used for parsing the following tags:
045     * <ul>
046     *     <li>
047     *         <B><code>&lt;pipeline&gt;&lt;/pipeline&gt;</code></B><br/>
048     *         The root element of the XML configuration file for a pipeline. This tag
049     *         supports two optional attributes that are for use only when configuring
050     *         branch pipelines, <code>key</code> and <code>configURI</code>. These
051     *         attributes are described more fully below in the <code>&lt;branch&gt;</code>
052     *         documentation.
053     *     </li>
054     *     <li>
055     *         <B><code>&lt;driverFactory className="<em>MyDriverFactory</em>" id="<em>my_id</em>" ... /&gt;</code></B><br/>
056     *         This tag is used to create and configure a {@link StageDriverFactory} that
057     *         will be used to create {@link StageDriver} instances for stages
058     *         in the parent pipeline. Each {@link StageDriverFactory} is identified by a unique
059     *         string identifier that is used by the <code>&lt;stage&gt;</code> tag
060     *         to identify the factory used to create the driver for the stage.
061     *         The class of the factory (which must supply a no-argument constructor)
062     *         is specified by the <code>className</code> attribute, and all other
063     *         additional attributes are used by Digester to configure the associated properties
064     *         (using standard Java bean naming conventions) of the driver instance created.
065     *     </li>
066     *     <li>
067     *         <B><code>&lt;stage className="<em>MyStageClass</em>" driverFactoryId="<i>name</i>" ... &gt;&lt;/stage&gt;</code></B><br/>
068     *         A single stage is created, configured, and added to the parent pipeline using
069     *         this tag. Stages created in this manner are added to the pipeline in the order
070     *         that they occur in the configuration file. The class of the stage (which must
071     *         provide a no-argument constructor) is specified by the <em>className</em> attribute.
072     *         Each stage should be associated with a previously declared driver factory
073     *         by use of the <code>driverFactoryId</code> attribute. All other attributes are
074     *         used by Digester to set bean properties on the newly created Stage object.
075     *     </li>
076     *     <li>
077     *         <B><code>&lt;feed/&gt;</code></B><br/>
078     *         Enqueue an object onto the first stage in the pipeline. Note that this
079     *         must come <em>after</em> the first stage declaration in the configuration file,
080     *         otherwise the queue for the first stage does not exist yet and the fed
081     *         values will be discarded.
082     *         <p/>
083     *         There are two types of usage available, provided by the following subtags:
084     *         <ul>
085     *             <li>
086     *                 <B><code>&lt;value&gt;my_value&lt;/value&gt;</code></B><br/>
087     *                 Feed the string value of the body of this tag to the first stage in the
088     *                 pipeline.
089     *             </li>
090     *             <li>
091     *                 <B><code>&lt;object className="MyClass" ... /&gt;</code></B><br/>
092     *                 This tag uses the standard Digester ObjectCreateRule to create an
093     *                 arbitrary object of the specified class (which must provide a
094     *                 no-argument constructor) to the first stage in the pipeline.
095     *                 All attributes other than <code>className</codee> are used by
096     *                 Digester to set bean properties on the newly created object.
097     *             </li>
098     *         </ul>
099     *     </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    }