/** * This file is part of Waarp Project. * * Copyright 2009, Frederic Bregier, and individual contributors by the @author tags. See the * COPYRIGHT.txt in the distribution for a full listing of individual contributors. * * All Waarp Project is free software: you can redistribute it and/or modify it under the terms of * the GNU General Public License as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along with Waarp. If not, see * <http://www.gnu.org/licenses/>. */ package org.waarp.openr66.context.task; import java.sql.Timestamp; import java.util.Calendar; import java.util.Date; import java.util.Map; import org.waarp.common.logging.WaarpLogger; import org.waarp.common.logging.WaarpLoggerFactory; import org.waarp.openr66.context.ErrorCode; import org.waarp.openr66.context.R66Result; import org.waarp.openr66.context.R66Session; import org.waarp.openr66.context.task.exception.OpenR66RunnerErrorException; import org.waarp.openr66.database.data.DbTaskRunner; /** * Reschedule Transfer task to a time delayed by the specified number of milliseconds, if the error * code is one of the specified codes and the optional intervals of date are compatible with the new * time schedule<br> * <br> * * Result of arguments will be as following options (the two first are mandatory):<br> * <br> * * "-delay ms" where ms is the added number of ms on current time before retry on schedule<br> * <br> * "-case errorCode,errorCode,..." where errorCode is one of the following error of the current * transfer (either literal or code in 1 character:<br> * ConnectionImpossible(C), ServerOverloaded(l), BadAuthent(A), ExternalOp(E), TransferError(T), * MD5Error(M), Disconnection(D), RemoteShutdown(r), FinalOp(F), Unimplemented(U), Shutdown(S), * RemoteError(R), Internal(I), StoppedTransfer(H), CanceledTransfer(K), Warning(W), Unknown(-), * QueryAlreadyFinished(Q), QueryStillRunning(s), NotKnownHost(N), QueryRemotelyUnknown(u), * FileNotFound(f), CommandNotFound(c), PassThroughMode(p)<br> * <br> * "-between start;end" and/or "-notbetween start;end" (multiple times are allowed, start or end can * be not set) and where start and stop are in the following format:<br> * Yn:Mn:Dn:Hn:mn:Sn where n is a number for each time specification, each specification is * optional, as Y=Year, M=Month, D=Day, H=Hour, m=minute, s=second.<br> * Format can be X+n, X-n, X=n or Xn where X+-n means adding/subtracting n to current date value, * while X=n or Xn means setting exact value<br> * If one time specification is not set, it is based on the current date.<br> * <br> * "-count limit" will be the limit of retry. The current value limit is taken from the "transferInfo" internal code * (not any more the "information of transfer")and not from the rule * as "{"CPTLIMIT": limit}" as JSON code. Each time this function is called, the * limit value will be replaced as newlimit = limit - 1 in the "transferInfo" as "{"CPTLIMIT": limit}" as JSON code.<br> * To ensure correctness, the value must be in the "transferInfo" internal code since this value will be * changed statically in the "transferInfo". If taken from the rule, it will be wrong since * the value will never decrease. However, a value must be setup in the rule in order to reset the value * when the count reach 0. <br> * So in the rule, "-count resetlimit" must be present, where resetlimit will be * the new value set when the limit reach 0, and in the "transferInfo" internal code, * "{"CPTLIMIT": limit}" as JSON code must be present. If one is missing, the condition is not applied. <br> * <br> * If "-notbetween" is specified, the planned date must not be in the area.<br> * If "-between" is specified, the planned date must be found in any such specified areas (could be * in any of the occurrence). If not specified, it only depends on "-notbetween".<br> * If none is specified, the planned date is always valid.<br> * <br> * * Note that if a previous called to a reschedule was done for this attempt and was successful, the * following calls will be ignored.<br> * <br> * <B>Important note: any subsequent task will be ignored and not executed once the reschedule is accepted.</B><br> * <br> * In case start > end, end will be +1 day<br> * In case start and end < current planned date, both will have +1 day.<br> * <br> * * Example: -delay 3600000 -case ConnectionImpossible,ServerOverloaded,Shutdown -notbetween * H7:m0:S0;H19:m0:S0 -notbetween H1:m0:S0;H=3:m0:S0<br> * means retry in case of error during initialization of connection in 1 hour if not between 7AM to * 7PM and not between 1AM to 3AM.<br> * * @author Frederic Bregier * */ public class RescheduleTransferTask extends AbstractTask { /** * Internal Logger */ private static final WaarpLogger logger = WaarpLoggerFactory .getLogger(RescheduleTransferTask.class); /** * Delimiter for -count option in Reschedule to be placed in the transfer info of transfer as * {"CPTLIMIT": limit} where limit is an integer. */ public static final String CPTLIMIT = "CPTLIMIT"; public static final String CPTTOTAL = "CPTTOTAL"; protected long newdate = 0; protected Calendar newDate = null; protected boolean countUsed = false; protected int limitCount = -1; protected int totalCount = 0; protected int resetCount = -1; protected DbTaskRunner runner = null; /** * @param argRule * @param delay * @param argTransfer * @param session */ public RescheduleTransferTask(String argRule, int delay, String argTransfer, R66Session session) { super(TaskType.RESCHEDULE, delay, argRule, argTransfer, session); } @Override public void run() { logger.info("Reschedule with " + argRule + ":" + argTransfer + " and {}", session); runner = session.getRunner(); if (runner == null) { futureCompletion.setFailure(new OpenR66RunnerErrorException( "No valid runner in Reschedule")); return; } if (runner.isRescheduledTransfer()) { // Already rescheduled so ignore R66Result result = new R66Result(session, false, ErrorCode.Warning, runner); futureCompletion.setResult(result); logger.warn("Transfer already Rescheduled: " + runner.toShortString()); futureCompletion.setSuccess(); return; } if (runner.isSelfRequested()) { // Self Requested Request so reschedule is ignored R66Result result = new R66Result(session, false, ErrorCode.LoopSelfRequestedHost, runner); futureCompletion.setResult(result); futureCompletion.setFailure(new OpenR66RunnerErrorException( "No valid runner in Reschedule since Self Requested")); return; } String finalname = argRule; finalname = getReplacedValue(finalname, argTransfer.split(" ")); String[] args = finalname.split(" "); if (args.length < 4) { R66Result result = new R66Result(session, false, ErrorCode.Warning, runner); futureCompletion.setResult(result); logger.warn("Not enough argument in Reschedule: " + runner.toShortString()); futureCompletion.setSuccess(); return; } if (!validateArgs(args)) { R66Result result = new R66Result(session, false, ErrorCode.Warning, runner); futureCompletion.setResult(result); logger.warn("Reschedule unallowed due to argument: " + runner.toShortString()); futureCompletion.setSuccess(); return; } if (countUsed) { limitCount--; if (limitCount >= 0) { // restart is allowed so resetting to new limitCount resetCount = limitCount; } resetInformation(resetCount); if (limitCount < 0) { // Must not reschedule since limit is reached try { runner.saveStatus(); } catch (OpenR66RunnerErrorException e) { } R66Result result = new R66Result(session, false, ErrorCode.Warning, runner); futureCompletion.setResult(result); logger.warn("Reschedule unallowed due to limit reached: " + runner.toShortString()); futureCompletion.setSuccess(); return; } } Timestamp start = new Timestamp(newdate); try { runner.setStart(start); if (runner.restart(true)) { runner.saveStatus(); } } catch (OpenR66RunnerErrorException e) { logger.error( "Prepare transfer in FAILURE " + runner.toShortString() + " <AT>" + (new Date(newdate)).toString() + "</AT>", e); futureCompletion.setFailure(new OpenR66RunnerErrorException( "Reschedule failed: " + e.getMessage(), e)); return; } runner.setRescheduledTransfer(); R66Result result = new R66Result(session, false, ErrorCode.Warning, runner); futureCompletion.setResult(result); logger.warn("Reschedule transfer in SUCCESS " + runner.toShortString() + " <AT>" + (new Date(newdate)).toString() + "</AT>"); futureCompletion.setSuccess(); } protected void resetInformation(int value) { Map<String, Object> root = runner.getTransferMap(); root.put(CPTLIMIT, value); try { totalCount = (Integer) root.get(CPTTOTAL); totalCount++; root.put(CPTTOTAL, totalCount); } catch (Exception e) { totalCount = 1; root.put(CPTTOTAL, totalCount); } runner.setTransferMap(root); } protected boolean validateArgs(String[] args) { boolean validCode = false; for (int i = 0; i < args.length; i++) { if (args[i].equalsIgnoreCase("-delay")) { i++; try { newdate = Long.parseLong(args[i]); } catch (NumberFormatException e) { logger.warn("Bad Long format: args[i]"); return false; } } else if (args[i].equalsIgnoreCase("-case")) { i++; if (!validCode) { String[] codes = args[i].split(","); for (int j = 0; j < codes.length; j++) { ErrorCode code = ErrorCode.getFromCode(codes[j]); if (session.getLocalChannelReference().getCurrentCode() == code) { logger.debug("Code valid: " + code); validCode = true; } } } } else if (args[i].equalsIgnoreCase("-count")) { i++; try { resetCount = Integer.parseInt(args[i]); } catch (NumberFormatException e) { logger.warn("ResetLimit is not an integer: " + args[i]); countUsed = false; return false; } Map<String, Object> root = runner.getTransferMap(); Integer limit = null; try { limit = (Integer) root.get(CPTLIMIT); } catch (Exception e) { logger.warn("Bad Long format: CPTLIMIT", e); return false; } if (limit != null) { limitCount = limit; } else { limitCount = resetCount; root.put(CPTLIMIT, limitCount); } countUsed = true; } } // now we have new delay plus code if (!validCode) { logger.warn("No valid Code found"); return false; } if (newdate <= 0) { logger.warn("Delay is negative: " + newdate); return false; } newdate += System.currentTimeMillis(); newDate = Calendar.getInstance(); newDate.setTimeInMillis(newdate); boolean betweenTest = false; boolean betweenResult = false; for (int i = 0; i < args.length; i++) { if (args[i].equalsIgnoreCase("-notbetween")) { i++; String[] elmts = args[i].split(";"); boolean startModified = false; String[] values = elmts[0].split(":"); Calendar start = getCalendar(values); if (start != null) { startModified = true; } else { start = Calendar.getInstance(); } boolean stopModified = false; values = elmts[1].split(":"); Calendar stop = getCalendar(values); if (stop != null) { stopModified = true; } else { stop = Calendar.getInstance(); } logger.debug("Dates before check: Not between " + start.getTime() + " and " + stop.getTime()); // Check that start < stop if (start.compareTo(stop) > 0) { // no so add 24H to stop stop.add(Calendar.DAY_OF_MONTH, 1); } // Check that start and stop > newDate (only start is checked since start <= stop) if (start.compareTo(newDate) < 0) { start.add(Calendar.DAY_OF_MONTH, 1); stop.add(Calendar.DAY_OF_MONTH, 1); } logger.debug("Dates after check: Not between " + start.getTime() + " and " + stop.getTime()); if (!startModified) { if (newDate.compareTo(stop) < 0) { logger.debug("newDate: " + newDate.getTime() + " Should not be between " + start.getTime() + " and " + stop.getTime()); return false; } } else if (start.compareTo(newDate) < 0) { if ((!stopModified) || (newDate.compareTo(stop) < 0)) { logger.debug("newDate: " + newDate.getTime() + " Should not be between " + start.getTime() + " and " + stop.getTime()); return false; } } } else if (args[i].equalsIgnoreCase("-between")) { i++; betweenTest = true; String[] elmts = args[i].split(";"); boolean startModified = false; String[] values = elmts[0].split(":"); Calendar start = getCalendar(values); if (start != null) { startModified = true; } else { start = Calendar.getInstance(); } boolean stopModified = false; values = elmts[1].split(":"); Calendar stop = getCalendar(values); if (stop != null) { stopModified = true; } else { stop = Calendar.getInstance(); } logger.debug("Dates before check: Between " + start.getTime() + " and " + stop.getTime()); // Check that start < stop if (start.compareTo(stop) > 0) { // no so add 24H to stop stop.add(Calendar.DAY_OF_MONTH, 1); } // Check that start and stop > newDate (only start is checked since start <= stop) if (start.compareTo(newDate) < 0) { start.add(Calendar.DAY_OF_MONTH, 1); stop.add(Calendar.DAY_OF_MONTH, 1); } logger.debug("Dates before check: Between " + start.getTime() + " and " + stop.getTime()); if (!startModified) { if (newDate.compareTo(stop) < 0) { logger.debug("newDate: " + newDate.getTime() + " is between " + start.getTime() + " and " + stop.getTime()); betweenResult = true; } } else if (start.compareTo(newDate) < 0) { if ((!stopModified) || (newDate.compareTo(stop) < 0)) { logger.debug("newDate: " + newDate.getTime() + " is between " + start.getTime() + " and " + stop.getTime()); betweenResult = true; } } } } if (betweenTest) { logger.debug("Since between is specified, do we found newDate: " + newDate.getTime() + " Result: " + betweenResult); return betweenResult; } logger.debug("newDate: " + newDate.getTime() + " rescheduled"); return true; } /** * * @param values * as X+n or X-n or X=n or Xn where X=Y/M/D/H/m/s, n=number and +/- meaning * adding/subtracting from current date and = meaning specific set value * @return the Calendar if any specification, or null if no calendar specified */ private Calendar getCalendar(String[] values) { Calendar newcal = Calendar.getInstance(); boolean isModified = false; for (int j = 0; j < values.length; j++) { if (values[j].length() > 1) { int addvalue = 0; // will be different of 0 int value = -1; // will be >= 0 switch (values[j].charAt(0)) { case '+': try { addvalue = Integer.parseInt(values[j].substring(2)); } catch (NumberFormatException e) { continue; } break; case '-': try { addvalue = Integer.parseInt(values[j].substring(1)); } catch (NumberFormatException e) { continue; } break; case '=': try { value = Integer.parseInt(values[j].substring(2)); } catch (NumberFormatException e) { continue; } break; default: // no sign try { value = Integer.parseInt(values[j].substring(1)); } catch (NumberFormatException e) { continue; } } switch (values[j].charAt(0)) { case 'Y': if (value >= 0) { newcal.set(Calendar.YEAR, value); } else { newcal.add(Calendar.YEAR, addvalue); } isModified = true; break; case 'M': if (value >= 0) { newcal.set(Calendar.MONTH, value); } else { newcal.add(Calendar.MONTH, addvalue); } isModified = true; break; case 'D': if (value >= 0) { newcal.set(Calendar.DAY_OF_MONTH, value); } else { newcal.add(Calendar.DAY_OF_MONTH, addvalue); } isModified = true; break; case 'H': if (value >= 0) { newcal.set(Calendar.HOUR_OF_DAY, value); } else { newcal.add(Calendar.HOUR_OF_DAY, addvalue); } isModified = true; break; case 'm': if (value >= 0) { newcal.set(Calendar.MINUTE, value); } else { newcal.add(Calendar.MINUTE, addvalue); } isModified = true; break; case 'S': if (value >= 0) { newcal.set(Calendar.SECOND, value); } else { newcal.add(Calendar.SECOND, addvalue); } isModified = true; break; } } } if (isModified) return newcal; return null; } }