/* * The MIT License (MIT) * * Copyright (c) 2016 Lachlan Dowding * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package permafrost.tundra.server; import com.wm.app.b2b.server.ServiceException; import com.wm.app.b2b.server.scheduler.ScheduledTask; import com.wm.data.IData; import com.wm.data.IDataCursor; import com.wm.data.IDataFactory; import com.wm.data.IDataUtil; import permafrost.tundra.data.IDataHelper; import permafrost.tundra.flow.ConditionEvaluator; import permafrost.tundra.lang.BooleanHelper; import permafrost.tundra.lang.ExceptionHelper; import permafrost.tundra.math.LongHelper; import permafrost.tundra.time.DateTimeHelper; import permafrost.tundra.time.DurationHelper; import permafrost.tundra.time.DurationPattern; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * A collection of convenience methods for working with Integration Server scheduled tasks. */ public final class ScheduleHelper { /** * Disallow instantiation of this class. */ private ScheduleHelper() {} /** * Returns the scheduled task identified by the given ID, or null if no task for that ID exists. * * @param identity The ID of the scheduled task to be returned. * @return The scheduled task with the given ID, or null if no task with that ID exists. * @throws ServiceException If an error occurs when retrieving scheduled tasks. */ public static IData get(String identity) throws ServiceException { if (identity == null || !exists(identity)) return null; IData pipeline = IDataFactory.create(); IDataCursor cursor = pipeline.getCursor(); IDataUtil.put(cursor, "taskID", identity); cursor.destroy(); pipeline = ServiceHelper.invoke("pub.scheduler:getTaskInfo", pipeline); cursor = pipeline.getCursor(); String type = IDataUtil.getString(cursor, "type"); String user = IDataUtil.getString(cursor, "runAsUser"); String target = IDataUtil.getString(cursor, "target"); String description = IDataUtil.getString(cursor, "description"); String lateness = IDataUtil.getString(cursor, "lateness"); String latenessAction = IDataUtil.getString(cursor, "latenessAction"); String service = IDataUtil.getString(cursor, "service"); long next = Long.parseLong(IDataUtil.getString(cursor, "nextRun")); int state = Integer.parseInt(IDataUtil.getString(cursor, "execState")); IData inputs = IDataUtil.getIData(cursor, "inputs"); boolean overlap = true; String startDate = null, startTime = null, startDateTime = null, endDate = null, endTime = null, endDateTime = null; IData info = null; if (type.equals("repeat")) { info = IDataUtil.getIData(cursor, "repeatingTaskInfo"); IDataCursor ic = info.getCursor(); String intervalSeconds = IDataUtil.getString(ic, "interval"); overlap = !Boolean.valueOf(IDataUtil.getString(ic, "doNotOverlap")); startDate = IDataUtil.getString(ic, "startDate"); startTime = IDataUtil.getString(ic, "startTime"); endDate = IDataUtil.getString(ic, "endDate"); endTime = IDataUtil.getString(ic, "endTime"); ic.destroy(); info = IDataFactory.create(); ic = info.getCursor(); IDataUtil.put(ic, "interval", DurationHelper.format(intervalSeconds, DurationPattern.SECONDS, DurationPattern.XML)); ic.destroy(); } else if (type.equals("complex")) { info = IDataUtil.getIData(cursor, "complexTaskInfo"); IDataCursor ic = info.getCursor(); overlap = !Boolean.valueOf(IDataUtil.getString(ic, "doNotOverlap")); startDate = IDataUtil.getString(ic, "startDate"); startTime = IDataUtil.getString(ic, "startTime"); endDate = IDataUtil.getString(ic, "endDate"); endTime = IDataUtil.getString(ic, "endTime"); String[] minutes = IDataUtil.getStringArray(ic, "minutes"); String[] hours = IDataUtil.getStringArray(ic, "hours"); String[] months = IDataUtil.getStringArray(ic, "months"); String[] daysOfMonth = IDataUtil.getStringArray(ic, "daysOfMonth"); String[] daysOfWeek = IDataUtil.getStringArray(ic, "daysOfWeek"); ic.destroy(); info = IDataFactory.create(); ic = info.getCursor(); if (months != null) IDataUtil.put(ic, "months", months); if (daysOfMonth != null) IDataUtil.put(ic, "days", daysOfMonth); if (daysOfWeek != null) IDataUtil.put(ic, "weekdays", daysOfWeek); if (hours != null) IDataUtil.put(ic, "hours", hours); if (minutes != null) IDataUtil.put(ic, "minutes", minutes); ic.destroy(); } else { info = IDataUtil.getIData(cursor, "oneTimeTaskInfo"); IDataCursor ic = info.getCursor(); startDate = endDate = IDataUtil.getString(ic, "date"); startTime = endTime = IDataUtil.getString(ic, "time"); ic.destroy(); info = null; } cursor.destroy(); IData output = IDataFactory.create(); cursor = output.getCursor(); IDataUtil.put(cursor, "id", identity); if (inputs != null) { IDataCursor inputCursor = inputs.getCursor(); String name = IDataUtil.getString(inputCursor, "$schedule.name"); inputCursor.destroy(); if (name != null) IDataUtil.put(cursor, "name", name); } if (description != null) IDataUtil.put(cursor, "description", description); IDataUtil.put(cursor, "type", type); IDataUtil.put(cursor, "service", service); String packageName = ServiceHelper.getPackageName(service); if (packageName != null) IDataUtil.put(cursor, "package", packageName); IDataUtil.put(cursor, "target", target); if (SchedulerHelper.self().equalsIgnoreCase(target)) { IDataUtil.put(cursor, "target.logical", "$self"); } else { IDataUtil.put(cursor, "target.logical", target); } IDataUtil.put(cursor, "user", user); String pattern = "yyyy/MM/dd HH:mm:ss"; if (startDate != null && startTime != null) { startDateTime = DateTimeHelper.format(startDate + " " + startTime, pattern, "datetime"); IDataUtil.put(cursor, "start", startDateTime); } if (endDate != null && endTime != null) { endDateTime = DateTimeHelper.format(endDate + " " + endTime, pattern, "datetime"); IDataUtil.put(cursor, "end", endDateTime); } IDataUtil.put(cursor, "overlap?", BooleanHelper.emit(overlap)); if (lateness != null) { IData late = IDataFactory.create(); IDataCursor lc = late.getCursor(); IDataUtil.put(lc, "duration", DurationHelper.format(lateness, DurationPattern.MINUTES, DurationPattern.XML)); if (latenessAction.equals("0") || latenessAction.equalsIgnoreCase("run immediately")) { latenessAction = "run immediately"; } else if (latenessAction.equals("1") || latenessAction.equalsIgnoreCase("skip and run at next scheduled interval")) { latenessAction = "skip and run at next scheduled interval"; } else if (latenessAction.equals("2") || latenessAction.equalsIgnoreCase("suspend")) { latenessAction = "suspend"; } IDataUtil.put(lc, "action", latenessAction); lc.destroy(); IDataUtil.put(cursor, "lateness", late); } if (info != null) IDataUtil.put(cursor, type, info); if (inputs != null && IDataHelper.size(inputs) > 0) IDataUtil.put(cursor, "pipeline", inputs); String stateString = null; switch(state) { case ScheduledTask.STATE_READY: stateString = "waiting"; break; case ScheduledTask.STATE_RUNNING: stateString = "running"; break; case ScheduledTask.STATE_SUSPENDED: stateString = "suspended"; break; case ScheduledTask.STATE_DELETED: stateString = "cancelled"; break; default: ExceptionHelper.raise("Scheduled task '" + identity + "' has unsupported status: " + state); break; } IDataUtil.put(cursor, "status", stateString); if (next > 0) IDataUtil.put(cursor, "next", DateTimeHelper.format(LongHelper.emit(next), "milliseconds", "datetime")); cursor.destroy(); return output; } /** * Returns the scheduled task identified by the given task name, or null if no task for that name exists. * * @param name The name of the task to be returned. * @return The task with the given name, or null if no task with the given name exists. * @throws ServiceException If an error occurs retrieving scheduled tasks. */ public static IData getByName(String name) throws ServiceException { if (name == null) return null; IData[] results = list(name, null, null, null); return results.length > 0 ? results[0] : null; } /** * Returns true if a scheduled task with the given ID exists. * * @param identity The ID of the scheduled task to check existence of. * @return Whether a scheduled task with the given ID exists. * @throws ServiceException If an error occurs when retrieving the scheduled tasks. */ public static boolean exists(String identity) throws ServiceException { return identity != null && Arrays.binarySearch(listAll(), identity) >= 0; } /** * Returns true if a scheduled task exists with the given name. * * @param name The name of the scheduled task to check existence of. * @return Whether a scheduled task with the given name exists. * @throws ServiceException If an error occurs when retrieving the scheduled tasks. */ public static boolean existsByName(String name) throws ServiceException { return getByName(name) != null; } /** * Returns all scheduled tasks matching the given filter condition. * * @param filter An optional filter condition which when true will include the matching task in the * returned results. * @param pipeline The pipeline used when evaluating the given filter condition. * @return The scheduled tasks that match the given name, service, and filter condition. * @throws ServiceException If an error occurs when retrieving scheduled tasks or evaluating filter conditions. */ public static IData[] list(String filter, IData pipeline) throws ServiceException { return list(null, filter, pipeline); } /** * Returns all scheduled tasks matching the given service, and filter condition. * * @param service An optional service name which filters the returned results to only tasks that execute * the given service. * @param filter An optional filter condition which when true will include the matching task in the * returned results. * @param pipeline The pipeline used when evaluating the given filter condition. * @return The scheduled tasks that match the given name, service, and filter condition. * @throws ServiceException If an error occurs when retrieving scheduled tasks or evaluating filter conditions. */ public static IData[] list(String service, String filter, IData pipeline) throws ServiceException { return list(null, service, filter, pipeline); } /** * Returns all scheduled tasks matching the given name, service, and filter condition. * * @param name An optional task name which filters the returned results to only tasks that have the * given task name. * @param service An optional service name which filters the returned results to only tasks that execute * the given service. * @param filter An optional filter condition which when true will include the matching task in the * returned results. * @param pipeline The pipeline used when evaluating the given filter condition. * @return The scheduled tasks that match the given name, service, and filter condition. * @throws ServiceException If an error occurs when retrieving scheduled tasks or evaluating filter conditions. */ public static IData[] list(String name, String service, String filter, IData pipeline) throws ServiceException { if (pipeline == null) pipeline = IDataFactory.create(); String[] identities = listAll(); List<IData> tasks = new ArrayList<IData>(identities.length); for (String identity : identities) { IData task = get(identity); boolean matched = true; if (name != null) { IDataCursor cursor = task.getCursor(); String taskName = IDataUtil.getString(cursor, "name"); cursor.destroy(); matched = name.equals(taskName); } if (matched && service != null) { IDataCursor cursor = task.getCursor(); String taskService = IDataUtil.getString(cursor, "service"); cursor.destroy(); matched = service.equals(taskService); } if (matched && filter != null) { IData scope = IDataUtil.clone(pipeline); IDataCursor cursor = scope.getCursor(); IDataUtil.put(cursor, "$schedule", task); cursor.destroy(); matched = ConditionEvaluator.evaluate(filter, scope); } if (matched) tasks.add(task); } return tasks.toArray(new IData[tasks.size()]); } /** * Returns all scheduled task IDs. * * @return All scheduled task IDs. * @throws ServiceException If an error is encountered while retrieving the schedule IDs. */ private static String[] listAll() throws ServiceException { return list(false); } /** * Returns currently running scheduled task IDs. * * @return Currently running scheduled task IDs. * @throws ServiceException If an error is encountered while retrieving the schedule IDs. */ private static String[] listRunning() throws ServiceException { return list(true); } /** * Returns all scheduled task IDs, or only those that are currently running if the given flag is true. * * @param running If true, only schedules currently running are returned, otherwise all are returned. * @return The resulting list of scheduled task IDs. * @throws ServiceException If an error is encountered while retrieving the schedule IDs. */ private static String[] list(boolean running) throws ServiceException { IData pipeline = IDataFactory.create(); IDataCursor cursor = pipeline.getCursor(); IDataUtil.put(cursor, "running", BooleanHelper.emit(running)); cursor.destroy(); pipeline = ServiceHelper.invoke("pub.scheduler:getTaskIDs", pipeline); cursor = pipeline.getCursor(); String[] list = IDataUtil.getStringArray(cursor, "taskIDs"); cursor.destroy(); if (list == null) { list = new String[0]; } else if (list.length > 1) { Arrays.sort(list); } return list; } }