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 }