2017年5月26日
package org.gdharley.activiti.integration.rest;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.Expression;
import org.activiti.engine.delegate.JavaDelegate;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
/**
* Created by gharley on 5/2/17.
*/
public class SimpleRestDelegate implements JavaDelegate {
private static final Logger logger = LoggerFactory.getLogger(SimpleRestDelegate.class);
protected Expression endpointUrl;
protected Expression httpMethod;
protected Expression isSecure;
protected Expression payload;
// 一个Content-Type是application/json的请求,具体看起来是这样的:
// POST /some-path HTTP/1.1
// Content-Type: application/json
//
// { "foo" : "bar", "name" : "John" }
//
//
// { "foo" : "bar", "name" : "John" } 就是这个请求的payload
protected Expression headers;
protected Expression responseMapping;
protected ObjectMapper objectMapper = new ObjectMapper();
// Create a mixin to force the BasicNameValuePair constructor
protected static abstract class BasicNameValuePairMixIn {
private BasicNameValuePairMixIn(@JsonProperty("name") String name, @JsonProperty("value") String value) {
}
}
public void execute(DelegateExecution execution) throws Exception {
logger.info("Started Generic REST call delegate");
if (endpointUrl == null || httpMethod == null) {
throw new IllegalArgumentException("An endpoint URL and http method are required");
}
String restUrl = getExpressionAsString(endpointUrl, execution);
String payloadStr = getExpressionAsString(payload, execution);
String headersJSON = getExpressionAsString(headers, execution); // [{"name":"headerName", "value":"headerValue"}]
String method = getExpressionAsString(httpMethod, execution);
String rMapping = getExpressionAsString(responseMapping, execution);
String secure = getExpressionAsString(isSecure, execution);
String scheme = secure == "true" ? "https" : "http";
// validate URI and create create request
URI restEndpointURI = composeURI(restUrl, execution);
logger.info("Using Generic REST URI " + restEndpointURI.toString());
HttpRequestBase httpRequest = createHttpRequest(restEndpointURI, scheme, method, headersJSON, payloadStr, rMapping);
// create http client
CloseableHttpClient httpClient = createHttpClient(httpRequest, scheme, execution);
// execute request
HttpResponse response = executeHttpRequest(httpClient, httpRequest);
// map response to process instance variables
if (responseMapping != null) {
mapResponse(response, rMapping, execution);
}
logger.info("Ended Generic REST call delegate");
}
protected URI composeURI(String restUrl, DelegateExecution execution)
throws URISyntaxException {
URIBuilder uriBuilder = null;
uriBuilder = encodePath(restUrl, uriBuilder);
return uriBuilder.build();
}
protected URIBuilder encodePath(String restUrl, URIBuilder uriBuilder) throws URISyntaxException {
if (StringUtils.isNotEmpty(restUrl)) {
// check if there are URL params
if (restUrl.indexOf('?') > -1) {
List<NameValuePair> params = URLEncodedUtils.parse(new URI(restUrl), "UTF-8");
if (params != null && !params.isEmpty()) {
restUrl = restUrl.substring(0, restUrl.indexOf('?'));
uriBuilder = new URIBuilder(restUrl);
uriBuilder.addParameters(params);
}
} else {
uriBuilder = new URIBuilder(restUrl);
}
}
return uriBuilder;
}
protected HttpRequestBase createHttpRequest(URI restEndpointURI, String scheme, String httpMethod, String headers, String payload, String responseMapping) {
if (StringUtils.isEmpty(httpMethod)) {
throw new ActivitiException("no HTTP method provided");
}
if (restEndpointURI == null) {
throw new ActivitiException("no REST endpoint URI provided");
}
HttpRequestBase httpRequest = null;
HttpMethod parsedMethod = HttpMethod.valueOf(httpMethod.toUpperCase());
StringEntity input;
URIBuilder builder = new URIBuilder(restEndpointURI);
switch (parsedMethod) {
case GET:
try {
httpRequest = new HttpGet(builder.build());
httpRequest = addHeadersToRequest(httpRequest, headers);
} catch (URISyntaxException use) {
throw new ActivitiException("Error while building GET request", use);
}
break;
case POST:
try {
httpRequest = new HttpPost(builder.build());
input = new StringEntity(payload);
// input.setContentType(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
((HttpPost) httpRequest).setEntity(input);
httpRequest = addHeadersToRequest(httpRequest, headers);
break;
} catch (Exception e) {
throw new ActivitiException("Error while building POST request", e);
}
case PUT:
try {
httpRequest = new HttpPut(builder.build());
input = new StringEntity(payload);
// input.setContentType(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
((HttpPut) httpRequest).setEntity(input);
httpRequest = addHeadersToRequest(httpRequest, headers);
break;
} catch (Exception e) {
throw new ActivitiException("Error while building PUT request", e);
}
case DELETE:
try {
httpRequest = new HttpDelete(builder.build());
httpRequest = addHeadersToRequest(httpRequest, headers);
} catch (URISyntaxException use) {
throw new ActivitiException("Error while building DELETE request", use);
}
break;
default:
throw new ActivitiException("unknown HTTP method provided");
}
return httpRequest;
}
protected CloseableHttpClient createHttpClient(HttpRequestBase request, String scheme, DelegateExecution execution) {
SSLConnectionSocketFactory sslsf = null;
// Allow self signed certificates and hostname mismatches.
if (StringUtils.equalsIgnoreCase(scheme, "https")) {
try {
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
sslsf = new SSLConnectionSocketFactory(builder.build(), SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
} catch (Exception e) {
logger.warn("Could not configure HTTP client to use SSL", e);
}
}
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
if (sslsf != null) {
httpClientBuilder.setSSLSocketFactory(sslsf);
}
return httpClientBuilder.build();
}
protected HttpResponse executeHttpRequest(CloseableHttpClient httpClient, HttpRequestBase httpRequest) {
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpRequest);
} catch (IOException e) {
throw new ActivitiException("error while executing http request: " + httpRequest.getURI(), e);
}
if (response.getStatusLine().getStatusCode() >= 400) {
throw new ActivitiException("error while executing http request " + httpRequest.getURI() + " with status code: "
+ response.getStatusLine().getStatusCode());
}
return response;
}
protected void mapResponse(HttpResponse response, String responseMapping, DelegateExecution execution) {
if (responseMapping == null || responseMapping.trim().length() == 0) {
return;
}
JsonNode jsonNode = null;
try {
String jsonString = EntityUtils.toString(response.getEntity());
jsonNode = objectMapper.readTree(jsonString);
} catch (Exception e) {
throw new ActivitiException("error while parsing response", e);
}
if (jsonNode == null) {
throw new ActivitiException("didn't expect an empty response body");
}
execution.setVariable(responseMapping, jsonNode.toString());
}
protected HttpRequestBase addHeadersToRequest(HttpRequestBase httpRequest, String headerJSON) {
Boolean contentTypeDetected = false;
if (headerJSON != null) {
// Convert JSON to array
try {
// configuration for Jackson/fasterxml
objectMapper.addMixInAnnotations(BasicNameValuePair.class, BasicNameValuePairMixIn.class);
NameValuePair[] headers = objectMapper.readValue(headerJSON, BasicNameValuePair[].class);
for (NameValuePair header : headers) {
httpRequest.addHeader(header.getName(), header.getValue());
if (header.getName().equals(HTTP.CONTENT_TYPE)) {
contentTypeDetected = true;
}
}
} catch (Exception e) {
throw new ActivitiException("Unable to parse JSON header array", e);
}
}
// Now add content type if necessary
if (!contentTypeDetected) {
httpRequest.addHeader(HTTP.CONTENT_TYPE, "application/json");
}
return httpRequest;
}
/**
* @return string value of expression.
* @throws {@link IllegalArgumentException} when the expression resolves to a value which is not a string
* or if the value is null.
*/
protected String getExpressionAsString(Expression expression, DelegateExecution execution) {
if (expression == null) {
return null;
} else {
Object value = expression.getValue(execution);
if (value instanceof String) {
return (String) value;
} else {
throw new IllegalArgumentException("Expression does not resolve to a string or is null: " + expression.getExpressionText());
}
}
}
}
posted @
2017-05-26 08:01 华梦行 阅读(224) |
评论 (0) |
编辑 收藏
2016年5月7日
1. Quickstart
The cron4j main entity is the scheduler. With a it.sauronsoftware.cron4j.Scheduler instance you can execute tasks at fixed moments, during all the year. A scheduler can execute a task once a minute, once every five minutes, Friday at 10:00, on February the 16th at 12:30 but only if it is Saturday, and so on.
The use of the cron4j scheduler is a four steps operation:
- Create your Scheduler instance.
- Schedule your actions. To schedule an action you have to tell the scheduler what it has to do and when. You can specify what using a java.lang.Runnable or a it.sauronsoftware.cron4j.Task instance, and you can specify when using a scheduling pattern, which can be represented with a string or with a it.sauronsoftware.cron4j.SchedulingPattern instance.
- Starts the scheduler.
- Stops the scheduler, when you don't need it anymore.
Consider this simple example:
import it.sauronsoftware.cron4j.Scheduler; public class Quickstart { public static void main(String[] args) { // Creates a Scheduler instance. Scheduler s = new Scheduler(); // Schedule a once-a-minute task. s.schedule("* * * * *", new Runnable() { public void run() { System.out.println("Another minute ticked away..."); } }); // Starts the scheduler. s.start(); // Will run for ten minutes. try { Thread.sleep(1000L * 60L * 10L); } catch (InterruptedException e) { ; } // Stops the scheduler. s.stop(); } }
This example runs for ten minutes. At every minute change it will print the sad (but true) message "Another minute ticked away...".
Some other key concepts:
- You can schedule how many tasks you want.
- You can schedule a task when you want, also after the scheduler has been started.
- You can change the scheduling pattern of an already scheduled task, also while the scheduler is running (reschedule operation).
- You can remove a previously scheduled task, also while the scheduler is running (deschedule operation).
- You can start and stop a scheduler how many times you want.
- You can schedule from a file.
- You can schedule from any source you want.
- You can supply listeners to the scheduler in order to receive events about the executed task.
- You can control any ongoing task.
- You can manually launch a task, without using a scheduling pattern.
- You can change the scheduler working Time Zone.
- You can validate your scheduling patterns before using them with the scheduler.
- You can predict when a scheduling pattern will cause a task execution.
Back to index
2. Scheduling patterns
A UNIX crontab-like pattern is a string split in five space separated parts. Each part is intended as:
- Minutes sub-pattern. During which minutes of the hour should the task been launched? The values range is from 0 to 59.
- Hours sub-pattern. During which hours of the day should the task been launched? The values range is from 0 to 23.
- Days of month sub-pattern. During which days of the month should the task been launched? The values range is from 1 to 31. The special value "L" can be used to recognize the last day of month.
- Months sub-pattern. During which months of the year should the task been launched? The values range is from 1 (January) to 12 (December), otherwise this sub-pattern allows the aliases "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov" and "dec".
- Days of week sub-pattern. During which days of the week should the task been launched? The values range is from 0 (Sunday) to 6 (Saturday), otherwise this sub-pattern allows the aliases "sun", "mon", "tue", "wed", "thu", "fri" and "sat".
The star wildcard character is also admitted, indicating "every minute of the hour", "every hour of the day", "every day of the month", "every month of the year" and "every day of the week", according to the sub-pattern in which it is used.
Once the scheduler is started, a task will be launched when the five parts in its scheduling pattern will be true at the same time.
Scheduling patterns can be represented with it.sauronsoftware.cron4j.SchedulingPattern instances. Invalid scheduling patterns are cause of it.sauronsoftware.cron4j.InvalidPatternExceptions. The SchedulingPattern class offers also a static validate(String) method, that can be used to validate a string before using it as a scheduling pattern.
Some examples:
5 * * * *
This pattern causes a task to be launched once every hour, at the begin of the fifth minute (00:05, 01:05, 02:05 etc.).
* * * * *
This pattern causes a task to be launched every minute.
* 12 * * Mon
This pattern causes a task to be launched every minute during the 12th hour of Monday.
* 12 16 * Mon
This pattern causes a task to be launched every minute during the 12th hour of Monday, 16th, but only if the day is the 16th of the month.
Every sub-pattern can contain two or more comma separated values.
59 11 * * 1,2,3,4,5
This pattern causes a task to be launched at 11:59AM on Monday, Tuesday, Wednesday, Thursday and Friday.
Values intervals are admitted and defined using the minus character.
59 11 * * 1-5
This pattern is equivalent to the previous one.
The slash character can be used to identify step values within a range. It can be used both in the form */c and a-b/c. The subpattern is matched every c values of the range 0,maxvalue or a-b.
*/5 * * * *
This pattern causes a task to be launched every 5 minutes (0:00, 0:05, 0:10, 0:15 and so on).
3-18/5 * * * *
This pattern causes a task to be launched every 5 minutes starting from the third minute of the hour, up to the 18th (0:03, 0:08, 0:13, 0:18, 1:03, 1:08 and so on).
*/15 9-17 * * *
This pattern causes a task to be launched every 15 minutes between the 9th and 17th hour of the day (9:00, 9:15, 9:30, 9:45 and so on... note that the last execution will be at 17:45).
All the fresh described syntax rules can be used together.
* 12 10-16/2 * *
This pattern causes a task to be launched every minute during the 12th hour of the day, but only if the day is the 10th, the 12th, the 14th or the 16th of the month.
* 12 1-15,17,20-25 * *
This pattern causes a task to be launched every minute during the 12th hour of the day, but the day of the month must be between the 1st and the 15th, the 20th and the 25, or at least it must be the 17th.
Finally cron4j lets you combine more scheduling patterns into one, with the pipe character:
0 5 * * *|8 10 * * *|22 17 * * *
This pattern causes a task to be launched every day at 05:00, 10:08 and 17:22.
Back to index
3. How to schedule, reschedule and deschedule a task
The simplest manner to build a task is to implement the well-known java.lang.Runnable interface. Once the task is ready, it can be scheduled with the it.sauronsoftware.cron4j.Scheduler.schedule(String, Runnable) method. This method throws an it.sauronsoftware.cron4j.InvalidPatternException when the supplied string does not represent a valid scheduling pattern.
Another way to build a task is to extend the it.sauronsoftware.cron4j.Task abstract class, which is more powerful and let the developer access some other cron4j features. This is better discussed in the "Building your own task" paragraph. Task instances can be scheduled with the schedule(String, Task) and the schedule(SchedulingPattern, Task) methods.
Scheduling methods available in the scheduler always return an ID used to recognize and retrieve the scheduled operation. This ID can be used later to reschedule the task (changing its scheduling pattern) with the reschedule(String, String) or the reschedule(String, SchedulingPattern) methods, and to deschedule the task (remove the task from the scheduler) with the deschedule(String) method.
The same ID can also be used to retrieve the scheduling pattern associated with a scheduled task, with the getSchedulingPattern(String) method, or to retrieve the task itself, with the getTask(String) method.
Back to index
4. How to schedule a system process
System processes can be easily scheduled using the ProcessTask class:
ProcessTask task = new ProcessTask("C:\\Windows\\System32\\notepad.exe"); Scheduler scheduler = new Scheduler(); scheduler.schedule("* * * * *", task); scheduler.start(); // ...
Arguments for the process can be supplied by using a string array instead of a single command string:
String[] command = { "C:\\Windows\\System32\\notepad.exe", "C:\\File.txt" }; ProcessTask task = new ProcessTask(command); // ...
Environment variables for the process can be supplied using a second string array, whose elements have to be in the NAME=VALUE form:
String[] command = { "C:\\tomcat\\bin\\catalina.bat", "start" }; String[] envs = { "CATALINA_HOME=C:\\tomcat", "JAVA_HOME=C:\\jdks\\jdk5" }; ProcessTask task = new ProcessTask(command, envs); // ...
The default working directory for the process can be changed using a third parameter in the constructor:
String[] command = { "C:\\tomcat\\bin\\catalina.bat", "start" }; String[] envs = { "CATALINA_HOME=C:\\tomcat", "JAVA_HOME=C:\\jdks\\jdk5" }; File directory = "C:\\MyDirectory"; ProcessTask task = new ProcessTask(command, envs, directory); // ...
If you want to change the default working directory but you have not any environment variable, the envs parameter of the constructor can be set to null:
ProcessTask task = new ProcessTask(command, null, directory);
When envs is null the process inherits every environment variable of the current JVM:
Environment variables and the working directory can also be set by calling the setEnvs(String[]) and setDirectory(java.io.File) methods.
The process standard output and standard error channels can be redirected to files by using the setStdoutFile(java.io.File) and setStderrFile(java.io.File) methods:
ProcessTask task = new ProcessTask(command, envs, directory); task.setStdoutFile(new File("out.txt")); task.setStderrFile(new File("err.txt"));
In a siminal manner, the standard input channel can be read from an existing file, calling the setStdinFile(java.io.File) method:
ProcessTask task = new ProcessTask(command, envs, directory); task.setStdinFile(new File("in.txt"));
5. How to schedule processes from a file
The cron4j scheduler can also schedule a set of processes from a file.
You have to prepare a file, very similar to the ones used by the UNIX crontab, and register it in the scheduler calling the scheduleFile(File) method. The file can be descheduled by calling the descheduleFile(File) method. Scheduled files can be retrieved by calling the getScheduledFiles() method.
Scheduled files are parsed every minute. The scheduler will launch every process declared in the file whose scheduling pattern matches the current system time.
Syntax rules for cron4j scheduling files are reported in the "Cron parser" paragraph.
Back to index
6. Building your own task
A java.lang.Runnable object is the simplest task ever possible, but to gain control you need to extend the it.sauronsoftware.cron4j.Task class. In the plainest form, implementing Runnable or extending Task are very similar operations: while the first requires a run() method, the latter requires the implementation of execute(TaskExecutionContext). The execute(TaskExecutionContext) method provides a it.sauronsoftware.cron4j.TaskExecutionContext instance, which the Runnable.run() method does not provide. The context can be used in the following ways:
A task can communicate with its executor, by notifying its internal state with a textual description. This is called status tracking. If you are interested in supporting status tracking in your task, you have to override the supportsStatusTracking() method, which should return true. Once this has been done, within the execute(TaskExecutionContext) method you are enabled to call the context setStatusMessage(String) method. This will propagate your task status message to its executor. The status message, through the executor, can be retrieved by an external user (see the "Executors" paragraph).
A task can communicate with its executor, by notifying its completeness level with a numeric value. This is called completeness tracking. If you are interested in supporting completeness tracking in your task, you have to override the supportsCompletenessTracking() method, which should return true. Once this has been done, within the execute(TaskExecutionContext) method you are enabled to call the context setCompleteness(double) method, with a value between 0 and 1. This will propagate your task completeness level to its executor. The completeness level, through the executor, can be retrieved by an external user (see the "Executors" paragraph).
A task can be optionally paused. If you are interested in supporting pausing and resuming in your task, you have to override the canBePaused() method, which should return true. Once this has been done, within the execute(TaskExecutionContext) method you have to periodically call the context pauseIfRequested() method. This will pause the task execution until it will be resumed (or stopped) by an external user (see the "Executors" paragraph).
A task can be optionally stopped. If you are interested in supporting stopping in your task, you have to override the canBeStopped() method, which should return true. Once this has been done, within the execute(TaskExecutionContext) method you have to periodically call the context isStopped() method. This will return true when the execution has be demanded to be stopped by an external user (see the "Executors" paragraph). Then it's your responsibility to handle the event, by letting your task gently stop its ongoing activities.
Through the context, the task can retrieve the scheduler, calling getScheduler().
Through the context, the task can retrieve its executor, calling getTaskExecutor().
A custom task can be scheduled, launched immediately or returned by a task collector.
Back to index
7. Building your own collector
You can build and plug within the scheduler your own task source, via the task collector API.
The cron4j scheduler supports the registration of one or more it.sauronsoftware.cron4j.TaskCollector instances, with the addTaskCollector(TaskCollector) method. Registered collectors can be retrieved with the scheduler getTaskCollectors() method. A previously registered collector can be removed from the scheduler with the removeTaskCollector(TaskCollector) method. Collectors can be added, queried or removed at every moment, also when the scheduler is started and it is running.
Each registered task collector is queried by the scheduler once a minute. The scheduler calls the collector getTasks() method. The implementation must return a it.sauronsoftware.cron4j.TaskTable instance. A TaskTable is a table that associates tasks and scheduling patterns. The scheduler, once the table has been retrieved, will examine the reported entries, and it will execute every task whose scheduling pattern matches the current system time.
A custom collector can be used to tie the scheduler with an external task source, i.e. a database or a XML file, which can be managed and changed in its contents also at run time.
Back to index
8. Building your own scheduler listener
The it.sauronsoftware.cron4j.SchedulerListener API can be used to listen to scheduler events.
The SchedulerListener interface requires the implementation of the following methods:
See the "Executors" paragraph for more info about task executors.
Once your SchedulerListener instance is ready, you can register it on a Scheduler object by calling its addSchedulerListener(SchedulerListener) method. Already registered listeners can be removed by calling the removeSchedulerListener(SchedulerListener) method. The scheduler can also give back any registered listener, with the getSchedulerListeners() method.
SchedulerListeners can be added and removed at every moment, also while the scheduler is running.
Back to index
9. Executors
The scheduler, once it has been started and it is running, can be queried to give back its executors. An executor is similar to a thread. Executors is used by the scheduler to execute tasks.
By calling the Scheduler.getExecutingTasks() method you can obtain the currently ongoing executors.
You can also obtain an executor through a SchedulerListener instance (see the "Building your own scheduler listener" paragraph).
Each executor, represented by a it.sauronsoftware.cron4j.TaskExecutor instance, performs a different task execution.
The task can be retrieved with the getTask() method.
The executor status can be checked with the isAlive() method: it returns true if the executor is currently running.
If the executor is running, the current thread can be paused until the execution will be completed, calling the join() method.
The supportsStatusTracking() method returns true if the currently executing task supports status tracking. It means that the task communicates to the executor its status, represented by a string. The current status message can be retrieved by calling the executor getStatusMessage() method.
The supportsCompletenessTracking() method returns true if the currently executing task supports completeness tracking. It means that the task communicates to the executor its own completeness level. The current completeness level can be retrieved by calling the executor getCompleteness() method. Returned values are between 0 (task just started and still nothing done) and 1 (task completed).
The canBePaused() method returns true if the currently executing task supports execution pausing. It means that the task execution can be paused by calling the executor pause() method. The pause status of the executor can be checked with the isPaused() method. A paused executor can be resumed by calling its resume() method.
The canBeStopped() method returns true if the currently executing task supports execution interruption. It means that the task execution can be stopped by calling the executor stop() method. The interruption status of the executor can be checked with the isStopped() method. Stopped executors cannot be resumed.
The getStartTime() method returns a time stamp reporting the start time of the executor, or a value less than 0 if the executor has not been yet started.
The getScheduler() method returns the scheduler which is the owner of the executor.
The getGuid() method returns a textual GUID for the executor.
Executors offer also an event-driven API, through the it.sauronsoftware.cron4j.TaskExecutorListener class. A TaskExecutorListener can be added to a TaskExecutor with its addTaskExecutorListener(TaskExecutorListener) method. Listeners can be removed with the removeTaskExecutorListener(TaskExecutorListener) method, and they can also be retrieved with the getTaskExecutorListeners() method. A TaskExecutorListener must implement the following methods:
executionPausing(TaskExecutor)
Called when the executor is requested to pause the ongoing task. The given parameter represents the source TaskExecutor instance.
executionResuming(TaskExecutor)
Called when the executor is requested to resume the execution of the previously paused task. The given parameter represents the source TaskExecutor instance.
executionStopping(TaskExecutor)
Called when the executor is requested to stop the task execution. The given parameter represents the source TaskExecutor instance.
executionTerminated(TaskExecutor, Throwable)
Called when the executor has completed the task execution. The first parameter represents the source TaskExecutor instance, while the second is the optional exception that has caused the task to be terminated. If the task has been completed successfully, the given value is null.
statusMessageChanged(TaskExecutor, String)
Called every time the execution status message changes. The first parameter represents the source TaskExecutor instance, while the second is the new message issued by the task.
completenessValueChanged(TaskExecutor, double)
Called every time the execution completeness value changes. The first parameter represents the source TaskExecutor instance, while the second is the new completeness value (between 0 and 1) issued by the task.
Back to index
10. Manual task launch
If the scheduler is started and running, it is possible to manually launch a task, without scheduling it with a pattern. The method is Scheduler.launch(Task). The task will be immediately launched, and a TaskExecutor instace is returned to the caller. The returned object can be used to control the task execution (see the "Executors" paragraph).
Back to index
11. Working with Time Zones
Scheduler instances, by default, work with the system default Time Zone. I.e. a scheduling pattern whose value is 0 2 * * * will activate its task at 02:00 AM according to the default system Time Zone. The scheduler can be requested to work with a different Time Zone, which is not the system default one. Call Scheduler.setTimeZone(TimeZone) and Scheduler.getTimeZone() to control this feature.
Once the default Time Zone has been changed, system current time is adapted to the supplied zone before comparing it with registered scheduling patterns. The result is that any supplied scheduling pattern is treated according to the specified Time Zone. Suppose this situation:
- System time: 10:00
- System time zone: GMT+1
- Scheduler time zone: GMT+3
The scheduler, before comparing system time with patterns, translates 10:00 from GMT+1 to GMT+3. It means that 10:00 becomes 12:00. The resulted time is then used by the scheduler to activate tasks. So, in the given configuration at the given moment, any task scheduled as 0 12 * * * will be executed, while any 0 10 * * * will not.
Back to index
12. Daemon threads
The Java Virtual Machine exits when the only threads running are all daemon threads. The cron4j scheduler can be configured to spawn only daemon threads. To control this feature call the Scheduler.setDaemon(boolean) method. This method must be called before the scheduler is started. Default value is false. To check the scheduler current daemon status call the Scheduler.isDaemon() method.
Back to index
13. Predictor
The it.sauronsoftware.cron4j.Predictor class is able to predict when a scheduling pattern will be matched.
Suppose you want to know when the scheduler will execute a task scheduled with the pattern 0 3 * jan-jun,sep-dec mon-fri. You can predict the next n execution of the task using a Predictor instance:
String pattern = "0 3 * jan-jun,sep-dec mon-fri"; Predictor p = new Predictor(pattern); for (int i = 0; i < n; i++) { System.out.println(p.nextMatchingDate()); }
Back to index
14. Cron parser
The it.sauronsoftware.cron4j.CronParser class can be used to parse crontab-like formatted file and character streams.
If you want to schedule a list of tasks declared in a crontab-like file you don't need the CronParser, since you can do it by adding the file to the scheduler, with the Scheduler.scheduleFile(File) method.
Consider to use the CronParser if the Scheduler.scheduleFile(File) method is not enough for you. In example, you may need to fetch the task list from a remote source which is not representable as a java.io.File object (a document on a remote server, a DBMS result set and so on). To solve the problem you can implement your own it.sauronsoftware.cron4j.TaskCollector, getting the advantage of the CronParser to easily parse any crontab-like content.
You can parse a whole file/stream, but you can also parse a single line.
A line can be empty, can contain a comment or it can be a scheduling line.
A line containing no characters or a line with only space characters is considered an empty line.
A line whose first non-space character is a number sign (#) is considered a comment.
Empty lines and comment lines are ignored by the parser.
Any other kind of line is parsed as a scheduling line.
A valid scheduling line respects the following structure:
scheduling-pattern [options] command [args]
- scheduling-pattern is a valid scheduling pattern, according with the definition given by the it.sauronsoftware.cron4j.SchedulingPattern class.
- options is a list of optional information used by cron4j to prepare the task execution environment. See below for a more detailed description.
- command is a system valid command, such an executable call.
- args is a list of optional arguments for the command.
After the scheduling pattern item, other tokens in each line are space separated or delimited with double quotation marks (").
Double quotation marks delimited items can take advantage of the following escape sequences:
- \" - quotation mark
- \\ - back slash
- \/ - slash
- \b - back space
- \f - form feed
- \n - new line
- \r - carriage return
- \t - horizontal tab
- \ufour-hex-digits - the character at the given Unicode index
The options token collection can include one or more of the following elements:
- IN:file-path - Redirects the command standard input channel to the specified file.
- OUT:file-path - Redirects the command standard output channel to the specified file.
- ERR:file-path - Redirects the command standard error channel to the specified file.
- ENV:name=value - Defines an environment variable in the scope of the command.
- DIR:directory-path - Sets the path of the working directory for the command. This feature is not supported if the executing JVM is less than 1.3.
It is also possible to schedule the invocation of a method of a Java class in the scope of the parser ClassLoader. The method has to be static and it must accept an array of strings as its sole argument. To invoke a method of this kind the syntax is:
scheduling-pattern java:className#methodName [args]
The #methodName part can be omitted: in this case the main(String[]) method will be assumed.
Please note that static methods are invoked within the scheduler same JVM, without spawning any external process. Thus IN, OUT, ERR, ENV and DIR options can't be applied.
Invalid scheduling lines are discarded without blocking the parsing procedure, but an error message is sent to the application standard error channel.
Valid examples:
0 5 * * * sol.exe 0,30 * * * * OUT:C:\ping.txt ping 10.9.43.55 0,30 4 * * * "OUT:C:\Documents and Settings\Carlo\ping.txt" ping 10.9.43.55 0 3 * * * ENV:JAVA_HOME=C:\jdks\1.4.2_15 DIR:C:\myproject OUT:C:\myproject\build.log C:\myproject\build.bat "Nightly Build" 0 4 * * * java:mypackage.MyClass#startApplication myOption1 myOption2
posted @
2016-05-07 15:39 华梦行 阅读(1986) |
评论 (0) |
编辑 收藏