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