001    /**
002     * Copyright (c) 2010 Yahoo! Inc. All rights reserved.
003     * Licensed under the Apache License, Version 2.0 (the "License");
004     * you may not use this file except in compliance with the License.
005     * You may obtain a copy of the License at
006     *
007     *   http://www.apache.org/licenses/LICENSE-2.0
008     *
009     *  Unless required by applicable law or agreed to in writing, software
010     *  distributed under the License is distributed on an "AS IS" BASIS,
011     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012     *  See the License for the specific language governing permissions and
013     *  limitations under the License. See accompanying LICENSE file.
014     */
015    package org.apache.oozie.client;
016    
017    import java.io.BufferedReader;
018    import java.io.IOException;
019    import java.io.InputStreamReader;
020    import java.io.OutputStream;
021    import java.io.Reader;
022    import java.net.HttpURLConnection;
023    import java.net.URL;
024    import java.net.URLEncoder;
025    import java.util.ArrayList;
026    import java.util.Collections;
027    import java.util.Enumeration;
028    import java.util.HashMap;
029    import java.util.Iterator;
030    import java.util.List;
031    import java.util.Map;
032    import java.util.Properties;
033    import java.util.concurrent.Callable;
034    
035    import javax.xml.parsers.DocumentBuilderFactory;
036    import javax.xml.transform.Transformer;
037    import javax.xml.transform.TransformerFactory;
038    import javax.xml.transform.dom.DOMSource;
039    import javax.xml.transform.stream.StreamResult;
040    
041    import org.apache.oozie.BuildInfo;
042    import org.apache.oozie.client.rest.JsonTags;
043    import org.apache.oozie.client.rest.JsonToBean;
044    import org.apache.oozie.client.rest.RestConstants;
045    import org.json.simple.JSONArray;
046    import org.json.simple.JSONObject;
047    import org.json.simple.JSONValue;
048    import org.w3c.dom.Document;
049    import org.w3c.dom.Element;
050    
051    /**
052     * Client API to submit and manage Oozie workflow jobs against an Oozie intance.
053     * <p/>
054     * This class is thread safe.
055     * <p/>
056     * Syntax for filter for the {@link #getJobsInfo(String)} {@link #getJobsInfo(String, int, int)} methods:
057     * <code>[NAME=VALUE][;NAME=VALUE]*</code>.
058     * <p/>
059     * Valid filter names are:
060     * <p/>
061     * <ul/>
062     * <li>name: the workflow application name from the workflow definition.</li>
063     * <li>user: the user that submitted the job.</li>
064     * <li>group: the group for the job.</li>
065     * <li>status: the status of the job.</li>
066     * </ul>
067     * <p/>
068     * 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
069     * name. Multiple values must be specified as different name value pairs.
070     */
071    public class OozieClient {
072    
073        public static final long WS_PROTOCOL_VERSION_0 = 0;
074    
075        public static final long WS_PROTOCOL_VERSION = 1;
076    
077        public static final String USER_NAME = "user.name";
078    
079        public static final String GROUP_NAME = "group.name";
080    
081        public static final String APP_PATH = "oozie.wf.application.path";
082    
083        public static final String COORDINATOR_APP_PATH = "oozie.coord.application.path";
084    
085        public static final String EXTERNAL_ID = "oozie.wf.external.id";
086    
087        public static final String WORKFLOW_NOTIFICATION_URL = "oozie.wf.workflow.notification.url";
088    
089        public static final String ACTION_NOTIFICATION_URL = "oozie.wf.action.notification.url";
090    
091        public static final String COORD_ACTION_NOTIFICATION_URL = "oozie.coord.action.notification.url";
092    
093        public static final String RERUN_SKIP_NODES = "oozie.wf.rerun.skip.nodes";
094    
095        public static final String LOG_TOKEN = "oozie.wf.log.token";
096    
097        public static final String ACTION_MAX_RETRIES = "oozie.wf.action.max.retries";
098    
099        public static final String ACTION_RETRY_INTERVAL = "oozie.wf.action.retry.interval";
100    
101        public static final String FILTER_USER = "user";
102    
103        public static final String FILTER_GROUP = "group";
104    
105        public static final String FILTER_NAME = "name";
106    
107        public static final String FILTER_STATUS = "status";
108    
109        public static final String CHANGE_VALUE_ENDTIME = "endtime";
110    
111        public static final String CHANGE_VALUE_PAUSETIME = "pausetime";
112    
113        public static final String CHANGE_VALUE_CONCURRENCY = "concurrency";
114        
115        public static final String LIBPATH = "oozie.libpath";
116    
117        public static final String USE_SYSTEM_LIBPATH = "oozie.use.system.libpath";
118    
119        public static enum SYSTEM_MODE {
120            NORMAL, NOWEBSERVICE, SAFEMODE
121        };
122        
123        /**
124         * debugMode =0 means no debugging. > 0 means debugging on.
125         */
126        public int debugMode = 0;
127    
128        private String baseUrl;
129        private String protocolUrl;
130        private boolean validatedVersion = false;
131        private Map<String, String> headers = new HashMap<String, String>();
132    
133    
134    
135        protected OozieClient() {
136        }
137    
138        /**
139         * Create a Workflow client instance.
140         *
141         * @param oozieUrl URL of the Oozie instance it will interact with.
142         */
143        public OozieClient(String oozieUrl) {
144            this.baseUrl = notEmpty(oozieUrl, "oozieUrl");
145            if (!this.baseUrl.endsWith("/")) {
146                this.baseUrl += "/";
147            }
148        }
149    
150        /**
151         * Return the Oozie URL of the workflow client instance.
152         * <p/>
153         * This URL is the base URL fo the Oozie system, with not protocol versioning.
154         *
155         * @return the Oozie URL of the workflow client instance.
156         */
157        public String getOozieUrl() {
158            return baseUrl;
159        }
160    
161        /**
162         * Return the Oozie URL used by the client and server for WS communications.
163         * <p/>
164         * This URL is the original URL plus the versioning element path.
165         *
166         * @return the Oozie URL used by the client and server for communication.
167         * @throws OozieClientException thrown in the client and the server are not protocol compatible.
168         */
169        public String getProtocolUrl() throws OozieClientException {
170            validateWSVersion();
171            return protocolUrl;
172        }
173    
174        /**
175         * @return current debug Mode
176         */
177        public int getDebugMode() {
178            return debugMode;
179        }
180    
181        /**
182         * Set debug mode.
183         *
184         * @param debugMode : 0 means no debugging. > 0 means debugging
185         */
186        public void setDebugMode(int debugMode) {
187            this.debugMode = debugMode;
188        }
189    
190        /**
191         * Validate that the Oozie client and server instances are protocol compatible.
192         *
193         * @throws OozieClientException thrown in the client and the server are not protocol compatible.
194         */
195        public synchronized void validateWSVersion() throws OozieClientException {
196            if (!validatedVersion) {
197                try {
198                    URL url = new URL(baseUrl + RestConstants.VERSIONS);
199                    HttpURLConnection conn = createConnection(url, "GET");
200                    if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
201                        JSONArray array = (JSONArray) JSONValue.parse(new InputStreamReader(conn.getInputStream()));
202                        if (array == null) {
203                            throw new OozieClientException("HTTP error", "no response message");
204                        }
205                        if (!array.contains(WS_PROTOCOL_VERSION) && !array.contains(WS_PROTOCOL_VERSION_0)) {
206                            StringBuilder msg = new StringBuilder();
207                            msg.append("Supported version [").append(WS_PROTOCOL_VERSION).append(
208                            "] or less, Unsupported versions[");
209                            String separator = "";
210                            for (Object version : array) {
211                                msg.append(separator).append(version);
212                            }
213                            msg.append("]");
214                            throw new OozieClientException(OozieClientException.UNSUPPORTED_VERSION, msg.toString());
215                        }
216                        if (array.contains(WS_PROTOCOL_VERSION)) {
217                            protocolUrl = baseUrl + "v" + WS_PROTOCOL_VERSION + "/";
218                        }
219                        else {
220                            if (array.contains(WS_PROTOCOL_VERSION_0)) {
221                                protocolUrl = baseUrl + "v" + WS_PROTOCOL_VERSION_0 + "/";
222                            }
223                        }
224                    }
225                    else {
226                        handleError(conn);
227                    }
228                }
229                catch (IOException ex) {
230                    throw new OozieClientException(OozieClientException.IO_ERROR, ex);
231                }
232                validatedVersion = true;
233            }
234        }
235    
236        /**
237         * Create an empty configuration with just the {@link #USER_NAME} set to the JVM user name.
238         *
239         * @return an empty configuration.
240         */
241        public Properties createConfiguration() {
242            Properties conf = new Properties();
243            conf.setProperty(USER_NAME, System.getProperty("user.name"));
244            return conf;
245        }
246    
247        /**
248         * Set a HTTP header to be used in the WS requests by the workflow instance.
249         *
250         * @param name header name.
251         * @param value header value.
252         */
253        public void setHeader(String name, String value) {
254            headers.put(notEmpty(name, "name"), notNull(value, "value"));
255        }
256    
257        /**
258         * Get the value of a set HTTP header from the workflow instance.
259         *
260         * @param name header name.
261         * @return header value, <code>null</code> if not set.
262         */
263        public String getHeader(String name) {
264            return headers.get(notEmpty(name, "name"));
265        }
266    
267        /**
268         * Get the set HTTP header
269         *
270         * @return map of header key and value
271         */
272        public Map<String, String> getHeaders() {
273            return headers;
274        }
275    
276        /**
277         * Remove a HTTP header from the workflow client instance.
278         *
279         * @param name header name.
280         */
281        public void removeHeader(String name) {
282            headers.remove(notEmpty(name, "name"));
283        }
284    
285        /**
286         * Return an iterator with all the header names set in the workflow instance.
287         *
288         * @return header names.
289         */
290        public Iterator<String> getHeaderNames() {
291            return Collections.unmodifiableMap(headers).keySet().iterator();
292        }
293    
294        private URL createURL(String collection, String resource, Map<String, String> parameters) throws IOException,
295        OozieClientException {
296            validateWSVersion();
297            StringBuilder sb = new StringBuilder();
298            sb.append(protocolUrl).append(collection);
299            if (resource != null && resource.length() > 0) {
300                sb.append("/").append(resource);
301            }
302            if (parameters.size() > 0) {
303                String separator = "?";
304                for (Map.Entry<String, String> param : parameters.entrySet()) {
305                    if (param.getValue() != null) {
306                        sb.append(separator).append(URLEncoder.encode(param.getKey(), "UTF-8")).append("=").append(
307                                URLEncoder.encode(param.getValue(), "UTF-8"));
308                        separator = "&";
309                    }
310                }
311            }
312            return new URL(sb.toString());
313        }
314    
315        private boolean validateCommand(String url) {
316            {
317                if (protocolUrl.contains(baseUrl + "v0")) {
318                    if (url.contains("dryrun") || url.contains("jobtype=c") || url.contains("systemmode")) {
319                        return false;
320                    }
321                }
322            }
323            return true;
324        }
325    
326        /**
327         * Create http connection to oozie server.
328         *
329         * @param url
330         * @param method
331         * @return connection
332         * @throws IOException
333         * @throws OozieClientException
334         */
335        protected HttpURLConnection createConnection(URL url, String method) throws IOException, OozieClientException {
336            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
337            conn.setRequestMethod(method);
338            if (method.equals("POST") || method.equals("PUT")) {
339                conn.setDoOutput(true);
340            }
341            for (Map.Entry<String, String> header : headers.entrySet()) {
342                conn.setRequestProperty(header.getKey(), header.getValue());
343            }
344            return conn;
345        }
346    
347        protected abstract class ClientCallable<T> implements Callable<T> {
348            private String method;
349            private String collection;
350            private String resource;
351            private Map<String, String> params;
352    
353            public ClientCallable(String method, String collection, String resource, Map<String, String> params) {
354                this.method = method;
355                this.collection = collection;
356                this.resource = resource;
357                this.params = params;
358            }
359    
360            public T call() throws OozieClientException {
361                try {
362                    URL url = createURL(collection, resource, params);
363                    if (validateCommand(url.toString())) {
364                        if (getDebugMode() > 0) {
365                            System.out.println("Connection URL:[" + url + "]");
366                        }
367                        HttpURLConnection conn = createConnection(url, method);
368                        return call(conn);
369                    }
370                    else {
371                        System.out
372                        .println("Option not supported in target server. Supported only on Oozie-2.0 or greater. Use 'oozie help' for details");
373                        throw new OozieClientException(OozieClientException.UNSUPPORTED_VERSION, new Exception());
374                    }
375                }
376                catch (IOException ex) {
377                    throw new OozieClientException(OozieClientException.IO_ERROR, ex);
378                }
379    
380            }
381    
382            protected abstract T call(HttpURLConnection conn) throws IOException, OozieClientException;
383        }
384    
385        static void handleError(HttpURLConnection conn) throws IOException, OozieClientException {
386            int status = conn.getResponseCode();
387            String error = conn.getHeaderField(RestConstants.OOZIE_ERROR_CODE);
388            String message = conn.getHeaderField(RestConstants.OOZIE_ERROR_MESSAGE);
389    
390            if (error == null) {
391                error = "HTTP error code: " + status;
392            }
393    
394            if (message == null) {
395                message = conn.getResponseMessage();
396            }
397            throw new OozieClientException(error, message);
398        }
399    
400        static Map<String, String> prepareParams(String... params) {
401            Map<String, String> map = new HashMap<String, String>();
402            for (int i = 0; i < params.length; i = i + 2) {
403                map.put(params[i], params[i + 1]);
404            }
405            return map;
406        }
407    
408        public void writeToXml(Properties props, OutputStream out) throws IOException {
409            try {
410                Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
411                Element conf = doc.createElement("configuration");
412                doc.appendChild(conf);
413                conf.appendChild(doc.createTextNode("\n"));
414                for (Enumeration e = props.keys(); e.hasMoreElements();) {
415                    String name = (String) e.nextElement();
416                    Object object = props.get(name);
417                    String value;
418                    if (object instanceof String) {
419                        value = (String) object;
420                    }
421                    else {
422                        continue;
423                    }
424                    Element propNode = doc.createElement("property");
425                    conf.appendChild(propNode);
426    
427                    Element nameNode = doc.createElement("name");
428                    nameNode.appendChild(doc.createTextNode(name.trim()));
429                    propNode.appendChild(nameNode);
430    
431                    Element valueNode = doc.createElement("value");
432                    valueNode.appendChild(doc.createTextNode(value.trim()));
433                    propNode.appendChild(valueNode);
434    
435                    conf.appendChild(doc.createTextNode("\n"));
436                }
437    
438                DOMSource source = new DOMSource(doc);
439                StreamResult result = new StreamResult(out);
440                TransformerFactory transFactory = TransformerFactory.newInstance();
441                Transformer transformer = transFactory.newTransformer();
442                transformer.transform(source, result);
443            }
444            catch (Exception e) {
445                throw new IOException(e);
446            }
447        }
448    
449        private class JobSubmit extends ClientCallable<String> {
450            private Properties conf;
451    
452            JobSubmit(Properties conf, boolean start) {
453                super("POST", RestConstants.JOBS, "", (start) ? prepareParams(RestConstants.ACTION_PARAM,
454                        RestConstants.JOB_ACTION_START) : prepareParams());
455                this.conf = notNull(conf, "conf");
456            }
457    
458            JobSubmit(String jobId, Properties conf) {
459                super("PUT", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.ACTION_PARAM,
460                        RestConstants.JOB_ACTION_RERUN));
461                this.conf = notNull(conf, "conf");
462            }
463    
464            public JobSubmit(Properties conf, String jobActionDryrun) {
465                super("POST", RestConstants.JOBS, "", prepareParams(RestConstants.ACTION_PARAM,
466                        RestConstants.JOB_ACTION_DRYRUN));
467                this.conf = notNull(conf, "conf");
468            }
469    
470            @Override
471            protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
472                conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
473                writeToXml(conf, conn.getOutputStream());
474                if (conn.getResponseCode() == HttpURLConnection.HTTP_CREATED) {
475                    JSONObject json = (JSONObject) JSONValue.parse(new InputStreamReader(conn.getInputStream()));
476                    return (String) json.get(JsonTags.JOB_ID);
477                }
478                if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
479                    handleError(conn);
480                }
481                return null;
482            }
483        }
484    
485        /**
486         * Submit a workflow job.
487         *
488         * @param conf job configuration.
489         * @return the job Id.
490         * @throws OozieClientException thrown if the job could not be submitted.
491         */
492        public String submit(Properties conf) throws OozieClientException {
493            return (new JobSubmit(conf, false)).call();
494        }
495    
496        private class JobAction extends ClientCallable<Void> {
497    
498            JobAction(String jobId, String action) {
499                super("PUT", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.ACTION_PARAM, action));
500            }
501    
502            JobAction(String jobId, String action, String params) {
503                super("PUT", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.ACTION_PARAM, action,
504                        RestConstants.JOB_CHANGE_VALUE, params));
505            }
506    
507            @Override
508            protected Void call(HttpURLConnection conn) throws IOException, OozieClientException {
509                if (!(conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
510                    handleError(conn);
511                }
512                return null;
513            }
514        }
515    
516        /**
517         * dryrun for a given job
518         *
519         * @param conf Job configuration.
520         */
521        public String dryrun(Properties conf) throws OozieClientException {
522            return new JobSubmit(conf, RestConstants.JOB_ACTION_DRYRUN).call();
523        }
524    
525        /**
526         * Start a workflow job.
527         *
528         * @param jobId job Id.
529         * @throws OozieClientException thrown if the job could not be started.
530         */
531        public void start(String jobId) throws OozieClientException {
532            new JobAction(jobId, RestConstants.JOB_ACTION_START).call();
533        }
534    
535        /**
536         * Submit and start a workflow job.
537         *
538         * @param conf job configuration.
539         * @return the job Id.
540         * @throws OozieClientException thrown if the job could not be submitted.
541         */
542        public String run(Properties conf) throws OozieClientException {
543            return (new JobSubmit(conf, true)).call();
544        }
545    
546        /**
547         * Rerun a workflow job.
548         *
549         * @param jobId job Id to rerun.
550         * @param conf configuration information for the rerun.
551         * @throws OozieClientException thrown if the job could not be started.
552         */
553        public void reRun(String jobId, Properties conf) throws OozieClientException {
554            new JobSubmit(jobId, conf).call();
555        }
556    
557        /**
558         * Suspend a workflow job.
559         *
560         * @param jobId job Id.
561         * @throws OozieClientException thrown if the job could not be suspended.
562         */
563        public void suspend(String jobId) throws OozieClientException {
564            new JobAction(jobId, RestConstants.JOB_ACTION_SUSPEND).call();
565        }
566    
567        /**
568         * Resume a workflow job.
569         *
570         * @param jobId job Id.
571         * @throws OozieClientException thrown if the job could not be resume.
572         */
573        public void resume(String jobId) throws OozieClientException {
574            new JobAction(jobId, RestConstants.JOB_ACTION_RESUME).call();
575        }
576    
577        /**
578         * Kill a workflow job.
579         *
580         * @param jobId job Id.
581         * @throws OozieClientException thrown if the job could not be killed.
582         */
583        public void kill(String jobId) throws OozieClientException {
584            new JobAction(jobId, RestConstants.JOB_ACTION_KILL).call();
585        }
586    
587        /**
588         * Change a coordinator job.
589         *
590         * @param jobId job Id.
591         * @param changeValue change value.
592         * @throws OozieClientException thrown if the job could not be changed.
593         */
594        public void change(String jobId, String changeValue) throws OozieClientException {
595            new JobAction(jobId, RestConstants.JOB_ACTION_CHANGE, changeValue).call();
596        }
597    
598        private class JobInfo extends ClientCallable<WorkflowJob> {
599    
600            JobInfo(String jobId, int start, int len) {
601                super("GET", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.JOB_SHOW_PARAM,
602                        RestConstants.JOB_SHOW_INFO, RestConstants.OFFSET_PARAM, Integer.toString(start),
603                        RestConstants.LEN_PARAM, Integer.toString(len)));
604            }
605    
606            @Override
607            protected WorkflowJob call(HttpURLConnection conn) throws IOException, OozieClientException {
608                if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
609                    Reader reader = new InputStreamReader(conn.getInputStream());
610                    JSONObject json = (JSONObject) JSONValue.parse(reader);
611                    return JsonToBean.createWorkflowJob(json);
612                }
613                else {
614                    handleError(conn);
615                }
616                return null;
617            }
618        }
619    
620        private class WorkflowActionInfo extends ClientCallable<WorkflowAction> {
621            WorkflowActionInfo(String actionId) {
622                super("GET", RestConstants.JOB, notEmpty(actionId, "id"), prepareParams(RestConstants.JOB_SHOW_PARAM,
623                        RestConstants.JOB_SHOW_INFO));
624            }
625    
626            @Override
627            protected WorkflowAction call(HttpURLConnection conn) throws IOException, OozieClientException {
628                if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
629                    Reader reader = new InputStreamReader(conn.getInputStream());
630                    JSONObject json = (JSONObject) JSONValue.parse(reader);
631                    return JsonToBean.createWorkflowAction(json);
632                }
633                else {
634                    handleError(conn);
635                }
636                return null;
637            }
638        }
639    
640        /**
641         * Get the info of a workflow job.
642         *
643         * @param jobId job Id.
644         * @return the job info.
645         * @throws OozieClientException thrown if the job info could not be retrieved.
646         */
647        public WorkflowJob getJobInfo(String jobId) throws OozieClientException {
648            return getJobInfo(jobId, 0, 0);
649        }
650    
651        /**
652         * Get the info of a workflow job and subset actions.
653         *
654         * @param jobId job Id.
655         * @param start starting index in the list of actions belonging to the job
656         * @param len number of actions to be returned
657         * @return the job info.
658         * @throws OozieClientException thrown if the job info could not be retrieved.
659         */
660        public WorkflowJob getJobInfo(String jobId, int start, int len) throws OozieClientException {
661            return new JobInfo(jobId, start, len).call();
662        }
663    
664        /**
665         * Get the info of a workflow action.
666         *
667         * @param actionId Id.
668         * @return the workflow action info.
669         * @throws OozieClientException thrown if the job info could not be retrieved.
670         */
671        public WorkflowAction getWorkflowActionInfo(String actionId) throws OozieClientException {
672            return new WorkflowActionInfo(actionId).call();
673        }
674    
675        /**
676         * Get the log of a workflow job.
677         *
678         * @param jobId job Id.
679         * @return the job log.
680         * @throws OozieClientException thrown if the job info could not be retrieved.
681         */
682        public String getJobLog(String jobId) throws OozieClientException {
683            return new JobLog(jobId).call();
684        }
685    
686        private class JobLog extends JobMetadata {
687    
688            JobLog(String jobId) {
689                super(jobId, RestConstants.JOB_SHOW_LOG);
690            }
691        }
692    
693        /**
694         * Get the definition of a workflow job.
695         *
696         * @param jobId job Id.
697         * @return the job log.
698         * @throws OozieClientException thrown if the job info could not be retrieved.
699         */
700        public String getJobDefinition(String jobId) throws OozieClientException {
701            return new JobDefinition(jobId).call();
702        }
703    
704        private class JobDefinition extends JobMetadata {
705    
706            JobDefinition(String jobId) {
707                super(jobId, RestConstants.JOB_SHOW_DEFINITION);
708            }
709        }
710    
711        private class JobMetadata extends ClientCallable<String> {
712    
713            JobMetadata(String jobId, String metaType) {
714                super("GET", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.JOB_SHOW_PARAM,
715                        metaType));
716            }
717    
718            @Override
719            protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
720                if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
721    
722                    String output = getReaderAsString(new InputStreamReader(conn.getInputStream()), -1);
723                    return output;
724                }
725                else {
726                    handleError(conn);
727                }
728                return null;
729            }
730    
731            /**
732             * Return a reader as string.
733             * <p/>
734             *
735             * @param reader reader to read into a string.
736             * @param maxLen max content length allowed, if -1 there is no limit.
737             * @return the reader content.
738             * @throws IOException thrown if the resource could not be read.
739             */
740            private String getReaderAsString(Reader reader, int maxLen) throws IOException {
741                if (reader == null) {
742                    throw new IllegalArgumentException("reader cannot be null");
743                }
744    
745                StringBuffer sb = new StringBuffer();
746                char[] buffer = new char[2048];
747                int read;
748                int count = 0;
749                while ((read = reader.read(buffer)) > -1) {
750                    count += read;
751    
752                    // read up to maxLen chars;
753                    if ((maxLen > -1) && (count > maxLen)) {
754                        break;
755                    }
756                    sb.append(buffer, 0, read);
757                }
758                reader.close();
759                return sb.toString();
760            }
761        }
762    
763        private class CoordJobInfo extends ClientCallable<CoordinatorJob> {
764    
765            CoordJobInfo(String jobId, int start, int len) {
766                super("GET", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.JOB_SHOW_PARAM,
767                        RestConstants.JOB_SHOW_INFO, RestConstants.OFFSET_PARAM, Integer.toString(start),
768                        RestConstants.LEN_PARAM, Integer.toString(len)));
769            }
770    
771            @Override
772            protected CoordinatorJob call(HttpURLConnection conn) throws IOException, OozieClientException {
773                if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
774                    Reader reader = new InputStreamReader(conn.getInputStream());
775                    JSONObject json = (JSONObject) JSONValue.parse(reader);
776                    return JsonToBean.createCoordinatorJob(json);
777                }
778                else {
779                    handleError(conn);
780                }
781                return null;
782            }
783        }
784    
785        private class CoordActionInfo extends ClientCallable<CoordinatorAction> {
786            CoordActionInfo(String actionId) {
787                super("GET", RestConstants.JOB, notEmpty(actionId, "id"), prepareParams(RestConstants.JOB_SHOW_PARAM,
788                        RestConstants.JOB_SHOW_INFO));
789            }
790    
791            @Override
792            protected CoordinatorAction call(HttpURLConnection conn) throws IOException, OozieClientException {
793                if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
794                    Reader reader = new InputStreamReader(conn.getInputStream());
795                    JSONObject json = (JSONObject) JSONValue.parse(reader);
796                    return JsonToBean.createCoordinatorAction(json);
797                }
798                else {
799                    handleError(conn);
800                }
801                return null;
802            }
803        }
804    
805        /**
806         * Get the info of a coordinator job.
807         *
808         * @param jobId job Id.
809         * @return the job info.
810         * @throws OozieClientException thrown if the job info could not be retrieved.
811         */
812        public CoordinatorJob getCoordJobInfo(String jobId) throws OozieClientException {
813            return new CoordJobInfo(jobId, 0, 0).call();
814        }
815    
816        /**
817         * Get the info of a coordinator job and subset actions.
818         *
819         * @param jobId job Id.
820         * @param start starting index in the list of actions belonging to the job
821         * @param len number of actions to be returned
822         * @return the job info.
823         * @throws OozieClientException thrown if the job info could not be retrieved.
824         */
825        public CoordinatorJob getCoordJobInfo(String jobId, int start, int len) throws OozieClientException {
826            return new CoordJobInfo(jobId, start, len).call();
827        }
828    
829        /**
830         * Get the info of a coordinator action.
831         *
832         * @param actionId Id.
833         * @return the coordinator action info.
834         * @throws OozieClientException thrown if the job info could not be retrieved.
835         */
836        public CoordinatorAction getCoordActionInfo(String actionId) throws OozieClientException {
837            return new CoordActionInfo(actionId).call();
838        }
839    
840        private class JobsStatus extends ClientCallable<List<WorkflowJob>> {
841    
842            JobsStatus(String filter, int start, int len) {
843                super("GET", RestConstants.JOBS, "", prepareParams(RestConstants.JOBS_FILTER_PARAM, filter,
844                        RestConstants.JOBTYPE_PARAM, "wf", RestConstants.OFFSET_PARAM, Integer.toString(start),
845                        RestConstants.LEN_PARAM, Integer.toString(len)));
846            }
847    
848            @Override
849            @SuppressWarnings("unchecked")
850            protected List<WorkflowJob> call(HttpURLConnection conn) throws IOException, OozieClientException {
851                conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
852                if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
853                    Reader reader = new InputStreamReader(conn.getInputStream());
854                    JSONObject json = (JSONObject) JSONValue.parse(reader);
855                    JSONArray workflows = (JSONArray) json.get(JsonTags.WORKFLOWS_JOBS);
856                    if (workflows == null) {
857                        workflows = new JSONArray();
858                    }
859                    return JsonToBean.createWorkflowJobList(workflows);
860                }
861                else {
862                    handleError(conn);
863                }
864                return null;
865            }
866        }
867    
868        private class CoordJobsStatus extends ClientCallable<List<CoordinatorJob>> {
869    
870            CoordJobsStatus(String filter, int start, int len) {
871                super("GET", RestConstants.JOBS, "", prepareParams(RestConstants.JOBS_FILTER_PARAM, filter,
872                        RestConstants.JOBTYPE_PARAM, "coord", RestConstants.OFFSET_PARAM, Integer.toString(start),
873                        RestConstants.LEN_PARAM, Integer.toString(len)));
874            }
875    
876            @Override
877            @SuppressWarnings("unchecked")
878            protected List<CoordinatorJob> call(HttpURLConnection conn) throws IOException, OozieClientException {
879                conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
880                if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
881                    Reader reader = new InputStreamReader(conn.getInputStream());
882                    JSONObject json = (JSONObject) JSONValue.parse(reader);
883                    JSONArray jobs = (JSONArray) json.get(JsonTags.COORDINATOR_JOBS);
884                    if (jobs == null) {
885                        jobs = new JSONArray();
886                    }
887                    return JsonToBean.createCoordinatorJobList(jobs);
888                }
889                else {
890                    handleError(conn);
891                }
892                return null;
893            }
894        }
895    
896        private class CoordRerun extends ClientCallable<List<CoordinatorAction>> {
897    
898            CoordRerun(String jobId, String rerunType, String scope, boolean refresh, boolean noCleanup) {
899                super("PUT", RestConstants.JOB, notEmpty(jobId, "jobId"), prepareParams(RestConstants.ACTION_PARAM,
900                        RestConstants.JOB_COORD_ACTION_RERUN, RestConstants.JOB_COORD_RERUN_TYPE_PARAM, rerunType,
901                        RestConstants.JOB_COORD_RERUN_SCOPE_PARAM, scope, RestConstants.JOB_COORD_RERUN_REFRESH_PARAM,
902                        Boolean.toString(refresh), RestConstants.JOB_COORD_RERUN_NOCLEANUP_PARAM, Boolean
903                        .toString(noCleanup)));
904            }
905    
906            @Override
907            protected List<CoordinatorAction> call(HttpURLConnection conn) throws IOException, OozieClientException {
908                conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
909                if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
910                    Reader reader = new InputStreamReader(conn.getInputStream());
911                    JSONObject json = (JSONObject) JSONValue.parse(reader);
912                    JSONArray coordActions = (JSONArray) json.get(JsonTags.COORDINATOR_ACTIONS);
913                    return JsonToBean.createCoordinatorActionList(coordActions);
914                }
915                else {
916                    handleError(conn);
917                }
918                return null;
919            }
920        }
921    
922        /**
923         * Rerun coordinator actions.
924         *
925         * @param jobId coordinator jobId
926         * @param rerunType rerun type 'date' if -date is used, 'action-id' if -action is used
927         * @param scope rerun scope for date or actionIds
928         * @param refresh true if -refresh is given in command option
929         * @param noCleanup true if -nocleanup is given in command option
930         * @throws OozieClientException
931         */
932        public List<CoordinatorAction> reRunCoord(String jobId, String rerunType, String scope, boolean refresh,
933                boolean noCleanup) throws OozieClientException {
934            return new CoordRerun(jobId, rerunType, scope, refresh, noCleanup).call();
935        }
936    
937        /**
938         * Return the info of the workflow jobs that match the filter.
939         *
940         * @param filter job filter. Refer to the {@link OozieClient} for the filter syntax.
941         * @param start jobs offset, base 1.
942         * @param len number of jobs to return.
943         * @return a list with the workflow jobs info, without node details.
944         * @throws OozieClientException thrown if the jobs info could not be retrieved.
945         */
946        public List<WorkflowJob> getJobsInfo(String filter, int start, int len) throws OozieClientException {
947            return new JobsStatus(filter, start, len).call();
948        }
949    
950        /**
951         * Return the info of the workflow jobs that match the filter.
952         * <p/>
953         * It returns the first 100 jobs that match the filter.
954         *
955         * @param filter job filter. Refer to the {@link OozieClient} for the filter syntax.
956         * @return a list with the workflow jobs info, without node details.
957         * @throws OozieClientException thrown if the jobs info could not be retrieved.
958         */
959        public List<WorkflowJob> getJobsInfo(String filter) throws OozieClientException {
960            return getJobsInfo(filter, 1, 50);
961        }
962    
963        /**
964         * Print sla info about coordinator and workflow jobs and actions.
965         *
966         * @param start starting offset
967         * @param len number of results
968         * @return
969         * @throws OozieClientException
970         */
971        public void getSlaInfo(int start, int len) throws OozieClientException {
972            new SlaInfo(start, len).call();
973        }
974    
975        private class SlaInfo extends ClientCallable<Void> {
976    
977            SlaInfo(int start, int len) {
978                super("GET", RestConstants.SLA, "", prepareParams(RestConstants.SLA_GT_SEQUENCE_ID,
979                        Integer.toString(start), RestConstants.MAX_EVENTS, Integer.toString(len)));
980            }
981    
982            @Override
983            @SuppressWarnings("unchecked")
984            protected Void call(HttpURLConnection conn) throws IOException, OozieClientException {
985                conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE);
986                if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
987                    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
988                    String line = null;
989                    while ((line = br.readLine()) != null) {
990                        System.out.println(line);
991                    }
992                }
993                else {
994                    handleError(conn);
995                }
996                return null;
997            }
998        }
999    
1000        private class JobIdAction extends ClientCallable<String> {
1001    
1002            JobIdAction(String externalId) {
1003                super("GET", RestConstants.JOBS, "", prepareParams(RestConstants.JOBTYPE_PARAM, "wf",
1004                        RestConstants.JOBS_EXTERNAL_ID_PARAM, externalId));
1005            }
1006    
1007            @Override
1008            @SuppressWarnings("unchecked")
1009            protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
1010                if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1011                    Reader reader = new InputStreamReader(conn.getInputStream());
1012                    JSONObject json = (JSONObject) JSONValue.parse(reader);
1013                    return (String) json.get(JsonTags.JOB_ID);
1014                }
1015                else {
1016                    handleError(conn);
1017                }
1018                return null;
1019            }
1020        }
1021    
1022        /**
1023         * Return the workflow job Id for an external Id.
1024         * <p/>
1025         * The external Id must have provided at job creation time.
1026         *
1027         * @param externalId external Id given at job creation time.
1028         * @return the workflow job Id for an external Id, <code>null</code> if none.
1029         * @throws OozieClientException thrown if the operation could not be done.
1030         */
1031        public String getJobId(String externalId) throws OozieClientException {
1032            return new JobIdAction(externalId).call();
1033        }
1034    
1035        private class SetSystemMode extends ClientCallable<Void> {
1036    
1037            public SetSystemMode(SYSTEM_MODE status) {
1038                super("PUT", RestConstants.ADMIN, RestConstants.ADMIN_STATUS_RESOURCE, prepareParams(
1039                        RestConstants.ADMIN_SYSTEM_MODE_PARAM, status + ""));
1040            }
1041    
1042            @Override
1043            public Void call(HttpURLConnection conn) throws IOException, OozieClientException {
1044                if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
1045                    handleError(conn);
1046                }
1047                return null;
1048            }
1049        }
1050    
1051        /**
1052         * Enable or disable safe mode. Used by OozieCLI. In safe mode, Oozie would not accept any commands except status
1053         * command to change and view the safe mode status.
1054         *
1055         * @param status true to enable safe mode, false to disable safe mode.
1056         * @throws OozieClientException if it fails to set the safe mode status.
1057         */
1058        public void setSystemMode(SYSTEM_MODE status) throws OozieClientException {
1059            new SetSystemMode(status).call();
1060        }
1061    
1062        private class GetSystemMode extends ClientCallable<SYSTEM_MODE> {
1063    
1064            GetSystemMode() {
1065                super("GET", RestConstants.ADMIN, RestConstants.ADMIN_STATUS_RESOURCE, prepareParams());
1066            }
1067    
1068            @Override
1069            protected SYSTEM_MODE call(HttpURLConnection conn) throws IOException, OozieClientException {
1070                if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1071                    Reader reader = new InputStreamReader(conn.getInputStream());
1072                    JSONObject json = (JSONObject) JSONValue.parse(reader);
1073                    return SYSTEM_MODE.valueOf((String) json.get(JsonTags.OOZIE_SYSTEM_MODE));
1074                }
1075                else {
1076                    handleError(conn);
1077                }
1078                return SYSTEM_MODE.NORMAL;
1079            }
1080        }
1081    
1082        /**
1083         * Returns if Oozie is in safe mode or not.
1084         *
1085         * @return true if safe mode is ON<br>
1086         *         false if safe mode is OFF
1087         * @throws OozieClientException throw if it could not obtain the safe mode status.
1088         */
1089        /*
1090         * public boolean isInSafeMode() throws OozieClientException { return new GetSafeMode().call(); }
1091         */
1092        public SYSTEM_MODE getSystemMode() throws OozieClientException {
1093            return new GetSystemMode().call();
1094        }
1095    
1096        private class GetBuildVersion extends ClientCallable<String> {
1097    
1098            GetBuildVersion() {
1099                super("GET", RestConstants.ADMIN, RestConstants.ADMIN_BUILD_VERSION_RESOURCE, prepareParams());
1100            }
1101    
1102            @Override
1103            protected String call(HttpURLConnection conn) throws IOException, OozieClientException {
1104                if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1105                    Reader reader = new InputStreamReader(conn.getInputStream());
1106                    JSONObject json = (JSONObject) JSONValue.parse(reader);
1107                    return (String) json.get(JsonTags.BUILD_VERSION);
1108                }
1109                else {
1110                    handleError(conn);
1111                }
1112                return null;
1113            }
1114        }
1115    
1116        /**
1117         * Return the Oozie server build version.
1118         *
1119         * @return the Oozie server build version.
1120         * @throws OozieClientException throw if it the server build version could not be retrieved.
1121         */
1122        public String getServerBuildVersion() throws OozieClientException {
1123            return new GetBuildVersion().call();
1124        }
1125    
1126        /**
1127         * Return the Oozie client build version.
1128         *
1129         * @return the Oozie client build version.
1130         */
1131        public String getClientBuildVersion() {
1132            return BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VERSION);
1133        }
1134    
1135        /**
1136         * Return the info of the coordinator jobs that match the filter.
1137         *
1138         * @param filter job filter. Refer to the {@link OozieClient} for the filter syntax.
1139         * @param start jobs offset, base 1.
1140         * @param len number of jobs to return.
1141         * @return a list with the coordinator jobs info
1142         * @throws OozieClientException thrown if the jobs info could not be retrieved.
1143         */
1144        public List<CoordinatorJob> getCoordJobsInfo(String filter, int start, int len) throws OozieClientException {
1145            return new CoordJobsStatus(filter, start, len).call();
1146        }
1147    
1148        private class GetQueueDump extends ClientCallable<List<String>> {
1149            GetQueueDump() {
1150                super("GET", RestConstants.ADMIN, RestConstants.ADMIN_QUEUE_DUMP_RESOURCE, prepareParams());
1151            }
1152    
1153            @Override
1154            protected List<String> call(HttpURLConnection conn) throws IOException, OozieClientException {
1155                if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) {
1156                    Reader reader = new InputStreamReader(conn.getInputStream());
1157                    JSONObject json = (JSONObject) JSONValue.parse(reader);
1158                    JSONArray array = (JSONArray) json.get(JsonTags.QUEUE_DUMP);
1159    
1160                    List<String> list = new ArrayList<String>();
1161                    for (Object o : array) {
1162                        JSONObject entry = (JSONObject) o;
1163                        if (entry.get(JsonTags.CALLABLE_DUMP) != null) {
1164                            String value = (String) entry.get(JsonTags.CALLABLE_DUMP);
1165                            list.add(value);
1166                        }
1167                    }
1168                    return list;
1169                }
1170                else {
1171                    handleError(conn);
1172                }
1173                return null;
1174            }
1175        }
1176    
1177        /**
1178         * Return the Oozie queue's commands' dump
1179         *
1180         * @return the list of strings of callable identification in queue
1181         * @throws OozieClientException throw if it the queue dump could not be retrieved.
1182         */
1183        public List<String> getQueueDump() throws OozieClientException {
1184            return new GetQueueDump().call();
1185        }
1186    
1187        /**
1188         * Check if the string is not null or not empty.
1189         *
1190         * @param str
1191         * @param name
1192         * @return string
1193         */
1194        public static String notEmpty(String str, String name) {
1195            if (str == null) {
1196                throw new IllegalArgumentException(name + " cannot be null");
1197            }
1198            if (str.length() == 0) {
1199                throw new IllegalArgumentException(name + " cannot be empty");
1200            }
1201            return str;
1202        }
1203    
1204        /**
1205         * Check if the object is not null.
1206         *
1207         * @param <T>
1208         * @param obj
1209         * @param name
1210         * @return string
1211         */
1212        public static <T> T notNull(T obj, String name) {
1213            if (obj == null) {
1214                throw new IllegalArgumentException(name + " cannot be null");
1215            }
1216            return obj;
1217        }
1218    
1219    }