Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   /* ************************************************************************
   #
   #  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.lang.op;
  
  import java.util.Arrays;
  import java.util.List;
  
  
  import divconq.hub.Hub;
Almost all code that executes after Hub.start should have a context. The context tells the code who the user responsible for the task is, what their access levels are (at a high level), what language/locale/chronology(timezone) they use, how to log the debug messages for the task, and whether or not the user has been authenticated or not. Although the task context is associated with the current thread, it is the task that the context belongs to, not the thread. If a task splits into multiple threads there is still one TaskContext, even if the task makes a remote call on DivConq's bus that remote call executes on the TaskContext. As long as you use the built-in features - work pool, scheduler, bus, database - the task context will smoothly come along with no effort from the app developer. A quick guide to what context to use where: Hub Context - useHubContext() The code is running as part of the Hub core. Root Context - useNewRoot() Root context is the same identity as Hub, but with useNewRoot() you get a new log id. Use this with code running batch tasks that belong to the system rather than a specific user. Guest Context - useNewGuest() Guest context is for use by an anonymous user. For example a user through the interchange (HTTP, FTP, SFTP, EDI, etc). User Context - new TaskContext + set(tc) When a user signs-in create and set a new context. No need to authenticate against the database that will happen automatically (as long as you follow DivConq development guidelines) so think of creating the user Task Context as information gathering not authentication.

Author(s):
Andy
  
  public class OperationContext {
  	static protected String runid = null;
  	static protected String hubid = "00001";
  	static protected OperationContext hubcontext = null;
  	static protected OperationContext defaultcontext = null;
  	
  	static protected ThreadLocal<OperationContextcontext = new ThreadLocal<OperationContext>();
  	static protected AtomicLong nextid = new AtomicLong();
  	
  	static {
  		
  		. = OperationContext.useNewRoot();
  		. = OperationContext.useNewGuest();		 
  		
 	}
 	
 	static public String getHubId() {
 	}
 	
 	static public void setHubId(String v) {
 		if (StringUtil.isEmpty(v))
 			return;
 		
 		// only set once
 			return;
 		
 		. = OperationContext.useNewRoot();		// reset the hub context 
 		. = OperationContext.useNewGuest();		 
 		
 	}
 	
 	static public String getRunId() {
 	}

Returns:
the hub context, use by code running in the "core" code of Hub (e.g. main thread)
 
 	static public OperationContext getHubContext() {
 	}

Sets the current thread to use the hub context

Returns:
the hub context, use by code running in the "core" code of Hub (e.g. main thread)
 
 	static public OperationContext useHubContext() {
 	}
 
 	public static void startHubContext(XElement config) {
 		// TODO load up info from config - this is the only time TC or UC
 		// should be mutated, only internally and only the special root
 		// instances
 		
 		OperationContext.updateHubContext();
 	}
 
 	public static void updateHubContext() {
 		// this is the only time TC or UC should be mutated, only internally
 		//  and only the special root instances
 		
 	}
 
 	// make sure messages size never gets too large, since server could run for months
 	// without a reset - this could throw off the markers in any OR pointing to hub/default
 	// but generally that is such a small problem...not worried about it.
 	// Hub startup and shutdown are exempt from this, which is when they are used most
 	/*
 	public static void cleanUp() {
 		while (OperationContext.hubcontext.messages.size() > 1000)
 			OperationContext.hubcontext.messages.remove(0);
 		
 		while (OperationContext.defaultcontext.messages.size() > 1000)
 			OperationContext.defaultcontext.messages.remove(0);
 	}
 	*/

Returns:
context of the current thread, if any otherwise the guest context
 
 	static public OperationContext get() {
 		
 		if (tc == null) {
 			// TODO someday monitor how often/where this happens
 			//System.out.println("someplace without a context");
 			
 		}
 		
 		return tc;
 	}
 	
 	// does the current thread have a context?
 	static public boolean hasContext() {
 		return (..get() != null);
 	}

Parameters:
v context for current thread to use
 
 	static public void set(OperationContext v) {
 		if (v != null)
 	}

Returns:
context of the current thread, if any, otherwise the hub context
 
 	static public OperationContext getOrHub() {
 		
 		if (tc == null)
 		
 		return tc;
 	}

