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    package org.apache.oozie.command.coord;
019    
020    import java.io.StringReader;
021    import java.net.URI;
022    import java.util.Calendar;
023    import java.util.Date;
024    import java.util.HashMap;
025    import java.util.List;
026    import java.util.Map;
027    
028    import org.apache.hadoop.conf.Configuration;
029    import org.apache.oozie.CoordinatorActionBean;
030    import org.apache.oozie.ErrorCode;
031    import org.apache.oozie.client.CoordinatorAction;
032    import org.apache.oozie.command.CommandException;
033    import org.apache.oozie.coord.CoordELEvaluator;
034    import org.apache.oozie.coord.CoordELFunctions;
035    import org.apache.oozie.coord.CoordUtils;
036    import org.apache.oozie.coord.CoordinatorJobException;
037    import org.apache.oozie.coord.SyncCoordAction;
038    import org.apache.oozie.coord.TimeUnit;
039    import org.apache.oozie.dependency.ActionDependency;
040    import org.apache.oozie.dependency.DependencyChecker;
041    import org.apache.oozie.dependency.URIHandler;
042    import org.apache.oozie.dependency.URIHandler.DependencyType;
043    import org.apache.oozie.service.Services;
044    import org.apache.oozie.service.URIHandlerService;
045    import org.apache.oozie.service.UUIDService;
046    import org.apache.oozie.util.DateUtils;
047    import org.apache.oozie.util.ELEvaluator;
048    import org.apache.oozie.util.XConfiguration;
049    import org.apache.oozie.util.XmlUtils;
050    import org.jdom.Element;
051    
052    public 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    }