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