Returns:
create a new guest context
 
 	static public OperationContext allocateGuest() {
 		// for occasions where no context is set when calling allocate - we need some context
 		if (!OperationContext.hasContext())
 
 	}

Sets the current thread to use a new guest context

Returns:
create a new guest context
 
 	static public OperationContext useNewGuest() {
 		OperationContext tc = OperationContext.allocateGuest();
 		return tc;
 	}

Returns:
create a new root context
 
 	static public OperationContext allocateRoot() {
 		// for occasions where no context is set when calling allocate - we need some context
 		if (!OperationContext.hasContext())
 
 	}

Sets the current thread to use a new root context

Returns:
create a new root context
 
 	static public OperationContext useNewRoot() {
 		OperationContext tc = OperationContext.allocateRoot();
 		return tc;
 	}
 	
 	/*
 	 * Sets the current thread to use a new context
 	 * 
 	 * @return create a new context
 	 */
 	static public OperationContext use(OperationContextBuilder tcb) {
 		OperationContext tc = OperationContext.allocate(tcb);
 		return tc;
 	}
 	
 	static public OperationContext use(UserContext ctxOperationContextBuilder tcb) {
 		OperationContext tc = OperationContext.allocate(ctxtcb);
 		return tc;
 	}
 	
 	/*
 	 * @param m create a task context from a message (RPC calls to dcBus), keep in mind
 	 * this is info gathering only, message must not be allowed to force an 
 	 * authenticated/elevated state inappropriately - from RPC clear "Elevated"
 	 * field before calling this
 	 */
 	static public OperationContext allocate(Message m) {
 		// for occasions where no context is set when calling allocate - we need some context
 		if (!OperationContext.hasContext())
 
 		return new OperationContext(m.getFieldAsRecord("Context")); 
 	}
 		
 	static public OperationContext allocate(RecordStruct ctx) {
 		// for occasions where no context is set when calling allocate - we need some context
 		if (!OperationContext.hasContext())
 
 		return new OperationContext(ctx); 
 	}
 	
 	static public OperationContext allocate(UserContext usrRecordStruct ctx) {
 		// for occasions where no context is set when calling allocate - we need some context
 		if (!OperationContext.hasContext())
 
 		return new OperationContext(usrctx); 
 	}
 	
 		// for occasions where no context is set when calling allocate - we need some context
 		if (!OperationContext.hasContext())
 
 		return new OperationContext(tcb.values); 
 	}
 		
 		// for occasions where no context is set when calling allocate - we need some context
 		if (!OperationContext.hasContext())
 
 		return new OperationContext(usrtcb.values); 
 	}	
 	
 	static protected String allocateOpId() {
 		
 		// TODO confirm this really does work
 		/*
 		if (num > 999999999999999L) {
 			synchronized (TaskContext.nextid) {
 				if (TaskContext.nextid.get()> 999999999999999L)
 					TaskContext.nextid.set(0);				
 			}
 			
 			num = TaskContext.nextid.getAndIncrement();
 		}
 		*/
 		
 		return OperationContext.getHubId() 
 			+ "_" + OperationContext.getRunId() 
 			+ "_" + StringUtil.leftPad(num + "", 15, '0');
 	}
 	
 	/*
 	 * set current thread context to null 
 	 */
 	static public void clear() {
 	}
 	
 	static public void isGuest(final FuncCallback<Booleancb) {
 		OperationContext.isGuest(OperationContext.get(), cb);
 	}
 	
 	/*
 	 * @return check to see if the task is really no more than a guest access.  does not change task context
 	 */
 	static public void isGuest(OperationContext ctxfinal FuncCallback<Booleancb) {
 		if ((ctx == null) || (ctx.userctx == null)) {
 			cb.setResult(false);
 			cb.complete();
 			return;
 		}
 			
 		if (ctx.userctx.looksLikeGuest()) {
 			cb.setResult(true);
 			cb.complete();
 			return;
 		}
 		
 			public void callback() {
 				cb.complete();
 			}
 		});
 	}
 
 	// generally don't mutate a context, but for sig-in support we need to
 	public static void switchUser(OperationContext ctxUserContext usr) {
 		// TODO change only if usr != this.opcontext.getUser  
 		ctx.userctx = usr;
 	}
 	
 	// instance code
 
 	// ======================================================
 	// these vars travel with calls to bus
 	// ======================================================
 	
 	protected RecordStruct opcontext = null;
 	protected UserContext userctx = null;
 	protected DebugLevel level = Logger.getGlobalLevel();
 	
 	protected boolean limitLog = true;
 	protected int logOffset = 0;
 	
 	protected List<RecordStructmessages = new ArrayList<>();
 	
 	// ======================================================
 	// these vars used only locally, not included in bus calls
 	// nor in any workqueue calls, these work locally only
 	// ======================================================
 	
 	// the current task run, if any
 	protected WeakReference<TaskRuntaskrun = null;
 	
 	protected OperationContext parent = null;
 
 	// progress tracking
     protected int progTotalSteps = 0;
     protected int progCurrStep = 0;
     protected String progStepName = null;
     protected int progComplete = 0;	
     protected String progMessage = null;
     
     protected IOperationLogger logger = null;
     
     // this tracks time stamp of signs of life from the job writing to the log/progress tracks
     // volatile helps keep threads on same page - issue found in code testing and this MAY have helped 
     volatile protected long lastactivity = System.currentTimeMillis();
 	
     public void touch() {
     	this. = System.currentTimeMillis();
     }
 
     // touch parent context too 
     public void deepTouch() {
     }
     
     public long getLastActivity() {
 		return this.;
 	}
 	
     public void setLimitLog(boolean v) {
 		this. = v;
 	}
     
     public boolean isLimitLog() {
     	return this.;
     }
     
     public int logMarker() {
     	return this. + this..size();
     }
     
 	// once elevated we can call any service we want, but first we must
 	// call a service we are allowed to call
 	
 	/*
 	 * Elevated tasks have been a) authenticated and b) passed successfully into
 	 * a service.  Once elevated all subsequent calls with the task no longer need
 	 * to be authenticated or authorized by DivConq framework (individual services/modules
 	 * may require it).  Meaning that "guest" cannot call "SendMail" unless it first 
 	 * goes through a service that is open to guests, such as password recovery.  
 	 * 
 	 * Mark the task context as elevated - typically app code does not need to call
 	 * this because the services and scheduler handlers decide when a task has met
 	 * the desired state. 
 	 */

