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.IOException;
021import java.io.StringReader;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.util.Date;
025import java.util.List;
026
027import org.apache.hadoop.conf.Configuration;
028import org.apache.oozie.CoordinatorActionBean;
029import org.apache.oozie.CoordinatorJobBean;
030import org.apache.oozie.ErrorCode;
031import org.apache.oozie.client.CoordinatorAction;
032import org.apache.oozie.client.Job;
033import org.apache.oozie.client.OozieClient;
034import org.apache.oozie.command.CommandException;
035import org.apache.oozie.command.PreconditionException;
036import org.apache.oozie.coord.CoordELEvaluator;
037import org.apache.oozie.coord.CoordELFunctions;
038import org.apache.oozie.dependency.URIHandler;
039import org.apache.oozie.dependency.URIHandlerException;
040import org.apache.oozie.executor.jpa.CoordActionGetForInputCheckJPAExecutor;
041import org.apache.oozie.executor.jpa.CoordActionUpdateForInputCheckJPAExecutor;
042import org.apache.oozie.executor.jpa.CoordActionUpdateForModifiedTimeJPAExecutor;
043import org.apache.oozie.executor.jpa.CoordJobGetJPAExecutor;
044import org.apache.oozie.executor.jpa.JPAExecutorException;
045import org.apache.oozie.service.CallableQueueService;
046import org.apache.oozie.service.EventHandlerService;
047import org.apache.oozie.service.JPAService;
048import org.apache.oozie.service.Service;
049import org.apache.oozie.service.Services;
050import org.apache.oozie.service.URIHandlerService;
051import org.apache.oozie.util.DateUtils;
052import org.apache.oozie.util.ELEvaluator;
053import org.apache.oozie.util.Instrumentation;
054import org.apache.oozie.util.LogUtils;
055import org.apache.oozie.util.ParamChecker;
056import org.apache.oozie.util.StatusUtils;
057import org.apache.oozie.util.XConfiguration;
058import org.apache.oozie.util.XLog;
059import org.apache.oozie.util.XmlUtils;
060import org.jdom.Element;
061
062/**
063 * The command to check if an action's data input paths exist in the file system.
064 */
065public class CoordActionInputCheckXCommand extends CoordinatorXCommand<Void> {
066
067    private final String actionId;
068    /**
069     * Property name of command re-queue interval for coordinator action input check in
070     * milliseconds.
071     */
072    public static final String CONF_COORD_INPUT_CHECK_REQUEUE_INTERVAL = Service.CONF_PREFIX
073            + "coord.input.check.requeue.interval";
074    /**
075     * Default re-queue interval in ms. It is applied when no value defined in
076     * the oozie configuration.
077     */
078    private final int DEFAULT_COMMAND_REQUEUE_INTERVAL = 60000; // 1 minute
079    private CoordinatorActionBean coordAction = null;
080    private CoordinatorJobBean coordJob = null;
081    private JPAService jpaService = null;
082    private String jobId = null;
083
084    public CoordActionInputCheckXCommand(String actionId, String jobId) {
085        super("coord_action_input", "coord_action_input", 1);
086        this.actionId = ParamChecker.notEmpty(actionId, "actionId");
087        this.jobId = jobId;
088    }
089
090    @Override
091    protected Void execute() throws CommandException {
092        LOG.info("[" + actionId + "]::ActionInputCheck:: Action is in WAITING state.");
093
094        // this action should only get processed if current time > nominal time;
095        // otherwise, requeue this action for delay execution;
096        Date nominalTime = coordAction.getNominalTime();
097        Date currentTime = new Date();
098        if (nominalTime.compareTo(currentTime) > 0) {
099            queue(new CoordActionInputCheckXCommand(coordAction.getId(), coordAction.getJobId()), Math.max((nominalTime.getTime() - currentTime
100                    .getTime()), getCoordInputCheckRequeueInterval()));
101            updateCoordAction(coordAction, false);
102            LOG.info("[" + actionId
103                    + "]::ActionInputCheck:: nominal Time is newer than current time, so requeue and wait. Current="
104                    + currentTime + ", nominal=" + nominalTime);
105
106            return null;
107        }
108
109        StringBuilder actionXml = new StringBuilder(coordAction.getActionXml());
110        Instrumentation.Cron cron = new Instrumentation.Cron();
111        boolean isChangeInDependency = false;
112        try {
113            Configuration actionConf = new XConfiguration(new StringReader(coordAction.getRunConf()));
114            cron.start();
115            StringBuilder existList = new StringBuilder();
116            StringBuilder nonExistList = new StringBuilder();
117            StringBuilder nonResolvedList = new StringBuilder();
118            String firstMissingDependency = "";
119            String missingDeps = coordAction.getMissingDependencies();
120            CoordCommandUtils.getResolvedList(missingDeps, nonExistList, nonResolvedList);
121
122            // For clarity regarding which is the missing dependency in synchronous order
123            // instead of printing entire list, some of which, may be available
124            if(nonExistList.length() > 0) {
125                firstMissingDependency = nonExistList.toString().split(CoordELFunctions.INSTANCE_SEPARATOR)[0];
126            }
127            LOG.info("[" + actionId + "]::CoordActionInputCheck:: Missing deps:" + firstMissingDependency + " "
128                    + nonResolvedList.toString());
129            // Updating the list of data dependencies that are available and those that are yet not
130            boolean status = checkInput(actionXml, existList, nonExistList, actionConf);
131            String pushDeps = coordAction.getPushMissingDependencies();
132            // Resolve latest/future only when all current missingDependencies and
133            // pushMissingDependencies are met
134            if (status && nonResolvedList.length() > 0) {
135                status = (pushDeps == null || pushDeps.length() == 0) ? checkUnResolvedInput(actionXml, actionConf)
136                        : false;
137            }
138            coordAction.setLastModifiedTime(currentTime);
139            coordAction.setActionXml(actionXml.toString());
140            if (nonResolvedList.length() > 0 && status == false) {
141                nonExistList.append(CoordCommandUtils.RESOLVED_UNRESOLVED_SEPARATOR).append(nonResolvedList);
142            }
143            String nonExistListStr = nonExistList.toString();
144            if (!nonExistListStr.equals(missingDeps) || missingDeps.isEmpty()) {
145                // missingDeps null or empty means action should become READY
146                isChangeInDependency = true;
147                coordAction.setMissingDependencies(nonExistListStr);
148            }
149            if (status && (pushDeps == null || pushDeps.length() == 0)) {
150                String newActionXml = resolveCoordConfiguration(actionXml, actionConf, actionId);
151                actionXml.replace(0, actionXml.length(), newActionXml);
152                coordAction.setActionXml(actionXml.toString());
153                coordAction.setStatus(CoordinatorAction.Status.READY);
154                // pass jobID to the CoordActionReadyXCommand
155                queue(new CoordActionReadyXCommand(coordAction.getJobId()), 100);
156            }
157            else if (!isTimeout(currentTime)) {
158                if (status == false) {
159                    queue(new CoordActionInputCheckXCommand(coordAction.getId(), coordAction.getJobId()),
160                            getCoordInputCheckRequeueInterval());
161                }
162            }
163            else {
164                if (!nonExistListStr.isEmpty() && pushDeps == null || pushDeps.length() == 0) {
165                    queue(new CoordActionTimeOutXCommand(coordAction, coordJob.getUser(), coordJob.getAppName()));
166                }
167                else {
168                    // Let CoordPushDependencyCheckXCommand queue the timeout
169                    queue(new CoordPushDependencyCheckXCommand(coordAction.getId()));
170                }
171            }
172        }
173        catch (Exception e) {
174            if (isTimeout(currentTime)) {
175                LOG.debug("Queueing timeout command");
176                // XCommand.queue() will not work when there is a Exception
177                Services.get().get(CallableQueueService.class)
178                        .queue(new CoordActionTimeOutXCommand(coordAction, coordJob.getUser(), coordJob.getAppName()));
179            }
180            throw new CommandException(ErrorCode.E1021, e.getMessage(), e);
181        }
182        finally {
183            cron.stop();
184            updateCoordAction(coordAction, isChangeInDependency);
185        }
186        return null;
187    }
188
189
190    static String resolveCoordConfiguration(StringBuilder actionXml, Configuration actionConf, String actionId) throws Exception {
191        Element eAction = XmlUtils.parseXml(actionXml.toString());
192        ELEvaluator eval = CoordELEvaluator.createDataEvaluator(eAction, actionConf, actionId);
193        materializeDataProperties(eAction, actionConf, eval);
194        return XmlUtils.prettyPrint(eAction).toString();
195    }
196
197    private boolean isTimeout(Date currentTime) {
198        long waitingTime = (currentTime.getTime() - Math.max(coordAction.getNominalTime().getTime(), coordAction
199                .getCreatedTime().getTime()))
200                / (60 * 1000);
201        int timeOut = coordAction.getTimeOut();
202        return (timeOut >= 0) && (waitingTime > timeOut);
203    }
204
205    private void updateCoordAction(CoordinatorActionBean coordAction, boolean isChangeInDependency)
206            throws CommandException {
207        coordAction.setLastModifiedTime(new Date());
208        if (jpaService != null) {
209            try {
210                if (isChangeInDependency) {
211                    jpaService.execute(new CoordActionUpdateForInputCheckJPAExecutor(coordAction));
212                    if (EventHandlerService.isEnabled()
213                            && coordAction.getStatus() != CoordinatorAction.Status.READY) {
214                        //since event is not to be generated unless action RUNNING via StartX
215                        generateEvent(coordAction, coordJob.getUser(), coordJob.getAppName(), null);
216                    }
217                }
218                else {
219                    jpaService.execute(new CoordActionUpdateForModifiedTimeJPAExecutor(coordAction));
220                }
221            }
222            catch (JPAExecutorException jex) {
223                throw new CommandException(ErrorCode.E1021, jex.getMessage(), jex);
224            }
225        }
226    }
227
228    /**
229     * This function reads the value of re-queue interval for coordinator input
230     * check command from the Oozie configuration provided by Configuration
231     * Service. If nothing defined in the configuration, it uses the code
232     * specified default value.
233     *
234     * @return re-queue interval in ms
235     */
236    public long getCoordInputCheckRequeueInterval() {
237        long requeueInterval = Services.get().getConf().getLong(CONF_COORD_INPUT_CHECK_REQUEUE_INTERVAL,
238                DEFAULT_COMMAND_REQUEUE_INTERVAL);
239        return requeueInterval;
240    }
241
242    /**
243     * To check the list of input paths if all of them exist
244     *
245     * @param actionXml action xml
246     * @param existList the list of existed paths
247     * @param nonExistList the list of non existed paths
248     * @param conf action configuration
249     * @return true if all input paths are existed
250     * @throws Exception thrown of unable to check input path
251     */
252    protected boolean checkInput(StringBuilder actionXml, StringBuilder existList, StringBuilder nonExistList,
253            Configuration conf) throws Exception {
254        Element eAction = XmlUtils.parseXml(actionXml.toString());
255        return checkResolvedUris(eAction, existList, nonExistList, conf);
256    }
257
258    protected boolean checkUnResolvedInput(StringBuilder actionXml, Configuration conf) throws Exception {
259        Element eAction = XmlUtils.parseXml(actionXml.toString());
260        LOG.debug("[" + actionId + "]::ActionInputCheck:: Checking Latest/future");
261        boolean allExist = checkUnresolvedInstances(eAction, conf);
262        if (allExist) {
263            actionXml.replace(0, actionXml.length(), XmlUtils.prettyPrint(eAction).toString());
264        }
265        return allExist;
266    }
267
268    /**
269     * Materialize data properties defined in <action> tag. it includes dataIn(<DS>) and dataOut(<DS>) it creates a list
270     * of files that will be needed.
271     *
272     * @param eAction action element
273     * @param conf action configuration
274     * @throws Exception thrown if failed to resolve data properties
275     * @update modify 'Action' element with appropriate list of files.
276     */
277    @SuppressWarnings("unchecked")
278    static void materializeDataProperties(Element eAction, Configuration conf, ELEvaluator eval) throws Exception {
279        Element configElem = eAction.getChild("action", eAction.getNamespace()).getChild("workflow",
280                eAction.getNamespace()).getChild("configuration", eAction.getNamespace());
281        if (configElem != null) {
282            for (Element propElem : (List<Element>) configElem.getChildren("property", configElem.getNamespace())) {
283                resolveTagContents("value", propElem, eval);
284            }
285        }
286    }
287
288    /**
289     * To resolve property value which contains el functions
290     *
291     * @param tagName tag name
292     * @param elem the child element of "property" element
293     * @param eval el functions evaluator
294     * @throws Exception thrown if unable to resolve tag value
295     */
296    private static void resolveTagContents(String tagName, Element elem, ELEvaluator eval) throws Exception {
297        if (elem == null) {
298            return;
299        }
300        Element tagElem = elem.getChild(tagName, elem.getNamespace());
301        if (tagElem != null) {
302            String updated = CoordELFunctions.evalAndWrap(eval, tagElem.getText());
303            tagElem.removeContent();
304            tagElem.addContent(updated);
305        }
306        else {
307            XLog.getLog(CoordActionInputCheckXCommand.class).warn(" Value NOT FOUND " + tagName);
308        }
309    }
310
311    /**
312     * Check if any unsolved paths under data output. Resolve the unresolved data input paths.
313     *
314     * @param eAction action element
315     * @param actionConf action configuration
316     * @return true if successful to resolve input and output paths
317     * @throws Exception thrown if failed to resolve data input and output paths
318     */
319    @SuppressWarnings("unchecked")
320    private boolean checkUnresolvedInstances(Element eAction, Configuration actionConf) throws Exception {
321        String strAction = XmlUtils.prettyPrint(eAction).toString();
322        Date nominalTime = DateUtils.parseDateOozieTZ(eAction.getAttributeValue("action-nominal-time"));
323        String actualTimeStr = eAction.getAttributeValue("action-actual-time");
324        Date actualTime = null;
325        if (actualTimeStr == null) {
326            LOG.debug("Unable to get action-actual-time from action xml, this job is submitted " +
327            "from previous version. Assign current date to actual time, action = " + actionId);
328            actualTime = new Date();
329        } else {
330            actualTime = DateUtils.parseDateOozieTZ(actualTimeStr);
331        }
332
333        StringBuffer resultedXml = new StringBuffer();
334
335        boolean ret;
336        Element inputList = eAction.getChild("input-events", eAction.getNamespace());
337        if (inputList != null) {
338            ret = materializeUnresolvedEvent(inputList.getChildren("data-in", eAction.getNamespace()), nominalTime,
339                    actualTime, actionConf);
340            if (ret == false) {
341                resultedXml.append(strAction);
342                return false;
343            }
344        }
345
346        // Using latest() or future() in output-event is not intuitive.
347        // We need to make sure, this assumption is correct.
348        Element outputList = eAction.getChild("output-events", eAction.getNamespace());
349        if (outputList != null) {
350            for (Element dEvent : (List<Element>) outputList.getChildren("data-out", eAction.getNamespace())) {
351                if (dEvent.getChild(CoordCommandUtils.UNRESOLVED_INST_TAG, dEvent.getNamespace()) != null) {
352                    throw new CommandException(ErrorCode.E1006, "coord:latest()/future()",
353                            " not permitted in output-event ");
354                }
355            }
356        }
357        return true;
358    }
359
360    /**
361     * Resolve the list of data input paths
362     *
363     * @param eDataEvents the list of data input elements
364     * @param nominalTime action nominal time
365     * @param actualTime current time
366     * @param conf action configuration
367     * @return true if all unresolved URIs can be resolved
368     * @throws Exception thrown if failed to resolve data input paths
369     */
370    @SuppressWarnings("unchecked")
371    private boolean materializeUnresolvedEvent(List<Element> eDataEvents, Date nominalTime, Date actualTime,
372            Configuration conf) throws Exception {
373        for (Element dEvent : eDataEvents) {
374            if (dEvent.getChild(CoordCommandUtils.UNRESOLVED_INST_TAG, dEvent.getNamespace()) == null) {
375                continue;
376            }
377            ELEvaluator eval = CoordELEvaluator.createLazyEvaluator(actualTime, nominalTime, dEvent, conf);
378            String uresolvedInstance = dEvent.getChild(CoordCommandUtils.UNRESOLVED_INST_TAG, dEvent.getNamespace()).getTextTrim();
379            String unresolvedList[] = uresolvedInstance.split(CoordELFunctions.INSTANCE_SEPARATOR);
380            StringBuffer resolvedTmp = new StringBuffer();
381            for (int i = 0; i < unresolvedList.length; i++) {
382                String ret = CoordELFunctions.evalAndWrap(eval, unresolvedList[i]);
383                Boolean isResolved = (Boolean) eval.getVariable("is_resolved");
384                if (isResolved == false) {
385                    LOG.info("[" + actionId + "]::Cannot resolve: " + ret);
386                    return false;
387                }
388                if (resolvedTmp.length() > 0) {
389                    resolvedTmp.append(CoordELFunctions.INSTANCE_SEPARATOR);
390                }
391                resolvedTmp.append((String) eval.getVariable("resolved_path"));
392            }
393            if (resolvedTmp.length() > 0) {
394                if (dEvent.getChild("uris", dEvent.getNamespace()) != null) {
395                    resolvedTmp.append(CoordELFunctions.INSTANCE_SEPARATOR).append(
396                            dEvent.getChild("uris", dEvent.getNamespace()).getTextTrim());
397                    dEvent.removeChild("uris", dEvent.getNamespace());
398                }
399                Element uriInstance = new Element("uris", dEvent.getNamespace());
400                uriInstance.addContent(resolvedTmp.toString());
401                dEvent.getContent().add(1, uriInstance);
402            }
403            dEvent.removeChild(CoordCommandUtils.UNRESOLVED_INST_TAG, dEvent.getNamespace());
404        }
405
406        return true;
407    }
408
409    /**
410     * Check all resolved URIs existence
411     *
412     * @param eAction action element
413     * @param existList the list of existed paths
414     * @param nonExistList the list of paths to check existence
415     * @param conf action configuration
416     * @return true if all nonExistList paths exist
417     * @throws IOException thrown if unable to access the path
418     */
419    private boolean checkResolvedUris(Element eAction, StringBuilder existList, StringBuilder nonExistList,
420            Configuration conf) throws IOException {
421        LOG.info("[" + actionId + "]::ActionInputCheck:: In checkResolvedUris...");
422        Element inputList = eAction.getChild("input-events", eAction.getNamespace());
423        if (inputList != null) {
424            if (nonExistList.length() > 0) {
425                checkListOfPaths(existList, nonExistList, conf);
426            }
427            return nonExistList.length() == 0;
428        }
429        return true;
430    }
431
432    /**
433     * Check a list of non existed paths and add to exist list if it exists
434     *
435     * @param existList the list of existed paths
436     * @param nonExistList the list of paths to check existence
437     * @param conf action configuration
438     * @return true if all nonExistList paths exist
439     * @throws IOException thrown if unable to access the path
440     */
441    private boolean checkListOfPaths(StringBuilder existList, StringBuilder nonExistList, Configuration conf)
442            throws IOException {
443
444        String[] uriList = nonExistList.toString().split(CoordELFunctions.INSTANCE_SEPARATOR);
445        if (uriList[0] != null) {
446            LOG.info("[" + actionId + "]::ActionInputCheck:: In checkListOfPaths: " + uriList[0] + " is Missing.");
447        }
448
449        nonExistList.delete(0, nonExistList.length());
450        boolean allExists = true;
451        String existSeparator = "", nonExistSeparator = "";
452        String user = ParamChecker.notEmpty(conf.get(OozieClient.USER_NAME), OozieClient.USER_NAME);
453        for (int i = 0; i < uriList.length; i++) {
454            if (allExists) {
455                allExists = pathExists(uriList[i], conf, user);
456                LOG.info("[" + actionId + "]::ActionInputCheck:: File:" + uriList[i] + ", Exists? :" + allExists);
457            }
458            if (allExists) {
459                existList.append(existSeparator).append(uriList[i]);
460                existSeparator = CoordELFunctions.INSTANCE_SEPARATOR;
461            }
462            else {
463                nonExistList.append(nonExistSeparator).append(uriList[i]);
464                nonExistSeparator = CoordELFunctions.INSTANCE_SEPARATOR;
465            }
466        }
467        return allExists;
468    }
469
470    /**
471     * Check if given path exists
472     *
473     * @param sPath uri path
474     * @param actionConf action configuration
475     * @return true if path exists
476     * @throws IOException thrown if unable to access the path
477     */
478    protected boolean pathExists(String sPath, Configuration actionConf, String user) throws IOException {
479        LOG.debug("checking for the file " + sPath);
480        try {
481            URI uri = new URI(sPath);
482            URIHandlerService service = Services.get().get(URIHandlerService.class);
483            URIHandler handler = service.getURIHandler(uri);
484            return handler.exists(uri, actionConf, user);
485        }
486        catch (URIHandlerException e) {
487            coordAction.setErrorCode(e.getErrorCode().toString());
488            coordAction.setErrorMessage(e.getMessage());
489            throw new IOException(e);
490        } catch (URISyntaxException e) {
491            coordAction.setErrorCode(ErrorCode.E0906.toString());
492            coordAction.setErrorMessage(e.getMessage());
493            throw new IOException(e);
494        }
495    }
496
497    /**
498     * The function create a list of URIs separated by "," using the instances time stamp and URI-template
499     *
500     * @param event : <data-in> event
501     * @param instances : List of time stamp seprated by ","
502     * @param unresolvedInstances : list of instance with latest/future function
503     * @return : list of URIs separated by ",".
504     * @throws Exception thrown if failed to create URIs from unresolvedInstances
505     */
506    @SuppressWarnings("unused")
507    private String createURIs(Element event, String instances, StringBuilder unresolvedInstances) throws Exception {
508        if (instances == null || instances.length() == 0) {
509            return "";
510        }
511        String[] instanceList = instances.split(CoordELFunctions.INSTANCE_SEPARATOR);
512        StringBuilder uris = new StringBuilder();
513
514        for (int i = 0; i < instanceList.length; i++) {
515            int funcType = CoordCommandUtils.getFuncType(instanceList[i]);
516            if (funcType == CoordCommandUtils.LATEST || funcType == CoordCommandUtils.FUTURE) {
517                if (unresolvedInstances.length() > 0) {
518                    unresolvedInstances.append(CoordELFunctions.INSTANCE_SEPARATOR);
519                }
520                unresolvedInstances.append(instanceList[i]);
521                continue;
522            }
523            ELEvaluator eval = CoordELEvaluator.createURIELEvaluator(instanceList[i]);
524            if (uris.length() > 0) {
525                uris.append(CoordELFunctions.INSTANCE_SEPARATOR);
526            }
527            uris.append(CoordELFunctions.evalAndWrap(eval, event.getChild("dataset", event.getNamespace()).getChild(
528                    "uri-template", event.getNamespace()).getTextTrim()));
529        }
530        return uris.toString();
531    }
532
533    /**
534     * getting the error code of the coord action. (used mainly for unit testing)
535     */
536    protected String getCoordActionErrorCode() {
537        if (coordAction != null) {
538            return coordAction.getErrorCode();
539        }
540        return null;
541    }
542
543    /**
544     * getting the error message of the coord action. (used mainly for unit testing)
545     */
546    protected String getCoordActionErrorMsg() {
547        if (coordAction != null) {
548            return coordAction.getErrorMessage();
549        }
550        return null;
551    }
552
553    @Override
554    public String getEntityKey() {
555        return this.jobId;
556    }
557
558    @Override
559    protected boolean isLockRequired() {
560        return true;
561    }
562
563    @Override
564    protected void loadState() throws CommandException {
565        if (jpaService == null) {
566            jpaService = Services.get().get(JPAService.class);
567        }
568        try {
569            coordAction = jpaService.execute(new CoordActionGetForInputCheckJPAExecutor(actionId));
570            coordJob = jpaService.execute(new CoordJobGetJPAExecutor(coordAction.getJobId()));
571        }
572        catch (JPAExecutorException je) {
573            throw new CommandException(je);
574        }
575        LogUtils.setLogInfo(coordAction, logInfo);
576    }
577
578    @Override
579    protected void verifyPrecondition() throws CommandException, PreconditionException {
580        if (coordAction.getStatus() != CoordinatorActionBean.Status.WAITING) {
581            throw new PreconditionException(ErrorCode.E1100, "[" + actionId
582                    + "]::CoordActionInputCheck:: Ignoring action. Should be in WAITING state, but state="
583                    + coordAction.getStatus());
584        }
585
586        // if eligible to do action input check when running with backward support is true
587        if (StatusUtils.getStatusForCoordActionInputCheck(coordJob)) {
588            return;
589        }
590
591        if (coordJob.getStatus() != Job.Status.RUNNING && coordJob.getStatus() != Job.Status.RUNNINGWITHERROR && coordJob.getStatus() != Job.Status.PAUSED
592                && coordJob.getStatus() != Job.Status.PAUSEDWITHERROR) {
593            throw new PreconditionException(
594                    ErrorCode.E1100, "["+ actionId + "]::CoordActionInputCheck:: Ignoring action." +
595                                " Coordinator job is not in RUNNING/RUNNINGWITHERROR/PAUSED/PAUSEDWITHERROR state, but state="
596                            + coordJob.getStatus());
597        }
598    }
599
600    @Override
601    public String getKey(){
602        return getName() + "_" + actionId;
603    }
604
605}