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><pipeline></pipeline></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><branch></code>
52 * documentation.
53 * </li>
54 * <li>
55 * <B><code><driverFactory className="<em>MyDriverFactory</em>" id="<em>my_id</em>" ... /></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><stage></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><stage className="<em>MyStageClass</em>" driverFactoryId="<i>name</i>" ... ></stage></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><feed/></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><value>my_value</value></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><object className="MyClass" ... /></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><branch/></code></B><br/>
102 * Add a branch to a pipeline. The contents of this tag should
103 * be one or more <code><pipeline/></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 }