Returns:
a unique task id - unique across all deployed hub, across runs of a hub
 
 	public String getOpId() {
 		return this..getFieldAsString("OpId");
 	}

Returns:
the the user context for this task (user context may be shared with other tasks)
 
 		return this.;
 	}

not all tasks will have a session, but if there is a session here it is. id is in the format of hubid_sessionid

Returns:
the id of the session that spawned this task
 
 	public String getSessionId() {
 		return this..getFieldAsString("SessionId");
 	}

not all tasks will have a session, but if there is a session here it is. sessions are local to a hub and are not transfered to another hub with the rest of the task info when calling a remote service.

Returns:
the session for this task (user context may be shared with other tasks)
 
 	public Session getSession() {
 	}
 	
 	public TaskRun getTaskRun() {
 		WeakReference<TaskRuntrr = this.;
 		
 		if (trr != null
 			return trr.get();
 		
 		return null;
 	}
 	
 	public void setTaskRun(TaskRun v) {
 		this. = new WeakReference<TaskRun>(v);
 	}

Returns:
logging level to use with this task
 
 	public DebugLevel getLevel() {
 		return this.;
 	}
 	
 	public void setLevel(DebugLevel v) {
 		this. = v;
 	}

Origin indicates where this task originated from. "hub:" means it was started by the a hub (task id gives away which hub). "http:[ip address]" means the task was started in response to a web request. "ws:[ip address]" means the task was started in response to a web scoket request. "ftp:[ip address]" means the task was started in response to a ftp request. Etc.

Returns:
origin string
 
 	public String getOrigin() {
 		return this..getFieldAsString("Origin");
 	}

Elevated tasks have been a) authenticated and b) passed successfully into a service. Once elevated all subsequent calls with the task no longer need to be authenticated or authorized by DivConq framework (individual services/modules may require it). Meaning that "guest" cannot call "SendMail" unless it first goes through a service that is open to guests, such as password recovery.

