sirius.kernel.async.TaskContext Maven / Gradle / Ivy
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - [email protected]
*/
package sirius.kernel.async;
import sirius.kernel.commons.RateLimit;
import sirius.kernel.commons.Strings;
import sirius.kernel.di.std.Part;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* Provides an interface between a running task and a monitoring system.
*
* Any task or background job can access its TaskContext using either {@link TaskContext#get()} or
* {@link sirius.kernel.async.CallContext#get(Class)}. This provides an interface to a monitoring system which
* might be present (by calling {@link sirius.kernel.async.TaskContext#setAdapter(TaskContextAdapter)}. If no
* monitoring is available, the default mechanisms of the platform are used.
*/
public class TaskContext implements SubContext {
/**
* Forms the default value used to specify the system string which identifies the currently active module.
*
* @see #getSystemString()
*/
private static final String GENERIC = "GENERIC";
/**
* One the system string is changed, it will be updated in the mapped diagnostic context (MDC) using this name.
*
* @see #setSystem(String)
* @see #setSubSystem(String)
* @see #setJob(String)
*/
public static final String MDC_SYSTEM = "system";
private static final int STATE_UPDATE_INTERVAL = 5;
private TaskContextAdapter adapter;
private String system = GENERIC;
private String subSystem = GENERIC;
private String job = GENERIC;
private CallContext parent;
private RateLimit stateUpdate = RateLimit.timeInterval(STATE_UPDATE_INTERVAL, TimeUnit.SECONDS);
@Part
private static Tasks tasks;
/**
* Generates a new TaskContext.
*
* Normally this is should only be invoked by {@link CallContext}. Use {@link CallContext#get(Class)} to obtain an
* instance.
*/
public TaskContext() {
this.adapter = new BasicTaskContextAdapter(this);
this.parent = CallContext.getCurrent();
}
/**
* Provides access to the TaskContext for the current thread.
*
* This is boilerplate for {@code CallContext.getCurrent().get(TaskContext.class)}
*
* @return the task context for the current thread
*/
public static TaskContext get() {
return CallContext.getCurrent().get(TaskContext.class);
}
/**
* Writes a log message to the monitor.
*
* If no monitor is available, the async logger will be used.
*
* @param message the message to log
* @param args the parameters used to format the message (see {@link Strings#apply(String, Object...)})
*/
public void log(String message, Object... args) {
if (adapter != null) {
adapter.log(Strings.apply(message, args));
} else {
Tasks.LOG.INFO(getSystemString() + ": " + Strings.apply(message, args));
}
}
/**
* Writes a debug message to the monitor.
*
* If no monitor is available, the async logger will be used.
*
* @param message the message to log
* @param args the parameters used to format the message (see {@link Strings#apply(String, Object...)})
*/
public void trace(String message, Object... args) {
adapter.trace(Strings.apply(message, args));
}
/**
* Logs the given message and sets it as current state.
*
* @param message the message to log
* @param args the parameters used to format the message (see {@link Strings#apply(String, Object...)})
*/
public void logAsCurrentState(String message, Object... args) {
log(message, args);
setState(message, args);
}
/**
* Sets the new state of the current task.
*
* @param newState the message to set as state
* @param args the parameters used to format the state message (see {@link Strings#apply(String, Object...)})
*/
public void setState(String newState, Object... args) {
adapter.setState(Strings.apply(newState, args));
}
/**
* Can be used to determine if the state should be refreshed.
*
* By calling {@code shouldUpdateState().check()} an inner loop can detect if a state update should be
* performed. This will limit the number of updates to a reasonable value.
*
* @return a rate limit which limits the number of updates to a reasonable value
*/
public RateLimit shouldUpdateState() {
return stateUpdate;
}
/**
* Signals the monitor that the execution had an error.
*
* Although an error is signaled, this will not cancel or interrupt the execution of the task. This is merely
* a signal for an user or administrator that an unexpected or non-anticipated event occurred.
*/
public void markErroneous() {
adapter.markErroneous();
}
/**
* Determines if the execution of this task is still active.
*
* A task can be either stopped via the {@link #cancel()} method or due to a system shutdown. In any case it is
* wise for a task to check this flag every once in a while to keep the overall app responsive.
*
* @return true as long as the task is expected to be executed, false otherwise
*/
public boolean isActive() {
return adapter.isActive() && tasks.isRunning();
}
/**
* Cancels the execution of this task.
*
* Note that this will not kill the underlying thread. This will merely toggle the canceled flag. It is
* however the task programmers job to check this flag and interrupt / terminate all computations.
*/
public void cancel() {
adapter.cancel();
}
/**
* Utility to iterate through a collection while checking the cancelled flag.
*
* @param iterable the collection to iterate through
* @param consumer the processor invoked for each element
* @param the type of elements being processed
*/
public void iterateWhileActive(Iterable iterable, Consumer consumer) {
for (T obj : iterable) {
if (!isActive()) {
return;
}
consumer.accept(obj);
}
}
/**
* Returns the System String.
*
* This will consist of three parts: System, Sub-System and Job. It is used to provide information which
* module is currently active. Therefore the System will provide a raw information which module is
* active. This might be HTTP for the web server or the category of an executor in {@link Tasks}.
*
* The Sub-System will provide a more detailed information, like the class name or the name of
* a component which is currently active.
*
* Finally the Job will provide a detailed information what's being currently processed. This might be
* the effective URI of the request being processed by the web server or the name of a file currently being
* imported.
*
* @return the System String with a format like System::Sub-System::Job
*/
public String getSystemString() {
return system + "::" + subSystem + "::" + job;
}
/**
* Returns the System component of the System String
*
* @return the system component of the system string
* @see #getSystemString()
*/
public String getSystem() {
return system;
}
/**
* Sets the System component of the System String
*
* @param system the new system component to set
* @return the task context itself for fluent method calls
* @see #getSystemString()
*/
public TaskContext setSystem(String system) {
if (Strings.isEmpty(system)) {
this.system = GENERIC;
} else {
this.system = system;
}
parent.addToMDC(MDC_SYSTEM, getSystemString());
return this;
}
/**
* Returns the Sub-System component of the System String
*
* @return the sub system component of the system string
* @see #getSystemString()
*/
public String getSubSystem() {
return subSystem;
}
/**
* Sets the Sub-System component of the System String
*
* @param subSystem the new sub system component to set
* @return the task context itself for fluent method calls
* @see #getSystemString()
*/
public TaskContext setSubSystem(String subSystem) {
if (Strings.isEmpty(subSystem)) {
this.subSystem = GENERIC;
} else {
this.subSystem = subSystem;
}
parent.addToMDC(MDC_SYSTEM, getSystemString());
return this;
}
/**
* Returns the Job component of the System String
*
* @return the job component of the system string
* @see #getSystemString()
*/
public String getJob() {
return job;
}
/**
* Sets the Job component of the System String
*
* @param job the new job component to set
* @return the task context itself for fluent method calls
* @see #getSystemString()
*/
public TaskContext setJob(String job) {
if (Strings.isEmpty(job)) {
this.job = GENERIC;
} else {
this.job = job;
}
parent.addToMDC(MDC_SYSTEM, getSystemString());
return this;
}
@Override
public String toString() {
return getSystemString();
}
/**
* Returns the monitoring adapter which is currently active.
*
* @return the monitoring adapter or null if no adapter is active
*/
public TaskContextAdapter getAdapter() {
return adapter;
}
/**
* Installs the given adapter as monitoring adapter.
*
* @param adapter the adapter to install
*/
public void setAdapter(TaskContextAdapter adapter) {
this.adapter = adapter;
}
@Override
public SubContext fork() {
TaskContext child = new TaskContext();
child.adapter = adapter;
return child;
}
@Override
public void detach() {
// Nothing to do...
}
}