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 }