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 }