/* ************************************************************************ # # DivConq # # http://divconq.com/ # # Copyright: # Copyright 2014 eTimeline, LLC. All rights reserved. # # License: # See the license.txt file in the project's top-level directory for details. # # Authors: # * Andy White # ************************************************************************ */ package divconq.work; import java.util.ArrayList; import java.util.List; import java.util.UUID; import org.joda.time.DateTime; import divconq.bus.Message; import divconq.hub.Hub; import divconq.lang.op.IOperationObserver; import divconq.lang.op.OperationCallback; import divconq.lang.op.OperationContext; import divconq.lang.op.OperationLogger; import divconq.lang.op.OperationObserver; import divconq.lang.op.OperationResult; import divconq.log.Logger; import divconq.struct.FieldStruct; import divconq.struct.ListStruct; import divconq.struct.RecordStruct; import divconq.struct.Struct; import divconq.util.StringUtil; // conforms to dcTaskInfo data type public class Task { static public String nextTaskId() { return Task.nextTaskId("DEFAULT"); } static public String nextTaskId(String part) { return OperationContext.getHubId() + "_" + part + "_" + UUID.randomUUID().toString().replace("-", ""); } // create a subtask of a running task public static Task subtask(TaskRun run, String suffix, OperationCallback cb) { Task t = new Task(); Task parent = run.getTask(); t.context = parent.context.subContext(); // sub tasks can be found through "child" context //t.withId(parent.getId() + "_" + Session.nextUUId()); t.withId(Task.nextTaskId()); t.withTitle(parent.getTitle() + " - Subtask: " + suffix); t.withTimeout(parent.getTimeout()); t.withThrottle(parent.getThrottle()); // subtasks should almost always use default bucket if (cb != null) { t.withObserver(new OperationObserver() { @Override public void completed(OperationContext ctx) { cb.complete(); } }); } return t; } // used during run protected IWork work = null; protected List<IOperationObserver> observers = null; // used with run or queueable protected OperationContext context = null; protected RecordStruct info = null; public Task() { this.info = new RecordStruct(); this.context = OperationContext.get(); } public Task(RecordStruct info) { this.info = info; if (!info.isFieldEmpty("Context")) this.context = OperationContext.allocate(info.getFieldAsRecord("Context")); else this.context = OperationContext.get().subContext(); } public Task copy() { RecordStruct clone = (RecordStruct) this.info.deepCopyExclude("Context"); Task t = new Task(clone); t.context = this.context.subContext(); // ok not to copy, it is immutable return t; } public RecordStruct freezeToRecord() { RecordStruct clone = (RecordStruct) this.info.deepCopy(); clone.setField("Context", this.context.freezeToRecord()); return clone; } public Task withWork(IWork work) { this.work = work; if (this.work != null) this.info.setField("WorkClassname", this.work.getClass().getCanonicalName()); return this; } public Task withWork(Runnable work) { return this.withWork(new WorkAdapter(work)); } public Task withWork(Class<? extends IWork> classref) { this.info.setField("WorkClassname", classref.getCanonicalName()); return this; } // class name public Task withWork(String classname) { this.info.setField("WorkClassname", classname); return this; } public IWork getWork() { if (this.work == null) this.work = (IWork) Hub.instance.getInstance(this.getWorkClassname()); return this.work; } // won't create if not present public IWork getWorkInstance() { return this.work; } public String getWorkClassname() { return this.info.getFieldAsString("WorkClassname"); } public Task withBucket(String v) { this.info.setField("Bucket", v); return this; } public String getBucket() { String name = this.info.getFieldAsString("Bucket"); if (StringUtil.isEmpty(name)) name = "Default"; return name; } public Task withContext(OperationContext v) { this.context = v; return this; } public Task withSubContext() { this.context = OperationContext.get().subContext(); return this; } public Task withRootContext() { this.context = OperationContext.allocateRoot(); return this; } public Task withGuestContext() { this.context = OperationContext.allocateGuest(); return this; } public OperationContext getContext() { return this.context; } public Task withObserver(IOperationObserver watcher) { if (this.observers == null) this.observers = new ArrayList<>(); if (!this.observers.contains(watcher)) this.observers.add(watcher); if (watcher instanceof RecordStruct) { RecordStruct w = (RecordStruct)watcher; w.setField("_Classname", watcher.getClass().getCanonicalName()); return this.withObserverRec(w); } return this.withObserver(watcher.getClass().getCanonicalName()); } public Task withObserver(Class<? extends IOperationObserver> classref) { return this.withObserver(classref.getCanonicalName()); } public Task withObserver(String classname) { return this.withObserverRec(new RecordStruct( new FieldStruct("_Classname", classname) )); } public Task withObserverRec(RecordStruct observer) { ListStruct buildobservers = this.info.getFieldAsList("Observers"); if (buildobservers == null) { buildobservers = new ListStruct(); this.info.setField("Observers", buildobservers); } for (Struct rs : buildobservers.getItems()) { RecordStruct rob = (RecordStruct)rs; if (observer.getFieldAsString("_Classname").equals(rob.getFieldAsString("_Classname"))) return this; } buildobservers.addItem(observer); return this; } public Task withDefaultLogger() { return this.withObserver(OperationLogger.class); } public List<IOperationObserver> getObservers() { if (this.observers != null) return this.observers; this.observers = new ArrayList<>(); ListStruct buildobservers = this.info.getFieldAsList("Observers"); if (buildobservers != null) { for (Struct s : buildobservers.getItems()) { RecordStruct orec = (RecordStruct) s; if (orec.isFieldEmpty("_Classname")) { Logger.warn("Missing observer classname (" + this.getId() + "): " + orec); continue; } IOperationObserver observer = (IOperationObserver) Hub.instance.getInstance(orec.getFieldAsString("_Classname").toString()); if (observer instanceof RecordStruct) ((RecordStruct)observer).copyFields(orec); this.observers.add(observer); } } return this.observers; } /* public String getObserverClassname() { return this.info.getFieldAsString("ObserverClassname"); } public boolean hasObserver() { if (this.observer != null) return true; return !this.info.isFieldEmpty("ObserverClassname"); } */ public Task withUsesTempFolder(boolean v) { this.info.setField("UsesTempFolder", v); return this; } public boolean isUsesTempFolder() { return this.info.getFieldAsBooleanOrFalse("UsesTempFolder"); } public Task withId(String v) { this.info.setField("Id", v); return this; } public String getId() { return this.info.getFieldAsString("Id"); } public Task withTitle(String v) { this.info.setField("Title", v); return this; } public String getTitle() { return this.info.getFieldAsString("Title"); } public Task withStatus(String v) { this.info.setField("Status", v); return this; } public String getStatus() { return this.info.getFieldAsString("Status"); } public Task withSquad(String v) { this.info.setField("Squad", v); return this; } public String getSquad() { return this.info.getFieldAsString("Squad"); } public Task withHub(String v) { this.info.setField("HubId", v); return this; } public String getHub() { return this.info.getFieldAsString("HubId"); } public Task withWorkId(String v) { this.info.setField("WorkId", v); return this; } public String getWorkId() { return this.info.getFieldAsString("WorkId"); } public Task withAuditId(String v) { this.info.setField("AuditId", v); return this; } public String getAuditId() { return this.info.getFieldAsString("AuditId"); } public boolean hasAuditId() { return !this.info.isFieldEmpty("AuditId"); } public Task withCurrentTry(int v) { this.info.setField("CurrentTry", v); return this; } public int getCurrentTry() { return (int)this.info.getFieldAsInteger("CurrentTry", 0); } public void incCurrentTry() { int v = (int)this.info.getFieldAsInteger("CurrentTry", 0) + 1; this.info.setField("CurrentTry", v); } public Task withMaxTries(int v) { this.info.setField("MaxTries", v); return this; } public int getMaxTries() { return (int)this.info.getFieldAsInteger("MaxTries", 1); } public Task withThrottle(int v) { this.info.setField("Throttle", v); return this; } public Task withThrottleIfEmpty(int v) { if (this.info.isFieldEmpty("Throttle")) this.info.setField("Throttle", v); return this; } // default to 2 resumes public int getThrottle() { return (int)this.info.getFieldAsInteger("Throttle", 2); } public Task withClaimedStamp(String v) { this.info.setField("ClaimedStamp", v); return this; } public String getClaimedStamp() { return this.info.getFieldAsString("ClaimedStamp"); } public Task withAddStamp(DateTime v) { this.info.setField("AddStamp", v); return this; } public DateTime getAddStamp() { return this.info.getFieldAsDateTime("AddStamp"); } // do not retry this task on the queue public boolean getFinalTry() { return (this.info.getFieldAsBooleanOrFalse("FinalTry") || (this.getCurrentTry() >= this.getMaxTries())); } // finish the current run, but go no further, don't try again even if Tries left public Task withFinalTry(boolean v) { this.info.setField("FinalTry", v); return this; } public Task withSetTags(String... v) { this.info.setField("Tags", new ListStruct((Object[])v)); return this; } public Task withSetTags(ListStruct v) { this.info.setField("Tags", v); return this; } public Task withAddTags(String... v) { if (this.info.isFieldEmpty("Tags")) this.info.setField("Tags", new ListStruct((Object[])v)); else this.info.getFieldAsList("Tags").addItem((Object[])v); return this; } public Task withAddTags(ListStruct v) { if (this.info.isFieldEmpty("Tags")) this.info.setField("Tags", v); else this.info.getFieldAsList("Tags").addItem(v); return this; } public ListStruct getTags() { return this.info.getFieldAsList("Tags"); } /** * @param tags to search for with this task * @return true if this task has one of the requested tags */ public boolean isTagged(String... tags) { if (this.info.isFieldEmpty("Tags")) return false; for (Struct shas : this.info.getFieldAsList("Tags").getItems()) { String has = shas.toString(); for (String wants : tags) { if (has.equals(wants)) return true; } } return false; } public Task withParams(RecordStruct v) { this.info.setField("Params", v); return this; } public RecordStruct getParams() { return this.info.getFieldAsRecord("Params"); } public Message getParamsAsMessage() { return (Message) this.info.getFieldAsRecord("Params"); } public Task withExtra(RecordStruct v) { this.info.setField("Extra", v); return this; } public RecordStruct getExtra() { return this.info.getFieldAsRecord("Extra"); } /* * Timeout is when nothing happens for v minutes...see Overdue also * * @param v * @return */ public Task withTimeout(int v) { this.info.setField("Timeout", v); //if (v > this.getDeadline()) // this.withDeadline(v + 1); return this; } // in minutes public int getTimeout() { return (int) this.info.getFieldAsInteger("Timeout", 1); } public int getTimeoutMS() { return (int) this.info.getFieldAsInteger("Timeout", 1) * 60 * 1000; // convert to ms } /* * Deadline is v minutes until the task must complete, see Timeout also * * @param v * @return */ public Task withDeadline(int v) { this.info.setField("Deadline", v); return this; } // stalled even if still active, not getting anything done // in minutes public int getDeadline() { return (int) this.info.getFieldAsInteger("Deadline", 0); } public int getDeadlineMS() { return (int) this.info.getFieldAsInteger("Deadline", 0) * 60 * 1000; // convert to ms } public OperationResult validate() { return this.info.validate("dcTaskInfo"); } // happens after submit to pool or to queue public void prep() { if (this.info.isFieldEmpty("Title")) this.info.setField("Title", "[unnamed]"); if (this.info.isFieldEmpty("Id")) this.info.setField("Id", Task.nextTaskId()); } /* doesn't work with lambda's // this builder is going to be used with another task (repeat task) so cleanup public void reset() { // if we have the class name then start with a fresh instance each run if (!this.info.isFieldEmpty("WorkClassname")) this.work = null; // if we have the class name then start with a fresh instance each run if (!this.info.isFieldEmpty("ObserverClassname")) this.observers = null; this.info.removeField("FinalTry"); } */ public boolean isFromWorkQueue() { return (StringUtil.isNotEmpty(this.getWorkId())); } @Override public String toString() { return this.getTitle() + " (" + this.getId() + ")"; } public RecordStruct status() { return new RecordStruct( this.info.getFieldStruct("WorkId"), new FieldStruct("TaskId", this.info.getField("Id")), this.info.getFieldStruct("Title"), new FieldStruct("MaxTry", this.getMaxTries()), new FieldStruct("Added", this.getAddStamp()), new FieldStruct("Try", this.getCurrentTry()) ); } }