001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.oozie.coord; 020 021import com.google.common.collect.Lists; 022 023import org.apache.commons.lang.StringUtils; 024import org.apache.hadoop.conf.Configuration; 025import org.apache.oozie.ErrorCode; 026import org.apache.oozie.client.OozieClient; 027import org.apache.oozie.command.CommandException; 028import org.apache.oozie.coord.input.logic.CoordInputLogicEvaluatorUtil; 029import org.apache.oozie.dependency.URIHandler; 030import org.apache.oozie.dependency.URIHandler.Context; 031import org.apache.oozie.service.Services; 032import org.apache.oozie.service.URIHandlerService; 033import org.apache.oozie.util.DateUtils; 034import org.apache.oozie.util.ELEvaluator; 035import org.apache.oozie.util.ParamChecker; 036import org.apache.oozie.util.XLog; 037import org.jdom.JDOMException; 038 039import java.net.URI; 040import java.util.ArrayList; 041import java.util.Calendar; 042import java.util.Date; 043import java.util.GregorianCalendar; 044import java.util.List; 045import java.util.TimeZone; 046 047/** 048 * This class implements the EL function related to coordinator 049 */ 050 051public class CoordELFunctions { 052 final public static String DATASET = "oozie.coord.el.dataset.bean"; 053 final public static String COORD_ACTION = "oozie.coord.el.app.bean"; 054 final public static String CONFIGURATION = "oozie.coord.el.conf"; 055 final public static String LATEST_EL_USE_CURRENT_TIME = "oozie.service.ELService.latest-el.use-current-time"; 056 // INSTANCE_SEPARATOR is used to separate multiple directories into one tag. 057 final public static String INSTANCE_SEPARATOR = "#"; 058 final public static String DIR_SEPARATOR = ","; 059 // TODO: in next release, support flexibility 060 private static String END_OF_OPERATION_INDICATOR_FILE = "_SUCCESS"; 061 062 public static final long MINUTE_MSEC = 60 * 1000L; 063 public static final long HOUR_MSEC = 60 * MINUTE_MSEC; 064 public static final long DAY_MSEC = 24 * HOUR_MSEC; 065 public static final long WEEK_MSEC = 7 * DAY_MSEC; 066 067 /** 068 * Used in defining the frequency in 'day' unit. <p> domain: <code> val > 0</code> and should be integer. 069 * 070 * @param val frequency in number of days. 071 * @return number of days and also set the frequency timeunit to "day" 072 */ 073 public static int ph1_coord_days(int val) { 074 val = ParamChecker.checkGTZero(val, "n"); 075 ELEvaluator eval = ELEvaluator.getCurrent(); 076 eval.setVariable("timeunit", TimeUnit.DAY); 077 eval.setVariable("endOfDuration", TimeUnit.NONE); 078 return val; 079 } 080 081 /** 082 * Used in defining the frequency in 'month' unit. <p> domain: <code> val > 0</code> and should be integer. 083 * 084 * @param val frequency in number of months. 085 * @return number of months and also set the frequency timeunit to "month" 086 */ 087 public static int ph1_coord_months(int val) { 088 val = ParamChecker.checkGTZero(val, "n"); 089 ELEvaluator eval = ELEvaluator.getCurrent(); 090 eval.setVariable("timeunit", TimeUnit.MONTH); 091 eval.setVariable("endOfDuration", TimeUnit.NONE); 092 return val; 093 } 094 095 /** 096 * Used in defining the frequency in 'hour' unit. <p> parameter value domain: <code> val > 0</code> and should 097 * be integer. 098 * 099 * @param val frequency in number of hours. 100 * @return number of minutes and also set the frequency timeunit to "minute" 101 */ 102 public static int ph1_coord_hours(int val) { 103 val = ParamChecker.checkGTZero(val, "n"); 104 ELEvaluator eval = ELEvaluator.getCurrent(); 105 eval.setVariable("timeunit", TimeUnit.MINUTE); 106 eval.setVariable("endOfDuration", TimeUnit.NONE); 107 return val * 60; 108 } 109 110 /** 111 * Used in defining the frequency in 'minute' unit. <p> domain: <code> val > 0</code> and should be integer. 112 * 113 * @param val frequency in number of minutes. 114 * @return number of minutes and also set the frequency timeunit to "minute" 115 */ 116 public static int ph1_coord_minutes(int val) { 117 val = ParamChecker.checkGTZero(val, "n"); 118 ELEvaluator eval = ELEvaluator.getCurrent(); 119 eval.setVariable("timeunit", TimeUnit.MINUTE); 120 eval.setVariable("endOfDuration", TimeUnit.NONE); 121 return val; 122 } 123 124 /** 125 * Used in defining the frequency in 'day' unit and specify the "end of day" property. <p> Every instance will 126 * start at 00:00 hour of each day. <p> domain: <code> val > 0</code> and should be integer. 127 * 128 * @param val frequency in number of days. 129 * @return number of days and also set the frequency timeunit to "day" and end_of_duration flag to "day" 130 */ 131 public static int ph1_coord_endOfDays(int val) { 132 val = ParamChecker.checkGTZero(val, "n"); 133 ELEvaluator eval = ELEvaluator.getCurrent(); 134 eval.setVariable("timeunit", TimeUnit.DAY); 135 eval.setVariable("endOfDuration", TimeUnit.END_OF_DAY); 136 return val; 137 } 138 139 /** 140 * Used in defining the frequency in 'week' unit and specify the "end of 141 * week" property. 142 * <p> 143 * Every instance will start at 00:00 hour of start of week 144 * <p> 145 * domain: <code> val > 0</code> and should be integer. 146 * 147 * @param val frequency in number of weeks. 148 * @return number of weeks and also set the frequency timeunit to week of 149 * the year and end_of_duration flag to week of the year 150 */ 151 public static int ph1_coord_endOfWeeks(int val) { 152 val = ParamChecker.checkGTZero(val, "n"); 153 ELEvaluator eval = ELEvaluator.getCurrent(); 154 eval.setVariable("timeunit", TimeUnit.WEEK); 155 eval.setVariable("endOfDuration", TimeUnit.END_OF_WEEK); 156 return val; 157 } 158 159 160 /** 161 * Used in defining the frequency in 'month' unit and specify the "end of month" property. <p> Every instance will 162 * start at first day of each month at 00:00 hour. <p> domain: <code> val > 0</code> and should be integer. 163 * 164 * @param val frequency in number of months. 165 * @return number of months and also set the frequency timeunit to "month" and end_of_duration flag to "month" 166 */ 167 public static int ph1_coord_endOfMonths(int val) { 168 val = ParamChecker.checkGTZero(val, "n"); 169 ELEvaluator eval = ELEvaluator.getCurrent(); 170 eval.setVariable("timeunit", TimeUnit.MONTH); 171 eval.setVariable("endOfDuration", TimeUnit.END_OF_MONTH); 172 return val; 173 } 174 175 /** 176 * Calculate the difference of timezone offset in minutes between dataset and coordinator job. <p> Depends on: <p> 177 * 1. Timezone of both dataset and job <p> 2. Action creation Time 178 * 179 * @return difference in minutes (DataSet TZ Offset - Application TZ offset) 180 */ 181 public static int ph2_coord_tzOffset() { 182 long actionCreationTime = getActionCreationtime().getTime(); 183 TimeZone dsTZ = ParamChecker.notNull(getDatasetTZ(), "DatasetTZ"); 184 TimeZone jobTZ = ParamChecker.notNull(getJobTZ(), "JobTZ"); 185 return (dsTZ.getOffset(actionCreationTime) - jobTZ.getOffset(actionCreationTime)) / (1000 * 60); 186 } 187 188 public static int ph3_coord_tzOffset() { 189 return ph2_coord_tzOffset(); 190 } 191 192 /** 193 * Returns a date string that is offset from 'strBaseDate' by the amount specified. The unit can be one of 194 * DAY, MONTH, HOUR, MINUTE, MONTH. 195 * 196 * @param strBaseDate The base date 197 * @param offset any number 198 * @param unit one of DAY, MONTH, HOUR, MINUTE, MONTH 199 * @return the offset date string 200 * @throws Exception in case of error 201 */ 202 public static String ph2_coord_dateOffset(String strBaseDate, int offset, String unit) throws Exception { 203 Calendar baseCalDate = DateUtils.getCalendar(strBaseDate); 204 StringBuilder buffer = new StringBuilder(); 205 baseCalDate.add(TimeUnit.valueOf(unit).getCalendarUnit(), offset); 206 buffer.append(DateUtils.formatDateOozieTZ(baseCalDate)); 207 return buffer.toString(); 208 } 209 210 public static String ph3_coord_dateOffset(String strBaseDate, int offset, String unit) throws Exception { 211 return ph2_coord_dateOffset(strBaseDate, offset, unit); 212 } 213 214 /** 215 * Returns a date string that is offset from 'strBaseDate' by the difference from Oozie processing timezone to the given 216 * timezone. It will account for daylight saving time based on the given 'strBaseDate' and 'timezone'. 217 * 218 * @param strBaseDate The base date 219 * @param timezone the timezone 220 * @return the offset date string 221 * @throws Exception in case of error 222 */ 223 public static String ph2_coord_dateTzOffset(String strBaseDate, String timezone) throws Exception { 224 Calendar baseCalDate = DateUtils.getCalendar(strBaseDate); 225 StringBuilder buffer = new StringBuilder(); 226 baseCalDate.setTimeZone(DateUtils.getTimeZone(timezone)); 227 buffer.append(DateUtils.formatDate(baseCalDate)); 228 return buffer.toString(); 229 } 230 231 public static String ph3_coord_dateTzOffset(String strBaseDate, String timezone) throws Exception{ 232 return ph2_coord_dateTzOffset(strBaseDate, timezone); 233 } 234 235 /** 236 * Determine the date-time in Oozie processing timezone of n-th future available dataset instance 237 * from nominal Time but not beyond the instance specified as 'instance. 238 * <p> 239 * It depends on: 240 * <p> 241 * 1. Data set frequency 242 * <p> 243 * 2. Data set Time unit (day, month, minute) 244 * <p> 245 * 3. Data set Time zone/DST 246 * <p> 247 * 4. End Day/Month flag 248 * <p> 249 * 5. Data set initial instance 250 * <p> 251 * 6. Action Creation Time 252 * <p> 253 * 7. Existence of dataset's directory 254 * 255 * @param n :instance count 256 * <p> 257 * domain: n >= 0, n is integer 258 * @param instance How many future instance it should check? value should 259 * be >=0 260 * @return date-time in Oozie processing timezone of the n-th instance 261 * <p> 262 * @throws Exception if the dataset is asynchronous 263 */ 264 public static String ph3_coord_future(int n, int instance) throws Exception { 265 ParamChecker.checkGEZero(n, "future:n"); 266 ParamChecker.checkGTZero(instance, "future:instance"); 267 if (isSyncDataSet()) {// For Sync Dataset 268 return coord_future_sync(n, instance); 269 } 270 else { 271 throw new UnsupportedOperationException("Asynchronous Dataset is not supported yet"); 272 } 273 } 274 275 /** 276 * Determine the date-time in Oozie processing timezone of the future available dataset instances 277 * from start to end offsets from nominal Time but not beyond the instance specified as 'instance'. 278 * <p> 279 * It depends on: 280 * <p> 281 * 1. Data set frequency 282 * <p> 283 * 2. Data set Time unit (day, month, minute) 284 * <p> 285 * 3. Data set Time zone/DST 286 * <p> 287 * 4. End Day/Month flag 288 * <p> 289 * 5. Data set initial instance 290 * <p> 291 * 6. Action Creation Time 292 * <p> 293 * 7. Existence of dataset's directory 294 * 295 * @param start : start instance offset 296 * <p> 297 * domain: start >= 0, start is integer 298 * @param end : end instance offset 299 * <p> 300 * domain: end >= 0, end is integer 301 * @param instance How many future instance it should check? value should 302 * be >=0 303 * @return date-time in Oozie processing timezone of the instances from start to end offsets 304 * delimited by comma. 305 * @throws Exception if the dataset is asynchronous 306 */ 307 public static String ph3_coord_futureRange(int start, int end, int instance) throws Exception { 308 ParamChecker.checkGEZero(start, "future:n"); 309 ParamChecker.checkGEZero(end, "future:n"); 310 ParamChecker.checkGTZero(instance, "future:instance"); 311 if (isSyncDataSet()) {// For Sync Dataset 312 return coord_futureRange_sync(start, end, instance); 313 } 314 else { 315 throw new UnsupportedOperationException("Asynchronous Dataset is not supported yet"); 316 } 317 } 318 319 private static String coord_future_sync(int n, int instance) throws Exception { 320 return coord_futureRange_sync(n, n, instance); 321 } 322 323 private static String coord_futureRange_sync(int startOffset, int endOffset, int instance) throws Exception { 324 final XLog LOG = XLog.getLog(CoordELFunctions.class); 325 final Thread currentThread = Thread.currentThread(); 326 ELEvaluator eval = ELEvaluator.getCurrent(); 327 String retVal = ""; 328 int datasetFrequency = (int) getDSFrequency();// in minutes 329 TimeUnit dsTimeUnit = getDSTimeUnit(); 330 int[] instCount = new int[1]; 331 Calendar nominalInstanceCal = getCurrentInstance(getActionCreationtime(), instCount); 332 StringBuilder resolvedInstances = new StringBuilder(); 333 StringBuilder resolvedURIPaths = new StringBuilder(); 334 if (nominalInstanceCal != null) { 335 Calendar initInstance = getInitialInstanceCal(); 336 nominalInstanceCal = (Calendar) initInstance.clone(); 337 nominalInstanceCal.add(dsTimeUnit.getCalendarUnit(), instCount[0] * datasetFrequency); 338 339 SyncCoordDataset ds = (SyncCoordDataset) eval.getVariable(DATASET); 340 if (ds == null) { 341 throw new RuntimeException("Associated Dataset should be defined with key " + DATASET); 342 } 343 String uriTemplate = ds.getUriTemplate(); 344 Configuration conf = (Configuration) eval.getVariable(CONFIGURATION); 345 if (conf == null) { 346 throw new RuntimeException("Associated Configuration should be defined with key " + CONFIGURATION); 347 } 348 int available = 0, checkedInstance = 0; 349 boolean resolved = false; 350 String user = ParamChecker 351 .notEmpty((String) eval.getVariable(OozieClient.USER_NAME), OozieClient.USER_NAME); 352 String doneFlag = ds.getDoneFlag(); 353 URIHandlerService uriService = Services.get().get(URIHandlerService.class); 354 URIHandler uriHandler = null; 355 Context uriContext = null; 356 try { 357 while (instance >= checkedInstance && !currentThread.isInterrupted()) { 358 ELEvaluator uriEval = getUriEvaluator(nominalInstanceCal); 359 String uriPath = uriEval.evaluate(uriTemplate, String.class); 360 if (uriHandler == null) { 361 URI uri = new URI(uriPath); 362 uriHandler = uriService.getURIHandler(uri); 363 uriContext = uriHandler.getContext(uri, conf, user, true); 364 } 365 String uriWithDoneFlag = uriHandler.getURIWithDoneFlag(uriPath, doneFlag); 366 if (uriHandler.exists(new URI(uriWithDoneFlag), uriContext)) { 367 if (available == endOffset) { 368 LOG.debug("Matched future(" + available + "): " + uriWithDoneFlag); 369 resolved = true; 370 resolvedInstances.append(DateUtils.formatDateOozieTZ(nominalInstanceCal)); 371 resolvedURIPaths.append(uriPath); 372 retVal = resolvedInstances.toString(); 373 eval.setVariable(CoordELConstants.RESOLVED_PATH, resolvedURIPaths.toString()); 374 break; 375 } 376 else if (available >= startOffset) { 377 LOG.debug("Matched future(" + available + "): " + uriWithDoneFlag); 378 resolvedInstances.append(DateUtils.formatDateOozieTZ(nominalInstanceCal)).append( 379 INSTANCE_SEPARATOR); 380 resolvedURIPaths.append(uriPath).append(INSTANCE_SEPARATOR); 381 382 } 383 available++; 384 } 385 // nominalInstanceCal.add(dsTimeUnit.getCalendarUnit(), datasetFrequency); 386 nominalInstanceCal = (Calendar) initInstance.clone(); 387 instCount[0]++; 388 nominalInstanceCal.add(dsTimeUnit.getCalendarUnit(), instCount[0] * datasetFrequency); 389 checkedInstance++; 390 // DateUtils.moveToEnd(nominalInstanceCal, getDSEndOfFlag()); 391 } 392 if (!StringUtils.isEmpty(resolvedURIPaths.toString()) && eval.getVariable(CoordELConstants.RESOLVED_PATH) == null) { 393 eval.setVariable(CoordELConstants.RESOLVED_PATH, resolvedURIPaths.toString()); 394 } 395 396 } 397 finally { 398 if (uriContext != null) { 399 uriContext.destroy(); 400 } 401 } 402 if (!resolved) { 403 // return unchanged future function with variable 'is_resolved' 404 // to 'false' 405 eval.setVariable(CoordELConstants.IS_RESOLVED, Boolean.FALSE); 406 if (startOffset == endOffset) { 407 retVal = "${coord:future(" + startOffset + ", " + instance + ")}"; 408 } 409 else { 410 retVal = "${coord:futureRange(" + startOffset + ", " + endOffset + ", " + instance + ")}"; 411 } 412 } 413 else { 414 eval.setVariable(CoordELConstants.IS_RESOLVED, Boolean.TRUE); 415 } 416 } 417 else {// No feasible nominal time 418 eval.setVariable(CoordELConstants.IS_RESOLVED, Boolean.TRUE); 419 retVal = ""; 420 } 421 return retVal; 422 } 423 424 /** 425 * Return nominal time or Action Creation Time. 426 * 427 * @return coordinator action creation or materialization date time 428 * @throws Exception if unable to format the Date object to String 429 */ 430 public static String ph2_coord_nominalTime() throws Exception { 431 ELEvaluator eval = ELEvaluator.getCurrent(); 432 SyncCoordAction action = ParamChecker.notNull((SyncCoordAction) eval.getVariable(COORD_ACTION), 433 "Coordinator Action"); 434 return DateUtils.formatDateOozieTZ(action.getNominalTime()); 435 } 436 437 public static String ph3_coord_nominalTime() throws Exception { 438 return ph2_coord_nominalTime(); 439 } 440 441 /** 442 * Convert from standard date-time formatting to a desired format. 443 * @param dateTimeStr - A timestamp in standard (ISO8601) format. 444 * @param format - A string representing the desired format. 445 * @return coordinator action creation or materialization date time 446 * @throws Exception if unable to format the Date object to String 447 */ 448 public static String ph2_coord_formatTime(String dateTimeStr, String format) 449 throws Exception { 450 Date dateTime = DateUtils.parseDateOozieTZ(dateTimeStr); 451 return DateUtils.formatDateCustom(dateTime, format); 452 } 453 454 public static String ph3_coord_formatTime(String dateTimeStr, String format) 455 throws Exception { 456 return ph2_coord_formatTime(dateTimeStr, format); 457 } 458 459 /** 460 * Convert from standard date-time formatting to a Unix epoch time. 461 * @param dateTimeStr - A timestamp in standard (ISO8601) format. 462 * @param millis - "true" to include millis; otherwise will only include seconds 463 * @return coordinator action creation or materialization date time 464 * @throws Exception if unable to format the Date object to String 465 */ 466 public static String ph2_coord_epochTime(String dateTimeStr, String millis) 467 throws Exception { 468 Date dateTime = DateUtils.parseDateOozieTZ(dateTimeStr); 469 return DateUtils.formatDateEpoch(dateTime, Boolean.valueOf(millis)); 470 } 471 472 public static String ph3_coord_epochTime(String dateTimeStr, String millis) 473 throws Exception { 474 return ph2_coord_epochTime(dateTimeStr, millis); 475 } 476 477 /** 478 * Return Action Id. 479 * 480 * @return coordinator action Id 481 */ 482 public static String ph2_coord_actionId() throws Exception { 483 ELEvaluator eval = ELEvaluator.getCurrent(); 484 SyncCoordAction action = ParamChecker.notNull((SyncCoordAction) eval.getVariable(COORD_ACTION), 485 "Coordinator Action"); 486 return action.getActionId(); 487 } 488 489 public static String ph3_coord_actionId() throws Exception { 490 return ph2_coord_actionId(); 491 } 492 493 /** 494 * Return Job Name. <p> 495 * 496 * @return coordinator name 497 */ 498 public static String ph2_coord_name() throws Exception { 499 ELEvaluator eval = ELEvaluator.getCurrent(); 500 SyncCoordAction action = ParamChecker.notNull((SyncCoordAction) eval.getVariable(COORD_ACTION), 501 "Coordinator Action"); 502 return action.getName(); 503 } 504 505 public static String ph3_coord_name() throws Exception { 506 return ph2_coord_name(); 507 } 508 509 /** 510 * Return Action Start time. <p> 511 * 512 * @return coordinator action start time 513 * @throws Exception if unable to format the Date object to String 514 */ 515 public static String ph2_coord_actualTime() throws Exception { 516 ELEvaluator eval = ELEvaluator.getCurrent(); 517 SyncCoordAction coordAction = (SyncCoordAction) eval.getVariable(COORD_ACTION); 518 if (coordAction == null) { 519 throw new RuntimeException("Associated Application instance should be defined with key " + COORD_ACTION); 520 } 521 return DateUtils.formatDateOozieTZ(coordAction.getActualTime()); 522 } 523 524 public static String ph3_coord_actualTime() throws Exception { 525 return ph2_coord_actualTime(); 526 } 527 528 /** 529 * Used to specify a list of URI's that are used as input dir to the workflow job. <p> Look for two evaluator-level 530 * variables <p> A) .datain.<DATAIN_NAME> B) .datain.<DATAIN_NAME>.unresolved <p> A defines the current list of 531 * URI. <p> B defines whether there are any unresolved EL-function (i.e latest) <p> If there are something 532 * unresolved, this function will echo back the original function <p> otherwise it sends the uris. 533 * 534 * @param dataInName : Datain name 535 * @return the list of URI's separated by INSTANCE_SEPARATOR <p> if there are unresolved EL function (i.e. latest) 536 * , echo back <p> the function without resolving the function. 537 */ 538 public static String ph3_coord_dataIn(String dataInName) { 539 String uris = ""; 540 ELEvaluator eval = ELEvaluator.getCurrent(); 541 if (eval.getVariable(".datain." + dataInName) == null 542 && (eval.getVariable(".actionInputLogic") != null && !StringUtils.isEmpty(eval.getVariable( 543 ".actionInputLogic").toString()))) { 544 try { 545 return new CoordInputLogicEvaluatorUtil().getInputDependencies(dataInName, 546 (SyncCoordAction) eval.getVariable(COORD_ACTION)); 547 } 548 catch (JDOMException e) { 549 XLog.getLog(CoordELFunctions.class).error(e); 550 throw new RuntimeException(e.getMessage()); 551 } 552 } 553 554 uris = (String) eval.getVariable(".datain." + dataInName); 555 Object unResolvedObj = eval.getVariable(".datain." + dataInName + ".unresolved"); 556 if (unResolvedObj == null) { 557 return uris; 558 } 559 Boolean unresolved = Boolean.parseBoolean(unResolvedObj.toString()); 560 if (unresolved != null && unresolved.booleanValue() == true) { 561 return "${coord:dataIn('" + dataInName + "')}"; 562 } 563 return uris; 564 } 565 566 /** 567 * Used to specify a list of URI's that are output dir of the workflow job. <p> Look for one evaluator-level 568 * variable <p> dataout.<DATAOUT_NAME> <p> It defines the current list of URI. <p> otherwise it sends the uris. 569 * 570 * @param dataOutName : Dataout name 571 * @return the list of URI's separated by INSTANCE_SEPARATOR 572 */ 573 public static String ph3_coord_dataOut(String dataOutName) { 574 String uris = ""; 575 ELEvaluator eval = ELEvaluator.getCurrent(); 576 uris = (String) eval.getVariable(".dataout." + dataOutName); 577 return uris; 578 } 579 580 /** 581 * Determine the date-time in Oozie processing timezone of n-th dataset instance. <p> It depends on: <p> 1. 582 * Data set frequency <p> 2. 583 * Data set Time unit (day, month, minute) <p> 3. Data set Time zone/DST <p> 4. End Day/Month flag <p> 5. Data 584 * set initial instance <p> 6. Action Creation Time 585 * 586 * @param n instance count domain: n is integer 587 * @return date-time in Oozie processing timezone of the n-th instance returns 'null' means n-th instance is 588 * earlier than Initial-Instance of DS 589 * @throws Exception 590 */ 591 public static String ph2_coord_current(int n) throws Exception { 592 if (isSyncDataSet()) { // For Sync Dataset 593 return coord_current_sync(n); 594 } 595 else { 596 throw new UnsupportedOperationException("Asynchronous Dataset is not supported yet"); 597 } 598 } 599 600 /** 601 * Determine the date-time in Oozie processing timezone of current dataset instances 602 * from start to end offsets from the nominal time. <p> It depends 603 * on: <p> 1. Data set frequency <p> 2. Data set Time unit (day, month, minute) <p> 3. Data set Time zone/DST 604 * <p> 4. End Day/Month flag <p> 5. Data set initial instance <p> 6. Action Creation Time 605 * 606 * @param start :start instance offset <p> domain: start <= 0, start is integer 607 * @param end :end instance offset <p> domain: end <= 0, end is integer 608 * @return date-time in Oozie processing timezone of the instances from start to end offsets 609 * delimited by comma. <p> If the current instance time of the dataset based on the Action Creation Time 610 * is earlier than the Initial-Instance of DS an empty string is returned. 611 * If an instance within the range is earlier than Initial-Instance of DS that instance is ignored 612 * @throws Exception 613 */ 614 public static String ph2_coord_currentRange(int start, int end) throws Exception { 615 if (isSyncDataSet()) { // For Sync Dataset 616 return coord_currentRange_sync(start, end); 617 } 618 else { 619 throw new UnsupportedOperationException("Asynchronous Dataset is not supported yet"); 620 } 621 } 622 /** 623 * Determine the date-time in Oozie processing timezone of the given offset from the dataset effective nominal time. <p> It 624 * depends on: <p> 1. Data set frequency <p> 2. Data set Time Unit <p> 3. Data set Time zone/DST 625 * <p> 4. Data set initial instance <p> 5. Action Creation Time 626 * 627 * @param n offset amount (integer) 628 * @param timeUnit TimeUnit for offset n ("MINUTE", "HOUR", "DAY", "MONTH", "YEAR") 629 * @return date-time in Oozie processing timezone of the given offset from the dataset effective nominal time 630 * @throws Exception if there was a problem formatting 631 */ 632 public static String ph2_coord_offset(int n, String timeUnit) throws Exception { 633 if (isSyncDataSet()) { // For Sync Dataset 634 return coord_offset_sync(n, timeUnit); 635 } 636 else { 637 throw new UnsupportedOperationException("Asynchronous Dataset is not supported yet"); 638 } 639 } 640 641 /** 642 * Determine how many hours is on the date of n-th dataset instance. <p> It depends on: <p> 1. Data set frequency 643 * <p> 2. Data set Time unit (day, month, minute) <p> 3. Data set Time zone/DST <p> 4. End Day/Month flag <p> 5. 644 * Data set initial instance <p> 6. Action Creation Time 645 * 646 * @param n instance count <p> domain: n is integer 647 * @return number of hours on that day <p> returns -1 means n-th instance is earlier than Initial-Instance of DS 648 * @throws Exception 649 */ 650 public static int ph2_coord_hoursInDay(int n) throws Exception { 651 int datasetFrequency = (int) getDSFrequency(); 652 // /Calendar nominalInstanceCal = 653 // getCurrentInstance(getActionCreationtime()); 654 Calendar nominalInstanceCal = getEffectiveNominalTime(); 655 if (nominalInstanceCal == null) { 656 return -1; 657 } 658 nominalInstanceCal.add(getDSTimeUnit().getCalendarUnit(), datasetFrequency * n); 659 /* 660 * if (nominalInstanceCal.getTime().compareTo(getInitialInstance()) < 0) 661 * { return -1; } 662 */ 663 nominalInstanceCal.setTimeZone(getDatasetTZ());// Use Dataset TZ 664 // DateUtils.moveToEnd(nominalInstanceCal, getDSEndOfFlag()); 665 return DateUtils.hoursInDay(nominalInstanceCal); 666 } 667 668 public static int ph3_coord_hoursInDay(int n) throws Exception { 669 return ph2_coord_hoursInDay(n); 670 } 671 672 /** 673 * Calculate number of days in one month for n-th dataset instance. <p> It depends on: <p> 1. Data set frequency . 674 * <p> 2. Data set Time unit (day, month, minute) <p> 3. Data set Time zone/DST <p> 4. End Day/Month flag <p> 5. 675 * Data set initial instance <p> 6. Action Creation Time 676 * 677 * @param n instance count. domain: n is integer 678 * @return number of days in that month <p> returns -1 means n-th instance is earlier than Initial-Instance of DS 679 * @throws Exception 680 */ 681 public static int ph2_coord_daysInMonth(int n) throws Exception { 682 int datasetFrequency = (int) getDSFrequency();// in minutes 683 // Calendar nominalInstanceCal = 684 // getCurrentInstance(getActionCreationtime()); 685 Calendar nominalInstanceCal = getEffectiveNominalTime(); 686 if (nominalInstanceCal == null) { 687 return -1; 688 } 689 nominalInstanceCal.add(getDSTimeUnit().getCalendarUnit(), datasetFrequency * n); 690 /* 691 * if (nominalInstanceCal.getTime().compareTo(getInitialInstance()) < 0) 692 * { return -1; } 693 */ 694 nominalInstanceCal.setTimeZone(getDatasetTZ());// Use Dataset TZ 695 // DateUtils.moveToEnd(nominalInstanceCal, getDSEndOfFlag()); 696 return nominalInstanceCal.getActualMaximum(Calendar.DAY_OF_MONTH); 697 } 698 699 public static int ph3_coord_daysInMonth(int n) throws Exception { 700 return ph2_coord_daysInMonth(n); 701 } 702 703 /** 704 * Determine the date-time in Oozie processing timezone of n-th latest available dataset instance. <p> It depends 705 * on: <p> 1. Data set frequency <p> 2. Data set Time unit (day, month, minute) <p> 3. Data set Time zone/DST 706 * <p> 4. End Day/Month flag <p> 5. Data set initial instance <p> 6. Action Creation Time <p> 7. Existence of 707 * dataset's directory 708 * 709 * @param n :instance count <p> domain: n <= 0, n is integer 710 * @return date-time in Oozie processing timezone of the n-th instance <p> returns 'null' means n-th instance is 711 * earlier than Initial-Instance of DS 712 * @throws Exception 713 */ 714 public static String ph3_coord_latest(int n) throws Exception { 715 ParamChecker.checkLEZero(n, "latest:n"); 716 if (isSyncDataSet()) {// For Sync Dataset 717 return coord_latest_sync(n); 718 } 719 else { 720 throw new UnsupportedOperationException("Asynchronous Dataset is not supported yet"); 721 } 722 } 723 724 /** 725 * Determine the date-time in Oozie processing timezone of latest available dataset instances 726 * from start to end offsets from the nominal time. <p> It depends 727 * on: <p> 1. Data set frequency <p> 2. Data set Time unit (day, month, minute) <p> 3. Data set Time zone/DST 728 * <p> 4. End Day/Month flag <p> 5. Data set initial instance <p> 6. Action Creation Time <p> 7. Existence of 729 * dataset's directory 730 * 731 * @param start :start instance offset <p> domain: start <= 0, start is integer 732 * @param end :end instance offset <p> domain: end <= 0, end is integer 733 * @return date-time in Oozie processing timezone of the instances from start to end offsets 734 * delimited by comma. <p> returns 'null' means start offset instance is 735 * earlier than Initial-Instance of DS 736 * @throws Exception 737 */ 738 public static String ph3_coord_latestRange(int start, int end) throws Exception { 739 ParamChecker.checkLEZero(start, "latest:n"); 740 ParamChecker.checkLEZero(end, "latest:n"); 741 if (isSyncDataSet()) {// For Sync Dataset 742 return coord_latestRange_sync(start, end); 743 } 744 else { 745 throw new UnsupportedOperationException("Asynchronous Dataset is not supported yet"); 746 } 747 } 748 749 /** 750 * Configure an evaluator with data set and application specific information. <p> Helper method of associating 751 * dataset and application object 752 * 753 * @param evaluator : to set variables 754 * @param ds : Data Set object 755 * @param coordAction : Application instance 756 */ 757 public static void configureEvaluator(ELEvaluator evaluator, SyncCoordDataset ds, SyncCoordAction coordAction) { 758 evaluator.setVariable(COORD_ACTION, coordAction); 759 evaluator.setVariable(DATASET, ds); 760 } 761 762 /** 763 * Helper method to wrap around with "${..}". <p> 764 * 765 * 766 * @param eval :EL evaluator 767 * @param expr : expression to evaluate 768 * @return Resolved expression or echo back the same expression 769 * @throws Exception 770 */ 771 public static String evalAndWrap(ELEvaluator eval, String expr) throws Exception { 772 try { 773 eval.setVariable(".wrap", null); 774 String result = eval.evaluate(expr, String.class); 775 if (eval.getVariable(".wrap") != null) { 776 return "${" + result + "}"; 777 } 778 else { 779 return result; 780 } 781 } 782 catch (Exception e) { 783 throw new ElException(ErrorCode.E1004, "Unable to evaluate :" + expr + ":\n", e); 784 } 785 } 786 787 // Set of echo functions 788 789 public static String ph1_coord_current_echo(String n) { 790 return echoUnResolved("current", n); 791 } 792 793 public static String ph1_coord_absolute_echo(String date) { 794 return echoUnResolved("absolute", date); 795 } 796 797 public static String ph1_coord_endOfMonths_echo(String date) { 798 return echoUnResolved("endOfMonths", date); 799 } 800 801 public static String ph1_coord_endOfWeeks_echo(String date) { 802 return echoUnResolved("endOfWeeks", date); 803 } 804 805 public static String ph1_coord_endOfDays_echo(String date) { 806 return echoUnResolved("endOfDays", date); 807 } 808 809 public static String ph1_coord_currentRange_echo(String start, String end) { 810 return echoUnResolved("currentRange", start + ", " + end); 811 } 812 813 public static String ph1_coord_offset_echo(String n, String timeUnit) { 814 return echoUnResolved("offset", n + " , " + timeUnit); 815 } 816 817 public static String ph2_coord_current_echo(String n) { 818 return echoUnResolved("current", n); 819 } 820 821 public static String ph2_coord_currentRange_echo(String start, String end) { 822 return echoUnResolved("currentRange", start + ", " + end); 823 } 824 825 public static String ph2_coord_offset_echo(String n, String timeUnit) { 826 return echoUnResolved("offset", n + " , " + timeUnit); 827 } 828 829 public static String ph2_coord_absolute_echo(String date) { 830 return echoUnResolved("absolute", date); 831 } 832 833 public static String ph2_coord_endOfMonths_echo(String date) { 834 return echoUnResolved("endOfMonths", date); 835 } 836 837 public static String ph2_coord_endOfWeeks_echo(String date) { 838 return echoUnResolved("endOfWeeks", date); 839 } 840 841 public static String ph2_coord_endOfDays_echo(String date) { 842 return echoUnResolved("endOfDays", date); 843 } 844 845 public static String ph2_coord_absolute_range(String startInstance, int end) throws Exception { 846 int[] instanceCount = new int[1]; 847 Calendar startInstanceCal = DateUtils.getCalendar(startInstance); 848 Calendar currentInstance = getCurrentInstance(startInstanceCal.getTime(), instanceCount); 849 // getCurrentInstance() returns null, which means startInstance is less 850 // than initial instance 851 if (currentInstance == null) { 852 throw new CommandException(ErrorCode.E1010, 853 "initial-instance should be equal or earlier than the start-instance. initial-instance is " 854 + getInitialInstance() + " and start-instance is " + startInstance); 855 } 856 if (currentInstance.getTimeInMillis() != startInstanceCal.getTimeInMillis()) { 857 throw new CommandException(ErrorCode.E1010, 858 "initial-instance is not in phase with start-instance. initial-instance is " 859 + DateUtils.formatDateOozieTZ(getInitialInstanceCal()) + " and start-instance is " 860 + DateUtils.formatDateOozieTZ(startInstanceCal)); 861 } 862 int[] nominalCount = new int[1]; 863 if (getCurrentInstance(getActionCreationtime(), nominalCount) == null) { 864 throw new CommandException(ErrorCode.E1010, 865 "initial-instance should be equal or earlier than the nominal time. initial-instance is " 866 + getInitialInstance() + " and nominal time is " + getActionCreationtime()); 867 } 868 // getCurrentInstance return offset relative to initial instance. 869 // start instance offset - nominal offset = start offset relative to 870 // nominal time-stamp. 871 int start = instanceCount[0] - nominalCount[0]; 872 if (start > end) { 873 throw new CommandException(ErrorCode.E1010, 874 "start-instance should be equal or earlier than the end-instance. startInstance is " 875 + startInstance + " which is equivalent to current (" + instanceCount[0] 876 + ") but end is specified as current (" + end + ")"); 877 } 878 return ph2_coord_currentRange(start, end); 879 } 880 881 public static String ph1_coord_dateOffset_echo(String n, String offset, String unit) { 882 return echoUnResolved("dateOffset", n + " , " + offset + " , " + unit); 883 } 884 885 public static String ph1_coord_dateTzOffset_echo(String n, String timezone) { 886 return echoUnResolved("dateTzOffset", n + " , " + timezone); 887 } 888 889 public static String ph1_coord_epochTime_echo(String dateTime, String millis) { 890 // Quote the dateTime value since it would contain a ':'. 891 return echoUnResolved("epochTime", "'"+dateTime+"'" + " , " + millis); 892 } 893 894 public static String ph1_coord_formatTime_echo(String dateTime, String format) { 895 // Quote the dateTime value since it would contain a ':'. 896 return echoUnResolved("formatTime", "'"+dateTime+"'" + " , " + format); 897 } 898 899 public static String ph1_coord_latest_echo(String n) { 900 return echoUnResolved("latest", n); 901 } 902 903 public static String ph2_coord_latest_echo(String n) { 904 return ph1_coord_latest_echo(n); 905 } 906 907 public static String ph1_coord_future_echo(String n, String instance) { 908 return echoUnResolved("future", n + ", " + instance + ""); 909 } 910 911 public static String ph2_coord_future_echo(String n, String instance) { 912 return ph1_coord_future_echo(n, instance); 913 } 914 915 public static String ph1_coord_latestRange_echo(String start, String end) { 916 return echoUnResolved("latestRange", start + ", " + end); 917 } 918 919 public static String ph2_coord_latestRange_echo(String start, String end) { 920 return ph1_coord_latestRange_echo(start, end); 921 } 922 923 public static String ph1_coord_futureRange_echo(String start, String end, String instance) { 924 return echoUnResolved("futureRange", start + ", " + end + ", " + instance); 925 } 926 927 public static String ph2_coord_futureRange_echo(String start, String end, String instance) { 928 return ph1_coord_futureRange_echo(start, end, instance); 929 } 930 931 public static String ph1_coord_dataIn_echo(String n) { 932 ELEvaluator eval = ELEvaluator.getCurrent(); 933 String val = (String) eval.getVariable("oozie.dataname." + n); 934 if ((val == null || val.equals("data-in") == false)) { 935 XLog.getLog(CoordELFunctions.class).error("data_in_name " + n + " is not valid"); 936 throw new RuntimeException("data_in_name " + n + " is not valid"); 937 } 938 return echoUnResolved("dataIn", "'" + n + "'"); 939 } 940 941 public static String ph1_coord_dataOut_echo(String n) { 942 ELEvaluator eval = ELEvaluator.getCurrent(); 943 String val = (String) eval.getVariable("oozie.dataname." + n); 944 if (val == null || val.equals("data-out") == false) { 945 XLog.getLog(CoordELFunctions.class).error("data_out_name " + n + " is not valid"); 946 throw new RuntimeException("data_out_name " + n + " is not valid"); 947 } 948 return echoUnResolved("dataOut", "'" + n + "'"); 949 } 950 951 public static String ph1_coord_nominalTime_echo() { 952 return echoUnResolved("nominalTime", ""); 953 } 954 955 public static String ph1_coord_nominalTime_echo_wrap() { 956 // return "${coord:nominalTime()}"; // no resolution 957 return echoUnResolved("nominalTime", ""); 958 } 959 960 public static String ph1_coord_nominalTime_echo_fixed() { 961 return "2009-03-06T010:00"; // Dummy resolution 962 } 963 964 public static String ph1_coord_actualTime_echo_wrap() { 965 // return "${coord:actualTime()}"; // no resolution 966 return echoUnResolved("actualTime", ""); 967 } 968 969 public static String ph1_coord_actionId_echo() { 970 return echoUnResolved("actionId", ""); 971 } 972 973 public static String ph1_coord_name_echo() { 974 return echoUnResolved("name", ""); 975 } 976 977 // The following echo functions are not used in any phases yet 978 // They are here for future purpose. 979 public static String coord_minutes_echo(String n) { 980 return echoUnResolved("minutes", n); 981 } 982 983 public static String coord_hours_echo(String n) { 984 return echoUnResolved("hours", n); 985 } 986 987 public static String coord_days_echo(String n) { 988 return echoUnResolved("days", n); 989 } 990 991 public static String coord_endOfDay_echo(String n) { 992 return echoUnResolved("endOfDay", n); 993 } 994 995 public static String coord_months_echo(String n) { 996 return echoUnResolved("months", n); 997 } 998 999 public static String coord_endOfMonth_echo(String n) { 1000 return echoUnResolved("endOfMonth", n); 1001 } 1002 1003 public static String coord_actualTime_echo() { 1004 return echoUnResolved("actualTime", ""); 1005 } 1006 1007 // This echo function will always return "24" for validation only. 1008 // This evaluation ****should not**** replace the original XML 1009 // Create a temporary string and validate the function 1010 // This is **required** for evaluating an expression like 1011 // coord:HoursInDay(0) + 3 1012 // actual evaluation will happen in phase 2 or phase 3. 1013 public static String ph1_coord_hoursInDay_echo(String n) { 1014 return "24"; 1015 // return echoUnResolved("hoursInDay", n); 1016 } 1017 1018 // This echo function will always return "30" for validation only. 1019 // This evaluation ****should not**** replace the original XML 1020 // Create a temporary string and validate the function 1021 // This is **required** for evaluating an expression like 1022 // coord:daysInMonth(0) + 3 1023 // actual evaluation will happen in phase 2 or phase 3. 1024 public static String ph1_coord_daysInMonth_echo(String n) { 1025 // return echoUnResolved("daysInMonth", n); 1026 return "30"; 1027 } 1028 1029 // This echo function will always return "3" for validation only. 1030 // This evaluation ****should not**** replace the original XML 1031 // Create a temporary string and validate the function 1032 // This is **required** for evaluating an expression like coord:tzOffset + 2 1033 // actual evaluation will happen in phase 2 or phase 3. 1034 public static String ph1_coord_tzOffset_echo() { 1035 // return echoUnResolved("tzOffset", ""); 1036 return "3"; 1037 } 1038 1039 // Local methods 1040 /** 1041 * @param n 1042 * @return n-th instance Date-Time from current instance for data-set <p> return empty string ("") if the 1043 * Action_Creation_time or the n-th instance <p> is earlier than the Initial_Instance of dataset. 1044 * @throws Exception 1045 */ 1046 private static String coord_current_sync(int n) throws Exception { 1047 return coord_currentRange_sync(n, n); 1048 } 1049 1050 private static String coord_currentRange_sync(int start, int end) throws Exception { 1051 final XLog LOG = XLog.getLog(CoordELFunctions.class); 1052 int datasetFrequency = getDSFrequency();// in minutes 1053 TimeUnit dsTimeUnit = getDSTimeUnit(); 1054 int[] instCount = new int[1];// used as pass by ref 1055 Calendar nominalInstanceCal = getCurrentInstance(getActionCreationtime(), instCount); 1056 if (nominalInstanceCal == null) { 1057 LOG.warn("If the initial instance of the dataset is later than the nominal time, an empty string is" 1058 + " returned. This means that no data is available at the current-instance specified by the user" 1059 + " and the user could try modifying his initial-instance to an earlier time."); 1060 return ""; 1061 } else { 1062 Calendar initInstance = getInitialInstanceCal(); 1063 // Add in the reverse order - newest instance first. 1064 nominalInstanceCal = (Calendar) initInstance.clone(); 1065 nominalInstanceCal.add(dsTimeUnit.getCalendarUnit(), (instCount[0] + start) * datasetFrequency); 1066 List<String> instances = new ArrayList<String>(); 1067 for (int i = start; i <= end; i++) { 1068 if (nominalInstanceCal.compareTo(initInstance) < 0) { 1069 LOG.warn("If the initial instance of the dataset is later than the current-instance specified," 1070 + " such as coord:current({0}) in this case, an empty string is returned. This means that" 1071 + " no data is available at the current-instance specified by the user and the user could" 1072 + " try modifying his initial-instance to an earlier time.", start); 1073 } 1074 else { 1075 instances.add(DateUtils.formatDateOozieTZ(nominalInstanceCal)); 1076 } 1077 nominalInstanceCal.add(dsTimeUnit.getCalendarUnit(), datasetFrequency); 1078 } 1079 instances = Lists.reverse(instances); 1080 return StringUtils.join(instances, CoordELFunctions.INSTANCE_SEPARATOR); 1081 } 1082 } 1083 1084 /** 1085 * 1086 * @param n offset amount (integer) 1087 * @param timeUnit TimeUnit for offset n ("MINUTE", "HOUR", "DAY", "MONTH", "YEAR") 1088 * @return the offset time from the effective nominal time <p> return empty string ("") if the Action_Creation_time or the 1089 * offset instance <p> is earlier than the Initial_Instance of dataset. 1090 * @throws Exception 1091 */ 1092 private static String coord_offset_sync(int n, String timeUnit) throws Exception { 1093 Calendar rawCal = resolveOffsetRawTime(n, TimeUnit.valueOf(timeUnit), null); 1094 if (rawCal == null) { 1095 // warning already logged by resolveOffsetRawTime() 1096 return ""; 1097 } 1098 1099 int freq = getDSFrequency(); 1100 TimeUnit freqUnit = getDSTimeUnit(); 1101 int freqCount = 0; 1102 // We're going to manually turn back/forward cal by decrements/increments of freq and then check that it gives the same 1103 // time as rawCal; this is to check that the offset time resolves to a frequency offset of the effective nominal time 1104 // In other words, that there exists an integer x, such that coord:offset(n, timeUnit) == coord:current(x) is true 1105 // If not, then we'll "rewind" rawCal to the latest instance earlier than rawCal and use that. 1106 Calendar cal = getInitialInstanceCal(); 1107 if (rawCal.before(cal)) { 1108 while (cal.after(rawCal)) { 1109 cal.add(freqUnit.getCalendarUnit(), -freq); 1110 freqCount--; 1111 } 1112 } 1113 else if (rawCal.after(cal)) { 1114 while (cal.before(rawCal)) { 1115 cal.add(freqUnit.getCalendarUnit(), freq); 1116 freqCount++; 1117 } 1118 } 1119 if (cal.before(rawCal)) { 1120 rawCal = cal; 1121 } 1122 else if (cal.after(rawCal)) { 1123 cal.add(freqUnit.getCalendarUnit(), -freq); 1124 rawCal = cal; 1125 freqCount--; 1126 } 1127 String rawCalStr = DateUtils.formatDateOozieTZ(rawCal); 1128 1129 Calendar nominalInstanceCal = getInitialInstanceCal(); 1130 nominalInstanceCal.add(freqUnit.getCalendarUnit(), freq * freqCount); 1131 if (nominalInstanceCal.getTime().compareTo(getInitialInstance()) < 0) { 1132 XLog.getLog(CoordELFunctions.class).warn("If the initial instance of the dataset is later than the offset instance" 1133 + " specified, such as coord:offset({0}, {1}) in this case, an empty string is returned. This means that no" 1134 + " data is available at the offset instance specified by the user and the user could try modifying his" 1135 + " initial-instance to an earlier time.", n, timeUnit); 1136 return ""; 1137 } 1138 String nominalCalStr = DateUtils.formatDateOozieTZ(nominalInstanceCal); 1139 1140 if (!rawCalStr.equals(nominalCalStr)) { 1141 throw new RuntimeException("Shouldn't happen"); 1142 } 1143 return rawCalStr; 1144 } 1145 1146 /** 1147 * @param offset 1148 * @return n-th available latest instance Date-Time for SYNC data-set 1149 * @throws Exception 1150 */ 1151 private static String coord_latest_sync(int offset) throws Exception { 1152 return coord_latestRange_sync(offset, offset); 1153 } 1154 1155 private static String coord_latestRange_sync(int startOffset, int endOffset) throws Exception { 1156 final XLog LOG = XLog.getLog(CoordELFunctions.class); 1157 final Thread currentThread = Thread.currentThread(); 1158 ELEvaluator eval = ELEvaluator.getCurrent(); 1159 String retVal = ""; 1160 int datasetFrequency = (int) getDSFrequency();// in minutes 1161 TimeUnit dsTimeUnit = getDSTimeUnit(); 1162 int[] instCount = new int[1]; 1163 boolean useCurrentTime = Services.get().getConf().getBoolean(LATEST_EL_USE_CURRENT_TIME, false); 1164 Calendar nominalInstanceCal; 1165 if (useCurrentTime) { 1166 nominalInstanceCal = getCurrentInstance(new Date(), instCount); 1167 } 1168 else { 1169 nominalInstanceCal = getCurrentInstance(getActualTime(), instCount); 1170 } 1171 StringBuilder resolvedInstances = new StringBuilder(); 1172 StringBuilder resolvedURIPaths = new StringBuilder(); 1173 if (nominalInstanceCal != null) { 1174 Calendar initInstance = getInitialInstanceCal(); 1175 SyncCoordDataset ds = (SyncCoordDataset) eval.getVariable(DATASET); 1176 if (ds == null) { 1177 throw new RuntimeException("Associated Dataset should be defined with key " + DATASET); 1178 } 1179 String uriTemplate = ds.getUriTemplate(); 1180 Configuration conf = (Configuration) eval.getVariable(CONFIGURATION); 1181 if (conf == null) { 1182 throw new RuntimeException("Associated Configuration should be defined with key " + CONFIGURATION); 1183 } 1184 int available = 0; 1185 boolean resolved = false; 1186 String user = ParamChecker 1187 .notEmpty((String) eval.getVariable(OozieClient.USER_NAME), OozieClient.USER_NAME); 1188 String doneFlag = ds.getDoneFlag(); 1189 URIHandlerService uriService = Services.get().get(URIHandlerService.class); 1190 URIHandler uriHandler = null; 1191 Context uriContext = null; 1192 try { 1193 while (nominalInstanceCal.compareTo(initInstance) >= 0 && !currentThread.isInterrupted()) { 1194 ELEvaluator uriEval = getUriEvaluator(nominalInstanceCal); 1195 String uriPath = uriEval.evaluate(uriTemplate, String.class); 1196 if (uriHandler == null) { 1197 URI uri = new URI(uriPath); 1198 uriHandler = uriService.getURIHandler(uri); 1199 uriContext = uriHandler.getContext(uri, conf, user, true); 1200 } 1201 String uriWithDoneFlag = uriHandler.getURIWithDoneFlag(uriPath, doneFlag); 1202 if (uriHandler.exists(new URI(uriWithDoneFlag), uriContext)) { 1203 XLog.getLog(CoordELFunctions.class) 1204 .debug("Found latest(" + available + "): " + uriWithDoneFlag); 1205 if (available == startOffset) { 1206 LOG.debug("Matched latest(" + available + "): " + uriWithDoneFlag); 1207 resolved = true; 1208 resolvedInstances.append(DateUtils.formatDateOozieTZ(nominalInstanceCal)); 1209 resolvedURIPaths.append(uriPath); 1210 retVal = resolvedInstances.toString(); 1211 eval.setVariable(CoordELConstants.RESOLVED_PATH, resolvedURIPaths.toString()); 1212 1213 break; 1214 } 1215 else if (available <= endOffset) { 1216 LOG.debug("Matched latest(" + available + "): " + uriWithDoneFlag); 1217 resolvedInstances.append(DateUtils.formatDateOozieTZ(nominalInstanceCal)).append( 1218 INSTANCE_SEPARATOR); 1219 resolvedURIPaths.append(uriPath).append(INSTANCE_SEPARATOR); 1220 } 1221 1222 available--; 1223 } 1224 // nominalInstanceCal.add(dsTimeUnit.getCalendarUnit(), -datasetFrequency); 1225 nominalInstanceCal = (Calendar) initInstance.clone(); 1226 instCount[0]--; 1227 nominalInstanceCal.add(dsTimeUnit.getCalendarUnit(), instCount[0] * datasetFrequency); 1228 // DateUtils.moveToEnd(nominalInstanceCal, getDSEndOfFlag()); 1229 } 1230 if (!StringUtils.isEmpty(resolvedURIPaths.toString()) && eval.getVariable(CoordELConstants.RESOLVED_PATH) == null) { 1231 eval.setVariable(CoordELConstants.RESOLVED_PATH, resolvedURIPaths.toString()); 1232 } 1233 } 1234 finally { 1235 if (uriContext != null) { 1236 uriContext.destroy(); 1237 } 1238 } 1239 if (!resolved) { 1240 // return unchanged latest function with variable 'is_resolved' 1241 // to 'false' 1242 eval.setVariable(CoordELConstants.IS_RESOLVED, Boolean.FALSE); 1243 if (startOffset == endOffset) { 1244 retVal = "${coord:latest(" + startOffset + ")}"; 1245 } 1246 else { 1247 retVal = "${coord:latestRange(" + startOffset + "," + endOffset + ")}"; 1248 } 1249 } 1250 else { 1251 eval.setVariable(CoordELConstants.IS_RESOLVED, Boolean.TRUE); 1252 } 1253 } 1254 else {// No feasible nominal time 1255 eval.setVariable(CoordELConstants.IS_RESOLVED, Boolean.FALSE); 1256 } 1257 return retVal; 1258 } 1259 1260 /** 1261 * @param tm 1262 * @return a new Evaluator to be used for URI-template evaluation 1263 */ 1264 private static ELEvaluator getUriEvaluator(Calendar tm) { 1265 tm.setTimeZone(DateUtils.getOozieProcessingTimeZone()); 1266 ELEvaluator retEval = new ELEvaluator(); 1267 retEval.setVariable("YEAR", tm.get(Calendar.YEAR)); 1268 retEval.setVariable("MONTH", (tm.get(Calendar.MONTH) + 1) < 10 ? "0" + (tm.get(Calendar.MONTH) + 1) : (tm 1269 .get(Calendar.MONTH) + 1)); 1270 retEval.setVariable("DAY", tm.get(Calendar.DAY_OF_MONTH) < 10 ? "0" + tm.get(Calendar.DAY_OF_MONTH) : tm 1271 .get(Calendar.DAY_OF_MONTH)); 1272 retEval.setVariable("HOUR", tm.get(Calendar.HOUR_OF_DAY) < 10 ? "0" + tm.get(Calendar.HOUR_OF_DAY) : tm 1273 .get(Calendar.HOUR_OF_DAY)); 1274 retEval.setVariable("MINUTE", tm.get(Calendar.MINUTE) < 10 ? "0" + tm.get(Calendar.MINUTE) : tm 1275 .get(Calendar.MINUTE)); 1276 return retEval; 1277 } 1278 1279 /** 1280 * @return whether a data set is SYNCH or ASYNC 1281 */ 1282 private static boolean isSyncDataSet() { 1283 ELEvaluator eval = ELEvaluator.getCurrent(); 1284 SyncCoordDataset ds = (SyncCoordDataset) eval.getVariable(DATASET); 1285 if (ds == null) { 1286 throw new RuntimeException("Associated Dataset should be defined with key " + DATASET); 1287 } 1288 return ds.getType().equalsIgnoreCase("SYNC"); 1289 } 1290 1291 /** 1292 * Check whether a function should be resolved. 1293 * 1294 * @param functionName 1295 * @param n 1296 * @return null if the functionName needs to be resolved otherwise return the calling function unresolved. 1297 */ 1298 private static String checkIfResolved(String functionName, String n) { 1299 ELEvaluator eval = ELEvaluator.getCurrent(); 1300 String replace = (String) eval.getVariable("resolve_" + functionName); 1301 if (replace == null || (replace != null && replace.equalsIgnoreCase("false"))) { // Don't 1302 // resolve 1303 // return "${coord:" + functionName + "(" + n +")}"; //Unresolved 1304 eval.setVariable(".wrap", "true"); 1305 return "coord:" + functionName + "(" + n + ")"; // Unresolved 1306 } 1307 return null; // Resolved it 1308 } 1309 1310 private static String echoUnResolved(String functionName, String n) { 1311 return echoUnResolvedPre(functionName, n, "coord:"); 1312 } 1313 1314 private static String echoUnResolvedPre(String functionName, String n, String prefix) { 1315 ELEvaluator eval = ELEvaluator.getCurrent(); 1316 eval.setVariable(".wrap", "true"); 1317 return prefix + functionName + "(" + n + ")"; // Unresolved 1318 } 1319 1320 /** 1321 * @return the initial instance of a DataSet in DATE 1322 */ 1323 private static Date getInitialInstance() { 1324 ELEvaluator eval = ELEvaluator.getCurrent(); 1325 return getInitialInstance(eval); 1326 } 1327 1328 /** 1329 * @return the initial instance of a DataSet in DATE 1330 */ 1331 private static Date getInitialInstance(ELEvaluator eval) { 1332 return getInitialInstanceCal(eval).getTime(); 1333 // return ds.getInitInstance(); 1334 } 1335 1336 /** 1337 * @return the initial instance of a DataSet in Calendar 1338 */ 1339 private static Calendar getInitialInstanceCal() { 1340 ELEvaluator eval = ELEvaluator.getCurrent(); 1341 return getInitialInstanceCal(eval); 1342 } 1343 1344 /** 1345 * @return the initial instance of a DataSet in Calendar 1346 */ 1347 private static Calendar getInitialInstanceCal(ELEvaluator eval) { 1348 SyncCoordDataset ds = (SyncCoordDataset) eval.getVariable(DATASET); 1349 if (ds == null) { 1350 throw new RuntimeException("Associated Dataset should be defined with key " + DATASET); 1351 } 1352 Calendar effInitTS = new GregorianCalendar(ds.getTimeZone()); 1353 effInitTS.setTime(ds.getInitInstance()); 1354 // To adjust EOD/EOM 1355 DateUtils.moveToEnd(effInitTS, getDSEndOfFlag(eval)); 1356 return effInitTS; 1357 // return ds.getInitInstance(); 1358 } 1359 1360 /** 1361 * @return Nominal or action creation Time when all the dependencies of an application instance are met. 1362 */ 1363 private static Date getActionCreationtime() { 1364 ELEvaluator eval = ELEvaluator.getCurrent(); 1365 return getActionCreationtime(eval); 1366 } 1367 1368 /** 1369 * @return Nominal or action creation Time when all the dependencies of an application instance are met. 1370 */ 1371 private static Date getActionCreationtime(ELEvaluator eval) { 1372 SyncCoordAction coordAction = (SyncCoordAction) eval.getVariable(COORD_ACTION); 1373 if (coordAction == null) { 1374 throw new RuntimeException("Associated Application instance should be defined with key " + COORD_ACTION); 1375 } 1376 return coordAction.getNominalTime(); 1377 } 1378 1379 /** 1380 * @return Actual Time when all the dependencies of an application instance are met. 1381 */ 1382 private static Date getActualTime() { 1383 ELEvaluator eval = ELEvaluator.getCurrent(); 1384 SyncCoordAction coordAction = (SyncCoordAction) eval.getVariable(COORD_ACTION); 1385 if (coordAction == null) { 1386 throw new RuntimeException("Associated Application instance should be defined with key " + COORD_ACTION); 1387 } 1388 return coordAction.getActualTime(); 1389 } 1390 1391 /** 1392 * @return TimeZone for the application or job. 1393 */ 1394 private static TimeZone getJobTZ() { 1395 ELEvaluator eval = ELEvaluator.getCurrent(); 1396 SyncCoordAction coordAction = (SyncCoordAction) eval.getVariable(COORD_ACTION); 1397 if (coordAction == null) { 1398 throw new RuntimeException("Associated Application instance should be defined with key " + COORD_ACTION); 1399 } 1400 return coordAction.getTimeZone(); 1401 } 1402 1403 /** 1404 * Find the current instance based on effectiveTime (i.e Action_Creation_Time or Action_Start_Time) 1405 * 1406 * @return current instance i.e. current(0) returns null if effectiveTime is earlier than Initial Instance time of 1407 * the dataset. 1408 */ 1409 public static Calendar getCurrentInstance(Date effectiveTime, int instanceCount[]) { 1410 ELEvaluator eval = ELEvaluator.getCurrent(); 1411 return getCurrentInstance(effectiveTime, instanceCount, eval); 1412 } 1413 1414 /** 1415 * Find the current instance based on effectiveTime (i.e Action_Creation_Time or Action_Start_Time) 1416 * 1417 * @return current instance i.e. current(0) returns null if effectiveTime is earlier than Initial Instance time of 1418 * the dataset. 1419 */ 1420 private static Calendar getCurrentInstance(Date effectiveTime, int instanceCount[], ELEvaluator eval) { 1421 Date datasetInitialInstance = getInitialInstance(eval); 1422 TimeUnit dsTimeUnit = getDSTimeUnit(eval); 1423 TimeZone dsTZ = getDatasetTZ(eval); 1424 int dsFreq = getDSFrequency(eval); 1425 // Convert Date to Calendar for corresponding TZ 1426 Calendar current = Calendar.getInstance(dsTZ); 1427 current.setTime(datasetInitialInstance); 1428 1429 Calendar calEffectiveTime = new GregorianCalendar(dsTZ); 1430 calEffectiveTime.setTime(effectiveTime); 1431 if (instanceCount == null) { // caller doesn't care about this value 1432 instanceCount = new int[1]; 1433 } 1434 instanceCount[0] = 0; 1435 if (current.compareTo(calEffectiveTime) > 0) { 1436 return null; 1437 } 1438 1439 switch(dsTimeUnit) { 1440 case MINUTE: 1441 instanceCount[0] = (int) ((effectiveTime.getTime() - datasetInitialInstance.getTime()) / MINUTE_MSEC); 1442 break; 1443 case HOUR: 1444 instanceCount[0] = (int) ((effectiveTime.getTime() - datasetInitialInstance.getTime()) / HOUR_MSEC); 1445 break; 1446 case DAY: 1447 case END_OF_DAY: 1448 instanceCount[0] = (int) ((effectiveTime.getTime() - datasetInitialInstance.getTime()) / DAY_MSEC); 1449 break; 1450 case WEEK: 1451 case END_OF_WEEK: 1452 instanceCount[0] = (int) ((effectiveTime.getTime() - datasetInitialInstance.getTime()) / WEEK_MSEC); 1453 break; 1454 case MONTH: 1455 case END_OF_MONTH: 1456 int diffYear = calEffectiveTime.get(Calendar.YEAR) - current.get(Calendar.YEAR); 1457 instanceCount[0] = diffYear * 12 + calEffectiveTime.get(Calendar.MONTH) - current.get(Calendar.MONTH); 1458 break; 1459 case YEAR: 1460 instanceCount[0] = calEffectiveTime.get(Calendar.YEAR) - current.get(Calendar.YEAR); 1461 break; 1462 default: 1463 throw new IllegalArgumentException("Unhandled dataset time unit " + dsTimeUnit); 1464 } 1465 1466 if (instanceCount[0] > 2) { 1467 instanceCount[0] = (instanceCount[0] / dsFreq); 1468 current.add(dsTimeUnit.getCalendarUnit(), instanceCount[0] * dsFreq); 1469 } else { 1470 instanceCount[0] = 0; 1471 } 1472 while (!current.getTime().after(effectiveTime)) { 1473 current.add(dsTimeUnit.getCalendarUnit(), dsFreq); 1474 instanceCount[0]++; 1475 } 1476 current.add(dsTimeUnit.getCalendarUnit(), -dsFreq); 1477 instanceCount[0]--; 1478 return current; 1479 } 1480 1481 /** 1482 * Find the current instance based on effectiveTime (i.e Action_Creation_Time or Action_Start_Time) 1483 * 1484 * @return current instance i.e. current(0) returns null if effectiveTime is earlier than Initial Instance time of 1485 * the dataset. 1486 */ 1487 private static Calendar getCurrentInstance_old(Date effectiveTime, int instanceCount[], ELEvaluator eval) { 1488 Date datasetInitialInstance = getInitialInstance(eval); 1489 TimeUnit dsTimeUnit = getDSTimeUnit(eval); 1490 TimeZone dsTZ = getDatasetTZ(eval); 1491 int dsFreq = getDSFrequency(eval); 1492 // Convert Date to Calendar for corresponding TZ 1493 Calendar current = Calendar.getInstance(); 1494 current.setTime(datasetInitialInstance); 1495 current.setTimeZone(dsTZ); 1496 1497 Calendar calEffectiveTime = Calendar.getInstance(); 1498 calEffectiveTime.setTime(effectiveTime); 1499 calEffectiveTime.setTimeZone(dsTZ); 1500 if (instanceCount == null) { // caller doesn't care about this value 1501 instanceCount = new int[1]; 1502 } 1503 instanceCount[0] = 0; 1504 if (current.compareTo(calEffectiveTime) > 0) { 1505 return null; 1506 } 1507 Calendar origCurrent = (Calendar) current.clone(); 1508 while (current.compareTo(calEffectiveTime) <= 0) { 1509 current = (Calendar) origCurrent.clone(); 1510 instanceCount[0]++; 1511 current.add(dsTimeUnit.getCalendarUnit(), instanceCount[0] * dsFreq); 1512 } 1513 instanceCount[0]--; 1514 1515 current = (Calendar) origCurrent.clone(); 1516 current.add(dsTimeUnit.getCalendarUnit(), instanceCount[0] * dsFreq); 1517 return current; 1518 } 1519 1520 public static Calendar getEffectiveNominalTime() { 1521 Date datasetInitialInstance = getInitialInstance(); 1522 TimeZone dsTZ = getDatasetTZ(); 1523 // Convert Date to Calendar for corresponding TZ 1524 Calendar current = Calendar.getInstance(); 1525 current.setTime(datasetInitialInstance); 1526 current.setTimeZone(dsTZ); 1527 1528 Calendar calEffectiveTime = Calendar.getInstance(); 1529 calEffectiveTime.setTime(getActionCreationtime()); 1530 calEffectiveTime.setTimeZone(dsTZ); 1531 if (current.compareTo(calEffectiveTime) > 0) { 1532 // Nominal Time < initial Instance 1533 // TODO: getClass() call doesn't work from static method. 1534 // XLog.getLog("CoordELFunction.class").warn("ACTION CREATED BEFORE INITIAL INSTACE "+ 1535 // current.getTime()); 1536 return null; 1537 } 1538 return calEffectiveTime; 1539 } 1540 1541 /** 1542 * @return dataset frequency in minutes 1543 */ 1544 private static int getDSFrequency() { 1545 ELEvaluator eval = ELEvaluator.getCurrent(); 1546 return getDSFrequency(eval); 1547 } 1548 1549 /** 1550 * @return dataset frequency in minutes 1551 */ 1552 private static int getDSFrequency(ELEvaluator eval) { 1553 SyncCoordDataset ds = (SyncCoordDataset) eval.getVariable(DATASET); 1554 if (ds == null) { 1555 throw new RuntimeException("Associated Dataset should be defined with key " + DATASET); 1556 } 1557 return ds.getFrequency(); 1558 } 1559 1560 /** 1561 * @return dataset TimeUnit 1562 */ 1563 private static TimeUnit getDSTimeUnit() { 1564 ELEvaluator eval = ELEvaluator.getCurrent(); 1565 return getDSTimeUnit(eval); 1566 } 1567 1568 /** 1569 * @return dataset TimeUnit 1570 */ 1571 public static TimeUnit getDSTimeUnit(ELEvaluator eval) { 1572 SyncCoordDataset ds = (SyncCoordDataset) eval.getVariable(DATASET); 1573 if (ds == null) { 1574 throw new RuntimeException("Associated Dataset should be defined with key " + DATASET); 1575 } 1576 return ds.getTimeUnit(); 1577 } 1578 1579 /** 1580 * @return dataset TimeZone 1581 */ 1582 public static TimeZone getDatasetTZ() { 1583 ELEvaluator eval = ELEvaluator.getCurrent(); 1584 return getDatasetTZ(eval); 1585 } 1586 1587 /** 1588 * @return dataset TimeZone 1589 */ 1590 public static TimeZone getDatasetTZ(ELEvaluator eval) { 1591 SyncCoordDataset ds = (SyncCoordDataset) eval.getVariable(DATASET); 1592 if (ds == null) { 1593 throw new RuntimeException("Associated Dataset should be defined with key " + DATASET); 1594 } 1595 return ds.getTimeZone(); 1596 } 1597 1598 /** 1599 * @return dataset TimeUnit 1600 */ 1601 private static TimeUnit getDSEndOfFlag() { 1602 ELEvaluator eval = ELEvaluator.getCurrent(); 1603 return getDSEndOfFlag(eval); 1604 } 1605 1606 /** 1607 * @return dataset TimeUnit 1608 */ 1609 private static TimeUnit getDSEndOfFlag(ELEvaluator eval) { 1610 SyncCoordDataset ds = (SyncCoordDataset) eval.getVariable(DATASET); 1611 if (ds == null) { 1612 throw new RuntimeException("Associated Dataset should be defined with key " + DATASET); 1613 } 1614 return ds.getEndOfDuration();// == null ? "": ds.getEndOfDuration(); 1615 } 1616 1617 /** 1618 * Return a job configuration property for the coordinator. 1619 * 1620 * @param property property name. 1621 * @return the value of the property, <code>null</code> if the property is undefined. 1622 */ 1623 public static String coord_conf(String property) { 1624 ELEvaluator eval = ELEvaluator.getCurrent(); 1625 return (String) eval.getVariable(property); 1626 } 1627 1628 /** 1629 * Return the user that submitted the coordinator job. 1630 * 1631 * @return the user that submitted the coordinator job. 1632 */ 1633 public static String coord_user() { 1634 ELEvaluator eval = ELEvaluator.getCurrent(); 1635 return (String) eval.getVariable(OozieClient.USER_NAME); 1636 } 1637 1638 /** 1639 * Takes two offset times and returns a list of multiples of the frequency offset from the effective nominal time that occur 1640 * between them. The caller should make sure that startCal is earlier than endCal. 1641 * <p> 1642 * As a simple example, assume its the same day: startCal is 1:00, endCal is 2:00, frequency is 20min, and effective nominal 1643 * time is 1:20 -- then this method would return a list containing: -20, 0, 20, 40, 60 1644 * 1645 * @param startCal The earlier offset time 1646 * @param endCal The later offset time 1647 * @param eval The ELEvaluator to use; cannot be null 1648 * @return A list of multiple of the frequency offset from the effective nominal time that occur between the startCal and endCal 1649 */ 1650 public static List<Integer> expandOffsetTimes(Calendar startCal, Calendar endCal, ELEvaluator eval) { 1651 List<Integer> expandedFreqs = new ArrayList<Integer>(); 1652 // Use eval because the "current" eval isn't set 1653 int freq = getDSFrequency(eval); 1654 TimeUnit freqUnit = getDSTimeUnit(eval); 1655 Calendar cal = getCurrentInstance(getActionCreationtime(eval), null, eval); 1656 int totalFreq = 0; 1657 if (startCal.before(cal)) { 1658 while (cal.after(startCal)) { 1659 cal.add(freqUnit.getCalendarUnit(), -freq); 1660 totalFreq += -freq; 1661 } 1662 if (cal.before(startCal)) { 1663 cal.add(freqUnit.getCalendarUnit(), freq); 1664 totalFreq += freq; 1665 } 1666 } 1667 else if (startCal.after(cal)) { 1668 while (cal.before(startCal)) { 1669 cal.add(freqUnit.getCalendarUnit(), freq); 1670 totalFreq += freq; 1671 } 1672 } 1673 // At this point, cal is the smallest multiple of the dataset frequency that is >= to the startCal and offset from the 1674 // effective nominal time. Now we can find all of the instances that occur between startCal and endCal, inclusive. 1675 while (cal.before(endCal) || cal.equals(endCal)) { 1676 expandedFreqs.add(totalFreq); 1677 cal.add(freqUnit.getCalendarUnit(), freq); 1678 totalFreq += freq; 1679 } 1680 return expandedFreqs; 1681 } 1682 1683 /** 1684 * Resolve the offset time from the effective nominal time 1685 * 1686 * @param n offset amount (integer) 1687 * @param timeUnit TimeUnit for offset n ("MINUTE", "HOUR", "DAY", "MONTH", "YEAR") 1688 * @param eval The ELEvaluator to use; or null to use the "current" eval 1689 * @return A Calendar of the offset time 1690 */ 1691 public static Calendar resolveOffsetRawTime(int n, TimeUnit timeUnit, ELEvaluator eval) { 1692 // Use eval if given (for when the "current" eval isn't set) 1693 Calendar cal; 1694 if (eval == null) { 1695 cal = getCurrentInstance(getActionCreationtime(), null); 1696 } 1697 else { 1698 cal = getCurrentInstance(getActionCreationtime(eval), null, eval); 1699 } 1700 if (cal == null) { 1701 XLog.getLog(CoordELFunctions.class).warn("If the initial instance of the dataset is later than the nominal time, an" 1702 + " empty string is returned. This means that no data is available at the offset instance specified by the user" 1703 + " and the user could try modifying his or her initial-instance to an earlier time."); 1704 return null; 1705 } 1706 cal.add(timeUnit.getCalendarUnit(), n); 1707 return cal; 1708 } 1709}