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.client;
020
021import com.fasterxml.jackson.core.type.TypeReference;
022import com.fasterxml.jackson.databind.ObjectMapper;
023import com.google.common.collect.Lists;
024import org.apache.oozie.BuildInfo;
025import org.apache.oozie.cli.ValidationUtil;
026import org.apache.oozie.client.rest.JsonTags;
027import org.apache.oozie.client.rest.JsonToBean;
028import org.apache.oozie.client.rest.RestConstants;
029import org.apache.oozie.client.retry.ConnectionRetriableClient;
030import org.json.simple.JSONArray;
031import org.json.simple.JSONObject;
032import org.json.simple.JSONValue;
033import org.w3c.dom.Document;
034import org.w3c.dom.Element;
035
036import javax.xml.parsers.DocumentBuilderFactory;
037import javax.xml.transform.Transformer;
038import javax.xml.transform.TransformerFactory;
039import javax.xml.transform.dom.DOMSource;
040import javax.xml.transform.stream.StreamResult;
041
042import java.io.BufferedReader;
043import java.io.File;
044import java.io.FileInputStream;
045import java.io.IOException;
046import java.io.InputStream;
047import java.io.InputStreamReader;
048import java.io.OutputStream;
049import java.io.PrintStream;
050import java.io.Reader;
051import java.net.HttpURLConnection;
052import java.net.URL;
053import java.net.URLEncoder;
054import java.nio.charset.StandardCharsets;
055import java.util.Collections;
056import java.util.HashMap;
057import java.util.HashSet;
058import java.util.Iterator;
059import java.util.LinkedHashMap;
060import java.util.List;
061import java.util.Map;
062import java.util.Map.Entry;
063import java.util.Properties;
064import java.util.Set;
065import java.util.concurrent.Callable;
066
067
068/**
069 * Client API to submit and manage Oozie workflow jobs against an Oozie instance.
070 * <p>
071 * This class is thread safe.
072 * <p>
073 * Syntax for filter for the {@link #getJobsInfo(String)} {@link #getJobsInfo(String, int, int)} methods:
074 * <code>[NAME=VALUE][;NAME=VALUE]*</code>.
075 * <p>
076 * Valid filter names are:
077 * <ul>
078 * <li>name: the workflow application name from the workflow definition.</li>
079 * <li>user: the user that submitted the job.</li>
080 * <li>group: the group for the job.</li>
081 * <li>status: the status of the job.</li>
082 * </ul>
083 * <p>
084 * The query will do an AND among all the filter names. The query will do an OR among all the filter values for the same
085 * name. Multiple values must be specified as different name value pairs.
086 */
087public class OozieClient {
088
089    public static final long WS_PROTOCOL_VERSION_0 = 0;
090
091    public static final long WS_PROTOCOL_VERSION_1 = 1;
092
093    public static final long WS_PROTOCOL_VERSION = 2; // pointer to current version
094
095    public static final String USER_NAME = "user.name";
096
097    @Deprecated
098    public static final String GROUP_NAME = "group.name";
099
100    public static final String JOB_ACL = "oozie.job.acl";
101
102    public static final String APP_PATH = "oozie.wf.application.path";
103
104    public static final String COORDINATOR_APP_PATH = "oozie.coord.application.path";
105
106    public static final String BUNDLE_APP_PATH = "oozie.bundle.application.path";
107
108    public static final String BUNDLE_ID = "oozie.bundle.id";
109
110    public static final String EXTERNAL_ID = "oozie.wf.external.id";
111
112    public static final String WORKFLOW_NOTIFICATION_PROXY = "oozie.wf.workflow.notification.proxy";
113
114    public static final String WORKFLOW_NOTIFICATION_URL = "oozie.wf.workflow.notification.url";
115
116    public static final String ACTION_NOTIFICATION_URL = "oozie.wf.action.notification.url";
117
118    public static final String COORD_ACTION_NOTIFICATION_URL = "oozie.coord.action.notification.url";
119
120    public static final String COORD_ACTION_NOTIFICATION_PROXY = "oozie.coord.action.notification.proxy";
121
122    public static final String RERUN_SKIP_NODES = "oozie.wf.rerun.skip.nodes";
123
124    public static final String RERUN_FAIL_NODES = "oozie.wf.rerun.failnodes";
125
126    public static final String LOG_TOKEN = "oozie.wf.log.token";
127
128    public static final String ACTION_MAX_RETRIES = "oozie.wf.action.max.retries";
129
130    public static final String ACTION_RETRY_INTERVAL = "oozie.wf.action.retry.interval";
131
132    public static final String FILTER_USER = "user";
133
134    public static final String FILTER_TEXT = "text";
135
136    public static final String FILTER_GROUP = "group";
137
138    public static final String FILTER_NAME = "name";
139
140    public static final String FILTER_STATUS = "status";
141
142    public static final String FILTER_NOMINAL_TIME = "nominaltime";
143
144    public static final String FILTER_FREQUENCY = "frequency";
145
146    public static final String FILTER_ID = "id";
147
148    public static final String FILTER_UNIT = "unit";
149
150    public static final String FILTER_JOBID = "jobid";
151
152    public static final String FILTER_APPNAME = "appname";
153
154    public static final String FILTER_SLA_APPNAME = "app_name";
155
156    public static final String FILTER_SLA_ID = "id";
157
158    public static final String FILTER_SLA_PARENT_ID = "parent_id";
159
160    public static final String FILTER_BUNDLE = "bundle";
161
162    public static final String FILTER_SLA_EVENT_STATUS = "event_status";
163
164    public static final String FILTER_SLA_STATUS = "sla_status";
165
166    public static final String FILTER_SLA_NOMINAL_START = "nominal_start";
167
168    public static final String FILTER_SLA_NOMINAL_END = "nominal_end";
169
170    public static final String FILTER_CREATED_TIME_START = "startcreatedtime";
171
172    public static final String FILTER_CREATED_TIME_END = "endcreatedtime";
173
174    public static final String SLA_DISABLE_ALERT = "oozie.sla.disable.alerts";
175
176    public static final String SLA_ENABLE_ALERT = "oozie.sla.enable.alerts";
177
178    public static final String SLA_DISABLE_ALERT_OLDER_THAN = SLA_DISABLE_ALERT + ".older.than";
179
180    public static final String SLA_DISABLE_ALERT_COORD = SLA_DISABLE_ALERT + ".coord";
181
182    public static final String CHANGE_VALUE_ENDTIME = "endtime";
183
184    public static final String CHANGE_VALUE_PAUSETIME = "pausetime";
185
186    public static final String CHANGE_VALUE_CONCURRENCY = "concurrency";
187
188    public static final String CHANGE_VALUE_STATUS = "status";
189
190    public static final String LIBPATH = "oozie.libpath";
191
192    public static final String USE_SYSTEM_LIBPATH = "oozie.use.system.libpath";
193
194    public static final String OOZIE_SUSPEND_ON_NODES = "oozie.suspend.on.nodes";
195
196    public static final String FILTER_SORT_BY = "sortby";
197
198    public enum SORT_BY {
199        createdTime("createdTimestamp"), lastModifiedTime("lastModifiedTimestamp");
200        private final String fullname;
201
202        SORT_BY(String fullname) {
203            this.fullname = fullname;
204        }
205
206        public String getFullname() {
207            return fullname;
208        }
209    }
210
211    public static enum SYSTEM_MODE {
212        NORMAL, NOWEBSERVICE, SAFEMODE
213    }
214
215    private static final Set<String> COMPLETED_WF_STATUSES = new HashSet<>();
216    private static final Set<String> COMPLETED_COORD_AND_BUNDLE_STATUSES = new HashSet<>();
217    private static final Set<String> COMPLETED_COORD_ACTION_STATUSES = new HashSet<>();
218    static {
219        COMPLETED_WF_STATUSES.add(WorkflowJob.Status.FAILED.toString());
220        COMPLETED_WF_STATUSES.add(WorkflowJob.Status.KILLED.toString());
221        COMPLETED_WF_STATUSES.add(WorkflowJob.Status.SUCCEEDED.toString());
222        COMPLETED_COORD_AND_BUNDLE_STATUSES.add(Job.Status.FAILED.toString());
223        COMPLETED_COORD_AND_BUNDLE_STATUSES.add(Job.Status.KILLED.toString());
224        COMPLETED_COORD_AND_BUNDLE_STATUSES.add(Job.Status.SUCCEEDED.toString());
225        COMPLETED_COORD_AND_BUNDLE_STATUSES.add(Job.Status.DONEWITHERROR.toString());
226        COMPLETED_COORD_AND_BUNDLE_STATUSES.add(Job.Status.IGNORED.toString());
227        COMPLETED_COORD_ACTION_STATUSES.add(CoordinatorAction.Status.FAILED.toString());
228        COMPLETED_COORD_ACTION_STATUSES.add(CoordinatorAction.Status.IGNORED.toString());
229        COMPLETED_COORD_ACTION_STATUSES.add(CoordinatorAction.Status.KILLED.toString());
230        COMPLETED_COORD_ACTION_STATUSES.add(CoordinatorAction.Status.SKIPPED.toString());
231        COMPLETED_COORD_ACTION_STATUSES.add(CoordinatorAction.Status.SUCCEEDED.toString());
232        COMPLETED_COORD_ACTION_STATUSES.add(CoordinatorAction.Status.TIMEDOUT.toString());
233    }
234
235    /**
236     * debugMode =0 means no debugging. 1 means debugging on.
237     */
238    public int debugMode = 0;
239
240    private int retryCount = 4;
241
242
243    private String baseUrl;
244    private String protocolUrl;
245    private boolean validatedVersion = false;
246    private JSONArray supportedVersions;
247    private final Map<String, String> headers = new HashMap<>();
248
249    private static final ThreadLocal<String> USER_NAME_TL = new ThreadLocal<>();
250
251    /**
252     * Allows to impersonate other users in the Oozie server. The current user
253     * must be configured as a proxyuser in Oozie.
254     * <p>
255     * IMPORTANT: impersonation happens only with Oozie client requests done within
256     * doAs() calls.
257     *
258     * @param <T> the type of the callable
259     * @param userName user to impersonate.
260     * @param callable callable with {@link OozieClient} calls impersonating the specified user.
261     * @return any response returned by the {@link Callable#call()} method.
262     * @throws Exception thrown by the {@link Callable#call()} method.
263     */
264    public static <T> T doAs(String userName, Callable<T> callable) throws Exception {
265        notEmpty(userName, "userName");
266        notNull(callable, "callable");
267        try {
268            USER_NAME_TL.set(userName);
269            return callable.call();
270        }
271        finally {
272            USER_NAME_TL.remove();
273        }
274    }
275
276    protected OozieClient() {
277    }
278
279    /**
280     * Create a Workflow client instance.
281     *
282     * @param oozieUrl URL of the Oozie instance it will interact with.
283     */
284    public OozieClient(String oozieUrl) {
285        this.baseUrl = notEmpty(oozieUrl, "oozieUrl");
286        if (!this.baseUrl.endsWith("/")) {
287            this.baseUrl += "/";
288        }
289    }
290
291    /**
292     * Return the Oozie URL of the workflow client instance.
293     * <p>
294     * This URL is the base URL fo the Oozie system, with not protocol versioning.
295     *
296     * @return the Oozie URL of the workflow client instance.
297     */
298    public String getOozieUrl() {
299        return baseUrl;
300    }
301
302    /**
303     * Return the Oozie URL used by the client and server for WS communications.
304     * <p>
305     * This URL is the original URL plus the versioning element path.
306     *
307     * @return the Oozie URL used by the client and server for communication.
308     * @throws OozieClientException thrown in the client and the server are not protocol compatible.
309     */
310    public String getProtocolUrl() throws OozieClientException {
311        validateWSVersion();
312        return protocolUrl;
313    }
314
315    /**
316     * @return current debug Mode
317     */
318    public int getDebugMode() {
319        return debugMode;
320    }
321
322    /**
323     * Set debug mode.
324     *
325     * @param debugMode : 0 means no debugging. 1 means debugging
326     */
327    public void setDebugMode(int debugMode) {
328        this.debugMode = debugMode;
329    }
330
331    public int getRetryCount() {
332        return retryCount;
333    }
334
335
336    public void setRetryCount(int retryCount) {
337        this.retryCount = retryCount;
338    }
339
340    private String getBaseURLForVersion(long protocolVersion) throws OozieClientException {
341        try {
342            if (supportedVersions == null) {
343                supportedVersions = getSupportedProtocolVersions();
344            }
345            if (supportedVersions == null) {
346                throw new OozieClientException("HTTP error", "no response message");
347            }
348            if (supportedVersions.contains(protocolVersion)) {
349                return baseUrl + "v" + protocolVersion + "/";
350            }
351            else {
352                throw new OozieClientException(OozieClientException.UNSUPPORTED_VERSION, "Protocol version "
353                        + protocolVersion + " is not supported");
354            }
355        }
356        catch (IOException e) {
357            throw new OozieClientException(OozieClientException.IO_ERROR, e);
358        }
359    }
360
361    /**
362     * Validate that the Oozie client and server instances are protocol compatible.
363     *
364     * @throws OozieClientException thrown in the client and the server are not protocol compatible.
365     */
366    public synchronized void validateWSVersion() throws OozieClientException {
367        if (!validatedVersion) {
368            try {
369                supportedVersions = getSupportedProtocolVersions();
370                if (supportedVersions == null) {
371                    throw new OozieClientException("HTTP error", "no response message");
372                }
373                if (!supportedVersions.contains(WS_PROTOCOL_VERSION)
374                        && !supportedVersions.contains(WS_PROTOCOL_VERSION_1)
375                        && !supportedVersions.contains(WS_PROTOCOL_VERSION_0)) {
376                    StringBuilder msg = new StringBuilder();
377                    msg.append("Supported version [").append(WS_PROTOCOL_VERSION)
378                            .append("] or less, Unsupported versions[");
379                    String separator = "";
380                    for (Object version : supportedVersions) {
381                        msg.append(separator).append(version);
382                    }
383                    msg.append("]");
384                    throw new OozieClientException(OozieClientException.UNSUPPORTED_VERSION, msg.toString());
385                }
386                if (supportedVersions.contains(WS_PROTOCOL_VERSION)) {
387                    protocolUrl = baseUrl + "v" + WS_PROTOCOL_VERSION + "/";
388                }
389                else if (supportedVersions.contains(WS_PROTOCOL_VERSION_1)) {
390                    protocolUrl = baseUrl + "v" + WS_PROTOCOL_VERSION_1 + "/";
391                }
392                else {
393                    if (supportedVersions.contains(WS_PROTOCOL_VERSION_0)) {
394                        protocolUrl = baseUrl + "v" + WS_PROTOCOL_VERSION_0 + "/";
395                    }
396                }
397            }
398            catch (IOException ex) {
399                throw new OozieClientException(OozieClientException.IO_ERROR, ex);
400            }
401            validatedVersion = true;
402        }
403    }
404
405    private JSONArray getSupportedProtocolVersions() throws IOException, OozieClientException {
406        JSONArray versions = null;
407        final URL url = new URL(baseUrl + RestConstants.VERSIONS);
408
409        HttpURLConnection conn = createRetryableConnection(url, "GET");
410
411        if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
412            versions = (JSONArray) JSONValue.parse(new InputStreamReader(conn.getInputStream()));
413        }
414        else {
415            handleError(conn);
416        }
417        return versions;
418    }
419
420    /**
421     * Create an empty configuration with just the {@link #USER_NAME} set to the JVM user name.
422     *
423     * @return an empty configuration.
424     */
425    public Properties createConfiguration() {
426        Properties conf = new Properties();
427        String userName = USER_NAME_TL.get();
428        if (userName == null) {
429            userName = System.getProperty("user.name");
430        }
431        conf.setProperty(USER_NAME, userName);
432        return conf;
433    }
434
435    /**
436     * Set a HTTP header to be used in the WS requests by the workflow instance.
437     *
438     * @param name header name.
439     * @param value header value.
440     */
441    public void setHeader(String name, String value) {
442        headers.put(notEmpty(name, "name"), notNull(value, "value"));
443    }
444
445    /**
446     * Get the value of a set HTTP header from the workflow instance.
447     *
448     * @param name header name.
449     * @return header value, <code>null</code> if not set.
450     */
451    public String getHeader(String name) {
452        return headers.get(notEmpty(name, "name"));
453    }
454
455    /**
456     * Get the set HTTP header
457     *
458     * @return map of header key and value
459     */
460    public Map<String, String> getHeaders() {
461        return headers;
462    }
463
464    /**
465     * Remove a HTTP header from the workflow client instance.
466     *
467     * @param name header name.
468     */
469    public void removeHeader(String name) {
470        headers.remove(notEmpty(name, "name"));
471    }
472
473    /**
474     * Return an iterator with all the header names set in the workflow instance.
475     *
476     * @return header names.
477     */
478    public Iterator<String> getHeaderNames() {
479        return Collections.unmodifiableMap(headers).keySet().iterator();
480    }
481
482    private URL createURL(Long protocolVersion, String collection, String resource, Map<String, String> parameters)
483            throws IOException, OozieClientException {
484        validateWSVersion();
485        StringBuilder sb = new StringBuilder();
486        if (protocolVersion == null) {
487            sb.append(protocolUrl);
488        }
489        else {
490            sb.append(getBaseURLForVersion(protocolVersion));
491        }
492        sb.append(collection);
493        if (resource != null && resource.length() > 0) {
494            sb.append("/").append(resource);
495        }
496        if (parameters.size() > 0) {
497            String separator = "?";
498            for (Map.Entry<String, String> param : parameters.entrySet()) {
499                if (param.getValue() != null) {
500                    sb.append(separator).append(URLEncoder.encode(param.getKey(), "UTF-8")).append("=").append(
501                            URLEncoder.encode(param.getValue(), "UTF-8"));
502                    separator = "&";
503                }
504            }
505        }
506        return new URL(sb.toString());
507    }
508
509    private boolean validateCommand(String url) {
510        {
511            if (protocolUrl.contains(baseUrl + "v0")) {
512                if (url.contains("dryrun") || url.contains("jobtype=c") || url.contains("systemmode")) {
513                    return false;
514                }
515            }
516        }
517        return true;
518    }
519    /**
520     * Create retryable http connection to oozie server.
521     *
522     * @param url URL to create connection to
523     * @param method method to use - GET, POST, PUT
524     * @return connection
525     * @throws IOException in case of an exception during connection setup
526     */
527    protected HttpURLConnection createRetryableConnection(final URL url, final String method) throws IOException {
528        return (HttpURLConnection) new ConnectionRetriableClient(getRetryCount()) {
529            @Override
530            public Object doExecute(URL url, String method) throws IOException, OozieClientException {
531                HttpURLConnection conn = createConnection(url, method);
532                return conn;
533            }
534        }.execute(url, method);
535    }
536
537    /**
538     * Create http connection to oozie server.
539     *
540     * @param url URL to create connection to
541     * @param method method to use - GET, POST, PUT
542     * @return connection
543     * @throws IOException in case of an exception during connection setup
544     * @throws OozieClientException if the connection can't be set up
545     */
546    protected HttpURLConnection createConnection(URL url, String method) throws IOException, OozieClientException {
547        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
548        conn.setRequestMethod(method);
549        if (method.equals("POST") || method.equals("PUT")) {
550            conn.setDoOutput(true);
551        }
552        for (Map.Entry<String, String> header : headers.entrySet()) {
553            conn.setRequestProperty(header.getKey(), header.getValue());
554        }
555        return conn;
556    }
557
558    protected abstract class ClientCallable<T> implements Callable<T> {
559        private final String method;
560        private final String collection;
561        private final String resource;
562        private final Map<String, String> params;
563        private final Long protocolVersion;
564
565        public ClientCallable(String method, String collection, String resource, Map<String, String> params) {
566            this(method, null, collection, resource, params);
567        }
568
569        public ClientCallable(String method, Long protocolVersion, String collection, String resource, Map<String, String> params) {
570            this.method = method;
571            this.protocolVersion = protocolVersion;
572            this.collection = collection;
573            this.resource = resource;
574            this.params = params;
575        }
576
577        public T call() throws OozieClientException {
578            try {
579                URL url = createURL(protocolVersion, collection, resource, params);
580                if (validateCommand(url.toString())) {
581                    if (getDebugMode() > 0) {
582                        System.out.println(method + " " + url);
583                    }
584                    return call(createRetryableConnection(url, method));
585                }
586                else {
587                    System.out.println("Option not supported in target server. Supported only on Oozie-2.0 or greater."
588                            + " Use 'oozie help' for details");
589                    throw new OozieClientException(OozieClientException.UNSUPPORTED_VERSION, new Exception());
590                }
591            }
592            catch (IOException ex) {
593                throw new OozieClientException(OozieClientException.IO_ERROR, ex);
594            }
595        }
596
597        protected abstract T call(HttpURLConnection conn) throws IOException, OozieClientException;
598
599    }
600
601    protected abstract class MapClientCallable extends ClientCallable<Map<String, String>> {
602
603        MapClientCallable(String method, String collection, String resource, Map<String, String> params) {
604            super(method, collection, resource, params);
605        }
606
607        @Override
608        protected Map<String, String> call(HttpURLConnection conn) throws IOException, OozieClientException {
609            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
610                Reader reader = new InputStreamReader(conn.getInputStream());
611                JSONObject json = (JSONObject) JSONValue.parse(reader);
612                Map<String, String> map = new HashMap<>();
613                for (Object key : json.keySet()) {
614                    map.put((String)key, (String)json.get(key));
615                }
616                return map;
617            }
618            else {
619                handleError(conn);
620            }
621            return null;
622        }
623    }
624
625    static void handleError(HttpURLConnection conn) throws IOException, OozieClientException {
626        int status = conn.getResponseCode();
627        String error = conn.getHeaderField(RestConstants.OOZIE_ERROR_CODE);
628        String message = conn.getHeaderField(RestConstants.OOZIE_ERROR_MESSAGE);
629
630        if (error == null) {
631            error = "HTTP error code: " + status;
632        }
633
634        if (message == null) {
635            message = conn.getResponseMessage();
636        }
637        throw new OozieClientException(error, message);
638    }
639
640    static Map<String, String> prepareParams(String... params) {
641        Map<String, String> map = new LinkedHashMap<>();
642        for (int i = 0; i < params.length; i = i + 2) {
643            map.put(params[i], params[i + 1]);
644        }
645        String doAsUserName = USER_NAME_TL.get();
646        if (doAsUserName != null) {
647            map.put(RestConstants.DO_AS_PARAM, doAsUserName);
648        }
649        return map;
650    }
651
652    public void writeToXml(Properties props, OutputStream out) throws IOException {
653        try {
654            Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
655            Element conf = doc.createElement("configuration");
656            doc.appendChild(conf);
657            conf.appendChild(doc.createTextNode("\n"));
658            for (String name : props.stringPropertyNames()) { // Properties whose key or value is not of type String are omitted.
659                String value = props.getProperty(name);
660                Element propNode = doc.createElement("property");
661                conf.appendChild(propNode);
662
663                Element nameNode = doc.createElement("name");
664                nameNode.appendChild(doc.createTextNode(name.trim()));
665                propNode.appendChild(nameNode);
666
667                Element valueNode = doc.createElement("value");
668                valueNode.appendChild(doc.createTextNode(value.trim()));
669                propNode.appendChild(valueNode);
670
671                conf.appendChild(doc.createTextNode("\n"));
672            }
673
674            DOMSource source = new DOMSource(doc);
675            StreamResult result = new StreamResult(out);
676            TransformerFactory transFactory = TransformerFactory.newInstance();
677            transFactory.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true);
678            Transformer transformer = transFactory.newTransformer();
679            transformer.transform(source, result);
680            if (getDebugMode() > 0) {
681                result = new StreamResult(System.out);
682                transformer.transform(source, result);
683                System.out.println();
684            }
685        }
686        catch (Exception e) {
687            throw new IOException(e);
688        }
689    }
690
691    private class JobSubmit extends ClientCallable<String> {
692        private final Properties conf;
693
694        JobSubmit(Properties conf, boolean start) {
695            super("POST", RestConstants.JOBS, "", (start) ? prepareParams(RestConstants.ACTION_PARAM,
696                    RestConstants.JOB_ACTION_START) : prepareParams());
697            this.conf = notNull(conf, "conf");
698        }
699
700        JobSubmit(String jobId, Properties conf) {
701            super("PUT", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.ACTION_PARAM,
702                    RestConstants.JOB_ACTION_RERUN));
703            this.conf = notNull(conf, "conf");
704        }
705
706        public JobSubmit(Properties conf, String jobActionDryrun) {
707            super("POST", RestConstants.JOBS, "", prepareParams(RestConstants.ACTION_PARAM,
708                    RestConstants.JOB_ACTION_DRYRUN));
709            this.conf = notNull(conf, "conf");
710        }
711
712        @Override
713        protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
714            conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
715            writeToXml(conf, conn.getOutputStream());
716            if (conn.getResponseCode() == HttpURLConnection.HTTP_CREATED) {
717                JSONObject json = (JSONObject) JSONValue.parse(new InputStreamReader(conn.getInputStream()));
718                return (String) json.get(JsonTags.JOB_ID);
719            }
720            if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
721                handleError(conn);
722            }
723            return null;
724        }
725    }
726
727    /**
728     * Submit a workflow job.
729     *
730     * @param conf job configuration.
731     * @return the job Id.
732     * @throws OozieClientException thrown if the job could not be submitted.
733     */
734    public String submit(Properties conf) throws OozieClientException {
735        return (new JobSubmit(conf, false)).call();
736    }
737
738    private class JobAction extends ClientCallable<Void> {
739
740        JobAction(String jobId, String action) {
741            super("PUT", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.ACTION_PARAM, action));
742        }
743
744        JobAction(String jobId, String action, String params) {
745            super("PUT", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.ACTION_PARAM, action,
746                    RestConstants.JOB_CHANGE_VALUE, params));
747        }
748
749        @Override
750        protected Void call(HttpURLConnection conn) throws IOException, OozieClientException {
751            if (!(conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
752                handleError(conn);
753            }
754            return null;
755        }
756    }
757
758    private class JobsAction extends ClientCallable<JSONObject> {
759
760        JobsAction(String action, String filter, String jobType, int start, int len) {
761            super("PUT", RestConstants.JOBS, "",
762                    prepareParams(RestConstants.ACTION_PARAM, action,
763                            RestConstants.JOB_FILTER_PARAM, filter, RestConstants.JOBTYPE_PARAM, jobType,
764                            RestConstants.OFFSET_PARAM, Integer.toString(start),
765                            RestConstants.LEN_PARAM, Integer.toString(len)));
766        }
767
768        @Override
769        protected JSONObject call(HttpURLConnection conn) throws IOException, OozieClientException {
770            conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
771            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
772                Reader reader = new InputStreamReader(conn.getInputStream());
773                JSONObject json = (JSONObject) JSONValue.parse(reader);
774                return json;
775            }
776            else {
777                handleError(conn);
778            }
779            return null;
780        }
781    }
782    /**
783     * Update coord definition.
784     *
785     * @param jobId the job id
786     * @param conf the conf
787     * @param dryrun the dryrun
788     * @param showDiff the show diff
789     * @return the string
790     * @throws OozieClientException the oozie client exception
791     */
792    public String updateCoord(String jobId, Properties conf, String dryrun, String showDiff)
793            throws OozieClientException {
794        return (new UpdateCoord(jobId, conf, dryrun, showDiff)).call();
795    }
796
797    /**
798     * Update coord definition without properties.
799     *
800     * @param jobId the job id
801     * @param dryrun the dryrun
802     * @param showDiff the show diff
803     * @return the string
804     * @throws OozieClientException the oozie client exception
805     */
806    public String updateCoord(String jobId, String dryrun, String showDiff) throws OozieClientException {
807        return (new UpdateCoord(jobId, dryrun, showDiff)).call();
808    }
809
810    /**
811     * The Class UpdateCoord.
812     */
813    private class UpdateCoord extends ClientCallable<String> {
814        private final Properties conf;
815
816        public UpdateCoord(String jobId, Properties conf, String jobActionDryrun, String showDiff) {
817            super("PUT", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.ACTION_PARAM,
818                    RestConstants.JOB_COORD_UPDATE, RestConstants.JOB_ACTION_DRYRUN, jobActionDryrun,
819                    RestConstants.JOB_ACTION_SHOWDIFF, showDiff));
820            this.conf = conf;
821        }
822
823        public UpdateCoord(String jobId, String jobActionDryrun, String showDiff) {
824            this(jobId, new Properties(), jobActionDryrun, showDiff);
825        }
826
827        @Override
828        protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
829            conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
830            writeToXml(conf, conn.getOutputStream());
831
832            if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
833                JSONObject json = (JSONObject) JSONValue.parse(new InputStreamReader(conn.getInputStream()));
834                JSONObject update = (JSONObject) json.get(JsonTags.COORD_UPDATE);
835                if (update != null) {
836                    return (String) update.get(JsonTags.COORD_UPDATE_DIFF);
837                }
838                else {
839                    return "";
840                }
841            }
842            if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
843                handleError(conn);
844            }
845            return null;
846        }
847    }
848
849    /**
850     * dryrun for a given job
851     *
852     * @param conf Job configuration.
853     * @return new job.
854     * @throws org.apache.oozie.client.OozieClientException thrown if the job could not be submitted.
855     */
856    public String dryrun(Properties conf) throws OozieClientException {
857        return new JobSubmit(conf, RestConstants.JOB_ACTION_DRYRUN).call();
858    }
859
860    /**
861     * Start a workflow job.
862     *
863     * @param jobId job Id.
864     * @throws OozieClientException thrown if the job could not be started.
865     */
866    public void start(String jobId) throws OozieClientException {
867        new JobAction(jobId, RestConstants.JOB_ACTION_START).call();
868    }
869
870    /**
871     * Submit and start a workflow job.
872     *
873     * @param conf job configuration.
874     * @return the job Id.
875     * @throws OozieClientException thrown if the job could not be submitted.
876     */
877    public String run(Properties conf) throws OozieClientException {
878        return (new JobSubmit(conf, true)).call();
879    }
880
881    /**
882     * Rerun a workflow job.
883     *
884     * @param jobId job Id to rerun.
885     * @param conf configuration information for the rerun.
886     * @throws OozieClientException thrown if the job could not be started.
887     */
888    public void reRun(String jobId, Properties conf) throws OozieClientException {
889        new JobSubmit(jobId, conf).call();
890    }
891
892    /**
893     * Suspend a workflow job.
894     *
895     * @param jobId job Id.
896     * @throws OozieClientException thrown if the job could not be suspended.
897     */
898    public void suspend(String jobId) throws OozieClientException {
899        new JobAction(jobId, RestConstants.JOB_ACTION_SUSPEND).call();
900    }
901
902    /**
903     * Resume a workflow job.
904     *
905     * @param jobId job Id.
906     * @throws OozieClientException thrown if the job could not be resume.
907     */
908    public void resume(String jobId) throws OozieClientException {
909        new JobAction(jobId, RestConstants.JOB_ACTION_RESUME).call();
910    }
911
912    /**
913     * Kill a workflow/coord/bundle job.
914     *
915     * @param jobId job Id.
916     * @throws OozieClientException thrown if the job could not be killed.
917     */
918    public void kill(String jobId) throws OozieClientException {
919        new JobAction(jobId, RestConstants.JOB_ACTION_KILL).call();
920    }
921
922    /**
923     * Kill coordinator actions
924     * @param jobId coordinator Job Id
925     * @param rangeType type 'date' if -date is used, 'action-num' if -action is used
926     * @param scope kill scope for date or action nums
927     * @return list of coordinator actions that underwent kill
928     * @throws OozieClientException thrown if some actions could not be killed.
929     */
930    public List<CoordinatorAction> kill(String jobId, String rangeType, String scope) throws OozieClientException {
931        return new CoordActionsKill(jobId, rangeType, scope).call();
932    }
933
934    public JSONObject bulkModifyJobs(String actionType, String filter, String jobType, int start, int len)
935            throws OozieClientException {
936        return new JobsAction(actionType, filter, jobType, start, len).call();
937    }
938
939    public JSONObject killJobs(String filter, String jobType, int start, int len)
940            throws OozieClientException {
941        return bulkModifyJobs("kill", filter, jobType, start, len);
942    }
943
944    public JSONObject suspendJobs(String filter, String jobType, int start, int len)
945            throws OozieClientException {
946        return bulkModifyJobs("suspend", filter, jobType, start, len);
947    }
948
949    public JSONObject resumeJobs(String filter, String jobType, int start, int len)
950            throws OozieClientException {
951        return bulkModifyJobs("resume", filter, jobType, start, len);
952    }
953    /**
954     * Change a coordinator job.
955     *
956     * @param jobId job Id.
957     * @param changeValue change value.
958     * @throws OozieClientException thrown if the job could not be changed.
959     */
960    public void change(String jobId, String changeValue) throws OozieClientException {
961        new JobAction(jobId, RestConstants.JOB_ACTION_CHANGE, changeValue).call();
962    }
963
964    /**
965     * Ignore a coordinator job.
966     *
967     * @param jobId coord job Id.
968     * @param scope list of coord actions to be ignored
969     * @return the list ignored actions or null if there are none
970     * @throws OozieClientException thrown if the job could not be changed.
971     * @return list of ignored coordinator jobs
972     */
973    public List<CoordinatorAction> ignore(String jobId, String scope) throws OozieClientException {
974        return new CoordIgnore(jobId, RestConstants.JOB_COORD_SCOPE_ACTION, scope).call();
975    }
976
977    private class JobInfo extends ClientCallable<WorkflowJob> {
978
979        JobInfo(String jobId, int start, int len) {
980            super("GET", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.JOB_SHOW_PARAM,
981                    RestConstants.JOB_SHOW_INFO, RestConstants.OFFSET_PARAM, Integer.toString(start),
982                    RestConstants.LEN_PARAM, Integer.toString(len)));
983        }
984
985        @Override
986        protected WorkflowJob call(HttpURLConnection conn) throws IOException, OozieClientException {
987            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
988                Reader reader = new InputStreamReader(conn.getInputStream());
989                JSONObject json = (JSONObject) JSONValue.parse(reader);
990                return JsonToBean.createWorkflowJob(json);
991            }
992            else {
993                handleError(conn);
994            }
995            return null;
996        }
997    }
998
999    private class JMSInfo extends ClientCallable<JMSConnectionInfo> {
1000
1001        JMSInfo() {
1002            super("GET", RestConstants.ADMIN, RestConstants.ADMIN_JMS_INFO, prepareParams());
1003        }
1004
1005        protected JMSConnectionInfo call(HttpURLConnection conn) throws IOException, OozieClientException {
1006            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1007                Reader reader = new InputStreamReader(conn.getInputStream());
1008                JSONObject json = (JSONObject) JSONValue.parse(reader);
1009                return JsonToBean.createJMSConnectionInfo(json);
1010            }
1011            else {
1012                handleError(conn);
1013            }
1014            return null;
1015        }
1016    }
1017
1018    private class WorkflowActionInfo extends ClientCallable<WorkflowAction> {
1019        WorkflowActionInfo(String actionId) {
1020            super("GET", RestConstants.JOB, notEmpty(actionId, "id"), prepareParams(RestConstants.JOB_SHOW_PARAM,
1021                    RestConstants.JOB_SHOW_INFO));
1022        }
1023
1024        @Override
1025        protected WorkflowAction call(HttpURLConnection conn) throws IOException, OozieClientException {
1026            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1027                Reader reader = new InputStreamReader(conn.getInputStream());
1028                JSONObject json = (JSONObject) JSONValue.parse(reader);
1029                return JsonToBean.createWorkflowAction(json);
1030            }
1031            else {
1032                handleError(conn);
1033            }
1034            return null;
1035        }
1036    }
1037
1038    private class WorkflowActionRetriesInfo extends ClientCallable<List<Map<String, String>>> {
1039        WorkflowActionRetriesInfo(String actionId) {
1040            super("GET", RestConstants.JOB, notEmpty(actionId, "id"),
1041                    prepareParams(RestConstants.JOB_SHOW_PARAM, RestConstants.JOB_SHOW_ACTION_RETRIES_PARAM));
1042        }
1043
1044        @Override
1045        protected List<Map<String, String>> call(HttpURLConnection conn)
1046                throws IOException, OozieClientException {
1047            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1048                Reader reader = new InputStreamReader(conn.getInputStream());
1049                JSONObject json = (JSONObject) JSONValue.parse(reader);
1050                return new ObjectMapper().readValue(json.get(JsonTags.WORKFLOW_ACTION_RETRIES).toString(),
1051                        new TypeReference<List<Map<String, String>>>() {
1052                        });
1053            }
1054            else {
1055                handleError(conn);
1056            }
1057            return null;
1058        }
1059    }
1060
1061    /**
1062     * Get the info of a workflow job.
1063     *
1064     * @param jobId job Id.
1065     * @return the job info.
1066     * @throws OozieClientException thrown if the job info could not be retrieved.
1067     */
1068    public WorkflowJob getJobInfo(String jobId) throws OozieClientException {
1069        return getJobInfo(jobId, 0, 0);
1070    }
1071
1072    /**
1073     * Get the JMS Connection info
1074     * @return JMSConnectionInfo object
1075     * @throws OozieClientException thrown if the JMS info could not be retrieved.
1076     */
1077    public JMSConnectionInfo getJMSConnectionInfo() throws OozieClientException {
1078        return new JMSInfo().call();
1079    }
1080
1081    /**
1082     * Get the info of a workflow job and subset actions.
1083     *
1084     * @param jobId job Id.
1085     * @param start starting index in the list of actions belonging to the job
1086     * @param len number of actions to be returned
1087     * @return the job info.
1088     * @throws OozieClientException thrown if the job info could not be retrieved.
1089     */
1090    public WorkflowJob getJobInfo(String jobId, int start, int len) throws OozieClientException {
1091        return new JobInfo(jobId, start, len).call();
1092    }
1093
1094    /**
1095     * Get the info of a workflow action.
1096     *
1097     * @param actionId Id.
1098     * @return the workflow action info.
1099     * @throws OozieClientException thrown if the job info could not be retrieved.
1100     */
1101    public WorkflowAction getWorkflowActionInfo(String actionId) throws OozieClientException {
1102        return new WorkflowActionInfo(actionId).call();
1103    }
1104
1105
1106    /**
1107     * Get the info of a workflow action.
1108     *
1109     * @param actionId Id.
1110     * @return the workflow action retries info.
1111     * @throws OozieClientException thrown if the job info could not be retrieved.
1112     */
1113    public List<Map<String, String>> getWorkflowActionRetriesInfo(String actionId) throws OozieClientException {
1114        return new WorkflowActionRetriesInfo(actionId).call();
1115    }
1116
1117    /**
1118     * Get the log of a workflow job.
1119     *
1120     * @param jobId job Id.
1121     * @return the job log.
1122     * @throws OozieClientException thrown if the job info could not be retrieved.
1123     */
1124    public String getJobLog(String jobId) throws OozieClientException {
1125        return new JobLog(jobId).call();
1126    }
1127
1128    /**
1129     * Get the audit log of a job.
1130     *
1131     * @param jobId the id of the job
1132     * @param ps the stream to write the result into
1133     * @throws OozieClientException thrown if the job audit log could not be retrieved.
1134     */
1135    public void getJobAuditLog(String jobId, PrintStream ps) throws OozieClientException {
1136        new JobAuditLog(jobId, ps).call();
1137    }
1138
1139    /**
1140     * Get the log of a job.
1141     *
1142     * @param jobId job Id.
1143     * @param logRetrievalType Based on which filter criteria the log is retrieved
1144     * @param logRetrievalScope Value for the retrieval type
1145     * @param logFilter log filter
1146     * @param ps Printstream of command line interface
1147     * @throws OozieClientException thrown if the job info could not be retrieved.
1148     */
1149    public void getJobLog(String jobId, String logRetrievalType, String logRetrievalScope, String logFilter,
1150            PrintStream ps) throws OozieClientException {
1151        new JobLog(jobId, logRetrievalType, logRetrievalScope, logFilter, ps).call();
1152    }
1153
1154    /**
1155     * Get the error log of a job.
1156     *
1157     * @param jobId the id of the job
1158     * @param ps the stream to write the result into
1159     * @throws OozieClientException thrown if the job log could not be retrieved.
1160     */
1161    public void getJobErrorLog(String jobId, PrintStream ps) throws OozieClientException {
1162        new JobErrorLog(jobId, ps).call();
1163    }
1164
1165    /**
1166     * Get the log of a job.
1167     *
1168     * @param jobId job Id.
1169     * @param logRetrievalType Based on which filter criteria the log is retrieved
1170     * @param logRetrievalScope Value for the retrieval type
1171     * @param ps Printstream of command line interface
1172     * @throws OozieClientException thrown if the job info could not be retrieved.
1173     */
1174    public void getJobLog(String jobId, String logRetrievalType, String logRetrievalScope, PrintStream ps)
1175            throws OozieClientException {
1176        getJobLog(jobId, logRetrievalType, logRetrievalScope, null, ps);
1177    }
1178
1179    private class JobLog extends JobMetadata {
1180        JobLog(String jobId) {
1181            super(jobId, RestConstants.JOB_SHOW_LOG);
1182        }
1183        JobLog(String jobId, String logRetrievalType, String logRetrievalScope, String logFilter, PrintStream ps) {
1184            super(jobId, logRetrievalType, logRetrievalScope, RestConstants.JOB_SHOW_LOG, logFilter, ps);
1185        }
1186    }
1187
1188    private class JobErrorLog extends JobMetadata {
1189        JobErrorLog(String jobId, PrintStream ps) {
1190            super(jobId, RestConstants.JOB_SHOW_ERROR_LOG, ps);
1191        }
1192    }
1193
1194    private class JobAuditLog extends JobMetadata {
1195        JobAuditLog(String jobId, PrintStream ps) {
1196            super(jobId, RestConstants.JOB_SHOW_AUDIT_LOG, ps);
1197        }
1198    }
1199
1200
1201    /**
1202     * Gets the JMS topic name for a particular job
1203     * @param jobId given jobId
1204     * @return the JMS topic name
1205     * @throws OozieClientException thrown if the JMS topic could not be retrieved.
1206     */
1207    public String getJMSTopicName(String jobId) throws OozieClientException {
1208        return new JMSTopic(jobId).call();
1209    }
1210
1211    private class JMSTopic extends ClientCallable<String> {
1212
1213        JMSTopic(String jobId) {
1214            super("GET", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.JOB_SHOW_PARAM,
1215                    RestConstants.JOB_SHOW_JMS_TOPIC));
1216        }
1217
1218        protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
1219            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1220                Reader reader = new InputStreamReader(conn.getInputStream());
1221                JSONObject json = (JSONObject) JSONValue.parse(reader);
1222                return (String) json.get(JsonTags.JMS_TOPIC_NAME);
1223            }
1224            else {
1225                handleError(conn);
1226            }
1227            return null;
1228        }
1229    }
1230
1231    /**
1232     * Get the definition of a workflow job.
1233     *
1234     * @param jobId job Id.
1235     * @return the job log.
1236     * @throws OozieClientException thrown if the job info could not be retrieved.
1237     */
1238    public String getJobDefinition(String jobId) throws OozieClientException {
1239        return new JobDefinition(jobId).call();
1240    }
1241
1242    /**
1243     * Get coord action missing dependencies
1244     * @param jobId the id of the job
1245     * @param actionList the list of coordinator actions
1246     * @param dates a comma-separated list of date ranges. Each date range element is specified with two dates separated by '::'
1247     * @param ps the stream to write the result into
1248     * @throws OozieClientException thrown if the info could not be retrieved.
1249     */
1250    public void getCoordActionMissingDependencies(String jobId, String actionList, String dates, PrintStream ps)
1251            throws OozieClientException {
1252        new CoordActionMissingDependencies(jobId, actionList, dates, ps).call();
1253    }
1254
1255    /**
1256     * Get coord action missing dependencies
1257     * @param jobId the id of the job
1258     * @param actionList the list of coordinator actions
1259     * @param dates a comma-separated list of date ranges. Each date range element is specified with two dates separated by '::'
1260     * @throws OozieClientException thrown if the info could not be retrieved.
1261     */
1262    public void getCoordActionMissingDependencies(String jobId, String actionList, String dates)
1263            throws OozieClientException {
1264        new CoordActionMissingDependencies(jobId, actionList, dates).call();
1265    }
1266
1267    private class CoordActionMissingDependencies extends ClientCallable<String> {
1268        PrintStream printStream;
1269        public CoordActionMissingDependencies(String jobId, String actionList, String dates, PrintStream ps) {
1270            super("GET", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.JOB_SHOW_PARAM,
1271                    RestConstants.COORD_ACTION_MISSING_DEPENDENCIES, RestConstants.JOB_COORD_SCOPE_ACTION_LIST, actionList,
1272                    RestConstants.JOB_COORD_SCOPE_DATE, dates));
1273            this.printStream = ps;
1274        }
1275
1276        public CoordActionMissingDependencies(String jobId, String actionList, String dates) {
1277            this(jobId, actionList, dates, System.out);
1278        }
1279
1280
1281        @SuppressWarnings("unchecked")
1282        protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
1283            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1284                Reader reader = new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8);
1285                JSONObject json = (JSONObject) JSONValue.parse(reader);
1286                if (json != null) {
1287                    JSONArray inputDependencies = (JSONArray) json.get(JsonTags.COORD_ACTION_MISSING_DEPENDENCIES);
1288                    for (Object dependencies : inputDependencies) {
1289                        printStream.println();
1290                        printStream.println("CoordAction : "
1291                                + ((JSONObject) dependencies).get(JsonTags.COORDINATOR_ACTION_ID));
1292                        if (((JSONObject) dependencies).get(JsonTags.COORD_ACTION_FIRST_MISSING_DEPENDENCIES) != null) {
1293                            printStream.println("Blocked on  : "
1294                                    + ((JSONObject) dependencies)
1295                                            .get(JsonTags.COORD_ACTION_FIRST_MISSING_DEPENDENCIES));
1296                        }
1297
1298                        if (((JSONObject) dependencies).get(JsonTags.COORDINATOR_ACTION_DATASETS) != null) {
1299
1300                            JSONArray missingDependencies = (JSONArray) ((JSONObject) dependencies)
1301                                    .get(JsonTags.COORDINATOR_ACTION_DATASETS);
1302
1303                            for (Object missingDependenciesJson : missingDependencies) {
1304
1305                                printStream.println("Dataset     : "
1306                                        + ((JSONObject) missingDependenciesJson)
1307                                                .get(JsonTags.COORDINATOR_ACTION_DATASET));
1308
1309                                JSONArray inputDependenciesList = (JSONArray) ((JSONObject) missingDependenciesJson)
1310                                        .get(JsonTags.COORDINATOR_ACTION_MISSING_DEPS);
1311                                printStream.println("Pending Dependencies : ");
1312
1313                                if (inputDependenciesList != null) {
1314                                    Iterator<String> iterator = inputDependenciesList.iterator();
1315                                    while (iterator.hasNext()) {
1316                                        printStream.println("\t  " + iterator.next());
1317                                    }
1318                                }
1319                                printStream.println();
1320                            }
1321                        }
1322                    }
1323                }
1324                else {
1325                    printStream.println(" No missing input dependencies found");
1326                }
1327
1328            }
1329            else {
1330                handleError(conn);
1331            }
1332            return null;
1333        }
1334    }
1335
1336    private class JobDefinition extends JobMetadata {
1337
1338        JobDefinition(String jobId) {
1339            super(jobId, RestConstants.JOB_SHOW_DEFINITION);
1340        }
1341    }
1342
1343    private class JobMetadata extends ClientCallable<String> {
1344        PrintStream printStream;
1345
1346        JobMetadata(String jobId, String metaType) {
1347            super("GET", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.JOB_SHOW_PARAM,
1348                    metaType));
1349        }
1350
1351        JobMetadata(String jobId, String metaType, PrintStream ps) {
1352            this(jobId, metaType);
1353            printStream = ps;
1354
1355        }
1356
1357        JobMetadata(String jobId, String logRetrievalType, String logRetrievalScope, String metaType, String logFilter,
1358                PrintStream ps) {
1359            super("GET", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.JOB_SHOW_PARAM,
1360                    metaType, RestConstants.JOB_LOG_TYPE_PARAM, logRetrievalType, RestConstants.JOB_LOG_SCOPE_PARAM,
1361                    logRetrievalScope, RestConstants.LOG_FILTER_OPTION, logFilter));
1362            printStream = ps;
1363        }
1364
1365        @Override
1366        protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
1367            String returnVal = null;
1368            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1369                InputStream is = conn.getInputStream();
1370                InputStreamReader isr = new InputStreamReader(is);
1371                try {
1372                    if (printStream != null) {
1373                        sendToOutputStream(isr, -1, printStream);
1374                    }
1375                    else {
1376                        returnVal = getReaderAsString(isr, -1);
1377                    }
1378                }
1379                finally {
1380                    isr.close();
1381                }
1382            }
1383            else {
1384                handleError(conn);
1385            }
1386            return returnVal;
1387        }
1388
1389        /**
1390         * Output the log to command line interface
1391         *
1392         * @param reader reader to read into a string.
1393         * @param maxLen max content length allowed, if -1 there is no limit.
1394         * @throws IOException
1395         */
1396        private void sendToOutputStream(Reader reader, int maxLen, PrintStream printStream) throws IOException {
1397            notNull(reader, "reader");
1398            StringBuilder sb = new StringBuilder();
1399            char[] buffer = new char[2048];
1400            int read;
1401            int count = 0;
1402            int noOfCharstoFlush = 1024;
1403            while ((read = reader.read(buffer)) > -1) {
1404                count += read;
1405                if ((maxLen > -1) && (count > maxLen)) {
1406                    break;
1407                }
1408                sb.append(buffer, 0, read);
1409                if (sb.length() > noOfCharstoFlush) {
1410                    printStream.print(sb.toString());
1411                    sb = new StringBuilder("");
1412                }
1413            }
1414            printStream.print(sb.toString());
1415        }
1416
1417        /**
1418         * Return a reader as string.
1419         * <p>
1420         *
1421         * @param reader reader to read into a string.
1422         * @param maxLen max content length allowed, if -1 there is no limit.
1423         * @return the reader content.
1424         * @throws IOException thrown if the resource could not be read.
1425         */
1426        private String getReaderAsString(Reader reader, int maxLen) throws IOException {
1427            notNull(reader, "reader");
1428            StringBuffer sb = new StringBuffer();
1429            char[] buffer = new char[2048];
1430            int read;
1431            int count = 0;
1432            while ((read = reader.read(buffer)) > -1) {
1433                count += read;
1434
1435                // read up to maxLen chars;
1436                if ((maxLen > -1) && (count > maxLen)) {
1437                    break;
1438                }
1439                sb.append(buffer, 0, read);
1440            }
1441            reader.close();
1442            return sb.toString();
1443        }
1444    }
1445
1446    private class CoordJobInfo extends ClientCallable<CoordinatorJob> {
1447
1448        CoordJobInfo(String jobId, String filter, int start, int len, String order) {
1449            super("GET", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.JOB_SHOW_PARAM,
1450                    RestConstants.JOB_SHOW_INFO, RestConstants.JOB_FILTER_PARAM, filter, RestConstants.OFFSET_PARAM,
1451                    Integer.toString(start), RestConstants.LEN_PARAM, Integer.toString(len), RestConstants.ORDER_PARAM,
1452                    order));
1453        }
1454
1455        @Override
1456        protected CoordinatorJob call(HttpURLConnection conn) throws IOException, OozieClientException {
1457            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1458                Reader reader = new InputStreamReader(conn.getInputStream());
1459                JSONObject json = (JSONObject) JSONValue.parse(reader);
1460                return JsonToBean.createCoordinatorJob(json);
1461            }
1462            else {
1463                handleError(conn);
1464            }
1465            return null;
1466        }
1467    }
1468
1469    private class WfsForCoordAction extends ClientCallable<List<WorkflowJob>> {
1470
1471        WfsForCoordAction(String coordActionId) {
1472            super("GET", RestConstants.JOB, notEmpty(coordActionId, "coordActionId"), prepareParams(
1473                    RestConstants.JOB_SHOW_PARAM, RestConstants.ALL_WORKFLOWS_FOR_COORD_ACTION));
1474        }
1475
1476        @Override
1477        protected List<WorkflowJob> call(HttpURLConnection conn) throws IOException, OozieClientException {
1478            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1479                Reader reader = new InputStreamReader(conn.getInputStream());
1480                JSONObject json = (JSONObject) JSONValue.parse(reader);
1481                JSONArray workflows = (JSONArray) json.get(JsonTags.WORKFLOWS_JOBS);
1482                if (workflows == null) {
1483                    workflows = new JSONArray();
1484                }
1485                return JsonToBean.createWorkflowJobList(workflows);
1486            }
1487            else {
1488                handleError(conn);
1489            }
1490            return null;
1491        }
1492    }
1493
1494
1495    private class BundleJobInfo extends ClientCallable<BundleJob> {
1496
1497        BundleJobInfo(String jobId) {
1498            super("GET", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.JOB_SHOW_PARAM,
1499                    RestConstants.JOB_SHOW_INFO));
1500        }
1501
1502        @Override
1503        protected BundleJob call(HttpURLConnection conn) throws IOException, OozieClientException {
1504            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1505                Reader reader = new InputStreamReader(conn.getInputStream());
1506                JSONObject json = (JSONObject) JSONValue.parse(reader);
1507                return JsonToBean.createBundleJob(json);
1508            }
1509            else {
1510                handleError(conn);
1511            }
1512            return null;
1513        }
1514    }
1515
1516    private class CoordActionInfo extends ClientCallable<CoordinatorAction> {
1517        CoordActionInfo(String actionId) {
1518            super("GET", RestConstants.JOB, notEmpty(actionId, "id"), prepareParams(RestConstants.JOB_SHOW_PARAM,
1519                    RestConstants.JOB_SHOW_INFO));
1520        }
1521
1522        @Override
1523        protected CoordinatorAction call(HttpURLConnection conn) throws IOException, OozieClientException {
1524            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1525                Reader reader = new InputStreamReader(conn.getInputStream());
1526                JSONObject json = (JSONObject) JSONValue.parse(reader);
1527                return JsonToBean.createCoordinatorAction(json);
1528            }
1529            else {
1530                handleError(conn);
1531            }
1532            return null;
1533        }
1534    }
1535
1536    /**
1537     * Get the info of a bundle job.
1538     *
1539     * @param jobId job Id.
1540     * @return the job info.
1541     * @throws OozieClientException thrown if the job info could not be retrieved.
1542     */
1543    public BundleJob getBundleJobInfo(String jobId) throws OozieClientException {
1544        return new BundleJobInfo(jobId).call();
1545    }
1546
1547    /**
1548     * Get the info of a coordinator job.
1549     *
1550     * @param jobId job Id.
1551     * @return the job info.
1552     * @throws OozieClientException thrown if the job info could not be retrieved.
1553     */
1554    public CoordinatorJob getCoordJobInfo(String jobId) throws OozieClientException {
1555        return new CoordJobInfo(jobId, null, -1, -1, "asc").call();
1556    }
1557
1558    /**
1559     * Get the info of a coordinator job and subset actions.
1560     *
1561     * @param jobId job Id.
1562     * @param filter filter the status filter
1563     * @param start starting index in the list of actions belonging to the job
1564     * @param len number of actions to be returned
1565     * @return the job info.
1566     * @throws OozieClientException thrown if the job info could not be retrieved.
1567     */
1568    public CoordinatorJob getCoordJobInfo(String jobId, String filter, int start, int len)
1569            throws OozieClientException {
1570        return new CoordJobInfo(jobId, filter, start, len, "asc").call();
1571    }
1572
1573    /**
1574     * Get the info of a coordinator job and subset actions.
1575     *
1576     * @param jobId job Id.
1577     * @param filter filter the status filter
1578     * @param start starting index in the list of actions belonging to the job
1579     * @param len number of actions to be returned
1580     * @param order order to list coord actions (e.g, desc)
1581     * @return the job info.
1582     * @throws OozieClientException thrown if the job info could not be retrieved.
1583     */
1584    public CoordinatorJob getCoordJobInfo(String jobId, String filter, int start, int len, String order)
1585            throws OozieClientException {
1586        return new CoordJobInfo(jobId, filter, start, len, order).call();
1587    }
1588
1589    public List<WorkflowJob> getWfsForCoordAction(String coordActionId) throws OozieClientException {
1590        return new WfsForCoordAction(coordActionId).call();
1591    }
1592
1593    /**
1594     * Get the info of a coordinator action.
1595     *
1596     * @param actionId Id.
1597     * @return the coordinator action info.
1598     * @throws OozieClientException thrown if the job info could not be retrieved.
1599     */
1600    public CoordinatorAction getCoordActionInfo(String actionId) throws OozieClientException {
1601        return new CoordActionInfo(actionId).call();
1602    }
1603
1604    private class JobsStatus extends ClientCallable<List<WorkflowJob>> {
1605
1606        JobsStatus(String filter, int start, int len) {
1607            super("GET", RestConstants.JOBS, "", prepareParams(RestConstants.JOBS_FILTER_PARAM, filter,
1608                    RestConstants.JOBTYPE_PARAM, "wf", RestConstants.OFFSET_PARAM, Integer.toString(start),
1609                    RestConstants.LEN_PARAM, Integer.toString(len)));
1610        }
1611
1612        @Override
1613        protected List<WorkflowJob> call(HttpURLConnection conn) throws IOException, OozieClientException {
1614            conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
1615            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1616                Reader reader = new InputStreamReader(conn.getInputStream());
1617                JSONObject json = (JSONObject) JSONValue.parse(reader);
1618                JSONArray workflows = (JSONArray) json.get(JsonTags.WORKFLOWS_JOBS);
1619                if (workflows == null) {
1620                    workflows = new JSONArray();
1621                }
1622                return JsonToBean.createWorkflowJobList(workflows);
1623            }
1624            else {
1625                handleError(conn);
1626            }
1627            return null;
1628        }
1629    }
1630
1631    private class CoordJobsStatus extends ClientCallable<List<CoordinatorJob>> {
1632
1633        CoordJobsStatus(String filter, int start, int len) {
1634            super("GET", RestConstants.JOBS, "", prepareParams(RestConstants.JOBS_FILTER_PARAM, filter,
1635                    RestConstants.JOBTYPE_PARAM, "coord", RestConstants.OFFSET_PARAM, Integer.toString(start),
1636                    RestConstants.LEN_PARAM, Integer.toString(len)));
1637        }
1638
1639        @Override
1640        protected List<CoordinatorJob> call(HttpURLConnection conn) throws IOException, OozieClientException {
1641            conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
1642            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1643                Reader reader = new InputStreamReader(conn.getInputStream());
1644                JSONObject json = (JSONObject) JSONValue.parse(reader);
1645                JSONArray jobs = (JSONArray) json.get(JsonTags.COORDINATOR_JOBS);
1646                if (jobs == null) {
1647                    jobs = new JSONArray();
1648                }
1649                return JsonToBean.createCoordinatorJobList(jobs);
1650            }
1651            else {
1652                handleError(conn);
1653            }
1654            return null;
1655        }
1656    }
1657
1658    private class BundleJobsStatus extends ClientCallable<List<BundleJob>> {
1659
1660        BundleJobsStatus(String filter, int start, int len) {
1661            super("GET", RestConstants.JOBS, "", prepareParams(RestConstants.JOBS_FILTER_PARAM, filter,
1662                    RestConstants.JOBTYPE_PARAM, "bundle", RestConstants.OFFSET_PARAM, Integer.toString(start),
1663                    RestConstants.LEN_PARAM, Integer.toString(len)));
1664        }
1665
1666        @Override
1667        protected List<BundleJob> call(HttpURLConnection conn) throws IOException, OozieClientException {
1668            conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
1669            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1670                Reader reader = new InputStreamReader(conn.getInputStream());
1671                JSONObject json = (JSONObject) JSONValue.parse(reader);
1672                JSONArray jobs = (JSONArray) json.get(JsonTags.BUNDLE_JOBS);
1673                if (jobs == null) {
1674                    jobs = new JSONArray();
1675                }
1676                return JsonToBean.createBundleJobList(jobs);
1677            }
1678            else {
1679                handleError(conn);
1680            }
1681            return null;
1682        }
1683    }
1684
1685    private class BulkResponseStatus extends ClientCallable<List<BulkResponse>> {
1686
1687        BulkResponseStatus(String filter, int start, int len) {
1688            super("GET", RestConstants.JOBS, "", prepareParams(RestConstants.JOBS_BULK_PARAM, filter,
1689                    RestConstants.OFFSET_PARAM, Integer.toString(start), RestConstants.LEN_PARAM, Integer.toString(len)));
1690        }
1691
1692        @Override
1693        protected List<BulkResponse> call(HttpURLConnection conn) throws IOException, OozieClientException {
1694            conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
1695            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1696                Reader reader = new InputStreamReader(conn.getInputStream());
1697                JSONObject json = (JSONObject) JSONValue.parse(reader);
1698                JSONArray results = (JSONArray) json.get(JsonTags.BULK_RESPONSES);
1699                if (results == null) {
1700                    results = new JSONArray();
1701                }
1702                return JsonToBean.createBulkResponseList(results);
1703            }
1704            else {
1705                handleError(conn);
1706            }
1707            return null;
1708        }
1709    }
1710
1711    private class CoordActionsKill extends ClientCallable<List<CoordinatorAction>> {
1712
1713        CoordActionsKill(String jobId, String rangeType, String scope) {
1714            super("PUT", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.ACTION_PARAM,
1715                    RestConstants.JOB_ACTION_KILL, RestConstants.JOB_COORD_RANGE_TYPE_PARAM, rangeType,
1716                    RestConstants.JOB_COORD_SCOPE_PARAM, scope));
1717        }
1718
1719        @Override
1720        protected List<CoordinatorAction> call(HttpURLConnection conn) throws IOException, OozieClientException {
1721            conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
1722            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1723                Reader reader = new InputStreamReader(conn.getInputStream());
1724                JSONObject json = (JSONObject) JSONValue.parse(reader);
1725                JSONArray coordActions = (JSONArray) json.get(JsonTags.COORDINATOR_ACTIONS);
1726                return JsonToBean.createCoordinatorActionList(coordActions);
1727            }
1728            else {
1729                handleError(conn);
1730            }
1731            return null;
1732        }
1733    }
1734
1735    private class CoordIgnore extends ClientCallable<List<CoordinatorAction>> {
1736        CoordIgnore(String jobId, String rerunType, String scope) {
1737            super("PUT", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.ACTION_PARAM,
1738                    RestConstants.JOB_ACTION_IGNORE, RestConstants.JOB_COORD_RANGE_TYPE_PARAM,
1739                    rerunType, RestConstants.JOB_COORD_SCOPE_PARAM, scope));
1740        }
1741
1742        @Override
1743        protected List<CoordinatorAction> call(HttpURLConnection conn) throws IOException, OozieClientException {
1744            conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
1745            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1746                Reader reader = new InputStreamReader(conn.getInputStream());
1747                JSONObject json = (JSONObject) JSONValue.parse(reader);
1748                if(json != null) {
1749                    JSONArray coordActions = (JSONArray) json.get(JsonTags.COORDINATOR_ACTIONS);
1750                    return JsonToBean.createCoordinatorActionList(coordActions);
1751                }
1752            }
1753            else {
1754                handleError(conn);
1755            }
1756            return null;
1757        }
1758    }
1759    private class CoordRerun extends ClientCallable<List<CoordinatorAction>> {
1760        private final Properties conf;
1761
1762        CoordRerun(String jobId, String rerunType, String scope, boolean refresh, boolean noCleanup, boolean failed,
1763                   Properties conf) {
1764            super("PUT", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.ACTION_PARAM,
1765                    RestConstants.JOB_COORD_ACTION_RERUN, RestConstants.JOB_COORD_RANGE_TYPE_PARAM, rerunType,
1766                    RestConstants.JOB_COORD_SCOPE_PARAM, scope, RestConstants.JOB_COORD_RERUN_REFRESH_PARAM,
1767                    Boolean.toString(refresh), RestConstants.JOB_COORD_RERUN_NOCLEANUP_PARAM, Boolean
1768                            .toString(noCleanup), RestConstants.JOB_COORD_RERUN_FAILED_PARAM, Boolean.toString(failed)));
1769            this.conf = conf;
1770        }
1771
1772        @Override
1773        protected List<CoordinatorAction> call(HttpURLConnection conn) throws IOException, OozieClientException {
1774            conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
1775            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1776                Reader reader = new InputStreamReader(conn.getInputStream());
1777                JSONObject json = (JSONObject) JSONValue.parse(reader);
1778                JSONArray coordActions = (JSONArray) json.get(JsonTags.COORDINATOR_ACTIONS);
1779                return JsonToBean.createCoordinatorActionList(coordActions);
1780            }
1781            else {
1782                handleError(conn);
1783            }
1784            return null;
1785        }
1786    }
1787
1788    private class BundleRerun extends ClientCallable<Void> {
1789
1790        BundleRerun(String jobId, String coordScope, String dateScope, boolean refresh, boolean noCleanup) {
1791            super("PUT", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.ACTION_PARAM,
1792                    RestConstants.JOB_BUNDLE_ACTION_RERUN, RestConstants.JOB_BUNDLE_RERUN_COORD_SCOPE_PARAM,
1793                    coordScope, RestConstants.JOB_BUNDLE_RERUN_DATE_SCOPE_PARAM, dateScope,
1794                    RestConstants.JOB_COORD_RERUN_REFRESH_PARAM, Boolean.toString(refresh),
1795                    RestConstants.JOB_COORD_RERUN_NOCLEANUP_PARAM, Boolean.toString(noCleanup)));
1796        }
1797
1798        @Override
1799        protected Void call(HttpURLConnection conn) throws IOException, OozieClientException {
1800            conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
1801            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1802                return null;
1803            }
1804            else {
1805                handleError(conn);
1806            }
1807            return null;
1808        }
1809    }
1810
1811    /**
1812     * Rerun coordinator actions.
1813     *
1814     * @param jobId coordinator jobId
1815     * @param rerunType rerun type 'date' if -date is used, 'action-id' if -action is used
1816     * @param scope rerun scope for date or actionIds
1817     * @param refresh true if -refresh is given in command option
1818     * @param noCleanup true if -nocleanup is given in command option
1819     * @return the list of rerun coordinator actions
1820     * @throws OozieClientException thrown if the info could not be retrieved.
1821     */
1822    public List<CoordinatorAction> reRunCoord(String jobId, String rerunType, String scope, boolean refresh,
1823            boolean noCleanup) throws OozieClientException {
1824        return new CoordRerun(jobId, rerunType, scope, refresh, noCleanup, false, null).call();
1825    }
1826
1827    /**
1828     * Rerun coordinator actions with failed option.
1829     *
1830     * @param jobId coordinator jobId
1831     * @param rerunType rerun type 'date' if -date is used, 'action-id' if -action is used
1832     * @param scope rerun scope for date or actionIds
1833     * @param refresh true if -refresh is given in command option
1834     * @param noCleanup true if -nocleanup is given in command option
1835     * @param failed true if -failed is given in command option
1836     * @param props properties to use during rerun
1837     * @return new coordinator job execution
1838     * @throws OozieClientException thrown if the info could not be retrieved.
1839     */
1840    public List<CoordinatorAction> reRunCoord(String jobId, String rerunType, String scope, boolean refresh,
1841                                              boolean noCleanup, boolean failed, Properties props) throws OozieClientException {
1842        return new CoordRerun(jobId, rerunType, scope, refresh, noCleanup, failed, props).call();
1843    }
1844
1845    /**
1846     * Rerun bundle coordinators.
1847     *
1848     * @param jobId bundle jobId
1849     * @param coordScope rerun scope for coordinator jobs
1850     * @param dateScope rerun scope for date
1851     * @param refresh true if -refresh is given in command option
1852     * @param noCleanup true if -nocleanup is given in command option
1853     * @throws OozieClientException thrown if the info could not be retrieved.
1854     */
1855    public Void reRunBundle(String jobId, String coordScope, String dateScope, boolean refresh, boolean noCleanup)
1856            throws OozieClientException {
1857        return new BundleRerun(jobId, coordScope, dateScope, refresh, noCleanup).call();
1858    }
1859
1860    /**
1861     * Return the info of the workflow jobs that match the filter.
1862     *
1863     * @param filter job filter. Refer to the {@link OozieClient} for the filter syntax.
1864     * @param start jobs offset, base 1.
1865     * @param len number of jobs to return.
1866     * @return a list with the workflow jobs info, without node details.
1867     * @throws OozieClientException thrown if the jobs info could not be retrieved.
1868     */
1869    public List<WorkflowJob> getJobsInfo(String filter, int start, int len) throws OozieClientException {
1870        return new JobsStatus(filter, start, len).call();
1871    }
1872
1873    /**
1874     * Return the info of the workflow jobs that match the filter.
1875     * <p>
1876     * It returns the first 100 jobs that match the filter.
1877     *
1878     * @param filter job filter. Refer to the {@link OozieClient} for the filter syntax.
1879     * @return a list with the workflow jobs info, without node details.
1880     * @throws OozieClientException thrown if the jobs info could not be retrieved.
1881     */
1882    public List<WorkflowJob> getJobsInfo(String filter) throws OozieClientException {
1883        return getJobsInfo(filter, 1, 50);
1884    }
1885
1886    /**
1887     * Sla enable alert.
1888     *
1889     * @param jobIds the job ids
1890     * @param actions comma separated list of action ids or action id ranges
1891     * @param dates comma separated list of the nominal times
1892     * @throws OozieClientException the oozie client exception
1893     */
1894    public void slaEnableAlert(String jobIds, String actions, String dates) throws OozieClientException {
1895        new UpdateSLA(RestConstants.SLA_ENABLE_ALERT, jobIds, actions, dates, null).call();
1896    }
1897
1898    /**
1899     * Sla enable alert for bundle with coord name/id.
1900     *
1901     * @param bundleId the bundle id
1902     * @param actions comma separated list of action ids or action id ranges
1903     * @param dates comma separated list of the nominal times
1904     * @param coords the coordinators
1905     * @throws OozieClientException the oozie client exception
1906     */
1907    public void slaEnableAlert(String bundleId, String actions, String dates, String coords)
1908            throws OozieClientException {
1909        new UpdateSLA(RestConstants.SLA_ENABLE_ALERT, bundleId, actions, dates, coords).call();
1910    }
1911
1912    /**
1913     * Sla disable alert.
1914     *
1915     * @param jobIds the job ids
1916     * @param actions comma separated list of action ids or action id ranges
1917     * @param dates comma separated list of the nominal times
1918     * @throws OozieClientException the oozie client exception
1919     */
1920    public void slaDisableAlert(String jobIds, String actions, String dates) throws OozieClientException {
1921        new UpdateSLA(RestConstants.SLA_DISABLE_ALERT, jobIds, actions, dates, null).call();
1922    }
1923
1924    /**
1925     * Sla disable alert for bundle with coord name/id.
1926     *
1927     * @param bundleId the bundle id
1928     * @param actions comma separated list of action ids or action id ranges
1929     * @param dates comma separated list of the nominal times
1930     * @param coords the coordinators
1931     * @throws OozieClientException the oozie client exception
1932     */
1933    public void slaDisableAlert(String bundleId, String actions, String dates, String coords)
1934            throws OozieClientException {
1935        new UpdateSLA(RestConstants.SLA_DISABLE_ALERT, bundleId, actions, dates, coords).call();
1936    }
1937
1938    /**
1939     * Sla change definations.
1940     * SLA change definition parameters can be [&lt;key&gt;=&lt;value&gt;,...&lt;key&gt;=&lt;value&gt;]
1941     * Supported parameter key names are should-start, should-end and max-duration
1942     * @param jobIds the job ids
1943     * @param actions comma separated list of action ids or action id ranges.
1944     * @param dates comma separated list of the nominal times
1945     * @param newSlaParams the new sla params
1946     * @throws OozieClientException the oozie client exception
1947     */
1948    public void slaChange(String jobIds, String actions, String dates, String newSlaParams) throws OozieClientException {
1949        new UpdateSLA(RestConstants.SLA_CHANGE, jobIds, actions, dates, null, newSlaParams).call();
1950    }
1951
1952    /**
1953     * Sla change defination for bundle with coord name/id.
1954     * SLA change definition parameters can be [&lt;key&gt;=&lt;value&gt;,...&lt;key&gt;=&lt;value&gt;]
1955     * Supported parameter key names are should-start, should-end and max-duration
1956     * @param bundleId the bundle id
1957     * @param actions comma separated list of action ids or action id ranges
1958     * @param dates comma separated list of the nominal times
1959     * @param coords the coords
1960     * @param newSlaParams the new sla params
1961     * @throws OozieClientException the oozie client exception
1962     */
1963    public void slaChange(String bundleId, String actions, String dates, String coords, String newSlaParams)
1964            throws OozieClientException {
1965        new UpdateSLA(RestConstants.SLA_CHANGE, bundleId, actions, dates, coords, newSlaParams).call();
1966    }
1967
1968    /**
1969     * Sla change with new sla param as hasmap.
1970     * Supported parameter key names are should-start, should-end and max-duration
1971     * @param bundleId the bundle id
1972     * @param actions comma separated list of action ids or action id ranges
1973     * @param dates comma separated list of the nominal times
1974     * @param coords the coords
1975     * @param newSlaParams the new sla params
1976     * @throws OozieClientException the oozie client exception
1977     */
1978    public void slaChange(String bundleId, String actions, String dates, String coords, Map<String, String> newSlaParams)
1979            throws OozieClientException {
1980        new UpdateSLA(RestConstants.SLA_CHANGE, bundleId, actions, dates, coords, mapToString(newSlaParams)).call();
1981    }
1982
1983    /**
1984     * Convert Map to string.
1985     *
1986     * @param map the map
1987     * @return the string
1988     */
1989    protected String mapToString(Map<String, String> map) {
1990        StringBuilder sb = new StringBuilder();
1991        Iterator<Entry<String, String>> it = map.entrySet().iterator();
1992        while (it.hasNext()) {
1993            Entry<String, String> e = (Entry<String, String>) it.next();
1994            sb.append(e.getKey()).append("=").append(e.getValue()).append(";");
1995        }
1996        return sb.toString();
1997    }
1998
1999    private class UpdateSLA extends ClientCallable<Void> {
2000
2001        UpdateSLA(String action, String jobIds, String coordActions, String dates, String coords) {
2002            super("PUT", RestConstants.JOB, notEmpty(jobIds, "jobIds"), prepareParams(RestConstants.ACTION_PARAM,
2003                    action, RestConstants.JOB_COORD_SCOPE_ACTION_LIST, coordActions, RestConstants.JOB_COORD_SCOPE_DATE,
2004                    dates, RestConstants.COORDINATORS_PARAM, coords));
2005        }
2006
2007        UpdateSLA(String action, String jobIds, String coordActions, String dates, String coords, String newSlaParams) {
2008            super("PUT", RestConstants.JOB, notEmpty(jobIds, "jobIds"), prepareParams(RestConstants.ACTION_PARAM,
2009                    action, RestConstants.JOB_COORD_SCOPE_ACTION_LIST, coordActions, RestConstants.JOB_COORD_SCOPE_DATE,
2010                    dates, RestConstants.COORDINATORS_PARAM, coords, RestConstants.JOB_CHANGE_VALUE, newSlaParams));
2011        }
2012
2013        @Override
2014        protected Void call(HttpURLConnection conn) throws IOException, OozieClientException {
2015            conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
2016            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
2017                System.out.println("Done");
2018            }
2019            else {
2020                handleError(conn);
2021            }
2022            return null;
2023        }
2024    }
2025
2026    /**
2027    * Print sla info about coordinator and workflow jobs and actions.
2028    *
2029    * @param start starting offset
2030    * @param len number of results
2031    * @param filter filters to use. Elements must be semicolon-separated name=value pairs
2032    * @throws OozieClientException thrown if the sla info could not be retrieved
2033    */
2034        public void getSlaInfo(int start, int len, String filter) throws OozieClientException {
2035            new SlaInfo(start, len, filter).call();
2036        }
2037
2038        private class SlaInfo extends ClientCallable<Void> {
2039
2040            SlaInfo(int start, int len, String filter) {
2041                super("GET", WS_PROTOCOL_VERSION_1, RestConstants.SLA, "", prepareParams(RestConstants.SLA_GT_SEQUENCE_ID,
2042                        Integer.toString(start), RestConstants.MAX_EVENTS, Integer.toString(len),
2043                        RestConstants.JOBS_FILTER_PARAM, filter));
2044            }
2045
2046            @Override
2047            protected Void call(HttpURLConnection conn) throws IOException, OozieClientException {
2048                conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
2049                if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
2050                    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
2051                    String line = null;
2052                    while ((line = br.readLine()) != null) {
2053                        System.out.println(line);
2054                    }
2055                }
2056                else {
2057                    handleError(conn);
2058                }
2059                return null;
2060            }
2061        }
2062
2063    private class JobIdAction extends ClientCallable<String> {
2064
2065        JobIdAction(String externalId) {
2066            super("GET", RestConstants.JOBS, "", prepareParams(RestConstants.JOBTYPE_PARAM, "wf",
2067                    RestConstants.JOBS_EXTERNAL_ID_PARAM, externalId));
2068        }
2069
2070        @Override
2071        protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
2072            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
2073                Reader reader = new InputStreamReader(conn.getInputStream());
2074                JSONObject json = (JSONObject) JSONValue.parse(reader);
2075                return (String) json.get(JsonTags.JOB_ID);
2076            }
2077            else {
2078                handleError(conn);
2079            }
2080            return null;
2081        }
2082    }
2083
2084    /**
2085     * Return the workflow job Id for an external Id.
2086     * <p>
2087     * The external Id must have provided at job creation time.
2088     *
2089     * @param externalId external Id given at job creation time.
2090     * @return the workflow job Id for an external Id, <code>null</code> if none.
2091     * @throws OozieClientException thrown if the operation could not be done.
2092     */
2093    public String getJobId(String externalId) throws OozieClientException {
2094        return new JobIdAction(externalId).call();
2095    }
2096
2097    private class SetSystemMode extends ClientCallable<Void> {
2098
2099        public SetSystemMode(SYSTEM_MODE status) {
2100            super("PUT", RestConstants.ADMIN, RestConstants.ADMIN_STATUS_RESOURCE, prepareParams(
2101                    RestConstants.ADMIN_SYSTEM_MODE_PARAM, status + ""));
2102        }
2103
2104        @Override
2105        public Void call(HttpURLConnection conn) throws IOException, OozieClientException {
2106            if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
2107                handleError(conn);
2108            }
2109            return null;
2110        }
2111    }
2112
2113    /**
2114     * Enable or disable safe mode. Used by OozieCLI. In safe mode, Oozie would not accept any commands except status
2115     * command to change and view the safe mode status.
2116     *
2117     * @param status true to enable safe mode, false to disable safe mode.
2118     * @throws OozieClientException if it fails to set the safe mode status.
2119     */
2120    public void setSystemMode(SYSTEM_MODE status) throws OozieClientException {
2121        new SetSystemMode(status).call();
2122    }
2123
2124    private class GetSystemMode extends ClientCallable<SYSTEM_MODE> {
2125
2126        GetSystemMode() {
2127            super("GET", RestConstants.ADMIN, RestConstants.ADMIN_STATUS_RESOURCE, prepareParams());
2128        }
2129
2130        @Override
2131        protected SYSTEM_MODE call(HttpURLConnection conn) throws IOException, OozieClientException {
2132            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
2133                Reader reader = new InputStreamReader(conn.getInputStream());
2134                JSONObject json = (JSONObject) JSONValue.parse(reader);
2135                return SYSTEM_MODE.valueOf((String) json.get(JsonTags.OOZIE_SYSTEM_MODE));
2136            }
2137            else {
2138                handleError(conn);
2139            }
2140            return SYSTEM_MODE.NORMAL;
2141        }
2142    }
2143
2144    /**
2145     * Returns if Oozie is in safe mode or not.
2146     *
2147     * @return true if safe mode is ON<br>
2148     *         false if safe mode is OFF
2149     * @throws OozieClientException throw if it could not obtain the safe mode status.
2150     */
2151    /*
2152     * public boolean isInSafeMode() throws OozieClientException { return new GetSafeMode().call(); }
2153     */
2154    public SYSTEM_MODE getSystemMode() throws OozieClientException {
2155        return new GetSystemMode().call();
2156    }
2157
2158    public String updateShareLib() throws OozieClientException {
2159        return new UpdateSharelib().call();
2160    }
2161
2162    public String listShareLib(String sharelibKey) throws OozieClientException {
2163        return new ListShareLib(sharelibKey).call();
2164    }
2165
2166    private class GetBuildVersion extends ClientCallable<String> {
2167
2168        GetBuildVersion() {
2169            super("GET", RestConstants.ADMIN, RestConstants.ADMIN_BUILD_VERSION_RESOURCE, prepareParams());
2170        }
2171
2172        @Override
2173        protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
2174            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
2175                Reader reader = new InputStreamReader(conn.getInputStream());
2176                JSONObject json = (JSONObject) JSONValue.parse(reader);
2177                return json.get(JsonTags.BUILD_INFO).toString();
2178            }
2179            else {
2180                handleError(conn);
2181            }
2182            return null;
2183        }
2184    }
2185
2186    private class ValidateXML extends ClientCallable<String> {
2187
2188        String file = null;
2189
2190        ValidateXML(String file, String user) {
2191            super("POST", RestConstants.VALIDATE, "",
2192                    prepareParams(RestConstants.FILE_PARAM, file, RestConstants.USER_PARAM, user));
2193            this.file = file;
2194        }
2195
2196        @Override
2197        protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
2198            conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
2199            if (file.startsWith("/")) {
2200                FileInputStream fi = new FileInputStream(new File(file));
2201                byte[] buffer = new byte[1024];
2202                int n = 0;
2203                while (-1 != (n = fi.read(buffer))) {
2204                    conn.getOutputStream().write(buffer, 0, n);
2205                }
2206            }
2207            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
2208                Reader reader = new InputStreamReader(conn.getInputStream());
2209                JSONObject json = (JSONObject) JSONValue.parse(reader);
2210                return (String) json.get(JsonTags.VALIDATE);
2211            }
2212            else if ((conn.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND)) {
2213                return null;
2214            }
2215            else {
2216                handleError(conn);
2217            }
2218            return null;
2219        }
2220    }
2221
2222
2223    private  class UpdateSharelib extends ClientCallable<String> {
2224
2225        UpdateSharelib() {
2226            super("GET", RestConstants.ADMIN, RestConstants.ADMIN_UPDATE_SHARELIB, prepareParams(
2227                    RestConstants.ALL_SERVER_REQUEST, "true"));
2228        }
2229
2230        @Override
2231        protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
2232            StringBuffer bf = new StringBuffer();
2233            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
2234                Reader reader = new InputStreamReader(conn.getInputStream());
2235                Object sharelib = (Object) JSONValue.parse(reader);
2236                bf.append("[ShareLib update status]").append(System.getProperty("line.separator"));
2237                if (sharelib instanceof JSONArray) {
2238                    for (Object o : ((JSONArray) sharelib)) {
2239                        JSONObject obj = (JSONObject) ((JSONObject) o).get(JsonTags.SHARELIB_LIB_UPDATE);
2240                        for (Object key : obj.keySet()) {
2241                            bf.append("\t").append(key).append(" = ").append(obj.get(key))
2242                                    .append(System.getProperty("line.separator"));
2243                        }
2244                        bf.append(System.getProperty("line.separator"));
2245                    }
2246                }
2247                else{
2248                    JSONObject obj = (JSONObject) ((JSONObject) sharelib).get(JsonTags.SHARELIB_LIB_UPDATE);
2249                    for (Object key : obj.keySet()) {
2250                        bf.append("\t").append(key).append(" = ").append(obj.get(key))
2251                                .append(System.getProperty("line.separator"));
2252                    }
2253                    bf.append(System.getProperty("line.separator"));
2254                }
2255                return bf.toString();
2256            }
2257            else {
2258                handleError(conn);
2259            }
2260            return null;
2261        }
2262    }
2263
2264    private class ListShareLib extends ClientCallable<String> {
2265
2266        ListShareLib(String sharelibKey) {
2267            super("GET", RestConstants.ADMIN, RestConstants.ADMIN_LIST_SHARELIB, prepareParams(
2268                    RestConstants.SHARE_LIB_REQUEST_KEY, sharelibKey));
2269        }
2270
2271        @Override
2272        protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
2273
2274            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
2275                StringBuffer bf = new StringBuffer();
2276                Reader reader = new InputStreamReader(conn.getInputStream());
2277                JSONObject json = (JSONObject) JSONValue.parse(reader);
2278                Object sharelib = json.get(JsonTags.SHARELIB_LIB);
2279                bf.append("[Available ShareLib]").append(System.getProperty("line.separator"));
2280                if (sharelib instanceof JSONArray) {
2281                    for (Object o : ((JSONArray) sharelib)) {
2282                        JSONObject obj = (JSONObject) o;
2283                        bf.append(obj.get(JsonTags.SHARELIB_LIB_NAME))
2284                                .append(System.getProperty("line.separator"));
2285                        if (obj.get(JsonTags.SHARELIB_LIB_FILES) != null) {
2286                            for (Object file : ((JSONArray) obj.get(JsonTags.SHARELIB_LIB_FILES))) {
2287                                bf.append("\t").append(file).append(System.getProperty("line.separator"));
2288                            }
2289                        }
2290                    }
2291                    return bf.toString();
2292                }
2293            }
2294            else {
2295                handleError(conn);
2296            }
2297            return null;
2298        }
2299
2300    }
2301
2302    public String purgeCommand(String purgeOptions) throws OozieClientException {
2303        String workflowAge = "";
2304        String coordAge = "";
2305        String bundleAge = "";
2306        String purgeLimit = "";
2307        String oldCoordAction = "";
2308        if (purgeOptions != null) {
2309            String options[] = purgeOptions.split(";");
2310            for (String option : options) {
2311                String pair[] = option.split("=");
2312                if (pair.length < 2) {
2313                    throw new OozieClientException(OozieClientException.INVALID_INPUT,
2314                            "Invalid purge option pair [" + option + "] specified.");
2315                }
2316                String key = pair[0].toLowerCase();
2317                String value = pair[1];
2318                switch (key) {
2319                    case "wf":
2320                        workflowAge = String.valueOf(ValidationUtil.parsePositiveInteger(value));
2321                        break;
2322                    case "coord":
2323                        coordAge = String.valueOf(ValidationUtil.parsePositiveInteger(value));
2324                        break;
2325                    case "bundle":
2326                        bundleAge = String.valueOf(ValidationUtil.parsePositiveInteger(value));
2327                        break;
2328                    case "limit":
2329                        purgeLimit = String.valueOf(ValidationUtil.parsePositiveInteger(value));
2330                        break;
2331                    case "oldcoordaction":
2332                        oldCoordAction = value;
2333                        break;
2334                    default:
2335                        throw new OozieClientException(OozieClientException.INVALID_INPUT,
2336                                "Invalid purge option [" + key + "] specified.");
2337                }
2338            }
2339        }
2340        PurgeCommand purgeCommand = new PurgeCommand(workflowAge, coordAge, bundleAge, purgeLimit, oldCoordAction);
2341        return purgeCommand.call();
2342    }
2343
2344    private class PurgeCommand extends ClientCallable<String> {
2345
2346        PurgeCommand(String workflowAge, String coordAge, String bundleAge, String purgeLimit, String oldCoordAction) {
2347            super("PUT", RestConstants.ADMIN, RestConstants.ADMIN_PURGE, prepareParams(
2348                    RestConstants.PURGE_WF_AGE, workflowAge,
2349                    RestConstants.PURGE_COORD_AGE, coordAge,
2350                    RestConstants.PURGE_BUNDLE_AGE, bundleAge,
2351                    RestConstants.PURGE_LIMIT, purgeLimit,
2352                    RestConstants.PURGE_OLD_COORD_ACTION, oldCoordAction
2353            ));
2354        }
2355
2356        @Override
2357        protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
2358            if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
2359                Reader reader = new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8);
2360                JSONObject jsonObject = (JSONObject) JSONValue.parse(reader);
2361                Object msg = jsonObject.get(JsonTags.PURGE);
2362                return msg.toString();
2363            } else {
2364                handleError(conn);
2365            }
2366            return null;
2367        }
2368    }
2369
2370    /**
2371     * Return the Oozie server build version.
2372     *
2373     * @return the Oozie server build version.
2374     * @throws OozieClientException throw if the server build version could not be retrieved.
2375     */
2376    public String getServerBuildVersion() throws OozieClientException {
2377        return new GetBuildVersion().call();
2378    }
2379
2380    /**
2381     * Return the Oozie client build version.
2382     *
2383     * @return the Oozie client build version.
2384     */
2385    public String getClientBuildVersion() {
2386        return BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VERSION);
2387    }
2388
2389    /**
2390     * Return the workflow application is valid.
2391     *
2392     * @param file local file or hdfs file.
2393     * @return the workflow application is valid.
2394     * @throws OozieClientException throw if it the workflow application's validation could not be retrieved.
2395     */
2396    public String validateXML(String file) throws OozieClientException {
2397        String fileName = file;
2398        if (file.startsWith("file://")) {
2399            fileName = file.substring(7, file.length());
2400        }
2401        if (!fileName.contains("://")) {
2402            File f = new File(fileName);
2403            if (!f.isFile()) {
2404                throw new OozieClientException("File error", "File does not exist : " + f.getAbsolutePath());
2405            }
2406            fileName = f.getAbsolutePath();
2407        }
2408        String user = USER_NAME_TL.get();
2409        if (user == null) {
2410            user = System.getProperty("user.name");
2411        }
2412        return new ValidateXML(fileName, user).call();
2413    }
2414
2415    /**
2416     * Return the info of the coordinator jobs that match the filter.
2417     *
2418     * @param filter job filter. Refer to the {@link OozieClient} for the filter syntax.
2419     * @param start jobs offset, base 1.
2420     * @param len number of jobs to return.
2421     * @return a list with the coordinator jobs info
2422     * @throws OozieClientException thrown if the jobs info could not be retrieved.
2423     */
2424    public List<CoordinatorJob> getCoordJobsInfo(String filter, int start, int len) throws OozieClientException {
2425        return new CoordJobsStatus(filter, start, len).call();
2426    }
2427
2428    /**
2429     * Return the info of the bundle jobs that match the filter.
2430     *
2431     * @param filter job filter. Refer to the {@link OozieClient} for the filter syntax.
2432     * @param start jobs offset, base 1.
2433     * @param len number of jobs to return.
2434     * @return a list with the bundle jobs info
2435     * @throws OozieClientException thrown if the jobs info could not be retrieved.
2436     */
2437    public List<BundleJob> getBundleJobsInfo(String filter, int start, int len) throws OozieClientException {
2438        return new BundleJobsStatus(filter, start, len).call();
2439    }
2440
2441    public List<BulkResponse> getBulkInfo(String filter, int start, int len) throws OozieClientException {
2442        return new BulkResponseStatus(filter, start, len).call();
2443    }
2444
2445    /**
2446     * Poll a job (Workflow Job ID, Coordinator Job ID, Coordinator Action ID, or Bundle Job ID) and return when it has reached a
2447     * terminal state.
2448     * (i.e. FAILED, KILLED, SUCCEEDED)
2449     *
2450     * @param id The Job ID
2451     * @param timeout timeout in minutes (negative values indicate no timeout)
2452     * @param interval polling interval in minutes (must be positive)
2453     * @param verbose if true, the current status will be printed out at each poll; if false, no output
2454     * @throws OozieClientException thrown if the job's status could not be retrieved
2455     */
2456    public void pollJob(String id, int timeout, int interval, boolean verbose) throws OozieClientException {
2457        notEmpty("id", id);
2458        if (interval < 1) {
2459            throw new IllegalArgumentException("interval must be a positive integer");
2460        }
2461        boolean noTimeout = (timeout < 1);
2462        long endTime = System.currentTimeMillis() + timeout * 60 * 1000;
2463        interval *= 60 * 1000;
2464
2465        final Set<String> completedStatuses;
2466        if (id.endsWith("-W")) {
2467            completedStatuses = COMPLETED_WF_STATUSES;
2468        } else if (id.endsWith("-C")) {
2469            completedStatuses = COMPLETED_COORD_AND_BUNDLE_STATUSES;
2470        } else if (id.endsWith("-B")) {
2471            completedStatuses = COMPLETED_COORD_AND_BUNDLE_STATUSES;
2472        } else if (id.contains("-C@")) {
2473            completedStatuses = COMPLETED_COORD_ACTION_STATUSES;
2474        } else {
2475            throw new IllegalArgumentException("invalid job type");
2476        }
2477
2478        String status = getStatus(id);
2479        if (verbose) {
2480            System.out.println(status);
2481        }
2482        while(!completedStatuses.contains(status) && (noTimeout || System.currentTimeMillis() <= endTime)) {
2483            try {
2484                Thread.sleep(interval);
2485            } catch (InterruptedException ie) {
2486                // ignore
2487            }
2488            status = getStatus(id);
2489            if (verbose) {
2490                System.out.println(status);
2491            }
2492        }
2493    }
2494
2495    /**
2496     * Gets the status for a particular job (Workflow Job ID, Coordinator Job ID, Coordinator Action ID, or Bundle Job ID).
2497     *
2498     * @param jobId given jobId
2499     * @return the status
2500     * @throws OozieClientException thrown if the status could not be retrieved
2501     */
2502    public String getStatus(String jobId) throws OozieClientException {
2503        return new Status(jobId).call();
2504    }
2505
2506    private class Status extends ClientCallable<String> {
2507
2508        Status(String jobId) {
2509            super("GET", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.JOB_SHOW_PARAM,
2510                    RestConstants.JOB_SHOW_STATUS));
2511        }
2512
2513        @Override
2514        protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
2515            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
2516                Reader reader = new InputStreamReader(conn.getInputStream());
2517                JSONObject json = (JSONObject) JSONValue.parse(reader);
2518                return (String) json.get(JsonTags.STATUS);
2519            }
2520            else {
2521                handleError(conn);
2522            }
2523            return null;
2524        }
2525    }
2526
2527    private class GetQueueDump extends ClientCallable<List<String>> {
2528        GetQueueDump() {
2529            super("GET", RestConstants.ADMIN, RestConstants.ADMIN_QUEUE_DUMP_RESOURCE, prepareParams());
2530        }
2531
2532        @Override
2533        protected List<String> call(HttpURLConnection conn) throws IOException, OozieClientException {
2534            if ((conn.getResponseCode() != HttpURLConnection.HTTP_OK)) {
2535                handleError(conn);
2536                return null;
2537            }
2538
2539            Reader reader = new InputStreamReader(conn.getInputStream());
2540            JSONObject json = (JSONObject) JSONValue.parse(reader);
2541            List<String> queueDumpMessages = Lists.newArrayList();
2542
2543            addSeparator(queueDumpMessages);
2544
2545            addQueueMessages(json,
2546                    queueDumpMessages,
2547                    JsonTags.QUEUE_DUMP,
2548                    JsonTags.CALLABLE_DUMP,
2549                    "[Server Queue Dump]:",
2550                    "The queue dump is empty, nothing to display.");
2551
2552            addSeparator(queueDumpMessages);
2553
2554            addQueueMessages(json,
2555                    queueDumpMessages,
2556                    JsonTags.UNIQUE_MAP_DUMP,
2557                    JsonTags.UNIQUE_ENTRY_DUMP,
2558                    "[Server Uniqueness Map Dump]:",
2559                    "The uniqueness map dump is empty, nothing to display.");
2560
2561            addSeparator(queueDumpMessages);
2562
2563            return queueDumpMessages;
2564        }
2565
2566        private void addQueueMessages(JSONObject json,
2567                                      List<String> queueMessages,
2568                                      String queueName,
2569                                      String queueValue,
2570                                      String headerMessage,
2571                                      String emptyMessage) {
2572
2573            JSONArray queueDumpArray = (JSONArray) json.get(queueName);
2574            queueMessages.add(headerMessage);
2575
2576            for (Object o : queueDumpArray) {
2577                JSONObject entry = (JSONObject) o;
2578                if (entry.get(queueValue) != null) {
2579                    String value = (String) entry.get(queueValue);
2580                    queueMessages.add(value);
2581                }
2582            }
2583
2584            if (queueDumpArray.isEmpty()) {
2585                queueMessages.add(emptyMessage);
2586            }
2587        }
2588
2589        private void addSeparator(List<String> list) {
2590            list.add("******************************************");
2591        }
2592    }
2593
2594    /**
2595     * Return the Oozie queue's commands' dump
2596     *
2597     * @return the list of strings of callable identification in queue
2598     * @throws OozieClientException throw if it the queue dump could not be retrieved.
2599     */
2600    public List<String> getQueueDump() throws OozieClientException {
2601        return new GetQueueDump().call();
2602    }
2603
2604    private class GetAvailableOozieServers extends MapClientCallable {
2605
2606        GetAvailableOozieServers() {
2607            super("GET", RestConstants.ADMIN, RestConstants.ADMIN_AVAILABLE_OOZIE_SERVERS_RESOURCE, prepareParams());
2608        }
2609    }
2610
2611    /**
2612     * Return the list of available Oozie servers.
2613     *
2614     * @return the list of available Oozie servers.
2615     * @throws OozieClientException throw if it the list of available Oozie servers could not be retrieved.
2616     */
2617    public Map<String, String> getAvailableOozieServers() throws OozieClientException {
2618        return new GetAvailableOozieServers().call();
2619    }
2620
2621    private class GetServerConfiguration extends MapClientCallable {
2622
2623        GetServerConfiguration() {
2624            super("GET", RestConstants.ADMIN, RestConstants.ADMIN_CONFIG_RESOURCE, prepareParams());
2625        }
2626    }
2627
2628    /**
2629     * Return the Oozie system configuration.
2630     *
2631     * @return the Oozie system configuration.
2632     * @throws OozieClientException throw if the system configuration could not be retrieved.
2633     */
2634    public Map<String, String> getServerConfiguration() throws OozieClientException {
2635        return new GetServerConfiguration().call();
2636    }
2637
2638    private class GetJavaSystemProperties extends MapClientCallable {
2639
2640        GetJavaSystemProperties() {
2641            super("GET", RestConstants.ADMIN, RestConstants.ADMIN_JAVA_SYS_PROPS_RESOURCE, prepareParams());
2642        }
2643    }
2644
2645    /**
2646     * Return the Oozie Java system properties.
2647     *
2648     * @return the Oozie Java system properties.
2649     * @throws OozieClientException throw if the system properties could not be retrieved.
2650     */
2651    public Map<String, String> getJavaSystemProperties() throws OozieClientException {
2652        return new GetJavaSystemProperties().call();
2653    }
2654
2655    private class GetOSEnv extends MapClientCallable {
2656
2657        GetOSEnv() {
2658            super("GET", RestConstants.ADMIN, RestConstants.ADMIN_OS_ENV_RESOURCE, prepareParams());
2659        }
2660    }
2661
2662    /**
2663     * Return the Oozie system OS environment.
2664     *
2665     * @return the Oozie system OS environment.
2666     * @throws OozieClientException throw if the system OS environment could not be retrieved.
2667     */
2668    public Map<String, String> getOSEnv() throws OozieClientException {
2669        return new GetOSEnv().call();
2670    }
2671
2672    private class GetMetrics extends ClientCallable<Metrics> {
2673
2674        GetMetrics() {
2675            super("GET", RestConstants.ADMIN, RestConstants.ADMIN_METRICS_RESOURCE, prepareParams());
2676        }
2677
2678        @Override
2679        protected Metrics call(HttpURLConnection conn) throws IOException, OozieClientException {
2680            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
2681                Reader reader = new InputStreamReader(conn.getInputStream());
2682                JSONObject json = (JSONObject) JSONValue.parse(reader);
2683                Metrics metrics = new Metrics(json);
2684                return metrics;
2685            }
2686            else if ((conn.getResponseCode() == HttpURLConnection.HTTP_UNAVAILABLE)) {
2687                // Use Instrumentation endpoint
2688                return null;
2689            }
2690            else {
2691                handleError(conn);
2692            }
2693            return null;
2694        }
2695    }
2696
2697    public class Metrics {
2698        private Map<String, Long> counters;
2699        private Map<String, Object> gauges;
2700        private Map<String, Timer> timers;
2701        private Map<String, Histogram> histograms;
2702
2703        @SuppressWarnings("unchecked")
2704        public Metrics(JSONObject json) {
2705            JSONObject jCounters = (JSONObject) json.get("counters");
2706            counters = new HashMap<String, Long>(jCounters.size());
2707            for (Object entO : jCounters.entrySet()) {
2708                Entry<String, JSONObject> ent = (Entry<String, JSONObject>) entO;
2709                counters.put(ent.getKey(), (Long)ent.getValue().get("count"));
2710            }
2711
2712            JSONObject jGuages = (JSONObject) json.get("gauges");
2713            gauges = new HashMap<String, Object>(jGuages.size());
2714            for (Object entO : jGuages.entrySet()) {
2715                Entry<String, JSONObject> ent = (Entry<String, JSONObject>) entO;
2716                gauges.put(ent.getKey(), ent.getValue().get("value"));
2717            }
2718
2719            JSONObject jTimers = (JSONObject) json.get("timers");
2720            timers = new HashMap<String, Timer>(jTimers.size());
2721            for (Object entO : jTimers.entrySet()) {
2722                Entry<String, JSONObject> ent = (Entry<String, JSONObject>) entO;
2723                timers.put(ent.getKey(), new Timer(ent.getValue()));
2724            }
2725
2726            JSONObject jHistograms = (JSONObject) json.get("histograms");
2727            histograms = new HashMap<String, Histogram>(jHistograms.size());
2728            for (Object entO : jHistograms.entrySet()) {
2729                Entry<String, JSONObject> ent = (Entry<String, JSONObject>) entO;
2730                histograms.put(ent.getKey(), new Histogram(ent.getValue()));
2731            }
2732        }
2733
2734        public Map<String, Long> getCounters() {
2735            return counters;
2736        }
2737
2738        public Map<String, Object> getGauges() {
2739            return gauges;
2740        }
2741
2742        public Map<String, Timer> getTimers() {
2743            return timers;
2744        }
2745
2746        public Map<String, Histogram> getHistograms() {
2747            return histograms;
2748        }
2749
2750        public class Timer extends Histogram {
2751            private double m15Rate;
2752            private double m5Rate;
2753            private double m1Rate;
2754            private double meanRate;
2755            private String durationUnits;
2756            private String rateUnits;
2757
2758            public Timer(JSONObject json) {
2759                super(json);
2760                m15Rate = Double.valueOf(json.get("m15_rate").toString());
2761                m5Rate = Double.valueOf(json.get("m5_rate").toString());
2762                m1Rate = Double.valueOf(json.get("m1_rate").toString());
2763                meanRate = Double.valueOf(json.get("mean_rate").toString());
2764                durationUnits = json.get("duration_units").toString();
2765                rateUnits = json.get("rate_units").toString();
2766            }
2767
2768            public double get15MinuteRate() {
2769                return m15Rate;
2770            }
2771
2772            public double get5MinuteRate() {
2773                return m5Rate;
2774            }
2775
2776            public double get1MinuteRate() {
2777                return m1Rate;
2778            }
2779
2780            public double getMeanRate() {
2781                return meanRate;
2782            }
2783
2784            public String getDurationUnits() {
2785                return durationUnits;
2786            }
2787
2788            public String getRateUnits() {
2789                return rateUnits;
2790            }
2791
2792            @Override
2793            public String toString() {
2794                StringBuilder sb = new StringBuilder(super.toString());
2795                sb.append("\n\t15 minute rate : ").append(m15Rate);
2796                sb.append("\n\t5 minute rate : ").append(m5Rate);
2797                sb.append("\n\t1 minute rate : ").append(m15Rate);
2798                sb.append("\n\tmean rate : ").append(meanRate);
2799                sb.append("\n\tduration units : ").append(durationUnits);
2800                sb.append("\n\trate units : ").append(rateUnits);
2801                return sb.toString();
2802            }
2803        }
2804
2805        public class Histogram {
2806            private double p999;
2807            private double p99;
2808            private double p98;
2809            private double p95;
2810            private double p75;
2811            private double p50;
2812            private double mean;
2813            private double min;
2814            private double max;
2815            private double stdDev;
2816            private long count;
2817
2818            public Histogram(JSONObject json) {
2819                p999 = Double.valueOf(json.get("p999").toString());
2820                p99 = Double.valueOf(json.get("p99").toString());
2821                p98 = Double.valueOf(json.get("p98").toString());
2822                p95 = Double.valueOf(json.get("p95").toString());
2823                p75 = Double.valueOf(json.get("p75").toString());
2824                p50 = Double.valueOf(json.get("p50").toString());
2825                mean = Double.valueOf(json.get("mean").toString());
2826                min = Double.valueOf(json.get("min").toString());
2827                max = Double.valueOf(json.get("max").toString());
2828                stdDev = Double.valueOf(json.get("stddev").toString());
2829                count = Long.valueOf(json.get("count").toString());
2830            }
2831
2832            public double get999thPercentile() {
2833                return p999;
2834            }
2835
2836            public double get99thPercentile() {
2837                return p99;
2838            }
2839
2840            public double get98thPercentile() {
2841                return p98;
2842            }
2843
2844            public double get95thPercentile() {
2845                return p95;
2846            }
2847
2848            public double get75thPercentile() {
2849                return p75;
2850            }
2851
2852            public double get50thPercentile() {
2853                return p50;
2854            }
2855
2856            public double getMean() {
2857                return mean;
2858            }
2859
2860            public double getMin() {
2861                return min;
2862            }
2863
2864            public double getMax() {
2865                return max;
2866            }
2867
2868            public double getStandardDeviation() {
2869                return stdDev;
2870            }
2871
2872            public long getCount() {
2873                return count;
2874            }
2875
2876            @Override
2877            public String toString() {
2878                StringBuilder sb = new StringBuilder();
2879                sb.append("\t999th percentile : ").append(p999);
2880                sb.append("\n\t99th percentile : ").append(p99);
2881                sb.append("\n\t98th percentile : ").append(p98);
2882                sb.append("\n\t95th percentile : ").append(p95);
2883                sb.append("\n\t75th percentile : ").append(p75);
2884                sb.append("\n\t50th percentile : ").append(p50);
2885                sb.append("\n\tmean : ").append(mean);
2886                sb.append("\n\tmax : ").append(max);
2887                sb.append("\n\tmin : ").append(min);
2888                sb.append("\n\tcount : ").append(count);
2889                sb.append("\n\tstandard deviation : ").append(stdDev);
2890                return sb.toString();
2891            }
2892        }
2893    }
2894
2895    /**
2896     * Return the Oozie metrics.  If null is returned, then try {@link #getInstrumentation()}.
2897     *
2898     * @return the Oozie metrics or null.
2899     * @throws OozieClientException throw if the metrics could not be retrieved.
2900     */
2901    public Metrics getMetrics() throws OozieClientException {
2902        return new GetMetrics().call();
2903    }
2904
2905    private class GetInstrumentation extends ClientCallable<Instrumentation> {
2906
2907        GetInstrumentation() {
2908            super("GET", RestConstants.ADMIN, RestConstants.ADMIN_INSTRUMENTATION_RESOURCE, prepareParams());
2909        }
2910
2911        @Override
2912        protected Instrumentation call(HttpURLConnection conn) throws IOException, OozieClientException {
2913            if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
2914                Reader reader = new InputStreamReader(conn.getInputStream());
2915                JSONObject json = (JSONObject) JSONValue.parse(reader);
2916                Instrumentation instrumentation = new Instrumentation(json);
2917                return instrumentation;
2918            }
2919            else if ((conn.getResponseCode() == HttpURLConnection.HTTP_UNAVAILABLE)) {
2920                // Use Metrics endpoint
2921                return null;
2922            }
2923            else {
2924                handleError(conn);
2925            }
2926            return null;
2927        }
2928    }
2929
2930    public class Instrumentation {
2931        private Map<String, Long> counters;
2932        private Map<String, Object> variables;
2933        private Map<String, Double> samplers;
2934        private Map<String, Timer> timers;
2935
2936        public Instrumentation(JSONObject json) {
2937            JSONArray jCounters = (JSONArray) json.get("counters");
2938            counters = new HashMap<String, Long>(jCounters.size());
2939            for (Object groupO : jCounters) {
2940                JSONObject group = (JSONObject) groupO;
2941                String groupName = group.get("group").toString() + ".";
2942                JSONArray data = (JSONArray) group.get("data");
2943                for (Object datO : data) {
2944                    JSONObject dat = (JSONObject) datO;
2945                    counters.put(groupName + dat.get("name").toString(), Long.valueOf(dat.get("value").toString()));
2946                }
2947            }
2948
2949            JSONArray jVariables = (JSONArray) json.get("variables");
2950            variables = new HashMap<String, Object>(jVariables.size());
2951            for (Object groupO : jVariables) {
2952                JSONObject group = (JSONObject) groupO;
2953                String groupName = group.get("group").toString() + ".";
2954                JSONArray data = (JSONArray) group.get("data");
2955                for (Object datO : data) {
2956                    JSONObject dat = (JSONObject) datO;
2957                    variables.put(groupName + dat.get("name").toString(), dat.get("value"));
2958                }
2959            }
2960
2961            JSONArray jSamplers = (JSONArray) json.get("samplers");
2962            samplers = new HashMap<String, Double>(jSamplers.size());
2963            for (Object groupO : jSamplers) {
2964                JSONObject group = (JSONObject) groupO;
2965                String groupName = group.get("group").toString() + ".";
2966                JSONArray data = (JSONArray) group.get("data");
2967                for (Object datO : data) {
2968                    JSONObject dat = (JSONObject) datO;
2969                    samplers.put(groupName + dat.get("name").toString(), Double.valueOf(dat.get("value").toString()));
2970                }
2971            }
2972
2973            JSONArray jTimers = (JSONArray) json.get("timers");
2974            timers = new HashMap<String, Timer>(jTimers.size());
2975            for (Object groupO : jTimers) {
2976                JSONObject group = (JSONObject) groupO;
2977                String groupName = group.get("group").toString() + ".";
2978                JSONArray data = (JSONArray) group.get("data");
2979                for (Object datO : data) {
2980                    JSONObject dat = (JSONObject) datO;
2981                    timers.put(groupName + dat.get("name").toString(), new Timer(dat));
2982                }
2983            }
2984        }
2985
2986        public class Timer {
2987            private double ownTimeStdDev;
2988            private long ownTimeAvg;
2989            private long ownMaxTime;
2990            private long ownMinTime;
2991            private double totalTimeStdDev;
2992            private long totalTimeAvg;
2993            private long totalMaxTime;
2994            private long totalMinTime;
2995            private long ticks;
2996
2997            public Timer(JSONObject json) {
2998                ownTimeStdDev = Double.valueOf(json.get("ownTimeStdDev").toString());
2999                ownTimeAvg = Long.valueOf(json.get("ownTimeAvg").toString());
3000                ownMaxTime = Long.valueOf(json.get("ownMaxTime").toString());
3001                ownMinTime = Long.valueOf(json.get("ownMinTime").toString());
3002                totalTimeStdDev = Double.valueOf(json.get("totalTimeStdDev").toString());
3003                totalTimeAvg = Long.valueOf(json.get("totalTimeAvg").toString());
3004                totalMaxTime = Long.valueOf(json.get("totalMaxTime").toString());
3005                totalMinTime = Long.valueOf(json.get("totalMinTime").toString());
3006                ticks = Long.valueOf(json.get("ticks").toString());
3007            }
3008
3009            public double getOwnTimeStandardDeviation() {
3010                return ownTimeStdDev;
3011            }
3012
3013            public long getOwnTimeAverage() {
3014                return ownTimeAvg;
3015            }
3016
3017            public long getOwnMaxTime() {
3018                return ownMaxTime;
3019            }
3020
3021            public long getOwnMinTime() {
3022                return ownMinTime;
3023            }
3024
3025            public double getTotalTimeStandardDeviation() {
3026                return totalTimeStdDev;
3027            }
3028
3029            public long getTotalTimeAverage() {
3030                return totalTimeAvg;
3031            }
3032
3033            public long getTotalMaxTime() {
3034                return totalMaxTime;
3035            }
3036
3037            public long getTotalMinTime() {
3038                return totalMinTime;
3039            }
3040
3041            public long getTicks() {
3042                return ticks;
3043            }
3044
3045            @Override
3046            public String toString() {
3047                StringBuilder sb = new StringBuilder();
3048                sb.append("\town time standard deviation : ").append(ownTimeStdDev);
3049                sb.append("\n\town average time : ").append(ownTimeAvg);
3050                sb.append("\n\town max time : ").append(ownMaxTime);
3051                sb.append("\n\town min time : ").append(ownMinTime);
3052                sb.append("\n\ttotal time standard deviation : ").append(totalTimeStdDev);
3053                sb.append("\n\ttotal average time : ").append(totalTimeAvg);
3054                sb.append("\n\ttotal max time : ").append(totalMaxTime);
3055                sb.append("\n\ttotal min time : ").append(totalMinTime);
3056                sb.append("\n\tticks : ").append(ticks);
3057                return sb.toString();
3058            }
3059        }
3060
3061        public Map<String, Long> getCounters() {
3062            return counters;
3063        }
3064
3065        public Map<String, Object> getVariables() {
3066            return variables;
3067        }
3068
3069        public Map<String, Double> getSamplers() {
3070            return samplers;
3071        }
3072
3073        public Map<String, Timer> getTimers() {
3074            return timers;
3075        }
3076    }
3077
3078    /**
3079     * Return the Oozie instrumentation.  If null is returned, then try {@link #getMetrics()}.
3080     *
3081     * @return the Oozie intstrumentation or null.
3082     * @throws OozieClientException throw if the intstrumentation could not be retrieved.
3083     */
3084    public Instrumentation getInstrumentation() throws OozieClientException {
3085        return new GetInstrumentation().call();
3086    }
3087
3088    /**
3089     * Check if the string is not null or not empty.
3090     *
3091     * @param str the string to check
3092     * @param name the name to present in the error message
3093     * @return string
3094     */
3095    public static String notEmpty(String str, String name) {
3096        if (str == null) {
3097            throw new IllegalArgumentException(name + " cannot be null");
3098        }
3099        if (str.length() == 0) {
3100            throw new IllegalArgumentException(name + " cannot be empty");
3101        }
3102        return str;
3103    }
3104
3105    /**
3106     * Check if the object is not null.
3107     *
3108     * @param <T> the type of the object
3109     * @param obj the object to check
3110     * @param name the name to present in the error message
3111     * @return string
3112     */
3113    public static <T> T notNull(T obj, String name) {
3114        if (obj == null) {
3115            throw new IllegalArgumentException(name + " cannot be null");
3116        }
3117        return obj;
3118    }
3119
3120}