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