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 */
018package org.apache.oozie.command.coord;
019
020import java.io.StringReader;
021import java.net.URI;
022import java.util.Calendar;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027
028import org.apache.hadoop.conf.Configuration;
029import org.apache.oozie.CoordinatorActionBean;
030import org.apache.oozie.ErrorCode;
031import org.apache.oozie.client.CoordinatorAction;
032import org.apache.oozie.command.CommandException;
033import org.apache.oozie.coord.CoordELEvaluator;
034import org.apache.oozie.coord.CoordELFunctions;
035import org.apache.oozie.coord.CoordUtils;
036import org.apache.oozie.coord.CoordinatorJobException;
037import org.apache.oozie.coord.SyncCoordAction;
038import org.apache.oozie.coord.TimeUnit;
039import org.apache.oozie.dependency.ActionDependency;
040import org.apache.oozie.dependency.DependencyChecker;
041import org.apache.oozie.dependency.URIHandler;
042import org.apache.oozie.dependency.URIHandler.DependencyType;
043import org.apache.oozie.service.Services;
044import org.apache.oozie.service.URIHandlerService;
045import org.apache.oozie.service.UUIDService;
046import org.apache.oozie.util.DateUtils;
047import org.apache.oozie.util.ELEvaluator;
048import org.apache.oozie.util.XConfiguration;
049import org.apache.oozie.util.XmlUtils;
050import org.jdom.Element;
051
052public class CoordCommandUtils {
053    public static int CURRENT = 0;
054    public static int LATEST = 1;
055    public static int FUTURE = 2;
056    public static int OFFSET = 3;
057    public static int UNEXPECTED = -1;
058    public static final String RESOLVED_UNRESOLVED_SEPARATOR = "!!";
059    public static final String UNRESOLVED_INST_TAG = "unresolved-instances";
060
061    /**
062     * parse a function like coord:latest(n)/future() and return the 'n'.
063     * <p/>
064     *
065     * @param function
066     * @param restArg
067     * @return int instanceNumber
068     * @throws Exception
069     */
070    public static int getInstanceNumber(String function, StringBuilder restArg) throws Exception {
071        int funcType = getFuncType(function);
072        if (funcType == CURRENT || funcType == LATEST) {
073            return parseOneArg(function);
074        }
075        else {
076            return parseMoreArgs(function, restArg);
077        }
078    }
079
080    /**
081     * Evaluates function for coord-action-create-inst tag
082     * @param event
083     * @param appInst
084     * @param conf
085     * @param function
086     * @return evaluation result
087     * @throws Exception
088     */
089    private static String evaluateInstanceFunction(Element event, SyncCoordAction appInst, Configuration conf,
090            String function) throws Exception {
091        ELEvaluator eval = CoordELEvaluator.createInstancesELEvaluator("coord-action-create-inst", event, appInst, conf);
092        return CoordELFunctions.evalAndWrap(eval, function);
093    }
094
095    public static int parseOneArg(String funcName) throws Exception {
096        int firstPos = funcName.indexOf("(");
097        int lastPos = funcName.lastIndexOf(")");
098        if (firstPos >= 0 && lastPos > firstPos) {
099            String tmp = funcName.substring(firstPos + 1, lastPos).trim();
100            if (tmp.length() > 0) {
101                return (int) Double.parseDouble(tmp);
102            }
103        }
104        throw new RuntimeException("Unformatted function :" + funcName);
105    }
106
107    private static int parseMoreArgs(String funcName, StringBuilder restArg) throws Exception {
108        int firstPos = funcName.indexOf("(");
109        int secondPos = funcName.lastIndexOf(",");
110        int lastPos = funcName.lastIndexOf(")");
111        if (firstPos >= 0 && secondPos > firstPos) {
112            String tmp = funcName.substring(firstPos + 1, secondPos).trim();
113            if (tmp.length() > 0) {
114                restArg.append(funcName.substring(secondPos + 1, lastPos).trim());
115                return (int) Double.parseDouble(tmp);
116            }
117        }
118        throw new RuntimeException("Unformatted function :" + funcName);
119    }
120
121    /**
122     * @param EL function name
123     * @return type of EL function
124     */
125    public static int getFuncType(String function) {
126        if (function.indexOf("current") >= 0) {
127            return CURRENT;
128        }
129        else if (function.indexOf("latest") >= 0) {
130            return LATEST;
131        }
132        else if (function.indexOf("future") >= 0) {
133            return FUTURE;
134        }
135        else if (function.indexOf("offset") >= 0) {
136            return OFFSET;
137        }
138        return UNEXPECTED;
139        // throw new RuntimeException("Unexpected instance name "+ function);
140    }
141
142    /**
143     * @param startInst: EL function name
144     * @param endInst: EL function name
145     * @throws CommandException if both are not the same function
146     */
147    public static void checkIfBothSameType(String startInst, String endInst) throws CommandException {
148        if (getFuncType(startInst) != getFuncType(endInst)) {
149            throw new CommandException(ErrorCode.E1010,
150                    " start-instance and end-instance both should be either latest or current or future or offset\n"
151                            + " start " + startInst + " and end " + endInst);
152        }
153    }
154
155    /**
156     * Resolve list of <instance> </instance> tags.
157     *
158     * @param event
159     * @param instances
160     * @param actionInst
161     * @param conf
162     * @param eval: ELEvalautor
163     * @throws Exception
164     */
165    public static void resolveInstances(Element event, StringBuilder instances, SyncCoordAction actionInst,
166            Configuration conf, ELEvaluator eval) throws Exception {
167        for (Element eInstance : (List<Element>) event.getChildren("instance", event.getNamespace())) {
168            if (instances.length() > 0) {
169                instances.append(CoordELFunctions.INSTANCE_SEPARATOR);
170            }
171            instances.append(materializeInstance(event, eInstance.getTextTrim(), actionInst, conf, eval));
172        }
173        event.removeChildren("instance", event.getNamespace());
174    }
175
176    /**
177     * Resolve <start-instance> <end-insatnce> tag. Don't resolve any
178     * latest()/future()
179     *
180     * @param event
181     * @param instances
182     * @param appInst
183     * @param conf
184     * @param eval: ELEvalautor
185     * @throws Exception
186     */
187    public static void resolveInstanceRange(Element event, StringBuilder instances, SyncCoordAction appInst,
188            Configuration conf, ELEvaluator eval) throws Exception {
189        Element eStartInst = event.getChild("start-instance", event.getNamespace());
190        Element eEndInst = event.getChild("end-instance", event.getNamespace());
191        if (eStartInst != null && eEndInst != null) {
192            String strStart = evaluateInstanceFunction(event, appInst, conf, eStartInst.getTextTrim());
193            String strEnd = evaluateInstanceFunction(event, appInst, conf, eEndInst.getTextTrim());
194            checkIfBothSameType(strStart, strEnd);
195            StringBuilder restArg = new StringBuilder(); // To store rest
196                                                         // arguments for
197                                                         // future
198                                                         // function
199            int startIndex = getInstanceNumber(strStart, restArg);
200            String startRestArg = restArg.toString();
201            restArg.delete(0, restArg.length());
202            int endIndex = getInstanceNumber(strEnd, restArg);
203            String endRestArg = restArg.toString();
204            int funcType = getFuncType(strStart);
205            if (funcType == OFFSET) {
206                TimeUnit startU = TimeUnit.valueOf(startRestArg);
207                TimeUnit endU = TimeUnit.valueOf(endRestArg);
208                if (startU.getCalendarUnit() * startIndex > endU.getCalendarUnit() * endIndex) {
209                    throw new CommandException(ErrorCode.E1010,
210                            " start-instance should be equal or earlier than the end-instance \n"
211                                    + XmlUtils.prettyPrint(event));
212                }
213                Calendar startCal = CoordELFunctions.resolveOffsetRawTime(startIndex, startU, eval);
214                Calendar endCal = CoordELFunctions.resolveOffsetRawTime(endIndex, endU, eval);
215                if (startCal != null && endCal != null) {
216                    List<Integer> expandedFreqs = CoordELFunctions.expandOffsetTimes(startCal, endCal, eval);
217                    for (int i = expandedFreqs.size() - 1; i >= 0; i--) {
218                        String matInstance = materializeInstance(event, "${coord:offset(" + expandedFreqs.get(i)
219                                + ", \"MINUTE\")}", appInst, conf, eval);
220                        if (matInstance == null || matInstance.length() == 0) {
221                            // Earlier than dataset's initial instance
222                            break;
223                        }
224                        if (instances.length() > 0) {
225                            instances.append(CoordELFunctions.INSTANCE_SEPARATOR);
226                        }
227                        instances.append(matInstance);
228                    }
229                }
230            }
231            else {
232                if (startIndex > endIndex) {
233                    throw new CommandException(ErrorCode.E1010,
234                            " start-instance should be equal or earlier than the end-instance \n"
235                                    + XmlUtils.prettyPrint(event));
236                }
237                if (funcType == CURRENT) {
238                    // Everything could be resolved NOW. no latest() ELs
239                    String matInstance = materializeInstance(event, "${coord:currentRange(" + startIndex + ","
240                            + endIndex + ")}", appInst, conf, eval);
241                    if (matInstance != null && !matInstance.isEmpty()) {
242                        if (instances.length() > 0) {
243                            instances.append(CoordELFunctions.INSTANCE_SEPARATOR);
244                        }
245                        instances.append(matInstance);
246                    }
247                }
248                else { // latest(n)/future() EL is present
249                    if (funcType == LATEST) {
250                        instances.append("${coord:latestRange(").append(startIndex).append(",").append(endIndex)
251                                .append(")}");
252                    }
253                    else if (funcType == FUTURE) {
254                        instances.append("${coord:futureRange(").append(startIndex).append(",").append(endIndex)
255                                .append(",'").append(endRestArg).append("')}");
256                    }
257                }
258            }
259            // Remove start-instance and end-instances
260            event.removeChild("start-instance", event.getNamespace());
261            event.removeChild("end-instance", event.getNamespace());
262        }
263    }
264
265    /**
266     * Materialize one instance like current(-2)
267     *
268     * @param event : <data-in>
269     * @param expr : instance like current(-1)
270     * @param appInst : application specific info
271     * @param conf
272     * @param evalInst :ELEvaluator
273     * @return materialized date string
274     * @throws Exception
275     */
276    public static String materializeInstance(Element event, String expr, SyncCoordAction appInst, Configuration conf,
277            ELEvaluator evalInst) throws Exception {
278        if (event == null) {
279            return null;
280        }
281        // ELEvaluator eval = CoordELEvaluator.createInstancesELEvaluator(event,
282        // appInst, conf);
283        return CoordELFunctions.evalAndWrap(evalInst, expr);
284    }
285
286    /**
287     * Create two new tags with <uris> and <unresolved-instances>.
288     *
289     * @param event
290     * @param instances
291     * @throws Exception
292     */
293    private static String separateResolvedAndUnresolved(Element event, StringBuilder instances)
294            throws Exception {
295        StringBuilder unresolvedInstances = new StringBuilder();
296        StringBuilder urisWithDoneFlag = new StringBuilder();
297        StringBuilder depList = new StringBuilder();
298        String uris = createEarlyURIs(event, instances.toString(), unresolvedInstances, urisWithDoneFlag);
299        if (uris.length() > 0) {
300            Element uriInstance = new Element("uris", event.getNamespace());
301            uriInstance.addContent(uris);
302            event.getContent().add(1, uriInstance);
303            if (depList.length() > 0) {
304                depList.append(CoordELFunctions.INSTANCE_SEPARATOR);
305            }
306            depList.append(urisWithDoneFlag);
307        }
308        if (unresolvedInstances.length() > 0) {
309            Element elemInstance = new Element(UNRESOLVED_INST_TAG, event.getNamespace());
310            elemInstance.addContent(unresolvedInstances.toString());
311            event.getContent().add(1, elemInstance);
312        }
313        return depList.toString();
314    }
315
316    /**
317     * The function create a list of URIs separated by "," using the instances
318     * time stamp and URI-template
319     *
320     * @param event : <data-in> event
321     * @param instances : List of time stamp separated by ","
322     * @param unresolvedInstances : list of instance with latest function
323     * @param urisWithDoneFlag : list of URIs with the done flag appended
324     * @return : list of URIs separated by ";" as a string.
325     * @throws Exception
326     */
327    public static String createEarlyURIs(Element event, String instances, StringBuilder unresolvedInstances,
328            StringBuilder urisWithDoneFlag) throws Exception {
329        if (instances == null || instances.length() == 0) {
330            return "";
331        }
332        String[] instanceList = instances.split(CoordELFunctions.INSTANCE_SEPARATOR);
333        StringBuilder uris = new StringBuilder();
334
335        Element doneFlagElement = event.getChild("dataset", event.getNamespace()).getChild("done-flag",
336                event.getNamespace());
337        URIHandlerService uriService = Services.get().get(URIHandlerService.class);
338
339        for (int i = 0; i < instanceList.length; i++) {
340            if (instanceList[i].trim().length() == 0) {
341                continue;
342            }
343            int funcType = getFuncType(instanceList[i]);
344            if (funcType == LATEST || funcType == FUTURE) {
345                if (unresolvedInstances.length() > 0) {
346                    unresolvedInstances.append(CoordELFunctions.INSTANCE_SEPARATOR);
347                }
348                unresolvedInstances.append(instanceList[i]);
349                continue;
350            }
351            ELEvaluator eval = CoordELEvaluator.createURIELEvaluator(instanceList[i]);
352            if (uris.length() > 0) {
353                uris.append(CoordELFunctions.INSTANCE_SEPARATOR);
354                urisWithDoneFlag.append(CoordELFunctions.INSTANCE_SEPARATOR);
355            }
356
357            String uriPath = CoordELFunctions.evalAndWrap(eval, event.getChild("dataset", event.getNamespace())
358                    .getChild("uri-template", event.getNamespace()).getTextTrim());
359            URIHandler uriHandler = uriService.getURIHandler(uriPath);
360            uriHandler.validate(uriPath);
361            uris.append(uriPath);
362            urisWithDoneFlag.append(uriHandler.getURIWithDoneFlag(uriPath, CoordUtils.getDoneFlag(doneFlagElement)));
363        }
364        return uris.toString();
365    }
366
367    /**
368     * @param eSla
369     * @param nominalTime
370     * @param conf
371     * @return boolean to determine whether the SLA element is present or not
372     * @throws CoordinatorJobException
373     */
374    public static boolean materializeSLA(Element eSla, Date nominalTime, Configuration conf)
375            throws CoordinatorJobException {
376        if (eSla == null) {
377            // eAppXml.getNamespace("sla"));
378            return false;
379        }
380        try {
381            ELEvaluator evalSla = CoordELEvaluator.createSLAEvaluator(nominalTime, conf);
382            List<Element> elemList = eSla.getChildren();
383            for (Element elem : elemList) {
384                String updated;
385                try {
386                    updated = CoordELFunctions.evalAndWrap(evalSla, elem.getText().trim());
387                }
388                catch (Exception e) {
389                    throw new CoordinatorJobException(ErrorCode.E1004, e.getMessage(), e);
390                }
391                elem.removeContent();
392                elem.addContent(updated);
393            }
394        }
395        catch (Exception e) {
396            throw new CoordinatorJobException(ErrorCode.E1004, e.getMessage(), e);
397        }
398        return true;
399    }
400
401    /**
402     * Materialize one instance for specific nominal time. It includes: 1.
403     * Materialize data events (i.e. <data-in> and <data-out>) 2. Materialize
404     * data properties (i.e dataIn(<DS>) and dataOut(<DS>) 3. remove 'start' and
405     * 'end' tag 4. Add 'instance_number' and 'nominal-time' tag
406     *
407     * @param jobId coordinator job id
408     * @param dryrun true if it is dryrun
409     * @param eAction frequency unexploded-job
410     * @param nominalTime materialization time
411     * @param actualTime action actual time
412     * @param instanceCount instance numbers
413     * @param conf job configuration
414     * @param actionBean CoordinatorActionBean to materialize
415     * @return one materialized action for specific nominal time
416     * @throws Exception
417     */
418    @SuppressWarnings("unchecked")
419    public static String materializeOneInstance(String jobId, boolean dryrun, Element eAction, Date nominalTime,
420            Date actualTime, int instanceCount, Configuration conf, CoordinatorActionBean actionBean) throws Exception {
421        String actionId = Services.get().get(UUIDService.class).generateChildId(jobId, instanceCount + "");
422        SyncCoordAction appInst = new SyncCoordAction();
423        appInst.setActionId(actionId);
424        appInst.setName(eAction.getAttributeValue("name"));
425        appInst.setNominalTime(nominalTime);
426        appInst.setActualTime(actualTime);
427        int frequency = Integer.parseInt(eAction.getAttributeValue("frequency"));
428        appInst.setFrequency(frequency);
429        appInst.setTimeUnit(TimeUnit.valueOf(eAction.getAttributeValue("freq_timeunit")));
430        appInst.setTimeZone(DateUtils.getTimeZone(eAction.getAttributeValue("timezone")));
431        appInst.setEndOfDuration(TimeUnit.valueOf(eAction.getAttributeValue("end_of_duration")));
432
433        Map<String, StringBuilder> dependencyMap = null;
434
435        Element inputList = eAction.getChild("input-events", eAction.getNamespace());
436        List<Element> dataInList = null;
437        if (inputList != null) {
438            dataInList = inputList.getChildren("data-in", eAction.getNamespace());
439            dependencyMap = materializeDataEvents(dataInList, appInst, conf);
440        }
441
442        Element outputList = eAction.getChild("output-events", eAction.getNamespace());
443        List<Element> dataOutList = null;
444        if (outputList != null) {
445            dataOutList = outputList.getChildren("data-out", eAction.getNamespace());
446            materializeDataEvents(dataOutList, appInst, conf);
447        }
448
449        eAction.removeAttribute("start");
450        eAction.removeAttribute("end");
451        eAction.setAttribute("instance-number", Integer.toString(instanceCount));
452        eAction.setAttribute("action-nominal-time", DateUtils.formatDateOozieTZ(nominalTime));
453        eAction.setAttribute("action-actual-time", DateUtils.formatDateOozieTZ(actualTime));
454
455        boolean isSla = CoordCommandUtils.materializeSLA(
456                eAction.getChild("action", eAction.getNamespace()).getChild("info", eAction.getNamespace("sla")),
457                nominalTime, conf);
458
459        // Setting up action bean
460        actionBean.setCreatedConf(XmlUtils.prettyPrint(conf).toString());
461        actionBean.setRunConf(XmlUtils.prettyPrint(conf).toString());
462        actionBean.setCreatedTime(actualTime);
463        actionBean.setJobId(jobId);
464        actionBean.setId(actionId);
465        actionBean.setLastModifiedTime(new Date());
466        actionBean.setStatus(CoordinatorAction.Status.WAITING);
467        actionBean.setActionNumber(instanceCount);
468        if (dependencyMap != null) {
469            StringBuilder sbPull = dependencyMap.get(DependencyType.PULL.name());
470            if (sbPull != null) {
471                actionBean.setMissingDependencies(sbPull.toString());
472            }
473            StringBuilder sbPush = dependencyMap.get(DependencyType.PUSH.name());
474            if (sbPush != null) {
475                actionBean.setPushMissingDependencies(sbPush.toString());
476            }
477        }
478        actionBean.setNominalTime(nominalTime);
479        if (isSla == true) {
480            actionBean.setSlaXml(XmlUtils.prettyPrint(
481                    eAction.getChild("action", eAction.getNamespace()).getChild("info", eAction.getNamespace("sla")))
482                    .toString());
483        }
484
485        // actionBean.setTrackerUri(trackerUri);//TOOD:
486        // actionBean.setConsoleUrl(consoleUrl); //TODO:
487        // actionBean.setType(type);//TODO:
488        // actionBean.setErrorInfo(errorCode, errorMessage); //TODO:
489        // actionBean.setExternalStatus(externalStatus);//TODO
490        if (!dryrun) {
491            return XmlUtils.prettyPrint(eAction).toString();
492        }
493        else {
494            return dryRunCoord(eAction, actionBean);
495        }
496    }
497
498    /**
499     * @param eAction the actionXml related element
500     * @param actionBean the coordinator action bean
501     * @return
502     * @throws Exception
503     */
504    static String dryRunCoord(Element eAction, CoordinatorActionBean actionBean) throws Exception {
505        String action = XmlUtils.prettyPrint(eAction).toString();
506        StringBuilder actionXml = new StringBuilder(action);
507        Configuration actionConf = new XConfiguration(new StringReader(actionBean.getRunConf()));
508
509        boolean isPushDepAvailable = true;
510        if (actionBean.getPushMissingDependencies() != null) {
511            ActionDependency actionDep = DependencyChecker.checkForAvailability(
512                    actionBean.getPushMissingDependencies(), actionConf, true);
513            if (actionDep.getMissingDependencies().size() != 0) {
514                isPushDepAvailable = false;
515            }
516
517        }
518        boolean isPullDepAvailable = true;
519        CoordActionInputCheckXCommand coordActionInput = new CoordActionInputCheckXCommand(actionBean.getId(),
520                actionBean.getJobId());
521        if (actionBean.getMissingDependencies() != null) {
522            StringBuilder existList = new StringBuilder();
523            StringBuilder nonExistList = new StringBuilder();
524            StringBuilder nonResolvedList = new StringBuilder();
525            getResolvedList(actionBean.getMissingDependencies(), nonExistList, nonResolvedList);
526            isPullDepAvailable = coordActionInput.checkInput(actionXml, existList, nonExistList, actionConf);
527        }
528
529        if (isPullDepAvailable && isPushDepAvailable) {
530            // Check for latest/future
531            boolean isLatestFutureDepAvailable = coordActionInput.checkUnResolvedInput(actionXml, actionConf);
532            if (isLatestFutureDepAvailable) {
533                String newActionXml = CoordActionInputCheckXCommand.resolveCoordConfiguration(actionXml, actionConf,
534                        actionBean.getId());
535                actionXml.replace(0, actionXml.length(), newActionXml);
536            }
537        }
538
539        return actionXml.toString();
540    }
541
542    /**
543     * Materialize all <input-events>/<data-in> or <output-events>/<data-out>
544     * tags Create uris for resolved instances. Create unresolved instance for
545     * latest()/future().
546     *
547     * @param events
548     * @param appInst
549     * @param conf
550     * @throws Exception
551     */
552    public static Map<String, StringBuilder> materializeDataEvents(List<Element> events, SyncCoordAction appInst, Configuration conf
553            ) throws Exception {
554
555        if (events == null) {
556            return null;
557        }
558        StringBuilder unresolvedList = new StringBuilder();
559        Map<String, StringBuilder> dependencyMap = new HashMap<String, StringBuilder>();
560        URIHandlerService uriService = Services.get().get(URIHandlerService.class);
561        StringBuilder pullMissingDep = null;
562        StringBuilder pushMissingDep = null;
563
564        for (Element event : events) {
565            StringBuilder instances = new StringBuilder();
566            ELEvaluator eval = CoordELEvaluator.createInstancesELEvaluator(event, appInst, conf);
567            // Handle list of instance tag
568            resolveInstances(event, instances, appInst, conf, eval);
569            // Handle start-instance and end-instance
570            resolveInstanceRange(event, instances, appInst, conf, eval);
571            // Separate out the unresolved instances
572            String resolvedList = separateResolvedAndUnresolved(event, instances);
573            if (!resolvedList.isEmpty()) {
574                Element uri = event.getChild("dataset", event.getNamespace()).getChild("uri-template",
575                        event.getNamespace());
576                String uriTemplate = uri.getText();
577                URI baseURI = uriService.getAuthorityWithScheme(uriTemplate);
578                URIHandler handler = uriService.getURIHandler(baseURI);
579                if (handler.getDependencyType(baseURI).equals(DependencyType.PULL)) {
580                    pullMissingDep = (pullMissingDep == null) ? new StringBuilder(resolvedList) : pullMissingDep.append(
581                            CoordELFunctions.INSTANCE_SEPARATOR).append(resolvedList);
582                }
583                else {
584                    pushMissingDep = (pushMissingDep == null) ? new StringBuilder(resolvedList) : pushMissingDep.append(
585                            CoordELFunctions.INSTANCE_SEPARATOR).append(resolvedList);
586                }
587            }
588
589            String tmpUnresolved = event.getChildTextTrim(UNRESOLVED_INST_TAG, event.getNamespace());
590            if (tmpUnresolved != null) {
591                if (unresolvedList.length() > 0) {
592                    unresolvedList.append(CoordELFunctions.INSTANCE_SEPARATOR);
593                }
594                unresolvedList.append(tmpUnresolved);
595            }
596        }
597        if (unresolvedList.length() > 0) {
598            if (pullMissingDep == null) {
599                pullMissingDep = new StringBuilder();
600            }
601            pullMissingDep.append(RESOLVED_UNRESOLVED_SEPARATOR).append(unresolvedList);
602        }
603        dependencyMap.put(DependencyType.PULL.name(), pullMissingDep);
604        dependencyMap.put(DependencyType.PUSH.name(), pushMissingDep);
605        return dependencyMap;
606    }
607
608    /**
609     * Get resolved string from missDepList
610     *
611     * @param missDepList
612     * @param resolved
613     * @param unresolved
614     * @return resolved string
615     */
616    public static String getResolvedList(String missDepList, StringBuilder resolved, StringBuilder unresolved) {
617        if (missDepList != null) {
618            int index = missDepList.indexOf(RESOLVED_UNRESOLVED_SEPARATOR);
619            if (index < 0) {
620                resolved.append(missDepList);
621            }
622            else {
623                resolved.append(missDepList.substring(0, index));
624                unresolved.append(missDepList.substring(index + RESOLVED_UNRESOLVED_SEPARATOR.length()));
625            }
626        }
627        return resolved.toString();
628    }
629
630}