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.cli;
020
021import com.google.common.annotations.VisibleForTesting;
022import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
023import org.apache.commons.cli.CommandLine;
024import org.apache.commons.cli.Option;
025import org.apache.commons.cli.OptionBuilder;
026import org.apache.commons.cli.OptionGroup;
027import org.apache.commons.cli.Options;
028import org.apache.commons.cli.ParseException;
029import org.apache.commons.io.FilenameUtils;
030import org.apache.oozie.BuildInfo;
031import org.apache.oozie.client.ApiJarLoader;
032import org.apache.oozie.client.AuthOozieClient;
033import org.apache.oozie.client.BulkResponse;
034import org.apache.oozie.client.BundleJob;
035import org.apache.oozie.client.CoordinatorAction;
036import org.apache.oozie.client.CoordinatorJob;
037import org.apache.oozie.client.OozieClient;
038import org.apache.oozie.client.OozieClient.SYSTEM_MODE;
039import org.apache.oozie.client.OozieClientException;
040import org.apache.oozie.client.WorkflowAction;
041import org.apache.oozie.client.WorkflowJob;
042import org.apache.oozie.client.XOozieClient;
043import org.apache.oozie.client.rest.JsonTags;
044import org.apache.oozie.client.rest.JsonToBean;
045import org.apache.oozie.client.rest.RestConstants;
046import org.apache.oozie.fluentjob.api.serialization.WorkflowMarshaller;
047import org.apache.oozie.fluentjob.api.workflow.Workflow;
048import org.json.simple.JSONArray;
049import org.json.simple.JSONObject;
050import org.w3c.dom.DOMException;
051import org.w3c.dom.Document;
052import org.w3c.dom.Element;
053import org.w3c.dom.Node;
054import org.w3c.dom.NodeList;
055import org.w3c.dom.Text;
056import org.xml.sax.SAXException;
057
058import javax.xml.bind.JAXBException;
059import javax.xml.parsers.DocumentBuilder;
060import javax.xml.parsers.DocumentBuilderFactory;
061import javax.xml.parsers.ParserConfigurationException;
062import java.io.File;
063import java.io.FileInputStream;
064import java.io.IOException;
065import java.io.InputStream;
066import java.io.InputStreamReader;
067import java.io.PrintStream;
068import java.lang.reflect.InvocationTargetException;
069import java.nio.charset.StandardCharsets;
070import java.nio.file.Files;
071import java.nio.file.Path;
072import java.text.SimpleDateFormat;
073import java.util.ArrayList;
074import java.util.Date;
075import java.util.List;
076import java.util.Locale;
077import java.util.Map;
078import java.util.Properties;
079import java.util.TimeZone;
080import java.util.TreeMap;
081import java.util.concurrent.Callable;
082import java.util.regex.Matcher;
083import java.util.regex.Pattern;
084
085/**
086 * Oozie command line utility.
087 */
088public class OozieCLI {
089    public static final String ENV_OOZIE_URL = "OOZIE_URL";
090    public static final String ENV_OOZIE_DEBUG = "OOZIE_DEBUG";
091    public static final String ENV_OOZIE_TIME_ZONE = "OOZIE_TIMEZONE";
092    public static final String ENV_OOZIE_AUTH = "OOZIE_AUTH";
093    public static final String OOZIE_RETRY_COUNT = "oozie.connection.retry.count";
094    public static final String WS_HEADER_PREFIX = "header:";
095
096    public static final String HELP_CMD = "help";
097    public static final String VERSION_CMD = "version";
098    public static final String JOB_CMD = "job";
099    public static final String JOBS_CMD = "jobs";
100    public static final String ADMIN_CMD = "admin";
101    public static final String VALIDATE_CMD = "validate";
102    public static final String SLA_CMD = "sla";
103    public static final String PIG_CMD = "pig";
104    public static final String HIVE_CMD = "hive";
105    public static final String SQOOP_CMD = "sqoop";
106    public static final String MR_CMD = "mapreduce";
107    public static final String INFO_CMD = "info";
108
109    public static final String OOZIE_OPTION = "oozie";
110    public static final String CONFIG_OPTION = "config";
111    public static final String SUBMIT_OPTION = "submit";
112    public static final String OFFSET_OPTION = "offset";
113    public static final String START_OPTION = "start";
114    public static final String RUN_OPTION = "run";
115    public static final String DRYRUN_OPTION = "dryrun";
116    public static final String SUSPEND_OPTION = "suspend";
117    public static final String RESUME_OPTION = "resume";
118    public static final String KILL_OPTION = "kill";
119    public static final String CHANGE_OPTION = "change";
120    public static final String CHANGE_VALUE_OPTION = "value";
121    public static final String RERUN_OPTION = "rerun";
122    public static final String INFO_OPTION = "info";
123    public static final String LOG_OPTION = "log";
124    public static final String ERROR_LOG_OPTION = "errorlog";
125    public static final String AUDIT_LOG_OPTION = "auditlog";
126    public static final String VALIDATE_JAR_OPTION = "validatejar";
127    public static final String SUBMIT_JAR_OPTION = "submitjar";
128    public static final String RUN_JAR_OPTION = "runjar";
129
130    public static final String ACTION_OPTION = "action";
131    public static final String DEFINITION_OPTION = "definition";
132    public static final String CONFIG_CONTENT_OPTION = "configcontent";
133    public static final String SQOOP_COMMAND_OPTION = "command";
134    public static final String SHOWDIFF_OPTION = "diff";
135    public static final String UPDATE_OPTION = "update";
136    public static final String IGNORE_OPTION = "ignore";
137    public static final String POLL_OPTION = "poll";
138    public static final String TIMEOUT_OPTION = "timeout";
139    public static final String INTERVAL_OPTION = "interval";
140
141    public static final String DO_AS_OPTION = "doas";
142
143    public static final String LEN_OPTION = "len";
144    public static final String FILTER_OPTION = "filter";
145    public static final String JOBTYPE_OPTION = "jobtype";
146    public static final String SYSTEM_MODE_OPTION = "systemmode";
147    public static final String VERSION_OPTION = "version";
148    public static final String STATUS_OPTION = "status";
149    public static final String LOCAL_TIME_OPTION = "localtime";
150    public static final String TIME_ZONE_OPTION = "timezone";
151    public static final String QUEUE_DUMP_OPTION = "queuedump";
152    public static final String DATE_OPTION = "date";
153    public static final String RERUN_REFRESH_OPTION = "refresh";
154    public static final String RERUN_NOCLEANUP_OPTION = "nocleanup";
155    public static final String RERUN_FAILED_OPTION = "failed";
156    public static final String ORDER_OPTION = "order";
157    public static final String COORD_OPTION = "coordinator";
158
159    public static final String UPDATE_SHARELIB_OPTION = "sharelibupdate";
160
161    public static final String LIST_SHARELIB_LIB_OPTION = "shareliblist";
162    public static final String PURGE_OPTION = "purge";
163
164    public static final String SLA_DISABLE_ALERT = "sladisable";
165    public static final String SLA_ENABLE_ALERT = "slaenable";
166    public static final String SLA_CHANGE = "slachange";
167
168    public static final String SERVER_CONFIGURATION_OPTION = "configuration";
169    public static final String SERVER_OS_ENV_OPTION = "osenv";
170    public static final String SERVER_JAVA_SYSTEM_PROPERTIES_OPTION = "javasysprops";
171
172    public static final String METRICS_OPTION = "metrics";
173    public static final String INSTRUMENTATION_OPTION = "instrumentation";
174
175    public static final String AUTH_OPTION = "auth";
176
177    public static final String VERBOSE_OPTION = "verbose";
178    public static final String VERBOSE_DELIMITER = "\t";
179    public static final String DEBUG_OPTION = "debug";
180
181    public static final String SCRIPTFILE_OPTION = "file";
182
183    public static final String INFO_TIME_ZONES_OPTION = "timezones";
184
185    public static final String BULK_OPTION = "bulk";
186
187    public static final String AVAILABLE_SERVERS_OPTION = "servers";
188
189    public static final String ALL_WORKFLOWS_FOR_COORD_ACTION = "allruns";
190
191    public static final String WORKFLOW_ACTIONS_RETRIES = "retries";
192
193    public static final String COORD_ACTION_MISSING_DEPENDENCIES = "missingdeps";
194
195    private static final String[] OOZIE_HELP = {
196            "the env variable '" + ENV_OOZIE_URL + "' is used as default value for the '-" + OOZIE_OPTION + "' option",
197            "the env variable '" + ENV_OOZIE_TIME_ZONE + "' is used as default value for the '-" + TIME_ZONE_OPTION + "' option",
198            "the env variable '" + ENV_OOZIE_AUTH + "' is used as default value for the '-" + AUTH_OPTION + "' option",
199            "custom headers for Oozie web services can be specified using '-D" + WS_HEADER_PREFIX + "NAME=VALUE'" };
200
201    private static final String RULER;
202    private static final int LINE_WIDTH = 132;
203
204    private static final int RETRY_COUNT = 4;
205
206    private boolean used;
207
208    private static final String INSTANCE_SEPARATOR = "#";
209
210    private static final String MAPRED_MAPPER = "mapred.mapper.class";
211    private static final String MAPRED_MAPPER_2 = "mapreduce.map.class";
212    private static final String MAPRED_REDUCER = "mapred.reducer.class";
213    private static final String MAPRED_REDUCER_2 = "mapreduce.reduce.class";
214    private static final String MAPRED_INPUT = "mapred.input.dir";
215    private static final String MAPRED_OUTPUT = "mapred.output.dir";
216
217    private static final Pattern GMT_OFFSET_SHORTEN_PATTERN = Pattern.compile("(.* )GMT((?:-|\\+)\\d{2}:\\d{2})");
218
219    static {
220        StringBuilder sb = new StringBuilder();
221        for (int i = 0; i < LINE_WIDTH; i++) {
222            sb.append("-");
223        }
224        RULER = sb.toString();
225    }
226
227    /**
228     * Entry point for the Oozie CLI when invoked from the command line.
229     * <p>
230     * Upon completion this method exits the JVM with '0' (success) or '-1' (failure).
231     *
232     * @param args options and arguments for the Oozie CLI.
233     */
234    public static void main(String[] args) {
235        if (!System.getProperties().containsKey(AuthOozieClient.USE_AUTH_TOKEN_CACHE_SYS_PROP)) {
236            System.setProperty(AuthOozieClient.USE_AUTH_TOKEN_CACHE_SYS_PROP, "true");
237        }
238        System.exit(new OozieCLI().run(args));
239    }
240
241    /**
242     * Create an Oozie CLI instance.
243     */
244    public OozieCLI() {
245        used = false;
246    }
247
248    /**
249     * Return Oozie CLI top help lines.
250     *
251     * @return help lines.
252     */
253    protected String[] getCLIHelp() {
254        return OOZIE_HELP;
255    }
256
257    /**
258     * Add authentication specific options to oozie cli
259     *
260     * @param options the collection of options to add auth options
261     */
262    protected void addAuthOptions(Options options) {
263        Option auth = new Option(AUTH_OPTION, true, "select authentication type [SIMPLE|KERBEROS]");
264        options.addOption(auth);
265    }
266
267    /**
268     * Create option for command line option 'admin'
269     * @return admin options
270     */
271    protected Options createAdminOptions() {
272        Option oozie = new Option(OOZIE_OPTION, true, "Oozie URL");
273        Option system_mode = new Option(SYSTEM_MODE_OPTION, true,
274                "Supported in Oozie-2.0 or later versions ONLY. Change oozie system mode [NORMAL|NOWEBSERVICE|SAFEMODE]");
275        Option status = new Option(STATUS_OPTION, false, "show the current system status");
276        Option version = new Option(VERSION_OPTION, false, "show Oozie server build version");
277        Option queuedump = new Option(QUEUE_DUMP_OPTION, false, "show Oozie server queue elements");
278        Option doAs = new Option(DO_AS_OPTION, true, "doAs user, impersonates as the specified user");
279        Option availServers = new Option(AVAILABLE_SERVERS_OPTION, false, "list available Oozie servers"
280                + " (more than one only if HA is enabled)");
281        Option sharelibUpdate = new Option(UPDATE_SHARELIB_OPTION, false, "Update server to use a newer version of sharelib");
282        Option serverConfiguration = new Option(SERVER_CONFIGURATION_OPTION, false, "show Oozie system configuration");
283        Option osEnv = new Option(SERVER_OS_ENV_OPTION, false, "show Oozie system OS environment");
284        Option javaSysProps = new Option(SERVER_JAVA_SYSTEM_PROPERTIES_OPTION, false, "show Oozie Java system properties");
285        Option metrics = new Option(METRICS_OPTION, false, "show Oozie system metrics");
286        Option instrumentation = new Option(INSTRUMENTATION_OPTION, false, "show Oozie system instrumentation");
287
288        Option sharelib = new Option(LIST_SHARELIB_LIB_OPTION, false,
289                "List available sharelib that can be specified in a workflow action");
290        sharelib.setOptionalArg(true);
291        Option purge = new Option(PURGE_OPTION, true, "purge old oozie workflow, coordinator and bundle records from DB " +
292                "(parameter unit: day)");
293
294        Options adminOptions = new Options();
295        adminOptions.addOption(oozie);
296        adminOptions.addOption(doAs);
297        OptionGroup group = new OptionGroup();
298        group.addOption(system_mode);
299        group.addOption(status);
300        group.addOption(version);
301        group.addOption(queuedump);
302        group.addOption(availServers);
303        group.addOption(sharelibUpdate);
304        group.addOption(sharelib);
305        group.addOption(serverConfiguration);
306        group.addOption(osEnv);
307        group.addOption(javaSysProps);
308        group.addOption(metrics);
309        group.addOption(instrumentation);
310        group.addOption(purge);
311        adminOptions.addOptionGroup(group);
312        addAuthOptions(adminOptions);
313        return adminOptions;
314    }
315
316    /**
317     * Create option for command line option 'job'
318     * @return job options
319     */
320    protected Options createJobOptions() {
321        Option oozie = new Option(OOZIE_OPTION, true, "Oozie URL");
322        Option config = new Option(CONFIG_OPTION, true, "job configuration file '.xml' or '.properties'");
323        Option submit = new Option(SUBMIT_OPTION, false, "submit a job");
324        Option run = new Option(RUN_OPTION, false, "run a job");
325        Option debug = new Option(DEBUG_OPTION, false, "Use debug mode to see debugging statements on stdout");
326        Option rerun = new Option(RERUN_OPTION, true,
327                "rerun a job  (coordinator requires -action or -date, bundle requires -coordinator or -date)");
328        Option dryrun = new Option(DRYRUN_OPTION, false, "Dryrun a workflow (since 3.3.2), a coordinator (since 2.0) "
329                + " or a bundle (since 5.1) job without actually executing it");
330        Option update = new Option(UPDATE_OPTION, true, "Update coord definition and properties");
331        Option showdiff = new Option(SHOWDIFF_OPTION, true,
332                "Show diff of the new coord definition and properties with the existing one (default true)");
333        Option start = new Option(START_OPTION, true, "start a job");
334        Option suspend = new Option(SUSPEND_OPTION, true, "suspend a job");
335        Option resume = new Option(RESUME_OPTION, true, "resume a job");
336        Option kill = new Option(KILL_OPTION, true, "kill a job (coordinator can mention -action or -date)");
337        Option change = new Option(CHANGE_OPTION, true, "change a coordinator or bundle job");
338        Option changeValue = new Option(CHANGE_VALUE_OPTION, true,
339                "new endtime/concurrency/pausetime value for changing a coordinator job");
340        Option info = new Option(INFO_OPTION, true, "info of a job");
341        Option poll = new Option(POLL_OPTION, true, "poll Oozie until a job reaches a terminal state or a timeout occurs");
342        Option offset = new Option(OFFSET_OPTION, true, "job info offset of actions (default '1', requires -info)");
343        Option len = new Option(LEN_OPTION, true, "number of actions (default TOTAL ACTIONS, requires -info)");
344        Option filter = new Option(FILTER_OPTION, true,
345                "<key><comparator><value>[;<key><comparator><value>]*\n"
346                    + "(All Coordinator actions satisfying the filters will be retrieved).\n"
347                    + "key: status or nominaltime\n"
348                    + "comparator: =, !=, <, <=, >, >=. = is used as OR and others as AND\n"
349                    + "status: values are valid status like SUCCEEDED, KILLED etc. Only = and != apply for status\n"
350                    + "nominaltime: time of format yyyy-MM-dd'T'HH:mm'Z'");
351        Option order = new Option(ORDER_OPTION, true,
352                "order to show coord actions (default ascending order, 'desc' for descending order, requires -info)");
353        Option localtime = new Option(LOCAL_TIME_OPTION, false, "use local time (same as passing your time zone to -" +
354                TIME_ZONE_OPTION + "). Overrides -" + TIME_ZONE_OPTION + " option");
355        Option timezone = new Option(TIME_ZONE_OPTION, true,
356                "use time zone with the specified ID (default GMT).\nSee 'oozie info -timezones' for a list");
357        Option log = new Option(LOG_OPTION, true, "job log");
358        Option errorlog = new Option(ERROR_LOG_OPTION, true, "job error log");
359        Option auditlog = new Option(AUDIT_LOG_OPTION, true, "job audit log");
360        final Option generateAndCheck = new Option(VALIDATE_JAR_OPTION, true, "generate and check job definition");
361        final Option generateAndSubmit = new Option(SUBMIT_JAR_OPTION, true, "generate and submit job definition");
362        final Option generateAndRun = new Option(RUN_JAR_OPTION, true, "generate and run job definition");
363        Option logFilter = new Option(
364                RestConstants.LOG_FILTER_OPTION, true,
365                "job log search parameter. Can be specified as -logfilter opt1=val1;opt2=val1;opt3=val1. "
366                + "Supported options are recent, start, end, loglevel, text, limit and debug");
367        Option definition = new Option(DEFINITION_OPTION, true, "job definition");
368        Option config_content = new Option(CONFIG_CONTENT_OPTION, true, "job configuration");
369        Option verbose = new Option(VERBOSE_OPTION, false, "verbose mode");
370        Option action = new Option(ACTION_OPTION, true,
371                "coordinator rerun/kill on action ids (requires -rerun/-kill); coordinator log retrieval on action ids"
372                        + "(requires -log)");
373        Option date = new Option(DATE_OPTION, true,
374                "coordinator/bundle rerun on action dates (requires -rerun); "
375                + "coordinator log retrieval on action dates (requires -log)");
376        Option rerun_coord = new Option(COORD_OPTION, true, "bundle rerun on coordinator names (requires -rerun)");
377        Option rerun_refresh = new Option(RERUN_REFRESH_OPTION, false,
378                "re-materialize the coordinator rerun actions (requires -rerun)");
379        Option rerun_nocleanup = new Option(RERUN_NOCLEANUP_OPTION, false,
380                "do not clean up output-events of the coordinator rerun actions (requires -rerun)");
381        Option rerun_failed = new Option(RERUN_FAILED_OPTION, false,
382                "runs the failed workflow actions of the coordinator actions (requires -rerun)");
383        Option property = OptionBuilder.withArgName("property=value").hasArgs(2).withValueSeparator().withDescription(
384                "set/override value for given property").create("D");
385        Option getAllWorkflows = new Option(ALL_WORKFLOWS_FOR_COORD_ACTION, false,
386                "Get workflow jobs corresponding to a coordinator action including all the reruns");
387        Option ignore = new Option(IGNORE_OPTION, true,
388                "change status of a coordinator job or action to IGNORED"
389                + " (-action required to ignore coord actions)");
390        Option timeout = new Option(TIMEOUT_OPTION, true, "timeout in minutes (default is 30, negative values indicate no "
391                + "timeout, requires -poll)");
392        timeout.setType(Integer.class);
393        Option interval = new Option(INTERVAL_OPTION, true, "polling interval in minutes (default is 5, requires -poll)");
394        interval.setType(Integer.class);
395
396        Option slaDisableAlert = new Option(SLA_DISABLE_ALERT, true,
397                "disables sla alerts for the job and its children");
398        Option slaEnableAlert = new Option(SLA_ENABLE_ALERT, true,
399                "enables sla alerts for the job and its children");
400        Option slaChange = new Option(SLA_CHANGE, true,
401                "Update sla param for jobs, supported param are should-start, should-end, nominal-time and max-duration");
402        Option coordActionMissingDependencies = new Option(COORD_ACTION_MISSING_DEPENDENCIES, true,
403                "List missing dependencies of a coord action. To specify multiple actions, use with -action or -date option.");
404
405
406        Option doAs = new Option(DO_AS_OPTION, true, "doAs user, impersonates as the specified user");
407
408        Option workflowActionRetries = new Option(WORKFLOW_ACTIONS_RETRIES, true,
409                "Get information of the retry attempts for a given workflow action");
410
411        OptionGroup actions = new OptionGroup();
412        actions.addOption(submit);
413        actions.addOption(start);
414        actions.addOption(run);
415        actions.addOption(dryrun);
416        actions.addOption(suspend);
417        actions.addOption(resume);
418        actions.addOption(kill);
419        actions.addOption(change);
420        actions.addOption(update);
421        actions.addOption(info);
422        actions.addOption(rerun);
423        actions.addOption(log);
424        actions.addOption(errorlog);
425        actions.addOption(auditlog);
426        actions.addOption(generateAndCheck);
427        actions.addOption(generateAndSubmit);
428        actions.addOption(generateAndRun);
429        actions.addOption(definition);
430        actions.addOption(config_content);
431        actions.addOption(ignore);
432        actions.addOption(poll);
433        actions.addOption(slaDisableAlert);
434        actions.addOption(slaEnableAlert);
435        actions.addOption(slaChange);
436        actions.addOption(workflowActionRetries);
437        actions.addOption(coordActionMissingDependencies);
438        actions.setRequired(true);
439        Options jobOptions = new Options();
440        jobOptions.addOption(oozie);
441        jobOptions.addOption(doAs);
442        jobOptions.addOption(config);
443        jobOptions.addOption(property);
444        jobOptions.addOption(changeValue);
445        jobOptions.addOption(localtime);
446        jobOptions.addOption(timezone);
447        jobOptions.addOption(verbose);
448        jobOptions.addOption(debug);
449        jobOptions.addOption(offset);
450        jobOptions.addOption(len);
451        jobOptions.addOption(filter);
452        jobOptions.addOption(order);
453        jobOptions.addOption(action);
454        jobOptions.addOption(date);
455        jobOptions.addOption(rerun_coord);
456        jobOptions.addOption(rerun_refresh);
457        jobOptions.addOption(rerun_nocleanup);
458        jobOptions.addOption(rerun_failed);
459        jobOptions.addOption(getAllWorkflows);
460        jobOptions.addOptionGroup(actions);
461        jobOptions.addOption(logFilter);
462        jobOptions.addOption(timeout);
463        jobOptions.addOption(interval);
464        addAuthOptions(jobOptions);
465        jobOptions.addOption(showdiff);
466
467        //Needed to make dryrun and update mutually exclusive options
468        OptionGroup updateOption = new OptionGroup();
469        updateOption.addOption(dryrun);
470        jobOptions.addOptionGroup(updateOption);
471
472        return jobOptions;
473    }
474
475    /**
476     * Create option for command line option 'jobs'
477     * @return jobs options
478     */
479    protected Options createJobsOptions() {
480        Option oozie = new Option(OOZIE_OPTION, true, "Oozie URL");
481        Option start = new Option(OFFSET_OPTION, true, "jobs offset (default '1')");
482        Option jobtype = new Option(JOBTYPE_OPTION, true,
483                "job type ('Supported in Oozie-2.0 or later versions ONLY - 'coordinator' or 'bundle' or 'wf'(default))");
484        Option len = new Option(LEN_OPTION, true, "number of jobs (default '100')");
485        Option filter = new Option(FILTER_OPTION, true,
486                "text=<*>\\;user=<U>\\;name=<N>\\;group=<G>\\;status=<S>\\;frequency=<F>\\;unit=<M>" +
487                        "\\;startcreatedtime=<SC>\\;endcreatedtime=<EC> \\;sortBy=<SB>\n" +
488                        "(text filter: matches partially with name and user or complete match with job ID. " +
489                        "Valid unit values are 'months', 'days', 'hours' or 'minutes'. " +
490                        "startcreatedtime, endcreatedtime: time of format yyyy-MM-dd'T'HH:mm'Z'. " +
491                        "Valid values for sortBy are 'createdTime' or 'lastModifiedTime'.)");
492        Option localtime = new Option(LOCAL_TIME_OPTION, false, "use local time (same as passing your time zone to -" +
493                TIME_ZONE_OPTION + "). Overrides -" + TIME_ZONE_OPTION + " option");
494        Option kill = new Option(KILL_OPTION, false, "bulk kill operation");
495        Option suspend = new Option(SUSPEND_OPTION, false, "bulk suspend operation");
496        Option resume = new Option(RESUME_OPTION, false, "bulk resume operation");
497        Option timezone = new Option(TIME_ZONE_OPTION, true,
498                "use time zone with the specified ID (default GMT).\nSee 'oozie info -timezones' for a list");
499        Option verbose = new Option(VERBOSE_OPTION, false, "verbose mode");
500        Option doAs = new Option(DO_AS_OPTION, true, "doAs user, impersonates as the specified user");
501        Option bulkMonitor = new Option(BULK_OPTION, true, "key-value pairs to filter bulk jobs response. e.g. bundle=<B>\\;" +
502                "coordinators=<C>\\;actionstatus=<S>\\;startcreatedtime=<SC>\\;endcreatedtime=<EC>\\;" +
503                "startscheduledtime=<SS>\\;endscheduledtime=<ES>\\; bundle, " +
504                "coordinators and actionstatus can be multiple comma separated values. " +
505                "Bundle and coordinators can be id(s) or appName(s) of those jobs. " +
506                "Specifying bundle is mandatory, other params are optional");
507        start.setType(Integer.class);
508        len.setType(Integer.class);
509        Options jobsOptions = new Options();
510        jobsOptions.addOption(oozie);
511        jobsOptions.addOption(doAs);
512        jobsOptions.addOption(localtime);
513        jobsOptions.addOption(kill);
514        jobsOptions.addOption(suspend);
515        jobsOptions.addOption(resume);
516        jobsOptions.addOption(timezone);
517        jobsOptions.addOption(start);
518        jobsOptions.addOption(len);
519        jobsOptions.addOption(oozie);
520        jobsOptions.addOption(filter);
521        jobsOptions.addOption(jobtype);
522        jobsOptions.addOption(verbose);
523        jobsOptions.addOption(bulkMonitor);
524        addAuthOptions(jobsOptions);
525        return jobsOptions;
526    }
527
528    /**
529     * Create option for command line option 'sla'
530     *
531     * @return sla options
532     */
533    protected Options createSlaOptions() {
534        Option oozie = new Option(OOZIE_OPTION, true, "Oozie URL");
535        Option start = new Option(OFFSET_OPTION, true, "start offset (default '0')");
536        Option len = new Option(LEN_OPTION, true, "number of results (default '100', max '1000')");
537        Option filter = new Option(FILTER_OPTION, true, "filter of SLA events. e.g., jobid=<J>\\;appname=<A>");
538        start.setType(Integer.class);
539        len.setType(Integer.class);
540        Options slaOptions = new Options();
541        slaOptions.addOption(start);
542        slaOptions.addOption(len);
543        slaOptions.addOption(filter);
544        slaOptions.addOption(oozie);
545        addAuthOptions(slaOptions);
546        return slaOptions;
547    }
548
549    /**
550     * Create option for command line option 'validate'
551     *
552     * @return validate options
553     */
554    protected Options createValidateOptions() {
555        Option oozie = new Option(OOZIE_OPTION, true, "Oozie URL");
556        Options validateOption = new Options();
557        validateOption.addOption(oozie);
558        addAuthOptions(validateOption);
559        return validateOption;
560    }
561
562    /**
563     * Create option for command line option 'pig' or 'hive'
564     * @param jobType type of job - pig or hive
565     * @return pig or hive options
566     */
567    @SuppressWarnings("static-access")
568    protected Options createScriptLanguageOptions(String jobType) {
569        Option oozie = new Option(OOZIE_OPTION, true, "Oozie URL");
570        Option config = new Option(CONFIG_OPTION, true, "job configuration file '.properties'");
571        Option file = new Option(SCRIPTFILE_OPTION, true, jobType + " script");
572        Option property = OptionBuilder.withArgName("property=value").hasArgs(2).withValueSeparator().withDescription(
573                "set/override value for given property").create("D");
574        Option params = OptionBuilder.withArgName("property=value").hasArgs(2).withValueSeparator().withDescription(
575                "set parameters for script").create("P");
576        Option doAs = new Option(DO_AS_OPTION, true, "doAs user, impersonates as the specified user");
577        Options Options = new Options();
578        Options.addOption(oozie);
579        Options.addOption(doAs);
580        Options.addOption(config);
581        Options.addOption(property);
582        Options.addOption(params);
583        Options.addOption(file);
584        addAuthOptions(Options);
585        return Options;
586    }
587
588    /**
589     * Create option for command line option 'sqoop'
590     * @return sqoop options
591     */
592    @SuppressWarnings("static-access")
593    protected Options createSqoopCLIOptions() {
594        Option oozie = new Option(OOZIE_OPTION, true, "Oozie URL");
595        Option config = new Option(CONFIG_OPTION, true, "job configuration file '.properties'");
596        Option command = OptionBuilder.withArgName(SQOOP_COMMAND_OPTION).hasArgs().withValueSeparator().withDescription(
597                "sqoop command").create(SQOOP_COMMAND_OPTION);
598        Option property = OptionBuilder.withArgName("property=value").hasArgs(2).withValueSeparator().withDescription(
599                "set/override value for given property").create("D");
600        Option doAs = new Option(DO_AS_OPTION, true, "doAs user, impersonates as the specified user");
601        Options Options = new Options();
602        Options.addOption(oozie);
603        Options.addOption(doAs);
604        Options.addOption(config);
605        Options.addOption(property);
606        Options.addOption(command);
607        addAuthOptions(Options);
608        return Options;
609    }
610
611    /**
612     * Create option for command line option 'info'
613     * @return info options
614     */
615    protected Options createInfoOptions() {
616        Option timezones = new Option(INFO_TIME_ZONES_OPTION, false, "display a list of available time zones");
617        Options infoOptions = new Options();
618        infoOptions.addOption(timezones);
619        return infoOptions;
620    }
621
622    /**
623     * Create option for command line option 'mapreduce'
624     * @return mapreduce options
625     */
626    @SuppressWarnings("static-access")
627    protected Options createMROptions() {
628        Option oozie = new Option(OOZIE_OPTION, true, "Oozie URL");
629        Option config = new Option(CONFIG_OPTION, true, "job configuration file '.properties'");
630        Option property = OptionBuilder.withArgName("property=value").hasArgs(2).withValueSeparator().withDescription(
631                "set/override value for given property").create("D");
632        Option doAs = new Option(DO_AS_OPTION, true, "doAs user, impersonates as the specified user");
633        Options mrOptions = new Options();
634        mrOptions.addOption(oozie);
635        mrOptions.addOption(doAs);
636        mrOptions.addOption(config);
637        mrOptions.addOption(property);
638        addAuthOptions(mrOptions);
639        return mrOptions;
640    }
641
642    /**
643     * Run a CLI programmatically.
644     * <p>
645     * It does not exit the JVM.
646     * <p>
647     * A CLI instance can be used only once.
648     *
649     * @param args options and arguments for the Oozie CLI.
650     * @return '0' (success), '-1' (failure).
651     */
652    public synchronized int run(String[] args) {
653        if (used) {
654            throw new IllegalStateException("CLI instance already used");
655        }
656        used = true;
657        final CLIParser parser = getCLIParser();
658        try {
659            final CLIParser.Command command = parser.parse(args);
660
661            String doAsUser = command.getCommandLine().getOptionValue(DO_AS_OPTION);
662
663            if (doAsUser != null) {
664                OozieClient.doAs(doAsUser, new Callable<Void>() {
665                    @Override
666                    public Void call() throws Exception {
667                        processCommand(parser, command);
668                        return null;
669                    }
670                });
671            }
672            else {
673                processCommand(parser, command);
674            }
675            return 0;
676        }
677        catch (OozieCLIException ex) {
678            System.err.println("Error: " + ex.getMessage());
679            return -1;
680        }
681        catch (ParseException ex) {
682            System.err.println("Invalid sub-command: " + ex.getMessage());
683            System.err.println();
684            System.err.println(parser.shortHelp());
685            return -1;
686        }
687        catch (Exception ex) {
688            ex.printStackTrace();
689            System.err.println(ex.getMessage());
690            return -1;
691        }
692    }
693
694    @VisibleForTesting
695    public CLIParser getCLIParser(){
696        CLIParser parser = new CLIParser(OOZIE_OPTION, getCLIHelp());
697        parser.addCommand(HELP_CMD, "", "display usage for all commands or specified command", new Options(), false);
698        parser.addCommand(VERSION_CMD, "", "show client version", new Options(), false);
699        parser.addCommand(JOB_CMD, "", "job operations", createJobOptions(), false);
700        parser.addCommand(JOBS_CMD, "", "jobs status", createJobsOptions(), false);
701        parser.addCommand(ADMIN_CMD, "", "admin operations", createAdminOptions(), false);
702        parser.addCommand(VALIDATE_CMD, "", "validate a workflow, coordinator, bundle XML file", createValidateOptions(), true);
703        parser.addCommand(SLA_CMD, "", "sla operations (Deprecated with Oozie 4.0)", createSlaOptions(), false);
704        parser.addCommand(PIG_CMD, "-X ", "submit a pig job, everything after '-X' are pass-through parameters to pig, any '-D' "
705                + "arguments after '-X' are put in <configuration>", createScriptLanguageOptions(PIG_CMD), true);
706        parser.addCommand(HIVE_CMD, "-X ", "submit a hive job, everything after '-X' are pass-through parameters to hive, any '-D' "
707                + "arguments after '-X' are put in <configuration>", createScriptLanguageOptions(HIVE_CMD), true);
708        parser.addCommand(SQOOP_CMD, "-X ", "submit a sqoop job, everything after '-X' are pass-through parameters " +
709                "to sqoop, any '-D' arguments after '-X' are put in <configuration>", createSqoopCLIOptions(), true);
710        parser.addCommand(INFO_CMD, "", "get more detailed info about specific topics", createInfoOptions(), false);
711        parser.addCommand(MR_CMD, "", "submit a mapreduce job", createMROptions(), false);
712        return parser;
713    }
714
715    public void processCommand(CLIParser parser, CLIParser.Command command) throws Exception {
716        switch (command.getName()) {
717            case JOB_CMD:
718                jobCommand(command.getCommandLine());
719                break;
720            case JOBS_CMD:
721                jobsCommand(command.getCommandLine());
722                break;
723            case ADMIN_CMD:
724                adminCommand(command.getCommandLine());
725                break;
726            case VERSION_CMD:
727                versionCommand();
728                break;
729            case VALIDATE_CMD:
730                validateCommand(command.getCommandLine());
731                break;
732            case SLA_CMD:
733                slaCommand(command.getCommandLine());
734                break;
735            case PIG_CMD:
736                scriptLanguageCommand(command.getCommandLine(), PIG_CMD);
737                break;
738            case HIVE_CMD:
739                scriptLanguageCommand(command.getCommandLine(), HIVE_CMD);
740                break;
741            case SQOOP_CMD:
742                sqoopCommand(command.getCommandLine());
743                break;
744            case INFO_CMD:
745                infoCommand(command.getCommandLine());
746                break;
747            case MR_CMD:
748                mrCommand(command.getCommandLine());
749                break;
750            default:
751                parser.showHelp(command.getCommandLine());
752        }
753    }
754    protected String getOozieUrl(CommandLine commandLine) {
755        String url = commandLine.getOptionValue(OOZIE_OPTION);
756        if (url == null) {
757            url = System.getenv(ENV_OOZIE_URL);
758            if (url == null) {
759                throw new IllegalArgumentException(
760                        "Oozie URL is not available neither in command option or in the environment");
761            }
762        }
763        return url;
764    }
765
766    private String getTimeZoneId(CommandLine commandLine)
767    {
768        if (commandLine.hasOption(LOCAL_TIME_OPTION)) {
769            return null;
770        }
771        if (commandLine.hasOption(TIME_ZONE_OPTION)) {
772            return commandLine.getOptionValue(TIME_ZONE_OPTION);
773        }
774        String timeZoneId = System.getenv(ENV_OOZIE_TIME_ZONE);
775        if (timeZoneId != null) {
776            return timeZoneId;
777        }
778        return "GMT";
779    }
780
781    // Canibalized from Hadoop <code>Configuration.loadResource()</code>.
782    private Properties parse(InputStream is, Properties conf) throws IOException {
783        try {
784            DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
785            docBuilderFactory.setNamespaceAware(true);
786            // support for includes in the xml file
787            docBuilderFactory.setXIncludeAware(true);
788            // ignore all comments inside the xml file
789            docBuilderFactory.setIgnoringComments(true);
790            docBuilderFactory.setExpandEntityReferences(false);
791            docBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
792            DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();
793            Document doc = builder.parse(is);
794            return parseDocument(doc, conf);
795        }
796        catch (SAXException | ParserConfigurationException e) {
797            throw new IOException(e);
798        }
799    }
800
801    // Canibalized from Hadoop <code>Configuration.loadResource()</code>.
802    private Properties parseDocument(Document doc, Properties conf) throws IOException {
803        try {
804            Element root = doc.getDocumentElement();
805            if (!"configuration".equals(root.getLocalName())) {
806                throw new RuntimeException("bad conf file: top-level element not <configuration>");
807            }
808            NodeList props = root.getChildNodes();
809            for (int i = 0; i < props.getLength(); i++) {
810                Node propNode = props.item(i);
811                if (!(propNode instanceof Element)) {
812                    continue;
813                }
814                Element prop = (Element) propNode;
815                if (!"property".equals(prop.getLocalName())) {
816                    throw new RuntimeException("bad conf file: element not <property>");
817                }
818                NodeList fields = prop.getChildNodes();
819                String attr = null;
820                String value = null;
821                for (int j = 0; j < fields.getLength(); j++) {
822                    Node fieldNode = fields.item(j);
823                    if (!(fieldNode instanceof Element)) {
824                        continue;
825                    }
826                    Element field = (Element) fieldNode;
827                    if ("name".equals(field.getLocalName()) && field.hasChildNodes()) {
828                        attr = ((Text) field.getFirstChild()).getData();
829                    }
830                    if ("value".equals(field.getLocalName()) && field.hasChildNodes()) {
831                        value = ((Text) field.getFirstChild()).getData();
832                    }
833                }
834
835                if (attr != null && value != null) {
836                    conf.setProperty(attr, value);
837                }
838            }
839            return conf;
840        }
841        catch (DOMException e) {
842            throw new IOException(e);
843        }
844    }
845
846    private Properties getConfiguration(OozieClient wc, CommandLine commandLine) throws IOException {
847        if (!isConfigurationSpecified(wc, commandLine)) {
848            throw new IOException("configuration is not specified");
849        }
850        Properties conf = wc.createConfiguration();
851        String configFile = commandLine.getOptionValue(CONFIG_OPTION);
852        if (configFile != null) {
853            File file = new File(configFile);
854            if (!file.exists()) {
855                throw new IOException("configuration file [" + configFile + "] not found");
856            }
857            if (configFile.endsWith(".properties")) {
858                conf.load(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));
859            }
860            else if (configFile.endsWith(".xml")) {
861                parse(new FileInputStream(configFile), conf);
862            }
863            else {
864                throw new IllegalArgumentException("configuration must be a '.properties' or a '.xml' file");
865            }
866        }
867        if (commandLine.hasOption("D")) {
868            Properties commandLineProperties = commandLine.getOptionProperties("D");
869            conf.putAll(commandLineProperties);
870        }
871        return conf;
872    }
873
874    /**
875     * Check if configuration has specified
876     * @param wc
877     * @param commandLine
878     * @return isConf
879     * @throws IOException
880     */
881    private boolean isConfigurationSpecified(OozieClient wc, CommandLine commandLine) throws IOException {
882        boolean isConf;
883        String configFile = commandLine.getOptionValue(CONFIG_OPTION);
884        if (configFile == null) {
885            isConf = false;
886        }
887        else {
888            isConf = new File(configFile).exists();
889        }
890        if (commandLine.hasOption("D")) {
891            isConf = true;
892        }
893        return isConf;
894    }
895
896    /**
897     * @param commandLine command line string.
898     * @return change value specified by -value.
899     * @throws OozieCLIException
900     */
901        private String getChangeValue(CommandLine commandLine) throws OozieCLIException {
902        String changeValue = commandLine.getOptionValue(CHANGE_VALUE_OPTION);
903
904        if (changeValue == null) {
905            throw new OozieCLIException("-value option needs to be specified for -change option");
906        }
907
908        return changeValue;
909    }
910
911    protected void addHeader(OozieClient wc) {
912        for (Map.Entry entry : System.getProperties().entrySet()) {
913            String key = (String) entry.getKey();
914            if (key.startsWith(WS_HEADER_PREFIX)) {
915                String header = key.substring(WS_HEADER_PREFIX.length());
916                wc.setHeader(header, (String) entry.getValue());
917            }
918        }
919    }
920
921    /**
922     * Get auth option from command line
923     *
924     * @param commandLine the command line object
925     * @return auth option
926     */
927    protected String getAuthOption(CommandLine commandLine) {
928        String authOpt = commandLine.getOptionValue(AUTH_OPTION);
929        if (authOpt == null) {
930            authOpt = System.getenv(ENV_OOZIE_AUTH);
931        }
932        if (commandLine.hasOption(DEBUG_OPTION)) {
933            System.out.println(" Auth type : " + authOpt);
934        }
935        return authOpt;
936    }
937
938    /**
939     * Create a OozieClient.
940     * <p>
941     * It injects any '-Dheader:' as header to the the {@link org.apache.oozie.client.OozieClient}.
942     *
943     * @param commandLine the parsed command line options.
944     * @return a pre configured eXtended workflow client.
945     * @throws OozieCLIException thrown if the OozieClient could not be configured.
946     */
947    protected OozieClient createOozieClient(CommandLine commandLine) throws OozieCLIException {
948        return createXOozieClient(commandLine);
949    }
950
951    /**
952     * Create a XOozieClient.
953     * <p>
954     * It injects any '-Dheader:' as header to the the {@link org.apache.oozie.client.OozieClient}.
955     *
956     * @param commandLine the parsed command line options.
957     * @return a pre configured eXtended workflow client.
958     * @throws OozieCLIException thrown if the XOozieClient could not be configured.
959     */
960    protected XOozieClient createXOozieClient(CommandLine commandLine) throws OozieCLIException {
961        XOozieClient wc = new AuthOozieClient(getOozieUrl(commandLine), getAuthOption(commandLine));
962        addHeader(wc);
963        setDebugMode(wc,commandLine.hasOption(DEBUG_OPTION));
964        setRetryCount(wc);
965        return wc;
966    }
967
968    protected void setDebugMode(OozieClient wc, boolean debugOpt) {
969
970        String debug = System.getenv(ENV_OOZIE_DEBUG);
971        if (debug != null && !debug.isEmpty()) {
972            int debugVal = 0;
973            try {
974                debugVal = Integer.parseInt(debug.trim());
975            }
976            catch (Exception ex) {
977                System.out.println("Unable to parse the debug settings. May be not an integer [" + debug + "]");
978                ex.printStackTrace();
979            }
980            wc.setDebugMode(debugVal);
981        }
982        else if(debugOpt){  // CLI argument "-debug" used
983            wc.setDebugMode(1);
984        }
985    }
986
987    protected void setRetryCount(OozieClient wc) {
988        String retryCount = System.getProperty(OOZIE_RETRY_COUNT);
989        if (retryCount != null && !retryCount.isEmpty()) {
990            try {
991                int retry = Integer.parseInt(retryCount.trim());
992                wc.setRetryCount(retry);
993            }
994            catch (Exception ex) {
995                System.err.println("Unable to parse the retry settings. May be not an integer [" + retryCount + "]");
996                ex.printStackTrace();
997            }
998        }
999    }
1000
1001    private static String JOB_ID_PREFIX = "job: ";
1002
1003    private void jobCommand(CommandLine commandLine) throws IOException, OozieCLIException {
1004        XOozieClient wc = createXOozieClient(commandLine);
1005
1006        List<String> options = new ArrayList<>();
1007        for (Option option : commandLine.getOptions()) {
1008            options.add(option.getOpt());
1009        }
1010
1011        try {
1012            if (options.contains(SUBMIT_OPTION)) {
1013                System.out.println(JOB_ID_PREFIX + wc.submit(getConfiguration(wc, commandLine)));
1014            }
1015            else if (options.contains(START_OPTION)) {
1016                wc.start(commandLine.getOptionValue(START_OPTION));
1017            }
1018            else if (options.contains(DRYRUN_OPTION) && !options.contains(UPDATE_OPTION)) {
1019                String dryrunStr = wc.dryrun(getConfiguration(wc, commandLine));
1020                if (dryrunStr.equals("OK")) {  // workflow
1021                    System.out.println("OK");
1022                }
1023                else if (dryrunStr.contains("<bundle-app")) {
1024                    // bundle
1025                    System.out.println("***Bundle job after parsing: ***");
1026                    System.out.println(dryrunStr);
1027                }
1028                else {
1029                    // coordinator
1030                    String[] dryrunStrs = dryrunStr.split("action for new instance");
1031                    int arraysize = dryrunStrs.length;
1032                    System.out.println("***coordJob after parsing: ***");
1033                    System.out.println(dryrunStrs[0]);
1034                    int aLen = dryrunStrs.length - 1;
1035                    if (aLen < 0) {
1036                        aLen = 0;
1037                    }
1038                    System.out.println("***total coord actions is " + aLen + " ***");
1039                    for (int i = 1; i <= arraysize - 1; i++) {
1040                        System.out.println(RULER);
1041                        System.out.println("coordAction instance: " + i + ":");
1042                        System.out.println(dryrunStrs[i]);
1043                    }
1044                }
1045            }
1046            else if (options.contains(SUSPEND_OPTION)) {
1047                wc.suspend(commandLine.getOptionValue(SUSPEND_OPTION));
1048            }
1049            else if (options.contains(RESUME_OPTION)) {
1050                wc.resume(commandLine.getOptionValue(RESUME_OPTION));
1051            }
1052            else if (options.contains(IGNORE_OPTION)) {
1053                String ignoreScope = null;
1054                if (options.contains(ACTION_OPTION)) {
1055                    ignoreScope = commandLine.getOptionValue(ACTION_OPTION);
1056                    if (ignoreScope == null || ignoreScope.isEmpty()) {
1057                        throw new OozieCLIException("-" + ACTION_OPTION + " is empty");
1058                    }
1059                }
1060                printCoordActionsStatus(wc.ignore(commandLine.getOptionValue(IGNORE_OPTION), ignoreScope));
1061            }
1062            else if (options.contains(KILL_OPTION)) {
1063                if (commandLine.getOptionValue(KILL_OPTION).contains("-C")
1064                        && (options.contains(DATE_OPTION) || options.contains(ACTION_OPTION))) {
1065                    String coordJobId = commandLine.getOptionValue(KILL_OPTION);
1066                    String scope;
1067                    String rangeType;
1068                    if (options.contains(DATE_OPTION) && options.contains(ACTION_OPTION)) {
1069                        throw new OozieCLIException("Invalid options provided for rerun: either" + DATE_OPTION + " or "
1070                                + ACTION_OPTION + " expected. Don't use both at the same time.");
1071                    }
1072                    if (options.contains(DATE_OPTION)) {
1073                        rangeType = RestConstants.JOB_COORD_SCOPE_DATE;
1074                        scope = commandLine.getOptionValue(DATE_OPTION);
1075                    }
1076                    else if (options.contains(ACTION_OPTION)) {
1077                        rangeType = RestConstants.JOB_COORD_SCOPE_ACTION;
1078                        scope = commandLine.getOptionValue(ACTION_OPTION);
1079                    }
1080                    else {
1081                        throw new OozieCLIException("Invalid options provided for rerun: " + DATE_OPTION + " or "
1082                                + ACTION_OPTION + " expected.");
1083                    }
1084                    printCoordActions(wc.kill(coordJobId, rangeType, scope));
1085                }
1086                else {
1087                    wc.kill(commandLine.getOptionValue(KILL_OPTION));
1088                }
1089            }
1090            else if (options.contains(CHANGE_OPTION)) {
1091                wc.change(commandLine.getOptionValue(CHANGE_OPTION), getChangeValue(commandLine));
1092            }
1093            else if (options.contains(RUN_OPTION)) {
1094                System.out.println(JOB_ID_PREFIX + wc.run(getConfiguration(wc, commandLine)));
1095            }
1096            else if (options.contains(RERUN_OPTION)) {
1097                if (commandLine.getOptionValue(RERUN_OPTION).contains("-W")) {
1098                    if (isConfigurationSpecified(wc, commandLine)) {
1099                        wc.reRun(commandLine.getOptionValue(RERUN_OPTION), getConfiguration(wc, commandLine));
1100                    }
1101                    else {
1102                        wc.reRun(commandLine.getOptionValue(RERUN_OPTION), new Properties());
1103                    }
1104                }
1105                else if (commandLine.getOptionValue(RERUN_OPTION).contains("-B")) {
1106                    String bundleJobId = commandLine.getOptionValue(RERUN_OPTION);
1107                    String coordScope = null;
1108                    String dateScope = null;
1109                    boolean refresh = false;
1110                    boolean noCleanup = false;
1111                    if (options.contains(ACTION_OPTION)) {
1112                        throw new OozieCLIException("Invalid options provided for bundle rerun. " + ACTION_OPTION
1113                                + " is not valid for bundle rerun");
1114                    }
1115                    if (options.contains(DATE_OPTION)) {
1116                        dateScope = commandLine.getOptionValue(DATE_OPTION);
1117                    }
1118
1119                    if (options.contains(COORD_OPTION)) {
1120                        coordScope = commandLine.getOptionValue(COORD_OPTION);
1121                    }
1122
1123                    if (options.contains(RERUN_REFRESH_OPTION)) {
1124                        refresh = true;
1125                    }
1126                    if (options.contains(RERUN_NOCLEANUP_OPTION)) {
1127                        noCleanup = true;
1128                    }
1129                    wc.reRunBundle(bundleJobId, coordScope, dateScope, refresh, noCleanup);
1130                    if (coordScope != null && !coordScope.isEmpty()) {
1131                        System.out.println("Coordinators [" + coordScope + "] of bundle " + bundleJobId
1132                                + " are scheduled to rerun on date ranges [" + dateScope + "].");
1133                    }
1134                    else {
1135                        System.out.println("All coordinators of bundle " + bundleJobId
1136                                + " are scheduled to rerun on the date ranges [" + dateScope + "].");
1137                    }
1138                }
1139                else {
1140                    String coordJobId = commandLine.getOptionValue(RERUN_OPTION);
1141                    String scope;
1142                    String rerunType;
1143                    boolean refresh = false;
1144                    boolean noCleanup = false;
1145                    boolean failed = false;
1146                    if (options.contains(DATE_OPTION) && options.contains(ACTION_OPTION)) {
1147                        throw new OozieCLIException("Invalid options provided for rerun: either" + DATE_OPTION + " or "
1148                                + ACTION_OPTION + " expected. Don't use both at the same time.");
1149                    }
1150                    if (options.contains(DATE_OPTION)) {
1151                        rerunType = RestConstants.JOB_COORD_SCOPE_DATE;
1152                        scope = commandLine.getOptionValue(DATE_OPTION);
1153                    }
1154                    else if (options.contains(ACTION_OPTION)) {
1155                        rerunType = RestConstants.JOB_COORD_SCOPE_ACTION;
1156                        scope = commandLine.getOptionValue(ACTION_OPTION);
1157                    }
1158                    else {
1159                        throw new OozieCLIException("Invalid options provided for rerun: " + DATE_OPTION + " or "
1160                                + ACTION_OPTION + " expected.");
1161                    }
1162                    if (options.contains(RERUN_REFRESH_OPTION)) {
1163                        refresh = true;
1164                    }
1165                    if (options.contains(RERUN_NOCLEANUP_OPTION)) {
1166                        noCleanup = true;
1167                    }
1168
1169                    Properties props = null;
1170                    if(isConfigurationSpecified(wc, commandLine)) {
1171                        props = getConfiguration(wc, commandLine);
1172                    }
1173
1174                    if (options.contains(RERUN_FAILED_OPTION)) {
1175                        failed = true;
1176                    }
1177
1178                    printCoordActions(wc.reRunCoord(coordJobId, rerunType, scope, refresh, noCleanup, failed, props));
1179                }
1180            }
1181            else if (options.contains(INFO_OPTION)) {
1182                String timeZoneId = getTimeZoneId(commandLine);
1183                final String optionValue = commandLine.getOptionValue(INFO_OPTION);
1184                if (optionValue.endsWith("-B")) {
1185                    String filter = commandLine.getOptionValue(FILTER_OPTION);
1186                    if (filter != null) {
1187                        throw new OozieCLIException("Filter option is currently not supported for a Bundle job");
1188                    }
1189                    printBundleJob(wc.getBundleJobInfo(optionValue), timeZoneId,
1190                            options.contains(VERBOSE_OPTION));
1191                }
1192                else if (optionValue.endsWith("-C")) {
1193                    String s = commandLine.getOptionValue(OFFSET_OPTION);
1194                    int start = Integer.parseInt((s != null) ? s : "-1");
1195                    s = commandLine.getOptionValue(LEN_OPTION);
1196                    int len = Integer.parseInt((s != null) ? s : "-1");
1197                    String filter = commandLine.getOptionValue(FILTER_OPTION);
1198                    String order = commandLine.getOptionValue(ORDER_OPTION);
1199                    printCoordJob(wc.getCoordJobInfo(optionValue, filter, start, len, order), timeZoneId,
1200                            options.contains(VERBOSE_OPTION));
1201                }
1202                else if (optionValue.contains("-C@")) {
1203                    if (options.contains(ALL_WORKFLOWS_FOR_COORD_ACTION)) {
1204                        printWfsForCoordAction(wc.getWfsForCoordAction(optionValue), timeZoneId);
1205                    }
1206                    else {
1207                        String filter = commandLine.getOptionValue(FILTER_OPTION);
1208                        if (filter != null) {
1209                            throw new OozieCLIException("Filter option is not supported for a Coordinator action");
1210                        }
1211                        printCoordAction(wc.getCoordActionInfo(optionValue), timeZoneId);
1212                    }
1213                }
1214                else if (optionValue.contains("-W@")) {
1215                    String filter = commandLine.getOptionValue(FILTER_OPTION);
1216                    if (filter != null) {
1217                        throw new OozieCLIException("Filter option is not supported for a Workflow action");
1218                    }
1219                    printWorkflowAction(wc.getWorkflowActionInfo(optionValue), timeZoneId,
1220                            options.contains(VERBOSE_OPTION));
1221
1222                }
1223                else {
1224                    String filter = commandLine.getOptionValue(FILTER_OPTION);
1225                    if (filter != null) {
1226                        throw new OozieCLIException("Filter option is currently not supported for a Workflow job");
1227                    }
1228                    String s = commandLine.getOptionValue(OFFSET_OPTION);
1229                    int start = Integer.parseInt((s != null) ? s : "0");
1230                    s = commandLine.getOptionValue(LEN_OPTION);
1231                    String jobtype = commandLine.getOptionValue(JOBTYPE_OPTION);
1232                    jobtype = (jobtype != null) ? jobtype : "wf";
1233                    int len = Integer.parseInt((s != null) ? s : "0");
1234                    printJob(wc.getJobInfo(optionValue, start, len), timeZoneId,
1235                            options.contains(VERBOSE_OPTION));
1236                }
1237            }
1238            else if (options.contains(LOG_OPTION)) {
1239                PrintStream ps = System.out;
1240                String logFilter = null;
1241                if (options.contains(RestConstants.LOG_FILTER_OPTION)) {
1242                    logFilter = commandLine.getOptionValue(RestConstants.LOG_FILTER_OPTION);
1243                }
1244                if (commandLine.getOptionValue(LOG_OPTION).contains("-C")) {
1245                    String logRetrievalScope = null;
1246                    String logRetrievalType = null;
1247                    if (options.contains(ACTION_OPTION)) {
1248                        logRetrievalType = RestConstants.JOB_LOG_ACTION;
1249                        logRetrievalScope = commandLine.getOptionValue(ACTION_OPTION);
1250                    }
1251                    if (options.contains(DATE_OPTION)) {
1252                        logRetrievalType = RestConstants.JOB_LOG_DATE;
1253                        logRetrievalScope = commandLine.getOptionValue(DATE_OPTION);
1254                    }
1255                    try {
1256                        wc.getJobLog(commandLine.getOptionValue(LOG_OPTION), logRetrievalType, logRetrievalScope,
1257                                logFilter, ps);
1258                    }
1259                    finally {
1260                        ps.close();
1261                    }
1262                }
1263                else {
1264                    if (!options.contains(ACTION_OPTION) && !options.contains(DATE_OPTION)) {
1265                        wc.getJobLog(commandLine.getOptionValue(LOG_OPTION), null, null, logFilter, ps);
1266                    }
1267                    else {
1268                        throw new OozieCLIException("Invalid options provided for log retrieval. " + ACTION_OPTION
1269                                + " and " + DATE_OPTION + " are valid only for coordinator job log retrieval");
1270                    }
1271                }
1272            }
1273            else if (options.contains(ERROR_LOG_OPTION)) {
1274                try (PrintStream ps = System.out) {
1275                    wc.getJobErrorLog(commandLine.getOptionValue(ERROR_LOG_OPTION), ps);
1276                }
1277            }
1278            else if (options.contains(AUDIT_LOG_OPTION)) {
1279                try (PrintStream ps = System.out) {
1280                    wc.getJobAuditLog(commandLine.getOptionValue(AUDIT_LOG_OPTION), ps);
1281                }
1282            }
1283            else if (options.contains(DEFINITION_OPTION)) {
1284                System.out.println(wc.getJobDefinition(commandLine.getOptionValue(DEFINITION_OPTION)));
1285            }
1286            else if (options.contains(CONFIG_CONTENT_OPTION)) {
1287                if (commandLine.getOptionValue(CONFIG_CONTENT_OPTION).endsWith("-C")) {
1288                    System.out.println(wc.getCoordJobInfo(commandLine.getOptionValue(CONFIG_CONTENT_OPTION)).getConf());
1289                }
1290                else if (commandLine.getOptionValue(CONFIG_CONTENT_OPTION).endsWith("-W")) {
1291                    System.out.println(wc.getJobInfo(commandLine.getOptionValue(CONFIG_CONTENT_OPTION)).getConf());
1292                }
1293                else if (commandLine.getOptionValue(CONFIG_CONTENT_OPTION).endsWith("-B")) {
1294                    System.out
1295                            .println(wc.getBundleJobInfo(commandLine.getOptionValue(CONFIG_CONTENT_OPTION)).getConf());
1296                }
1297                else {
1298                    System.out.println("ERROR:  job id [" + commandLine.getOptionValue(CONFIG_CONTENT_OPTION)
1299                            + "] doesn't end with either C or W or B");
1300                }
1301            }
1302            else if (options.contains(UPDATE_OPTION)) {
1303                String coordJobId = commandLine.getOptionValue(UPDATE_OPTION);
1304                Properties conf = null;
1305
1306                String dryrun = "";
1307                String showdiff = "";
1308
1309                if (commandLine.getOptionValue(CONFIG_OPTION) != null) {
1310                    conf = getConfiguration(wc, commandLine);
1311                }
1312                if (options.contains(DRYRUN_OPTION)) {
1313                    dryrun = "true";
1314                }
1315                if (commandLine.getOptionValue(SHOWDIFF_OPTION) != null) {
1316                    showdiff = commandLine.getOptionValue(SHOWDIFF_OPTION);
1317                }
1318                if (conf == null) {
1319                    System.out.println(wc.updateCoord(coordJobId, dryrun, showdiff));
1320                }
1321                else {
1322                    System.out.println(wc.updateCoord(coordJobId, conf, dryrun, showdiff));
1323                }
1324            }
1325            else if (options.contains(POLL_OPTION)) {
1326                String jobId = commandLine.getOptionValue(POLL_OPTION);
1327                int timeout = 30;
1328                int interval = 5;
1329                String timeoutS = commandLine.getOptionValue(TIMEOUT_OPTION);
1330                if (timeoutS != null) {
1331                    timeout = Integer.parseInt(timeoutS);
1332                }
1333                String intervalS = commandLine.getOptionValue(INTERVAL_OPTION);
1334                if (intervalS != null) {
1335                    interval = Integer.parseInt(intervalS);
1336                }
1337                boolean verbose = commandLine.hasOption(VERBOSE_OPTION);
1338                wc.pollJob(jobId, timeout, interval, verbose);
1339            }
1340            else if (options.contains(SLA_ENABLE_ALERT)) {
1341                slaAlertCommand(commandLine.getOptionValue(SLA_ENABLE_ALERT), wc, commandLine, options);
1342            }
1343            else if (options.contains(SLA_DISABLE_ALERT)) {
1344                slaAlertCommand(commandLine.getOptionValue(SLA_DISABLE_ALERT), wc, commandLine, options);
1345            }
1346            else if (options.contains(SLA_CHANGE)) {
1347                slaAlertCommand(commandLine.getOptionValue(SLA_CHANGE), wc, commandLine, options);
1348            }
1349            else if (options.contains(WORKFLOW_ACTIONS_RETRIES)) {
1350                printWorkflowActionRetries(
1351                        wc.getWorkflowActionRetriesInfo(commandLine.getOptionValue(WORKFLOW_ACTIONS_RETRIES)),
1352                        commandLine.getOptionValue(WORKFLOW_ACTIONS_RETRIES));
1353            }
1354            else if (options.contains(COORD_ACTION_MISSING_DEPENDENCIES)) {
1355                String actions = null, dates = null;
1356
1357                if (options.contains(ACTION_OPTION)) {
1358                    actions = commandLine.getOptionValue(ACTION_OPTION);
1359                }
1360
1361                if (options.contains(DATE_OPTION)) {
1362                    dates = commandLine.getOptionValue(DATE_OPTION);
1363                }
1364                wc.getCoordActionMissingDependencies(commandLine.getOptionValue(COORD_ACTION_MISSING_DEPENDENCIES),
1365                        actions, dates, System.out);
1366            }
1367            else if (options.contains(VALIDATE_JAR_OPTION)) {
1368                checkApiJar(wc, commandLine, options.contains(VERBOSE_OPTION));
1369            }
1370            else if (options.contains(SUBMIT_JAR_OPTION)) {
1371                submitApiJar(wc, commandLine, options.contains(VERBOSE_OPTION));
1372            }
1373            else if (options.contains(RUN_JAR_OPTION)) {
1374                runApiJar(wc, commandLine, options.contains(VERBOSE_OPTION));
1375            }
1376        }
1377        catch (final OozieClientException ex) {
1378            throw new OozieCLIException(ex.toString(), ex);
1379        }
1380    }
1381
1382    private void checkApiJar(final XOozieClient wc, final CommandLine commandLine, final boolean verbose)
1383            throws OozieClientException {
1384        final String apiJarPath = commandLine.getOptionValue(VALIDATE_JAR_OPTION);
1385        logIfVerbose(verbose, "Checking API jar: " + apiJarPath);
1386
1387        final String generatedXml = loadApiJarAndGenerateXml(apiJarPath, verbose);
1388
1389        final Path workflowXml;
1390        try {
1391            workflowXml = Files.createTempFile("workflow", ".xml");
1392            Files.write(workflowXml, generatedXml.getBytes(StandardCharsets.UTF_8));
1393
1394            logIfVerbose(verbose, "API jar was written to " + workflowXml.toString());
1395        }
1396        catch (final IOException e) {
1397            throw new OozieClientException(e.getMessage(), e);
1398        }
1399
1400        logIfVerbose(verbose, "Servlet response is: ");
1401        System.out.println(wc.validateXML(workflowXml.toString()));
1402
1403        logIfVerbose(verbose, "API jar is valid.");
1404    }
1405
1406    private void logIfVerbose(final boolean verbose, final String message) {
1407        if (verbose) {
1408            System.out.println(message);
1409        }
1410    }
1411
1412    @SuppressFBWarnings(value = {"PATH_TRAVERSAL_IN", "WEAK_FILENAMEUTILS"},
1413            justification = "FilenameUtils is used to filter user input. JDK8+ is used.")
1414    private String loadApiJarAndGenerateXml(final String apiJarPath, final boolean verbose) throws OozieClientException {
1415        final String generatedXml;
1416        try {
1417            logIfVerbose(verbose, "Loading API jar " + apiJarPath);
1418
1419            final Workflow generatedWorkflow = new ApiJarLoader(new File(
1420                    FilenameUtils.getFullPath(apiJarPath) + FilenameUtils.getName(apiJarPath)))
1421                    .loadAndGenerate();
1422            generatedXml = WorkflowMarshaller.marshal(generatedWorkflow);
1423
1424            logIfVerbose(verbose, "Workflow job definition generated from API jar: \n" + generatedXml);
1425        }
1426        catch (final IOException | ClassNotFoundException | IllegalAccessException | NoSuchMethodException |
1427                InvocationTargetException | InstantiationException | JAXBException e) {
1428            throw new OozieClientException(e.getMessage(), e);
1429        }
1430
1431        return generatedXml;
1432    }
1433
1434    private void submitApiJar(final XOozieClient wc, final CommandLine commandLine, final boolean verbose)
1435            throws OozieClientException {
1436        final String apiJarPath = commandLine.getOptionValue(SUBMIT_JAR_OPTION);
1437        logIfVerbose(verbose, "Submitting a job based on API jar: " + apiJarPath);
1438
1439        try {
1440            System.out.println(JOB_ID_PREFIX + wc.submit(getConfiguration(wc, commandLine),
1441                    loadApiJarAndGenerateXml(apiJarPath, verbose)));
1442        }
1443        catch (final IOException e) {
1444            throw new OozieClientException(e.getMessage(), e);
1445        }
1446
1447        logIfVerbose(verbose, "Job based on API jar submitted successfully.");
1448    }
1449
1450    private void runApiJar(final XOozieClient wc, final CommandLine commandLine, final boolean verbose)
1451            throws OozieClientException {
1452        final String apiJarPath = commandLine.getOptionValue(RUN_JAR_OPTION);
1453        logIfVerbose(verbose, "Running a job based on API jar: " + apiJarPath);
1454
1455        try {
1456            System.out.println(JOB_ID_PREFIX + wc.run(getConfiguration(wc, commandLine),
1457                    loadApiJarAndGenerateXml(apiJarPath, verbose)));
1458        }
1459        catch (final IOException e) {
1460            throw new OozieClientException(e.getMessage(), e);
1461        }
1462
1463        logIfVerbose(verbose, "Job based on API jar run successfully.");
1464    }
1465
1466    @VisibleForTesting
1467    void printCoordJob(CoordinatorJob coordJob, String timeZoneId, boolean verbose) {
1468        System.out.println("Job ID : " + coordJob.getId());
1469
1470        System.out.println(RULER);
1471
1472        List<CoordinatorAction> actions = coordJob.getActions();
1473        System.out.println("Job Name    : " + maskIfNull(coordJob.getAppName()));
1474        System.out.println("App Path    : " + maskIfNull(coordJob.getAppPath()));
1475        System.out.println("Status      : " + coordJob.getStatus());
1476        System.out.println("Start Time  : " + maskDate(coordJob.getStartTime(), timeZoneId, false));
1477        System.out.println("End Time    : " + maskDate(coordJob.getEndTime(), timeZoneId, false));
1478        System.out.println("Pause Time  : " + maskDate(coordJob.getPauseTime(), timeZoneId, false));
1479        System.out.println("Concurrency : " + coordJob.getConcurrency());
1480        System.out.println(RULER);
1481
1482        if (verbose) {
1483            System.out.println("ID" + VERBOSE_DELIMITER + "Action Number" + VERBOSE_DELIMITER + "Console URL"
1484                    + VERBOSE_DELIMITER + "Error Code" + VERBOSE_DELIMITER + "Error Message" + VERBOSE_DELIMITER
1485                    + "External ID" + VERBOSE_DELIMITER + "External Status" + VERBOSE_DELIMITER + "Job ID"
1486                    + VERBOSE_DELIMITER + "Tracker URI" + VERBOSE_DELIMITER + "Created" + VERBOSE_DELIMITER
1487                    + "Nominal Time" + VERBOSE_DELIMITER + "Status" + VERBOSE_DELIMITER + "Last Modified"
1488                    + VERBOSE_DELIMITER + "Missing Dependencies");
1489            System.out.println(RULER);
1490
1491            for (CoordinatorAction action : actions) {
1492                System.out.println(maskIfNull(action.getId()) + VERBOSE_DELIMITER + action.getActionNumber()
1493                        + VERBOSE_DELIMITER + maskIfNull(action.getConsoleUrl()) + VERBOSE_DELIMITER
1494                        + maskIfNull(action.getErrorCode()) + VERBOSE_DELIMITER + maskIfNull(action.getErrorMessage())
1495                        + VERBOSE_DELIMITER + maskIfNull(action.getExternalId()) + VERBOSE_DELIMITER
1496                        + maskIfNull(action.getExternalStatus()) + VERBOSE_DELIMITER + maskIfNull(action.getJobId())
1497                        + VERBOSE_DELIMITER + maskIfNull(action.getTrackerUri()) + VERBOSE_DELIMITER
1498                        + maskDate(action.getCreatedTime(), timeZoneId, verbose) + VERBOSE_DELIMITER
1499                        + maskDate(action.getNominalTime(), timeZoneId, verbose) + action.getStatus() + VERBOSE_DELIMITER
1500                        + maskDate(action.getLastModifiedTime(), timeZoneId, verbose) + VERBOSE_DELIMITER
1501                        + maskIfNull(getFirstMissingDependencies(action)));
1502
1503                System.out.println(RULER);
1504            }
1505        }
1506        else {
1507            System.out.println(String.format(COORD_ACTION_FORMATTER, "ID", "Status", "Ext ID", "Err Code", "Created",
1508                    "Nominal Time", "Last Mod"));
1509
1510            for (CoordinatorAction action : actions) {
1511                System.out.println(String.format(COORD_ACTION_FORMATTER, maskIfNull(action.getId()),
1512                        action.getStatus(), maskIfNull(action.getExternalId()), maskIfNull(action.getErrorCode()),
1513                        maskDate(action.getCreatedTime(), timeZoneId, verbose),
1514                        maskDate(action.getNominalTime(), timeZoneId, verbose),
1515                        maskDate(action.getLastModifiedTime(), timeZoneId, verbose)));
1516
1517                System.out.println(RULER);
1518            }
1519        }
1520    }
1521
1522    @VisibleForTesting
1523    void printBundleJob(BundleJob bundleJob, String timeZoneId, boolean verbose) {
1524        System.out.println("Job ID : " + bundleJob.getId());
1525
1526        System.out.println(RULER);
1527
1528        List<CoordinatorJob> coordinators = bundleJob.getCoordinators();
1529        System.out.println("Job Name : " + maskIfNull(bundleJob.getAppName()));
1530        System.out.println("App Path : " + maskIfNull(bundleJob.getAppPath()));
1531        System.out.println("Status   : " + bundleJob.getStatus());
1532        System.out.println("Kickoff time   : " + bundleJob.getKickoffTime());
1533        System.out.println(RULER);
1534
1535        System.out.println(String.format(BUNDLE_COORD_JOBS_FORMATTER, "Job ID", "Status", "Freq", "Unit", "Started",
1536                "Next Materialized"));
1537        System.out.println(RULER);
1538
1539        for (CoordinatorJob job : coordinators) {
1540            System.out.println(String.format(BUNDLE_COORD_JOBS_FORMATTER, maskIfNull(job.getId()), job.getStatus(),
1541                    job.getFrequency(), job.getTimeUnit(), maskDate(job.getStartTime(), timeZoneId, verbose),
1542                    maskDate(job.getNextMaterializedTime(), timeZoneId, verbose)));
1543
1544            System.out.println(RULER);
1545        }
1546    }
1547
1548    @VisibleForTesting
1549    void printCoordAction(CoordinatorAction coordAction, String timeZoneId) {
1550        System.out.println("ID : " + maskIfNull(coordAction.getId()));
1551
1552        System.out.println(RULER);
1553
1554        System.out.println("Action Number        : " + coordAction.getActionNumber());
1555        System.out.println("Console URL          : " + maskIfNull(coordAction.getConsoleUrl()));
1556        System.out.println("Error Code           : " + maskIfNull(coordAction.getErrorCode()));
1557        System.out.println("Error Message        : " + maskIfNull(coordAction.getErrorMessage()));
1558        System.out.println("External ID          : " + maskIfNull(coordAction.getExternalId()));
1559        System.out.println("External Status      : " + maskIfNull(coordAction.getExternalStatus()));
1560        System.out.println("Job ID               : " + maskIfNull(coordAction.getJobId()));
1561        System.out.println("Tracker URI          : " + maskIfNull(coordAction.getTrackerUri()));
1562        System.out.println("Created              : " + maskDate(coordAction.getCreatedTime(), timeZoneId, false));
1563        System.out.println("Nominal Time         : " + maskDate(coordAction.getNominalTime(), timeZoneId, false));
1564        System.out.println("Status               : " + coordAction.getStatus());
1565        System.out.println("Last Modified        : " + maskDate(coordAction.getLastModifiedTime(), timeZoneId, false));
1566        System.out.println("First Missing Dependency : " + maskIfNull(getFirstMissingDependencies(coordAction)));
1567
1568        System.out.println(RULER);
1569    }
1570
1571    private void printCoordActions(List<CoordinatorAction> actions) {
1572        if (actions != null && actions.size() > 0) {
1573            System.out.println("Action ID" + VERBOSE_DELIMITER + "Nominal Time");
1574            System.out.println(RULER);
1575            for (CoordinatorAction action : actions) {
1576                System.out.println(maskIfNull(action.getId()) + VERBOSE_DELIMITER
1577                        + maskDate(action.getNominalTime(), null,false));
1578            }
1579        }
1580        else {
1581            System.out.println("No Actions match your criteria!");
1582        }
1583    }
1584
1585    private void printCoordActionsStatus(List<CoordinatorAction> actions) {
1586        if (actions != null && actions.size() > 0) {
1587            System.out.println("Action ID" + VERBOSE_DELIMITER + "Nominal Time" + VERBOSE_DELIMITER + "Status");
1588            System.out.println(RULER);
1589            for (CoordinatorAction action : actions) {
1590                System.out.println(maskIfNull(action.getId()) + VERBOSE_DELIMITER
1591                        + maskDate(action.getNominalTime(), null, false) + VERBOSE_DELIMITER
1592                        + maskIfNull(action.getStatus().name()));
1593            }
1594        }
1595    }
1596
1597    @VisibleForTesting
1598    void printWorkflowAction(WorkflowAction action, String timeZoneId, boolean verbose) {
1599
1600        System.out.println("ID : " + maskIfNull(action.getId()));
1601
1602        System.out.println(RULER);
1603
1604        System.out.println("Console URL       : " + maskIfNull(action.getConsoleUrl()));
1605        System.out.println("Error Code        : " + maskIfNull(action.getErrorCode()));
1606        System.out.println("Error Message     : " + maskIfNull(action.getErrorMessage()));
1607        System.out.println("External ID       : " + maskIfNull(action.getExternalId()));
1608        System.out.println("External Status   : " + maskIfNull(action.getExternalStatus()));
1609        System.out.println("Name              : " + maskIfNull(action.getName()));
1610        System.out.println("Retries           : " + action.getUserRetryCount());
1611        System.out.println("Tracker URI       : " + maskIfNull(action.getTrackerUri()));
1612        System.out.println("Type              : " + maskIfNull(action.getType()));
1613        System.out.println("Started           : " + maskDate(action.getStartTime(), timeZoneId, verbose));
1614        System.out.println("Status            : " + action.getStatus());
1615        System.out.println("Ended             : " + maskDate(action.getEndTime(), timeZoneId, verbose));
1616
1617        if (verbose) {
1618            System.out.println("External Stats    : " + action.getStats());
1619            System.out.println("External ChildIDs : " + action.getExternalChildIDs());
1620        }
1621
1622        System.out.println(RULER);
1623    }
1624
1625    void printWorkflowActionRetries(List<Map<String, String>> retries, String actionId) {
1626        System.out.println("ID : " + maskIfNull(actionId));
1627        if (retries.isEmpty()) {
1628            System.out.println("No Retries");
1629        }
1630        for (Map<String, String> retry: retries) {
1631            System.out.println(RULER);
1632            System.out.println("Attempt        : " + retry.get(JsonTags.ACTION_ATTEMPT));
1633            System.out.println("Start Time     : " + retry.get(JsonTags.WORKFLOW_ACTION_START_TIME));
1634            System.out.println("End Time       : " + retry.get(JsonTags.WORKFLOW_ACTION_END_TIME));
1635            if (null != retry.get(JsonTags.WORKFLOW_ACTION_CONSOLE_URL)) {
1636                System.out.println("Console URL    : " + retry.get(JsonTags.WORKFLOW_ACTION_CONSOLE_URL));
1637            }
1638            if (null != retry.get(JsonTags.WORKFLOW_ACTION_EXTERNAL_CHILD_IDS)) {
1639                System.out.println("Child URL      : " + retry.get(JsonTags.WORKFLOW_ACTION_EXTERNAL_CHILD_IDS));
1640            }
1641        }
1642        System.out.println(RULER);
1643    }
1644
1645    private static final String WORKFLOW_JOBS_FORMATTER = "%-41s%-13s%-10s%-10s%-10s%-24s%-24s";
1646    private static final String COORD_JOBS_FORMATTER = "%-41s%-15s%-10s%-5s%-13s%-24s%-24s";
1647    private static final String BUNDLE_JOBS_FORMATTER = "%-41s%-15s%-10s%-20s%-20s%-13s%-13s";
1648    private static final String BUNDLE_COORD_JOBS_FORMATTER = "%-41s%-15s%-5s%-13s%-24s%-24s";
1649
1650    private static final String WORKFLOW_ACTION_FORMATTER = "%-78s%-10s%-23s%-11s%-10s";
1651    private static final String COORD_ACTION_FORMATTER = "%-43s%-10s%-37s%-10s%-21s%-21s";
1652    private static final String BULK_RESPONSE_FORMATTER = "%-13s%-38s%-13s%-41s%-10s%-38s%-21s%-38s";
1653
1654    @VisibleForTesting
1655    void printJob(WorkflowJob job, String timeZoneId, boolean verbose) throws IOException {
1656        System.out.println("Job ID : " + maskIfNull(job.getId()));
1657
1658        System.out.println(RULER);
1659
1660        System.out.println("Workflow Name : " + maskIfNull(job.getAppName()));
1661        System.out.println("App Path      : " + maskIfNull(job.getAppPath()));
1662        System.out.println("Status        : " + job.getStatus());
1663        System.out.println("Run           : " + job.getRun());
1664        System.out.println("User          : " + maskIfNull(job.getUser()));
1665        System.out.println("Group         : " + maskIfNull(job.getGroup()));
1666        System.out.println("Created       : " + maskDate(job.getCreatedTime(), timeZoneId, verbose));
1667        System.out.println("Started       : " + maskDate(job.getStartTime(), timeZoneId, verbose));
1668        System.out.println("Last Modified : " + maskDate(job.getLastModifiedTime(), timeZoneId, verbose));
1669        System.out.println("Ended         : " + maskDate(job.getEndTime(), timeZoneId, verbose));
1670        System.out.println("CoordAction ID: " + maskIfNull(job.getParentId()));
1671
1672        List<WorkflowAction> actions = job.getActions();
1673
1674        if (actions != null && actions.size() > 0) {
1675            System.out.println();
1676            System.out.println("Actions");
1677            System.out.println(RULER);
1678
1679            if (verbose) {
1680                System.out.println("ID" + VERBOSE_DELIMITER + "Console URL" + VERBOSE_DELIMITER + "Error Code"
1681                        + VERBOSE_DELIMITER + "Error Message" + VERBOSE_DELIMITER + "External ID" + VERBOSE_DELIMITER
1682                        + "External Status" + VERBOSE_DELIMITER + "Name" + VERBOSE_DELIMITER + "Retries"
1683                        + VERBOSE_DELIMITER + "Tracker URI" + VERBOSE_DELIMITER + "Type" + VERBOSE_DELIMITER
1684                        + "Started" + VERBOSE_DELIMITER + "Status" + VERBOSE_DELIMITER + "Ended");
1685                System.out.println(RULER);
1686
1687                for (WorkflowAction action : job.getActions()) {
1688                    System.out.println(maskIfNull(action.getId()) + VERBOSE_DELIMITER
1689                            + maskIfNull(action.getConsoleUrl()) + VERBOSE_DELIMITER
1690                            + maskIfNull(action.getErrorCode()) + VERBOSE_DELIMITER
1691                            + maskIfNull(action.getErrorMessage()) + VERBOSE_DELIMITER
1692                            + maskIfNull(action.getExternalId()) + VERBOSE_DELIMITER
1693                            + maskIfNull(action.getExternalStatus()) + VERBOSE_DELIMITER + maskIfNull(action.getName())
1694                            + VERBOSE_DELIMITER + action.getRetries() + VERBOSE_DELIMITER
1695                            + maskIfNull(action.getTrackerUri()) + VERBOSE_DELIMITER + maskIfNull(action.getType())
1696                            + VERBOSE_DELIMITER + maskDate(action.getStartTime(), timeZoneId, verbose)
1697                            + VERBOSE_DELIMITER + action.getStatus() + VERBOSE_DELIMITER
1698                            + maskDate(action.getEndTime(), timeZoneId, verbose));
1699
1700                    System.out.println(RULER);
1701                }
1702            }
1703            else {
1704                System.out.println(String.format(WORKFLOW_ACTION_FORMATTER, "ID", "Status", "Ext ID", "Ext Status",
1705                        "Err Code"));
1706
1707                System.out.println(RULER);
1708
1709                for (WorkflowAction action : job.getActions()) {
1710                    System.out.println(String.format(WORKFLOW_ACTION_FORMATTER, maskIfNull(action.getId()), action
1711                            .getStatus(), maskIfNull(action.getExternalId()), maskIfNull(action.getExternalStatus()),
1712                            maskIfNull(action.getErrorCode())));
1713
1714                    System.out.println(RULER);
1715                }
1716            }
1717        }
1718        else {
1719            System.out.println(RULER);
1720        }
1721
1722        System.out.println();
1723    }
1724
1725    private void jobsCommand(CommandLine commandLine) throws IOException, OozieCLIException {
1726        XOozieClient wc = createXOozieClient(commandLine);
1727
1728        List<String> options = new ArrayList<>();
1729        for (Option option : commandLine.getOptions()) {
1730            options.add(option.getOpt());
1731        }
1732
1733        String filter = commandLine.getOptionValue(FILTER_OPTION);
1734        String s = commandLine.getOptionValue(OFFSET_OPTION);
1735        int start = Integer.parseInt((s != null) ? s : "0");
1736        s = commandLine.getOptionValue(LEN_OPTION);
1737        String jobtype = commandLine.getOptionValue(JOBTYPE_OPTION);
1738        String timeZoneId = getTimeZoneId(commandLine);
1739        jobtype = (jobtype != null) ? jobtype : "wf";
1740        int len = Integer.parseInt((s != null) ? s : "0");
1741        String bulkFilterString = commandLine.getOptionValue(BULK_OPTION);
1742
1743        try {
1744            if (options.contains(KILL_OPTION)) {
1745                printBulkModifiedJobs(wc.killJobs(filter, jobtype, start, len), timeZoneId, "killed");
1746            }
1747            else if (options.contains(SUSPEND_OPTION)) {
1748                printBulkModifiedJobs(wc.suspendJobs(filter, jobtype, start, len), timeZoneId, "suspended");
1749            }
1750            else if (options.contains(RESUME_OPTION)) {
1751                printBulkModifiedJobs(wc.resumeJobs(filter, jobtype, start, len), timeZoneId, "resumed");
1752            }
1753            else if (bulkFilterString != null) {
1754                printBulkJobs(wc.getBulkInfo(bulkFilterString, start, len), timeZoneId, commandLine.hasOption(VERBOSE_OPTION));
1755            }
1756            else if (jobtype.toLowerCase().contains("wf")) {
1757                printJobs(wc.getJobsInfo(filter, start, len), timeZoneId, commandLine.hasOption(VERBOSE_OPTION));
1758            }
1759            else if (jobtype.toLowerCase().startsWith("coord")) {
1760                printCoordJobs(wc.getCoordJobsInfo(filter, start, len), timeZoneId, commandLine.hasOption(VERBOSE_OPTION));
1761            }
1762            else if (jobtype.toLowerCase().startsWith("bundle")) {
1763                printBundleJobs(wc.getBundleJobsInfo(filter, start, len), timeZoneId, commandLine.hasOption(VERBOSE_OPTION));
1764            }
1765
1766        }
1767        catch (OozieClientException ex) {
1768            throw new OozieCLIException(ex.toString(), ex);
1769        }
1770    }
1771
1772    @VisibleForTesting
1773    void printBulkModifiedJobs(JSONObject json, String timeZoneId, String action) throws IOException {
1774        if (json.containsKey(JsonTags.WORKFLOWS_JOBS)) {
1775            JSONArray workflows = (JSONArray) json.get(JsonTags.WORKFLOWS_JOBS);
1776            if (workflows == null) {
1777                workflows = new JSONArray();
1778            }
1779            List<WorkflowJob> wfs = JsonToBean.createWorkflowJobList(workflows);
1780            if (wfs.isEmpty()) {
1781                System.out.println("bulk modify command did not modify any jobs");
1782            }
1783            else {
1784                System.out.println("the following jobs have been " + action);
1785                printJobs(wfs, timeZoneId, false);
1786            }
1787        }
1788        else if (json.containsKey(JsonTags.COORDINATOR_JOBS)) {
1789            JSONArray coordinators = (JSONArray) json.get(JsonTags.COORDINATOR_JOBS);
1790            if (coordinators == null) {
1791                coordinators = new JSONArray();
1792            }
1793            List<CoordinatorJob> coords = JsonToBean.createCoordinatorJobList(coordinators);
1794            if (coords.isEmpty()) {
1795                System.out.println("bulk modify command did not modify any jobs");
1796            }
1797            else {
1798                System.out.println("the following jobs have been " + action);
1799                printCoordJobs(coords, timeZoneId, false);
1800            }
1801        }
1802        else {
1803            JSONArray bundles = (JSONArray) json.get(JsonTags.BUNDLE_JOBS);
1804            if (bundles == null) {
1805                bundles = new JSONArray();
1806            }
1807            List<BundleJob> bundleJobs = JsonToBean.createBundleJobList(bundles);
1808            if (bundleJobs.isEmpty()) {
1809                System.out.println("bulk modify command did not modify any jobs");
1810            }
1811            else {
1812                System.out.println("the following jobs have been " + action);
1813                printBundleJobs(bundleJobs, timeZoneId, false);
1814            }
1815        }
1816    }
1817
1818    @VisibleForTesting
1819    void printCoordJobs(List<CoordinatorJob> jobs, String timeZoneId, boolean verbose) throws IOException {
1820        if (jobs != null && jobs.size() > 0) {
1821            if (verbose) {
1822                System.out.println("Job ID" + VERBOSE_DELIMITER + "App Name" + VERBOSE_DELIMITER + "App Path"
1823                        + VERBOSE_DELIMITER + "Console URL" + VERBOSE_DELIMITER + "User" + VERBOSE_DELIMITER + "Group"
1824                        + VERBOSE_DELIMITER + "Concurrency" + VERBOSE_DELIMITER + "Frequency" + VERBOSE_DELIMITER
1825                        + "Time Unit" + VERBOSE_DELIMITER + "Time Zone" + VERBOSE_DELIMITER + "Time Out"
1826                        + VERBOSE_DELIMITER + "Started" + VERBOSE_DELIMITER + "Next Materialize" + VERBOSE_DELIMITER
1827                        + "Status" + VERBOSE_DELIMITER + "Last Action" + VERBOSE_DELIMITER + "Ended");
1828                System.out.println(RULER);
1829
1830                for (CoordinatorJob job : jobs) {
1831                    System.out.println(maskIfNull(job.getId()) + VERBOSE_DELIMITER + maskIfNull(job.getAppName())
1832                            + VERBOSE_DELIMITER + maskIfNull(job.getAppPath()) + VERBOSE_DELIMITER
1833                            + maskIfNull(job.getConsoleUrl()) + VERBOSE_DELIMITER + maskIfNull(job.getUser())
1834                            + VERBOSE_DELIMITER + maskIfNull(job.getGroup()) + VERBOSE_DELIMITER + job.getConcurrency()
1835                            + VERBOSE_DELIMITER + job.getFrequency() + VERBOSE_DELIMITER + job.getTimeUnit()
1836                            + VERBOSE_DELIMITER + maskIfNull(job.getTimeZone()) + VERBOSE_DELIMITER + job.getTimeout()
1837                            + VERBOSE_DELIMITER + maskDate(job.getStartTime(), timeZoneId, verbose) + VERBOSE_DELIMITER
1838                            + maskDate(job.getNextMaterializedTime(), timeZoneId, verbose) + VERBOSE_DELIMITER
1839                            + job.getStatus() + VERBOSE_DELIMITER
1840                            + maskDate(job.getLastActionTime(), timeZoneId, verbose) + VERBOSE_DELIMITER
1841                            + maskDate(job.getEndTime(), timeZoneId, verbose));
1842
1843                    System.out.println(RULER);
1844                }
1845            }
1846            else {
1847                System.out.println(String.format(COORD_JOBS_FORMATTER, "Job ID", "App Name", "Status", "Freq", "Unit",
1848                        "Started", "Next Materialized"));
1849                System.out.println(RULER);
1850
1851                for (CoordinatorJob job : jobs) {
1852                    System.out.println(String.format(COORD_JOBS_FORMATTER, maskIfNull(job.getId()), maskIfNull(job
1853                            .getAppName()), job.getStatus(), job.getFrequency(), job.getTimeUnit(), maskDate(job
1854                            .getStartTime(), timeZoneId, verbose), maskDate(job.getNextMaterializedTime(), timeZoneId, verbose)));
1855
1856                    System.out.println(RULER);
1857                }
1858            }
1859        }
1860        else {
1861            System.out.println("No Jobs match your criteria!");
1862        }
1863    }
1864
1865    @VisibleForTesting
1866    void printBulkJobs(List<BulkResponse> jobs, String timeZoneId, boolean verbose) throws IOException {
1867        if (jobs != null && jobs.size() > 0) {
1868            for (BulkResponse response : jobs) {
1869                BundleJob bundle = response.getBundle();
1870                CoordinatorJob coord = response.getCoordinator();
1871                CoordinatorAction action = response.getAction();
1872                if (verbose) {
1873                    System.out.println();
1874                    System.out.println("Bundle Name : " + maskIfNull(bundle.getAppName()));
1875
1876                    System.out.println(RULER);
1877
1878                    System.out.println("Bundle ID        : " + maskIfNull(bundle.getId()));
1879                    System.out.println("Coordinator Name : " + maskIfNull(coord.getAppName()));
1880                    System.out.println("Coord Action ID  : " + maskIfNull(action.getId()));
1881                    System.out.println("Action Status    : " + action.getStatus());
1882                    System.out.println("External ID      : " + maskIfNull(action.getExternalId()));
1883                    System.out.println("Created Time     : " + maskDate(action.getCreatedTime(), timeZoneId, false));
1884                    System.out.println("User             : " + maskIfNull(bundle.getUser()));
1885                    System.out.println("Error Message    : " + maskIfNull(action.getErrorMessage()));
1886                    System.out.println(RULER);
1887                }
1888                else {
1889                    System.out.println(String.format(BULK_RESPONSE_FORMATTER, "Bundle Name", "Bundle ID", "Coord Name",
1890                            "Coord Action ID", "Status", "External ID", "Created Time", "Error Message"));
1891                    System.out.println(RULER);
1892                    System.out
1893                            .println(String.format(BULK_RESPONSE_FORMATTER, maskIfNull(bundle.getAppName()),
1894                                    maskIfNull(bundle.getId()), maskIfNull(coord.getAppName()),
1895                                    maskIfNull(action.getId()), action.getStatus(), maskIfNull(action.getExternalId()),
1896                                    maskDate(action.getCreatedTime(), timeZoneId, false),
1897                                    maskIfNull(action.getErrorMessage())));
1898                    System.out.println(RULER);
1899                }
1900            }
1901        }
1902        else {
1903            System.out.println("Bulk request criteria did not match any coordinator actions");
1904        }
1905    }
1906
1907    @VisibleForTesting
1908    void printBundleJobs(List<BundleJob> jobs, String timeZoneId, boolean verbose) throws IOException {
1909        if (jobs != null && jobs.size() > 0) {
1910            if (verbose) {
1911                System.out.println("Job ID" + VERBOSE_DELIMITER + "Bundle Name" + VERBOSE_DELIMITER + "Bundle Path"
1912                        + VERBOSE_DELIMITER + "User" + VERBOSE_DELIMITER + "Group" + VERBOSE_DELIMITER + "Status"
1913                        + VERBOSE_DELIMITER + "Kickoff" + VERBOSE_DELIMITER + "Pause" + VERBOSE_DELIMITER + "Created"
1914                        + VERBOSE_DELIMITER + "Console URL");
1915                System.out.println(RULER);
1916
1917                for (BundleJob job : jobs) {
1918                    System.out.println(maskIfNull(job.getId()) + VERBOSE_DELIMITER + maskIfNull(job.getAppName())
1919                            + VERBOSE_DELIMITER + maskIfNull(job.getAppPath()) + VERBOSE_DELIMITER
1920                            + maskIfNull(job.getUser()) + VERBOSE_DELIMITER + maskIfNull(job.getGroup())
1921                            + VERBOSE_DELIMITER + job.getStatus() + VERBOSE_DELIMITER
1922                            + maskDate(job.getKickoffTime(), timeZoneId, verbose) + VERBOSE_DELIMITER
1923                            + maskDate(job.getPauseTime(), timeZoneId, verbose) + VERBOSE_DELIMITER
1924                            + maskDate(job.getCreatedTime(), timeZoneId, verbose) + VERBOSE_DELIMITER
1925                            + maskIfNull(job.getConsoleUrl()));
1926
1927                    System.out.println(RULER);
1928                }
1929            }
1930            else {
1931                System.out.println(String.format(BUNDLE_JOBS_FORMATTER, "Job ID", "Bundle Name", "Status", "Kickoff",
1932                        "Created", "User", "Group"));
1933                System.out.println(RULER);
1934
1935                for (BundleJob job : jobs) {
1936                    System.out.println(String.format(BUNDLE_JOBS_FORMATTER, maskIfNull(job.getId()),
1937                            maskIfNull(job.getAppName()), job.getStatus(),
1938                            maskDate(job.getKickoffTime(), timeZoneId, verbose),
1939                            maskDate(job.getCreatedTime(), timeZoneId, verbose), maskIfNull(job.getUser()),
1940                            maskIfNull(job.getGroup())));
1941                    System.out.println(RULER);
1942                }
1943            }
1944        }
1945        else {
1946            System.out.println("No Jobs match your criteria!");
1947        }
1948    }
1949
1950    private void slaCommand(CommandLine commandLine) throws IOException, OozieCLIException {
1951        XOozieClient wc = createXOozieClient(commandLine);
1952
1953        String s = commandLine.getOptionValue(OFFSET_OPTION);
1954        int start = Integer.parseInt((s != null) ? s : "0");
1955        s = commandLine.getOptionValue(LEN_OPTION);
1956        int len = Integer.parseInt((s != null) ? s : "100");
1957        String filter = commandLine.getOptionValue(FILTER_OPTION);
1958
1959        try {
1960            wc.getSlaInfo(start, len, filter);
1961        }
1962        catch (OozieClientException ex) {
1963            throw new OozieCLIException(ex.toString(), ex);
1964        }
1965    }
1966
1967    private void adminCommand(CommandLine commandLine) throws OozieCLIException {
1968        XOozieClient wc = createXOozieClient(commandLine);
1969
1970        List<String> options = new ArrayList<>();
1971        for (Option option : commandLine.getOptions()) {
1972            options.add(option.getOpt());
1973        }
1974
1975        try {
1976            SYSTEM_MODE status;
1977            if (options.contains(VERSION_OPTION)) {
1978                System.out.println("Oozie server build version: " + wc.getServerBuildVersion());
1979            }
1980            else if (options.contains(SYSTEM_MODE_OPTION)) {
1981                String systemModeOption = commandLine.getOptionValue(SYSTEM_MODE_OPTION).toUpperCase();
1982                try {
1983                    status = SYSTEM_MODE.valueOf(systemModeOption);
1984                }
1985                catch (Exception e) {
1986                    throw new OozieCLIException("Invalid input provided for option: " + SYSTEM_MODE_OPTION
1987                            + " value given :" + systemModeOption
1988                            + " Expected values are: NORMAL/NOWEBSERVICE/SAFEMODE ");
1989                }
1990                wc.setSystemMode(status);
1991                System.out.println("System mode: " + status);
1992            }
1993            else if (options.contains(STATUS_OPTION)) {
1994                status = wc.getSystemMode();
1995                System.out.println("System mode: " + status);
1996            }
1997
1998            else if (options.contains(UPDATE_SHARELIB_OPTION)) {
1999                System.out.println(wc.updateShareLib());
2000            }
2001
2002            else if (options.contains(LIST_SHARELIB_LIB_OPTION)) {
2003                String sharelibKey = null;
2004                if (commandLine.getArgList().size() > 0) {
2005                    sharelibKey = (String) commandLine.getArgList().get(0);
2006                }
2007                System.out.println(wc.listShareLib(sharelibKey));
2008            }
2009
2010            else if (options.contains(QUEUE_DUMP_OPTION)) {
2011
2012                List<String> list = wc.getQueueDump();
2013                if (list != null && list.size() != 0) {
2014                    for (String str : list) {
2015                        System.out.println(str);
2016                    }
2017                }
2018                else {
2019                    System.out.println("QueueDump is null!");
2020                }
2021            }
2022            else if (options.contains(AVAILABLE_SERVERS_OPTION)) {
2023                Map<String, String> availableOozieServers = new TreeMap<>(wc.getAvailableOozieServers());
2024                for (Map.Entry<String, String> ent : availableOozieServers.entrySet()) {
2025                    System.out.println(ent.getKey() + " : " + ent.getValue());
2026                }
2027            } else if (options.contains(SERVER_CONFIGURATION_OPTION)) {
2028                Map<String, String> serverConfig = new TreeMap<>(wc.getServerConfiguration());
2029                for (Map.Entry<String, String> ent : serverConfig.entrySet()) {
2030                    System.out.println(ent.getKey() + " : " + ent.getValue());
2031                }
2032            } else if (options.contains(SERVER_OS_ENV_OPTION)) {
2033                Map<String, String> osEnv = new TreeMap<>(wc.getOSEnv());
2034                for (Map.Entry<String, String> ent : osEnv.entrySet()) {
2035                    System.out.println(ent.getKey() + " : " + ent.getValue());
2036                }
2037            } else if (options.contains(SERVER_JAVA_SYSTEM_PROPERTIES_OPTION)) {
2038                Map<String, String> javaSysProps = new TreeMap<>(wc.getJavaSystemProperties());
2039                for (Map.Entry<String, String> ent : javaSysProps.entrySet()) {
2040                    System.out.println(ent.getKey() + " : " + ent.getValue());
2041                }
2042            } else if (options.contains(METRICS_OPTION)) {
2043                OozieClient.Metrics metrics = wc.getMetrics();
2044                if (metrics == null) {
2045                    System.out.println("Metrics are unavailable.  Try Instrumentation (-" + INSTRUMENTATION_OPTION + ") instead");
2046                } else {
2047                    printMetrics(metrics);
2048                }
2049            } else if (options.contains(INSTRUMENTATION_OPTION)) {
2050                OozieClient.Instrumentation instrumentation = wc.getInstrumentation();
2051                if (instrumentation == null) {
2052                    System.out.println("Instrumentation is unavailable.  Try Metrics (-" + METRICS_OPTION + ") instead");
2053                } else {
2054                    printInstrumentation(instrumentation);
2055                }
2056            } else if (options.contains(PURGE_OPTION)) {
2057                String purgeOptions = commandLine.getOptionValue(PURGE_OPTION);
2058                System.out.println(wc.purgeCommand(purgeOptions));
2059            }
2060        }
2061        catch (OozieClientException ex) {
2062            throw new OozieCLIException(ex.toString(), ex);
2063        }
2064    }
2065
2066    private void versionCommand() throws OozieCLIException {
2067        StringBuilder sb = new StringBuilder();
2068        sb.append("Oozie client build version: ")
2069            .append(BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VERSION))
2070            .append("\nSource code repository: ")
2071            .append(BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VC_URL))
2072            .append("\nCompiled by ")
2073            .append(BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_USER_NAME))
2074            .append(" on ")
2075            .append(BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_TIME))
2076            .append("\nFrom source with checksum: ")
2077            .append(BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VC_REVISION));
2078        System.out.println(sb.toString());
2079    }
2080
2081    @VisibleForTesting
2082    void printJobs(List<WorkflowJob> jobs, String timeZoneId, boolean verbose) throws IOException {
2083        if (jobs != null && jobs.size() > 0) {
2084            if (verbose) {
2085                System.out.println("Job ID" + VERBOSE_DELIMITER + "App Name" + VERBOSE_DELIMITER + "App Path"
2086                        + VERBOSE_DELIMITER + "Console URL" + VERBOSE_DELIMITER + "User" + VERBOSE_DELIMITER + "Group"
2087                        + VERBOSE_DELIMITER + "Run" + VERBOSE_DELIMITER + "Created" + VERBOSE_DELIMITER + "Started"
2088                        + VERBOSE_DELIMITER + "Status" + VERBOSE_DELIMITER + "Last Modified" + VERBOSE_DELIMITER
2089                        + "Ended");
2090                System.out.println(RULER);
2091
2092                for (WorkflowJob job : jobs) {
2093                    System.out.println(maskIfNull(job.getId()) + VERBOSE_DELIMITER + maskIfNull(job.getAppName())
2094                            + VERBOSE_DELIMITER + maskIfNull(job.getAppPath()) + VERBOSE_DELIMITER
2095                            + maskIfNull(job.getConsoleUrl()) + VERBOSE_DELIMITER + maskIfNull(job.getUser())
2096                            + VERBOSE_DELIMITER + maskIfNull(job.getGroup()) + VERBOSE_DELIMITER + job.getRun()
2097                            + VERBOSE_DELIMITER + maskDate(job.getCreatedTime(), timeZoneId, verbose)
2098                            + VERBOSE_DELIMITER + maskDate(job.getStartTime(), timeZoneId, verbose) + VERBOSE_DELIMITER
2099                            + job.getStatus() + VERBOSE_DELIMITER
2100                            + maskDate(job.getLastModifiedTime(), timeZoneId, verbose) + VERBOSE_DELIMITER
2101                            + maskDate(job.getEndTime(), timeZoneId, verbose));
2102
2103                    System.out.println(RULER);
2104                }
2105            }
2106            else {
2107                System.out.println(String.format(WORKFLOW_JOBS_FORMATTER, "Job ID", "App Name", "Status", "User",
2108                        "Group", "Started", "Ended"));
2109                System.out.println(RULER);
2110
2111                for (WorkflowJob job : jobs) {
2112                    System.out.println(String.format(WORKFLOW_JOBS_FORMATTER, maskIfNull(job.getId()),
2113                            maskIfNull(job.getAppName()), job.getStatus(), maskIfNull(job.getUser()),
2114                            maskIfNull(job.getGroup()), maskDate(job.getStartTime(), timeZoneId, verbose),
2115                            maskDate(job.getEndTime(), timeZoneId, verbose)));
2116
2117                    System.out.println(RULER);
2118                }
2119            }
2120        }
2121        else {
2122            System.out.println("No Jobs match your criteria!");
2123        }
2124    }
2125
2126    void printWfsForCoordAction(List<WorkflowJob> jobs, String timeZoneId) throws IOException {
2127        if (jobs != null && jobs.size() > 0) {
2128            System.out.println(String.format("%-41s%-10s%-24s%-24s", "Job ID", "Status", "Started", "Ended"));
2129            System.out.println(RULER);
2130
2131            for (WorkflowJob job : jobs) {
2132                System.out
2133                        .println(String.format("%-41s%-10s%-24s%-24s", maskIfNull(job.getId()), job.getStatus(),
2134                                maskDate(job.getStartTime(), timeZoneId, false),
2135                                maskDate(job.getEndTime(), timeZoneId, false)));
2136                System.out.println(RULER);
2137            }
2138        }
2139    }
2140
2141    private String maskIfNull(String value) {
2142        if (value != null && value.length() > 0) {
2143            return value;
2144        }
2145        return "-";
2146    }
2147
2148    private String maskDate(Date date, String timeZoneId, boolean verbose) {
2149        if (date == null) {
2150            return "-";
2151        }
2152
2153        SimpleDateFormat dateFormater;
2154        if (verbose) {
2155            dateFormater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz", Locale.US);
2156        }
2157        else {
2158            dateFormater = new SimpleDateFormat("yyyy-MM-dd HH:mm zzz", Locale.US);
2159        }
2160
2161        if (timeZoneId != null) {
2162            dateFormater.setTimeZone(TimeZone.getTimeZone(timeZoneId));
2163        }
2164        String dateString = dateFormater.format(date);
2165        // Most TimeZones are 3 or 4 characters; GMT offsets (e.g. GMT-07:00) are 9, so lets remove the "GMT" part to make it 6
2166        // to fit better
2167        Matcher m = GMT_OFFSET_SHORTEN_PATTERN.matcher(dateString);
2168        if (m.matches() && m.groupCount() == 2) {
2169            dateString = m.group(1) + m.group(2);
2170        }
2171        return dateString;
2172    }
2173
2174    private void validateCommand(CommandLine commandLine) throws OozieCLIException {
2175        String[] args = commandLine.getArgs();
2176        if (args.length != 1) {
2177            throw new OozieCLIException("One file must be specified");
2178        }
2179        try {
2180            XOozieClient wc = createXOozieClient(commandLine);
2181            String result = wc.validateXML(args[0]);
2182            if (result == null) {
2183                return;
2184            }
2185            System.out.println(result);
2186        } catch (OozieClientException e) {
2187            throw new OozieCLIException(e.getMessage(), e);
2188        }
2189    }
2190
2191    private void scriptLanguageCommand(CommandLine commandLine, String jobType) throws IOException, OozieCLIException {
2192        List<String> args = commandLine.getArgList();
2193        if (args.size() > 0) {
2194            // checking if args starts with -X (because CLIParser cannot check this)
2195            if (!args.get(0).equals("-X")) {
2196                throw new OozieCLIException("Unrecognized option: " + args.get(0) + " Expecting -X");
2197            }
2198            args.remove(0);
2199        }
2200
2201        if (!commandLine.hasOption(SCRIPTFILE_OPTION)) {
2202            throw new OozieCLIException("Need to specify -file <scriptfile>");
2203        }
2204
2205        if (!commandLine.hasOption(CONFIG_OPTION)) {
2206            throw new OozieCLIException("Need to specify -config <configfile>");
2207        }
2208
2209        try {
2210            XOozieClient wc = createXOozieClient(commandLine);
2211            Properties conf = getConfiguration(wc, commandLine);
2212            String script = commandLine.getOptionValue(SCRIPTFILE_OPTION);
2213            List<String> paramsList = new ArrayList<>();
2214            if (commandLine.hasOption("P")) {
2215                Properties params = commandLine.getOptionProperties("P");
2216                for (String key : params.stringPropertyNames()) {
2217                    paramsList.add(key + "=" + params.getProperty(key));
2218                }
2219            }
2220            System.out.println(JOB_ID_PREFIX + wc.submitScriptLanguage(conf, script, args.toArray(new String[args.size()]),
2221                    paramsList.toArray(new String[paramsList.size()]), jobType));
2222        }
2223        catch (OozieClientException ex) {
2224            throw new OozieCLIException(ex.toString(), ex);
2225        }
2226    }
2227
2228    private void sqoopCommand(CommandLine commandLine) throws IOException, OozieCLIException {
2229        List<String> args = commandLine.getArgList();
2230        if (args.size() > 0) {
2231            // checking if args starts with -X (because CLIParser cannot check this)
2232            if (!args.get(0).equals("-X")) {
2233                throw new OozieCLIException("Unrecognized option: " + args.get(0) + " Expecting -X");
2234            }
2235            args.remove(0);
2236        }
2237
2238        if (!commandLine.hasOption(SQOOP_COMMAND_OPTION)) {
2239            throw new OozieCLIException("Need to specify -command");
2240        }
2241
2242        if (!commandLine.hasOption(CONFIG_OPTION)) {
2243            throw new OozieCLIException("Need to specify -config <configfile>");
2244        }
2245
2246        try {
2247            XOozieClient wc = createXOozieClient(commandLine);
2248            Properties conf = getConfiguration(wc, commandLine);
2249            String[] command = commandLine.getOptionValues(SQOOP_COMMAND_OPTION);
2250            System.out.println(JOB_ID_PREFIX + wc.submitSqoop(conf, command, args.toArray(new String[args.size()])));
2251        }
2252        catch (OozieClientException ex) {
2253            throw new OozieCLIException(ex.toString(), ex);
2254        }
2255    }
2256
2257    private void infoCommand(CommandLine commandLine) throws OozieCLIException {
2258        for (Option option : commandLine.getOptions()) {
2259            String opt = option.getOpt();
2260            if (opt.equals(INFO_TIME_ZONES_OPTION)) {
2261                printAvailableTimeZones();
2262            }
2263        }
2264    }
2265
2266    private void printAvailableTimeZones() {
2267        System.out.println("The format is \"SHORT_NAME (ID)\"\nGive the ID (GMT, UTC or Region/City)" +
2268        "to the -timezone argument");
2269        System.out.println("GMT offsets can also be used (e.g. GMT-07:00, GMT-0700, GMT+05:30, GMT+0530)");
2270        System.out.println("Available Time Zones:");
2271        for (String tzId : TimeZone.getAvailableIDs()) {
2272            // skip id's that are like "Etc/GMT+01:00" because their display names are like "GMT-01:00", which is confusing
2273            if (!tzId.startsWith("Etc/GMT")) {
2274                TimeZone tZone = TimeZone.getTimeZone(tzId);
2275                System.out.println("      " + tZone.getDisplayName(false, TimeZone.SHORT) + " (" + tzId + ")");
2276            }
2277        }
2278    }
2279
2280
2281    private void mrCommand(CommandLine commandLine) throws IOException, OozieCLIException {
2282        try {
2283            XOozieClient wc = createXOozieClient(commandLine);
2284            Properties conf = getConfiguration(wc, commandLine);
2285
2286            String mapper = conf.getProperty(MAPRED_MAPPER, conf.getProperty(MAPRED_MAPPER_2));
2287            if (mapper == null) {
2288                throw new OozieCLIException("mapper (" + MAPRED_MAPPER + " or " + MAPRED_MAPPER_2 + ") must be specified in conf");
2289            }
2290
2291            String reducer = conf.getProperty(MAPRED_REDUCER, conf.getProperty(MAPRED_REDUCER_2));
2292            if (reducer == null) {
2293                throw new OozieCLIException("reducer (" + MAPRED_REDUCER + " or " + MAPRED_REDUCER_2
2294                        + ") must be specified in conf");
2295            }
2296
2297            String inputDir = conf.getProperty(MAPRED_INPUT);
2298            if (inputDir == null) {
2299                throw new OozieCLIException("input dir (" + MAPRED_INPUT +") must be specified in conf");
2300            }
2301
2302            String outputDir = conf.getProperty(MAPRED_OUTPUT);
2303            if (outputDir == null) {
2304                throw new OozieCLIException("output dir (" + MAPRED_OUTPUT +") must be specified in conf");
2305            }
2306
2307            System.out.println(JOB_ID_PREFIX + wc.submitMapReduce(conf));
2308        }
2309        catch (OozieClientException ex) {
2310            throw new OozieCLIException(ex.toString(), ex);
2311        }
2312    }
2313
2314    private String getFirstMissingDependencies(CoordinatorAction action) {
2315        StringBuilder allDeps = new StringBuilder();
2316        String missingDep = action.getMissingDependencies();
2317        boolean depExists = false;
2318        if (missingDep != null && !missingDep.isEmpty()) {
2319            allDeps.append(missingDep.split(INSTANCE_SEPARATOR)[0]);
2320            depExists = true;
2321        }
2322        String pushDeps = action.getPushMissingDependencies();
2323        if (pushDeps != null && !pushDeps.isEmpty()) {
2324            if(depExists) {
2325                allDeps.append(INSTANCE_SEPARATOR);
2326            }
2327            allDeps.append(pushDeps.split(INSTANCE_SEPARATOR)[0]);
2328        }
2329        return allDeps.toString();
2330    }
2331
2332    private void slaAlertCommand(String jobIds, OozieClient wc, CommandLine commandLine, List<String> options)
2333            throws OozieCLIException, OozieClientException {
2334        String actions = null, coordinators = null, dates = null;
2335
2336        if (options.contains(ACTION_OPTION)) {
2337            actions = commandLine.getOptionValue(ACTION_OPTION);
2338        }
2339
2340        if (options.contains(DATE_OPTION)) {
2341            dates = commandLine.getOptionValue(DATE_OPTION);
2342        }
2343
2344        if (options.contains(COORD_OPTION)) {
2345            coordinators = commandLine.getOptionValue(COORD_OPTION);
2346            if (coordinators == null) {
2347                throw new OozieCLIException("No value specified for -coordinator option");
2348            }
2349        }
2350
2351        if (options.contains(SLA_ENABLE_ALERT)) {
2352            wc.slaEnableAlert(jobIds, actions, dates, coordinators);
2353        }
2354        else if (options.contains(SLA_DISABLE_ALERT)) {
2355            wc.slaDisableAlert(jobIds, actions, dates, coordinators);
2356        }
2357        else if (options.contains(SLA_CHANGE)) {
2358            String newSlaParams = commandLine.getOptionValue(CHANGE_VALUE_OPTION);
2359            wc.slaChange(jobIds, actions, dates, coordinators, newSlaParams);
2360        }
2361    }
2362
2363    private void printMetrics(OozieClient.Metrics metrics) {
2364        System.out.println("COUNTERS");
2365        System.out.println("--------");
2366        Map<String, Long> counters = new TreeMap<>(metrics.getCounters());
2367        for (Map.Entry<String, Long> ent : counters.entrySet()) {
2368            System.out.println(ent.getKey() + " : " + ent.getValue());
2369        }
2370        System.out.println("\nGAUGES");
2371        System.out.println("------");
2372        Map<String, Object> gauges = new TreeMap<>(metrics.getGauges());
2373        for (Map.Entry<String, Object> ent : gauges.entrySet()) {
2374            System.out.println(ent.getKey() + " : " + ent.getValue());
2375        }
2376        System.out.println("\nTIMERS");
2377        System.out.println("------");
2378        Map<String, OozieClient.Metrics.Timer> timers = new TreeMap<>(metrics.getTimers());
2379        for (Map.Entry<String, OozieClient.Metrics.Timer> ent : timers.entrySet()) {
2380            System.out.println(ent.getKey());
2381            System.out.println(ent.getValue());
2382        }
2383        System.out.println("\nHISTOGRAMS");
2384        System.out.println("----------");
2385        Map<String, OozieClient.Metrics.Histogram> histograms = new TreeMap<>(metrics.getHistograms());
2386        for (Map.Entry<String, OozieClient.Metrics.Histogram> ent : histograms.entrySet()) {
2387            System.out.println(ent.getKey());
2388            System.out.println(ent.getValue());
2389        }
2390    }
2391
2392    private void printInstrumentation(OozieClient.Instrumentation instrumentation) {
2393        System.out.println("COUNTERS");
2394        System.out.println("--------");
2395        Map<String, Long> counters = new TreeMap<>(instrumentation.getCounters());
2396        for (Map.Entry<String, Long> ent : counters.entrySet()) {
2397            System.out.println(ent.getKey() + " : " + ent.getValue());
2398        }
2399        System.out.println("\nVARIABLES");
2400        System.out.println("---------");
2401        Map<String, Object> variables = new TreeMap<>(instrumentation.getVariables());
2402        for (Map.Entry<String, Object> ent : variables.entrySet()) {
2403            System.out.println(ent.getKey() + " : " + ent.getValue());
2404        }
2405        System.out.println("\nSAMPLERS");
2406        System.out.println("---------");
2407        Map<String, Double> samplers = new TreeMap<>(instrumentation.getSamplers());
2408        for (Map.Entry<String, Double> ent : samplers.entrySet()) {
2409            System.out.println(ent.getKey() + " : " + ent.getValue());
2410        }
2411        System.out.println("\nTIMERS");
2412        System.out.println("---------");
2413        Map<String, OozieClient.Instrumentation.Timer> timers = new TreeMap<>(instrumentation.getTimers());
2414        for (Map.Entry<String, OozieClient.Instrumentation.Timer> ent : timers.entrySet()) {
2415            System.out.println(ent.getKey());
2416            System.out.println(ent.getValue());
2417        }
2418    }
2419}