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, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.oozie.workflow.lite; 020 021import java.io.ByteArrayInputStream; 022import java.io.ByteArrayOutputStream; 023import java.io.DataInput; 024import java.io.DataInputStream; 025import java.io.DataOutput; 026import java.io.DataOutputStream; 027import java.io.IOException; 028import java.io.Reader; 029import java.io.StringReader; 030import java.io.StringWriter; 031import java.util.ArrayList; 032import java.util.List; 033import java.util.zip.Deflater; 034import java.util.zip.DeflaterOutputStream; 035import java.util.zip.Inflater; 036import java.util.zip.InflaterInputStream; 037 038import javax.xml.transform.stream.StreamSource; 039import javax.xml.validation.Schema; 040import javax.xml.validation.Validator; 041 042import org.apache.commons.codec.binary.Base64; 043import org.apache.commons.lang.StringUtils; 044import org.apache.hadoop.conf.Configuration; 045import org.apache.hadoop.io.Writable; 046import org.apache.oozie.ErrorCode; 047import org.apache.oozie.action.ActionExecutor; 048import org.apache.oozie.action.hadoop.FsActionExecutor; 049import org.apache.oozie.action.oozie.SubWorkflowActionExecutor; 050import org.apache.oozie.service.ActionService; 051import org.apache.oozie.service.ConfigurationService; 052import org.apache.oozie.service.Services; 053import org.apache.oozie.util.ELUtils; 054import org.apache.oozie.util.IOUtils; 055import org.apache.oozie.util.ParameterVerifier; 056import org.apache.oozie.util.ParameterVerifierException; 057import org.apache.oozie.util.WritableUtils; 058import org.apache.oozie.util.XConfiguration; 059import org.apache.oozie.util.XmlUtils; 060import org.apache.oozie.workflow.WorkflowException; 061import org.jdom.Element; 062import org.jdom.JDOMException; 063import org.jdom.Namespace; 064import org.xml.sax.SAXException; 065 066/** 067 * Class to parse and validate workflow xml 068 */ 069public class LiteWorkflowAppParser { 070 071 private static final String LAUNCHER_E = "launcher"; 072 private static final String DECISION_E = "decision"; 073 private static final String ACTION_E = "action"; 074 private static final String END_E = "end"; 075 private static final String START_E = "start"; 076 private static final String JOIN_E = "join"; 077 private static final String FORK_E = "fork"; 078 private static final Object KILL_E = "kill"; 079 080 private static final String SLA_INFO = "info"; 081 private static final String CREDENTIALS = "credentials"; 082 private static final String GLOBAL = "global"; 083 private static final String PARAMETERS = "parameters"; 084 085 private static final String NAME_A = "name"; 086 private static final String CRED_A = "cred"; 087 private static final String USER_RETRY_MAX_A = "retry-max"; 088 private static final String USER_RETRY_INTERVAL_A = "retry-interval"; 089 private static final String TO_A = "to"; 090 private static final String USER_RETRY_POLICY_A = "retry-policy"; 091 092 private static final String FORK_PATH_E = "path"; 093 private static final String FORK_START_A = "start"; 094 095 private static final String ACTION_OK_E = "ok"; 096 private static final String ACTION_ERROR_E = "error"; 097 098 private static final String DECISION_SWITCH_E = "switch"; 099 private static final String DECISION_CASE_E = "case"; 100 private static final String DECISION_DEFAULT_E = "default"; 101 102 private static final String SUBWORKFLOW_E = "sub-workflow"; 103 104 private static final String KILL_MESSAGE_E = "message"; 105 public static final String VALIDATE_FORK_JOIN = "oozie.validate.ForkJoin"; 106 public static final String WF_VALIDATE_FORK_JOIN = "oozie.wf.validate.ForkJoin"; 107 108 public static final String DEFAULT_NAME_NODE = "oozie.actions.default.name-node"; 109 public static final String DEFAULT_JOB_TRACKER = "oozie.actions.default.job-tracker"; 110 public static final String OOZIE_GLOBAL = "oozie.wf.globalconf"; 111 112 private static final String JOB_TRACKER = "job-tracker"; 113 private static final String NAME_NODE = "name-node"; 114 private static final String JOB_XML = "job-xml"; 115 private static final String CONFIGURATION = "configuration"; 116 117 private Schema schema; 118 private Class<? extends ControlNodeHandler> controlNodeHandler; 119 private Class<? extends DecisionNodeHandler> decisionHandlerClass; 120 private Class<? extends ActionNodeHandler> actionHandlerClass; 121 122 private String defaultNameNode; 123 private String defaultJobTracker; 124 125 public LiteWorkflowAppParser(Schema schema, 126 Class<? extends ControlNodeHandler> controlNodeHandler, 127 Class<? extends DecisionNodeHandler> decisionHandlerClass, 128 Class<? extends ActionNodeHandler> actionHandlerClass) throws WorkflowException { 129 this.schema = schema; 130 this.controlNodeHandler = controlNodeHandler; 131 this.decisionHandlerClass = decisionHandlerClass; 132 this.actionHandlerClass = actionHandlerClass; 133 134 defaultNameNode = ConfigurationService.get(DEFAULT_NAME_NODE); 135 if (defaultNameNode != null) { 136 defaultNameNode = defaultNameNode.trim(); 137 if (defaultNameNode.isEmpty()) { 138 defaultNameNode = null; 139 } 140 } 141 defaultJobTracker = ConfigurationService.get(DEFAULT_JOB_TRACKER); 142 if (defaultJobTracker != null) { 143 defaultJobTracker = defaultJobTracker.trim(); 144 if (defaultJobTracker.isEmpty()) { 145 defaultJobTracker = null; 146 } 147 } 148 } 149 150 public LiteWorkflowApp validateAndParse(Reader reader, Configuration jobConf) throws WorkflowException { 151 return validateAndParse(reader, jobConf, null); 152 } 153 154 /** 155 * Parse and validate xml to {@link LiteWorkflowApp} 156 * 157 * @param reader 158 * @return LiteWorkflowApp 159 * @throws WorkflowException 160 */ 161 public LiteWorkflowApp validateAndParse(Reader reader, Configuration jobConf, Configuration configDefault) 162 throws WorkflowException { 163 try { 164 StringWriter writer = new StringWriter(); 165 IOUtils.copyCharStream(reader, writer); 166 String strDef = writer.toString(); 167 168 if (schema != null) { 169 Validator validator = schema.newValidator(); 170 validator.validate(new StreamSource(new StringReader(strDef))); 171 } 172 173 Element wfDefElement = XmlUtils.parseXml(strDef); 174 ParameterVerifier.verifyParameters(jobConf, wfDefElement); 175 LiteWorkflowApp app = parse(strDef, wfDefElement, configDefault, jobConf); 176 177 178 boolean validateForkJoin = false; 179 180 if (jobConf.getBoolean(WF_VALIDATE_FORK_JOIN, true) 181 && ConfigurationService.getBoolean(VALIDATE_FORK_JOIN)) { 182 validateForkJoin = true; 183 } 184 185 LiteWorkflowValidator validator = new LiteWorkflowValidator(); 186 validator.validateWorkflow(app, validateForkJoin); 187 188 return app; 189 } 190 catch (ParameterVerifierException ex) { 191 throw new WorkflowException(ex); 192 } 193 catch (JDOMException ex) { 194 throw new WorkflowException(ErrorCode.E0700, ex.getMessage(), ex); 195 } 196 catch (SAXException ex) { 197 throw new WorkflowException(ErrorCode.E0701, ex.getMessage(), ex); 198 } 199 catch (IOException ex) { 200 throw new WorkflowException(ErrorCode.E0702, ex.getMessage(), ex); 201 } 202 } 203 204 /** 205 * Parse xml to {@link LiteWorkflowApp} 206 * 207 * @param strDef 208 * @param root 209 * @param configDefault 210 * @param jobConf 211 * @return LiteWorkflowApp 212 * @throws WorkflowException 213 */ 214 @SuppressWarnings({"unchecked"}) 215 private LiteWorkflowApp parse(String strDef, Element root, Configuration configDefault, Configuration jobConf) 216 throws WorkflowException { 217 Namespace ns = root.getNamespace(); 218 219 LiteWorkflowApp def = null; 220 GlobalSectionData gData = jobConf.get(OOZIE_GLOBAL) == null ? 221 null : getGlobalFromString(jobConf.get(OOZIE_GLOBAL)); 222 boolean serializedGlobalConf = false; 223 for (Element eNode : (List<Element>) root.getChildren()) { 224 if (eNode.getName().equals(START_E)) { 225 def = new LiteWorkflowApp(root.getAttributeValue(NAME_A), strDef, 226 new StartNodeDef(controlNodeHandler, eNode.getAttributeValue(TO_A))); 227 } else if (eNode.getName().equals(END_E)) { 228 def.addNode(new EndNodeDef(eNode.getAttributeValue(NAME_A), controlNodeHandler)); 229 } else if (eNode.getName().equals(KILL_E)) { 230 def.addNode(new KillNodeDef(eNode.getAttributeValue(NAME_A), 231 eNode.getChildText(KILL_MESSAGE_E, ns), controlNodeHandler)); 232 } else if (eNode.getName().equals(FORK_E)) { 233 List<String> paths = new ArrayList<String>(); 234 for (Element tran : (List<Element>) eNode.getChildren(FORK_PATH_E, ns)) { 235 paths.add(tran.getAttributeValue(FORK_START_A)); 236 } 237 def.addNode(new ForkNodeDef(eNode.getAttributeValue(NAME_A), controlNodeHandler, paths)); 238 } else if (eNode.getName().equals(JOIN_E)) { 239 def.addNode(new JoinNodeDef(eNode.getAttributeValue(NAME_A), controlNodeHandler, eNode.getAttributeValue(TO_A))); 240 } else if (eNode.getName().equals(DECISION_E)) { 241 Element eSwitch = eNode.getChild(DECISION_SWITCH_E, ns); 242 List<String> transitions = new ArrayList<String>(); 243 for (Element e : (List<Element>) eSwitch.getChildren(DECISION_CASE_E, ns)) { 244 transitions.add(e.getAttributeValue(TO_A)); 245 } 246 transitions.add(eSwitch.getChild(DECISION_DEFAULT_E, ns).getAttributeValue(TO_A)); 247 248 String switchStatement = XmlUtils.prettyPrint(eSwitch).toString(); 249 def.addNode(new DecisionNodeDef(eNode.getAttributeValue(NAME_A), switchStatement, decisionHandlerClass, 250 transitions)); 251 } else if (ACTION_E.equals(eNode.getName())) { 252 String[] transitions = new String[2]; 253 Element eActionConf = null; 254 for (Element elem : (List<Element>) eNode.getChildren()) { 255 if (ACTION_OK_E.equals(elem.getName())) { 256 transitions[0] = elem.getAttributeValue(TO_A); 257 } else if (ACTION_ERROR_E.equals(elem.getName())) { 258 transitions[1] = elem.getAttributeValue(TO_A); 259 } else if (SLA_INFO.equals(elem.getName()) || CREDENTIALS.equals(elem.getName())) { 260 continue; 261 } else { 262 if (!serializedGlobalConf && elem.getName().equals(SubWorkflowActionExecutor.ACTION_TYPE) && 263 elem.getChild(("propagate-configuration"), ns) != null && gData != null) { 264 serializedGlobalConf = true; 265 jobConf.set(OOZIE_GLOBAL, getGlobalString(gData)); 266 } 267 eActionConf = elem; 268 if (SUBWORKFLOW_E.equals(elem.getName())) { 269 handleDefaultsAndGlobal(gData, null, elem, ns); 270 } 271 else { 272 handleDefaultsAndGlobal(gData, configDefault, elem, ns); 273 } 274 } 275 } 276 277 String credStr = eNode.getAttributeValue(CRED_A); 278 String userRetryMaxStr = eNode.getAttributeValue(USER_RETRY_MAX_A); 279 String userRetryIntervalStr = eNode.getAttributeValue(USER_RETRY_INTERVAL_A); 280 String userRetryPolicyStr = eNode.getAttributeValue(USER_RETRY_POLICY_A); 281 try { 282 if (!StringUtils.isEmpty(userRetryMaxStr)) { 283 userRetryMaxStr = ELUtils.resolveAppName(userRetryMaxStr, jobConf); 284 } 285 if (!StringUtils.isEmpty(userRetryIntervalStr)) { 286 userRetryIntervalStr = ELUtils.resolveAppName(userRetryIntervalStr, jobConf); 287 } 288 if (!StringUtils.isEmpty(userRetryPolicyStr)) { 289 userRetryPolicyStr = ELUtils.resolveAppName(userRetryPolicyStr, jobConf); 290 } 291 } 292 catch (Exception e) { 293 throw new WorkflowException(ErrorCode.E0703, e.getMessage()); 294 } 295 296 String actionConf = XmlUtils.prettyPrint(eActionConf).toString(); 297 def.addNode(new ActionNodeDef(eNode.getAttributeValue(NAME_A), actionConf, actionHandlerClass, 298 transitions[0], transitions[1], credStr, userRetryMaxStr, userRetryIntervalStr, 299 userRetryPolicyStr)); 300 } else if (SLA_INFO.equals(eNode.getName()) || CREDENTIALS.equals(eNode.getName())) { 301 // No operation is required 302 } else if (eNode.getName().equals(GLOBAL)) { 303 if(jobConf.get(OOZIE_GLOBAL) != null) { 304 gData = getGlobalFromString(jobConf.get(OOZIE_GLOBAL)); 305 handleDefaultsAndGlobal(gData, null, eNode, ns); 306 } 307 308 gData = parseGlobalSection(ns, eNode); 309 310 } else if (eNode.getName().equals(PARAMETERS)) { 311 // No operation is required 312 } else { 313 throw new WorkflowException(ErrorCode.E0703, eNode.getName()); 314 } 315 } 316 return def; 317 } 318 319 /** 320 * Read the GlobalSectionData from Base64 string. 321 * @param globalStr 322 * @return GlobalSectionData 323 * @throws WorkflowException 324 */ 325 private GlobalSectionData getGlobalFromString(String globalStr) throws WorkflowException { 326 GlobalSectionData globalSectionData = new GlobalSectionData(); 327 try { 328 byte[] data = Base64.decodeBase64(globalStr); 329 Inflater inflater = new Inflater(); 330 DataInputStream ois = new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(data), inflater)); 331 globalSectionData.readFields(ois); 332 ois.close(); 333 } catch (Exception ex) { 334 throw new WorkflowException(ErrorCode.E0700, "Error while processing global section conf"); 335 } 336 return globalSectionData; 337 } 338 339 340 /** 341 * Write the GlobalSectionData to a Base64 string. 342 * @param globalSectionData 343 * @return String 344 * @throws WorkflowException 345 */ 346 private String getGlobalString(GlobalSectionData globalSectionData) throws WorkflowException { 347 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 348 DataOutputStream oos = null; 349 try { 350 Deflater def = new Deflater(); 351 oos = new DataOutputStream(new DeflaterOutputStream(baos, def)); 352 globalSectionData.write(oos); 353 oos.close(); 354 } catch (IOException e) { 355 throw new WorkflowException(ErrorCode.E0700, "Error while processing global section conf"); 356 } 357 return Base64.encodeBase64String(baos.toByteArray()); 358 } 359 360 private void addChildElement(Element parent, Namespace ns, String childName, String childValue) { 361 Element child = new Element(childName, ns); 362 child.setText(childValue); 363 parent.addContent(child); 364 } 365 366 private class GlobalSectionData implements Writable { 367 String jobTracker; 368 String nameNode; 369 List<String> jobXmls; 370 Configuration conf; 371 372 public GlobalSectionData() { 373 } 374 375 public GlobalSectionData(String jobTracker, String nameNode, List<String> jobXmls, Configuration conf) { 376 this.jobTracker = jobTracker; 377 this.nameNode = nameNode; 378 this.jobXmls = jobXmls; 379 this.conf = conf; 380 } 381 382 @Override 383 public void write(DataOutput dataOutput) throws IOException { 384 WritableUtils.writeStr(dataOutput, jobTracker); 385 WritableUtils.writeStr(dataOutput, nameNode); 386 387 if(jobXmls != null && !jobXmls.isEmpty()) { 388 dataOutput.writeInt(jobXmls.size()); 389 for (String content : jobXmls) { 390 WritableUtils.writeStr(dataOutput, content); 391 } 392 } else { 393 dataOutput.writeInt(0); 394 } 395 if(conf != null) { 396 WritableUtils.writeStr(dataOutput, XmlUtils.prettyPrint(conf).toString()); 397 } else { 398 WritableUtils.writeStr(dataOutput, null); 399 } 400 } 401 402 @Override 403 public void readFields(DataInput dataInput) throws IOException { 404 jobTracker = WritableUtils.readStr(dataInput); 405 nameNode = WritableUtils.readStr(dataInput); 406 int length = dataInput.readInt(); 407 if (length > 0) { 408 jobXmls = new ArrayList<String>(); 409 for (int i = 0; i < length; i++) { 410 jobXmls.add(WritableUtils.readStr(dataInput)); 411 } 412 } 413 String confString = WritableUtils.readStr(dataInput); 414 if(confString != null) { 415 conf = new XConfiguration(new StringReader(confString)); 416 } 417 } 418 } 419 420 private GlobalSectionData parseGlobalSection(Namespace ns, Element global) throws WorkflowException { 421 GlobalSectionData gData = null; 422 if (global != null) { 423 String globalJobTracker = null; 424 Element globalJobTrackerElement = global.getChild(JOB_TRACKER, ns); 425 if (globalJobTrackerElement != null) { 426 globalJobTracker = globalJobTrackerElement.getValue(); 427 } 428 429 String globalNameNode = null; 430 Element globalNameNodeElement = global.getChild(NAME_NODE, ns); 431 if (globalNameNodeElement != null) { 432 globalNameNode = globalNameNodeElement.getValue(); 433 } 434 435 List<String> globalJobXmls = null; 436 @SuppressWarnings("unchecked") 437 List<Element> globalJobXmlElements = global.getChildren(JOB_XML, ns); 438 if (!globalJobXmlElements.isEmpty()) { 439 globalJobXmls = new ArrayList<String>(globalJobXmlElements.size()); 440 for(Element jobXmlElement: globalJobXmlElements) { 441 globalJobXmls.add(jobXmlElement.getText()); 442 } 443 } 444 445 Configuration globalConf = new XConfiguration(); 446 Element globalConfigurationElement = global.getChild(CONFIGURATION, ns); 447 if (globalConfigurationElement != null) { 448 try { 449 globalConf = new XConfiguration(new StringReader(XmlUtils.prettyPrint(globalConfigurationElement).toString())); 450 } catch (IOException ioe) { 451 throw new WorkflowException(ErrorCode.E0700, "Error while processing global section conf"); 452 } 453 } 454 455 Element globalLauncherElement = global.getChild(LAUNCHER_E, ns); 456 if (globalLauncherElement != null) { 457 LauncherConfigHandler launcherConfigHandler = new LauncherConfigHandler(globalConf, globalLauncherElement, ns); 458 launcherConfigHandler.processSettings(); 459 } 460 gData = new GlobalSectionData(globalJobTracker, globalNameNode, globalJobXmls, globalConf); 461 } 462 return gData; 463 } 464 465 private void handleDefaultsAndGlobal(GlobalSectionData gData, Configuration configDefault, Element actionElement, Namespace ns) 466 throws WorkflowException { 467 468 ActionExecutor ae = Services.get().get(ActionService.class).getExecutor(actionElement.getName()); 469 if (ae == null && !GLOBAL.equals(actionElement.getName())) { 470 throw new WorkflowException(ErrorCode.E0723, actionElement.getName(), ActionService.class.getName()); 471 } 472 473 Namespace actionNs = actionElement.getNamespace(); 474 475 // If this is the global section or ActionExecutor.requiresNameNodeJobTracker() returns true, we parse the action's 476 // <name-node> and <job-tracker> fields. If those aren't defined, we take them from the <global> section. If those 477 // aren't defined, we take them from the oozie-site defaults. If those aren't defined, we throw a WorkflowException. 478 // However, for the SubWorkflow and FS Actions, as well as the <global> section, we don't throw the WorkflowException. 479 // Also, we only parse the NN (not the JT) for the FS Action. 480 if (SubWorkflowActionExecutor.ACTION_TYPE.equals(actionElement.getName()) || 481 FsActionExecutor.ACTION_TYPE.equals(actionElement.getName()) || 482 GLOBAL.equals(actionElement.getName()) || ae.requiresNameNodeJobTracker()) { 483 if (actionElement.getChild(NAME_NODE, actionNs) == null) { 484 if (gData != null && gData.nameNode != null) { 485 addChildElement(actionElement, actionNs, NAME_NODE, gData.nameNode); 486 } else if (defaultNameNode != null) { 487 addChildElement(actionElement, actionNs, NAME_NODE, defaultNameNode); 488 } else if (!(SubWorkflowActionExecutor.ACTION_TYPE.equals(actionElement.getName()) || 489 FsActionExecutor.ACTION_TYPE.equals(actionElement.getName()) || 490 GLOBAL.equals(actionElement.getName()))) { 491 throw new WorkflowException(ErrorCode.E0701, "No " + NAME_NODE + " defined"); 492 } 493 } 494 if (actionElement.getChild(JOB_TRACKER, actionNs) == null && 495 !FsActionExecutor.ACTION_TYPE.equals(actionElement.getName())) { 496 if (gData != null && gData.jobTracker != null) { 497 addChildElement(actionElement, actionNs, JOB_TRACKER, gData.jobTracker); 498 } else if (defaultJobTracker != null) { 499 addChildElement(actionElement, actionNs, JOB_TRACKER, defaultJobTracker); 500 } else if (!(SubWorkflowActionExecutor.ACTION_TYPE.equals(actionElement.getName()) || 501 GLOBAL.equals(actionElement.getName()))) { 502 throw new WorkflowException(ErrorCode.E0701, "No " + JOB_TRACKER + " defined"); 503 } 504 } 505 } 506 507 // If this is the global section or ActionExecutor.supportsConfigurationJobXML() returns true, we parse the action's 508 // <configuration> and <job-xml> fields. We also merge this with those from the <global> section, if given. If none are 509 // defined, empty values are placed. Exceptions are thrown if there's an error parsing, but not if they're not given. 510 if (GLOBAL.equals(actionElement.getName()) || ae.supportsConfigurationJobXML()) { 511 @SuppressWarnings("unchecked") 512 List<Element> actionJobXmls = actionElement.getChildren(JOB_XML, actionNs); 513 if (gData != null && gData.jobXmls != null) { 514 for(String gJobXml : gData.jobXmls) { 515 boolean alreadyExists = false; 516 for (Element actionXml : actionJobXmls) { 517 if (gJobXml.equals(actionXml.getText())) { 518 alreadyExists = true; 519 break; 520 } 521 } 522 if (!alreadyExists) { 523 Element ejobXml = new Element(JOB_XML, actionNs); 524 ejobXml.setText(gJobXml); 525 actionElement.addContent(ejobXml); 526 } 527 } 528 } 529 530 try { 531 XConfiguration actionConf = new XConfiguration(); 532 if (configDefault != null) 533 XConfiguration.copy(configDefault, actionConf); 534 if (gData != null && gData.conf != null) { 535 XConfiguration.copy(gData.conf, actionConf); 536 } 537 538 Element launcherConfiguration = actionElement.getChild(LAUNCHER_E, actionNs); 539 if (launcherConfiguration != null) { 540 LauncherConfigHandler launcherConfigHandler = new LauncherConfigHandler(actionConf, launcherConfiguration, actionNs); 541 launcherConfigHandler.processSettings(); 542 } 543 544 Element actionConfiguration = actionElement.getChild(CONFIGURATION, actionNs); 545 if (actionConfiguration != null) { 546 //copy and override 547 XConfiguration.copy(new XConfiguration(new StringReader(XmlUtils.prettyPrint(actionConfiguration).toString())), 548 actionConf); 549 } 550 551 int position = actionElement.indexOf(actionConfiguration); 552 actionElement.removeContent(actionConfiguration); //replace with enhanced one 553 Element eConfXml = XmlUtils.parseXml(actionConf.toXmlString(false)); 554 eConfXml.detach(); 555 eConfXml.setNamespace(actionNs); 556 if (position > 0) { 557 actionElement.addContent(position, eConfXml); 558 } 559 else { 560 actionElement.addContent(eConfXml); 561 } 562 } 563 catch (IOException e) { 564 throw new WorkflowException(ErrorCode.E0700, "Error while processing action conf"); 565 } 566 catch (JDOMException e) { 567 throw new WorkflowException(ErrorCode.E0700, "Error while processing action conf"); 568 } 569 } 570 } 571}