Returns:
true if task has been elevated
 
 	public boolean isElevated() {
 		return this..getFieldAsBooleanOrFalse("Elevated");
 	}
 	
 	// only use during hub booting
 	protected OperationContext() {
 		this. = new RecordStruct();
 
 		this. = new UserContext();
 	}

Parameters:
ctx create a task context from a RecordStruct, keep in mind this is info gathering only, call must set authenticated/elevated state inappropriately
 
 	protected OperationContext(RecordStruct ctx) {
 		this. = ctx;
 
 		if (ctx.isFieldEmpty("OpId"))
 			ctx.setField("OpId", OperationContext.allocateOpId());		
 		
 		if (!ctx.isFieldEmpty("DebugLevel"))
 			this. = DebugLevel.valueOf(ctx.getFieldAsString("DebugLevel"));	
 		
 		this. = UserContext.allocateFromTask(ctx);
 	}
 	
 	protected OperationContext(UserContext usrRecordStruct ctx) {
 		this. = ctx;
 
 		if (ctx.isFieldEmpty("OpId"))
 			ctx.setField("OpId", OperationContext.allocateOpId());		
 		
 		if (!ctx.isFieldEmpty("DebugLevel"))
 			this. = DebugLevel.valueOf(ctx.getFieldAsString("DebugLevel"));	
 		
 		this. = usr;
 	}
 	
 	}

Parameters:
m store task context into a message - for context transfer over bus
 
 	public void freeze(Message m) {
 		m.setField("Context"this.freezeToRecord());
 	}
 	
 		
 		this..freeze(clone);
 		
 		clone.setField("DebugLevel"this..toString());
 		
 		return clone;
 	}
 	
 		
 		this..freezeSafe(clone);
 		
 		clone.setField("DebugLevel"this..toString());
 		
 		return clone;
 	}
 	
 	// return an approved/verified user context (guest if nothing else)
 	// verify says - the given auth token, if any, is valid - if there is none then you are a guest and that is valid
 	// 
 	public void verify(FuncCallback<UserContextcb) {
 		if (this. == null) {
 			cb.errorTr(444);
 			cb.setResult(UserContext.allocateGuest());
 			cb.complete();
 			return;
 		}
 			
 		if (this..isVerified() || this.isElevated()) {
 			cb.setResult(this.);
 			cb.complete();
 			return;
 		}
 		
 		Message msg = new Message("dcAuth""Authentication""Verify");
     	
 		..getBus().sendMessage(msgr ->	{		
 			if (r.hasErrors()) 
 				cb.setResult(UserContext.allocateGuest());
 			else 
 			
 			cb.complete();
 		});
 	}

Parameters:
tags to search for with this user
Returns:
true if this user has one of the requested authorization tags (does not check authentication)
 
 	public boolean isAuthorized(String... tags) {
 		if (this.isElevated())
 			return true;		// always ok
 		
 		if (!this..isVerified())
 			return false;
 		
 		return this..isTagged(tags);
 	}
 	
 	public String toString() {
 		// capture both this and the user
 		return this.freezeToRecord().toPrettyString(); 
 	}

Overrides any previous return codes and messages

