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><pipeline></pipeline></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><branch></code>
052 * documentation.
053 * </li>
054 * <li>
055 * <B><code><driverFactory className="<em>MyDriverFactory</em>" id="<em>my_id</em>" ... /></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><stage></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><stage className="<em>MyStageClass</em>" driverFactoryId="<i>name</i>" ... ></stage></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><feed/></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><value>my_value</value></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><object className="MyClass" ... /></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><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 }