Parameters:
code code for message
msg message
 
 	public void exit(long codeString msg) {
 		if (StringUtil.isNotEmpty(msg))
 			this.log(.codemsg"Exit");
 		else 
 			this.boundary("Code"code + """Exit");
 	}
 
 	public void clearExitCode() {
 		this.exit(0, null);
 	}
 
 	// search backward through log to find an error, if we hit a message with an Exit tag then
 	// stop, as Exit resets Error (unless it is an error itself)
 	// similar to findExitEntry but stops after last Error as we don't need to loop through all
 	public boolean hasErrors() {
 		for (int i = this..size() - 1; i >= 0; i--) {
 			RecordStruct msg =  this..get(i);
 			
 			if ("Error".equals(msg.getFieldAsString("Level")))
 				return true;
 		
 			if (msg.hasField("Tags")) {
 				ListStruct tags = msg.getFieldAsList("Tags");
 				
 				if (tags.stringStream().anyMatch(tag -> tag.equals("Exit")))
 					break;
 			}
 		}
 		
 		return false;
 	}
 
 	public long getCode() {
 		RecordStruct entry = this.findExitEntry();
 		
 		if (entry == null)
 			return 0;
 		
 		return entry.getFieldAsInteger("Code", 0);
 	}
 
 	public String getMessage() {
 		RecordStruct entry = this.findExitEntry();
 		
 		if (entry == null)
 			return null;
 		
 		return entry.getFieldAsString("Message");
 	}
 
 		return this.findExitEntry(0, -1);
 	}
 
 	// search backward through log to find an exit, if we hit a message with an Exit tag then
 	// stop, as Exit resets Error.  now return the first error after Exit.  if no errors after
 	// then return Exit
 	public RecordStruct findExitEntry(int msgStartint msgEnd) {
 		msgStart -= this.;		// adjust so the markers are relative to the current collection of messages, assuming some may have been purged
 		
 		if (msgEnd == -1)
 			msgEnd = this..size();
 		else
 			msgEnd -= this.;
 			
 		RecordStruct firsterror = null;
 		
 		for (int i = msgEnd - 1; i >= msgStarti--) {
 			RecordStruct msg =  this..get(i);
 			
 			if ("Error".equals(msg.getFieldAsString("Level")))
 				firsterror = msg;
 		
 			if (msg.hasField("Tags")) {
 				ListStruct tags = msg.getFieldAsList("Tags");
 				
 				if (tags.stringStream().anyMatch(tag -> tag.equals("Exit")))
 					return (firsterror != null) ? firsterror : msg;
 			}
 		}
 		
 		return firsterror;
 	}
 
 	public ListStruct getMessages() {
 		return new ListStruct(this..toArray());	
 	}
 
 	public ListStruct getMessages(int msgStartint msgEnd) {
 		msgStart -= this.;		// adjust so the markers are relative to the current collection of messages, assuming some may have been purged
 		
 		if (msgEnd == -1)
 			msgEnd = this..size();
 		else
 			msgEnd -= this.;
 		
 		return new ListStruct(this..subList(msgStartmsgEnd).toArray());	
 	}

Parameters:
code to search for
Returns:
true if an error code is present
 
 	public boolean hasCode(long code) {
 		return this.hasCode(code, 0, -1);
 	}
 
 	public boolean hasCode(long codeint msgStartint msgEnd) {
 		msgStart -= this.;		// adjust so the markers are relative to the current collection of messages, assuming some may have been purged
 		
 		if (msgEnd == -1)
 			msgEnd = this..size();
 		else
 			msgEnd -= this.;
 		
 		for (int i = msgStarti < msgEndi++) {
 			RecordStruct msg =  this..get(i); 
 		
 			if (msg.getFieldAsInteger("Code") == code)
 				return true;
 		}
 		
 		return false;
 	}
 
 		return this.;
 	}
 	
 	public String getLog() {
 		IOperationLogger logger = this.;
 
 		if (logger != null)
 				return logger.logToString();	
 				
 		// TODO reformat these as log entries not as JSON
 		return this.getMessages().toString();
 	}
 	
     public void error(String messageString... tags) {
 		this.log(., 1, messagetags);
     }
     
     public void error(long codeString messageString... tags) {
 		this.log(.codemessagetags);
     }
     
     public void warn(String messageString... tags) {
 		this.log(., 2, messagetags);
     }
     
     public void warn(long codeString messageString... tags) {
 		this.log(.codemessagetags);
     }
     
     public void info(String messageString... tags) {
 		this.log(., 0, messagetags);
     }
     
     public void info(long codeString messageString... tags) {
 		this.log(.codemessagetags);
     }
     
     public void debug(String messageString... tags) {
 		this.log(., 0, messagetags);
     }
     
     public void debug(long codeString messageString... tags) {
 		this.log(.codemessagetags);
     }
     
     public void trace(String messageString... tags) {
 		this.log(., 0, messagetags);
     }
     
     public void trace(long codeString messageString... tags) {
 		this.log(.codemessagetags);
     }
 	
     // let Logger translate to the language of the log file - let tasks translate to their own logs in
     // the language of the context 
     

Parameters:
code for message translation token
params for message translation
 
 	public void traceTr(long codeObject... params) {
 		this.logTr(.codeparams);
 	}

Parameters:
code for message translation token
params for message translation
 
 	public void debugTr(long codeObject... params) {
 		this.logTr(.codeparams);
 	}

Parameters:
code for message translation token
params for message translation
 
 	public void infoTr(long codeObject... params) {		
 		this.logTr(.codeparams);
 	}

Parameters:
code for message translation token
params for message translation
 
 	public void warnTr(long codeObject... params) {
 		this.logTr(.codeparams);
 	}

Parameters:
code for message translation token
params for message translation
 
 	public void errorTr(long codeObject... params) {
 		this.logTr(.codeparams);
 	}
 	
 	public void exitTr(long codeObject... params) {
 		String msg = this.tr("_code_" + codeparams);
 
 		this.exit(codemsg);
 	}

Parameters:
lvl level of message
code for message
msg text of message
tags of message
 
 	public void log(DebugLevel lvllong codeString msgString... tags) {
 		// must be some sort of message
 		if (StringUtil.isEmpty(msg))
 			return;
 		
 		RecordStruct entry = new RecordStruct(
 				new FieldStruct("Occurred"new DateTime(.)),
 				new FieldStruct("Level"lvl.toString()),
 				new FieldStruct("Code"code),
 				new FieldStruct("Message"msg)
 		);
 		
 		if (tags.length > 0)
 			entry.setField("Tags"new ListStruct((Object[])tags));
 		
 		this.log(entrylvl);
 		
 		// pass the message to logger 
 		if (this.getLevel().getCode() >= lvl.getCode()) {
 			// don't record 0, 1 or 2 - no generic codes
 			if (code > 2) {
 				tags = Arrays.copyOf(tagstags.length + 2);
 				tags[tags.length - 2] = "Code";
 				tags[tags.length - 1] = code + "";
 			}
 
 			Logger.logWr(this.getOpId(), lvlmsgtags);
 		}
 	}

Parameters:
lvl level of message
code for message
params parameters to the message string
 
 	public void logTr(DebugLevel lvllong codeObject... params) {
 		String msg = this.tr("_code_" + codeparams);
 		
 		RecordStruct entry = new RecordStruct(
 				new FieldStruct("Occurred"new DateTime(.)),
 				new FieldStruct("Level"lvl.toString()),
 				new FieldStruct("Code"code),
 				new FieldStruct("Message"msg)
 		);
 		
 		this.log(entrylvl);
 	
 		// pass the code to logger 
 		if (this.getLevel().getCode() >= lvl.getCode()) 
 			Logger.logWr(this.getOpId(), lvlcodeparams);
 	}
    
    
Add a logging boundary, delineating a new section of work for this task

Parameters:
tags identity of this boundary
 
     public void boundary(String... tags) {
 		RecordStruct entry = new RecordStruct(
 				new FieldStruct("Occurred"new DateTime(.)),
 				new FieldStruct("Level"..toString()),
 				new FieldStruct("Code", 0),
 				new FieldStruct("Tags"new ListStruct((Object[])tags))
 		);
 		
 		this.log(entry.);
 		
 		// pass the code to logger 
 		if (this.getLevel().getCode() >= ..getCode())
 			Logger.boundaryWr(this.getOpId(), tags);
     }
 	
 	// logging is hard on heap and GC - so only do it if necessary
 	// not generally called by code, internal use mostly
     // call this to bypass the Hub logger - for example a bus callback 
 	public void log(RecordStruct entry) {
 		this.log(entry, DebugLevel.parse(entry.getFieldAsString("Level")));
 	}
 	
 	public void log(RecordStruct entryDebugLevel lvl) {
 		// think twice about logging debug or trace so we don't overflow the OC log
 		// always log Info, Error, Warn so it bubbles up and so "hasCode" is relable at those levels
 		if ((lvl == .) || (lvl == .)) {
 			if (this.getLevel().getCode() < lvl.getCode()) 
 				return;
 		}
 		
 		if (this.) {
 			while (this..size() > 999) {		// no more than 1000 messages when limit is on
 				this..remove(0);
 				this.++;
 			}
 		}
 		
		// this isn't thread safe, and much of the time it won't be much of an issue
		// but could consider Stamp Lock approach to accessing messages array
		this..add(entry);
	public void logResult(RecordStruct v) {
		ListStruct h = v.getFieldAsList("Messages");
		if (h != null) {
			for (Struct st : h.getItems()) 
				this.log((RecordStructst);
	public boolean isLevel(DebugLevel debug) {
		return (this.getLevel().getCode() >= debug.getCode());
	// progress methods

Returns:
units/percentage of task completed
	public int getAmountCompleted() {
		return this.
	}

Parameters:
v units/percentage of task completed
	public void setAmountCompleted(int v) { 
		this. = v
	}

Returns:
status message about task progress
		return this.
	}

Parameters:
v status message about task progress
	public void setProgressMessage(String v) { 
		this. = v
	}

Parameters:
code message translation code
params for the message string
	public void setProgressMessageTr(int codeObject... params) { 
		this. = this.tr("_code_" + codeparams); 
	}

Returns:
total steps for this specific task
	public int getSteps() { 
		return this.
	}

Parameters:
v total steps for this specific task
	public void setSteps(int v) { 
		this. = v
	}

Returns:
current step within this specific task
	public int getCurrentStep() { 
		return this.
	}

Set step name first, this triggers observers

Parameters:
step current step number within this specific task
name current step name within this specific task
	public void setCurrentStep(int stepString name) { 
		this. = step
		this. = name
	}

Set step name first, this triggers observers

Parameters:
name current step name within this specific task
	public void nextStep(String name) { 
		this.++; 
		this. = name
	}

Returns:
name of current step
		return this.
	}

Parameters:
step number of current step
code message translation code
params for the message string
	public void setCurrentStepNameTr(int stepint codeObject... params) {
		String name = this.tr("_code_" + codeparams);
		this. = step
		this. = name
	}    

Parameters:
code message translation code
params for the message string
	public void nextStepTr(int codeObject... params) {
		String name = this.tr("_code_" + codeparams);
		this.++; 
		this. = name
	}    
	public void addObserver(IOperationObserver oo) {
		// the idea is that we want to unwind the callbacks in LILO order
		if (!this..contains(oo))
			this..add(0, oo);
		if ((oo instanceof IOperationLogger) && (this. == null))
			this. = (IOperationLoggeroo;
	public int countObservers() {
		return this..size();
	}	
		sub.setParent(this);
		//sub.addObserver(new ParentLogger(this));
		return sub;
	protected void setParent(OperationContext v) {
		this. = v;
    
    // events might fire from external context, keep this in mind
    public void fireEvent(OperationEvent eventObject detail) {
    	this.fireEvent(thiseventdetail);
    
    // events might fire from external context, keep this in mind
    public void fireEvent(OperationContext srcOperationEvent eventObject detail) {
		OperationContext curr = OperationContext.get();
		try {
			this.touch();
			for (IOperationObserver ob : this.) {
		    	OperationContext.set(this);
		    	
				ob.fireEvent(eventsrcdetail);
			if (this. != null) {
				if (event == .)
					this..log((RecordStructdetail);
				else if (event == .)
					this..fireEvent(srceventdetail);
			// TODO missing concept here
			// how do we capture a COMPLETED event after it has passed
			// some events need to be flagged as enduring and then kept in a list
			// when an observer is added it should be handed the enduring list
			// do not use locks here or in addObserver - too expensive - officially 
			// we only support listeners to COMPLETE, START, PREP, START that
			// are added before we start - so we may never want to add any more support
			// here, however, if we do, do not include a lock here...that is too expensive
			// relative to benefits
		finally {
	    	OperationContext.set(curr);
    }
	public String tr(String tokenObject... params) {
		return this..tr(tokenparams);
	public String trp(String pluraltokenString singulartokenObject... params) {
		return this..trp(pluraltokensingulartokenparams);