Commit deecc2a6 authored by bruse's avatar bruse
Browse files

Introduced asynchronous jobs:

- Export jobs will be sent asynchronously in separate threads.
- Export job results (CityGML files) will be download asynchronously in separate threads.
- Job status' will be polled asynchronously and frequently. The main application can register job status listeners at the jobs in order to be notified upon job status changes of asynchronously running jobs.
- Note: The connector implementation safe to use, but still blocking, which is no problem. Meaning, you have to use AsyncExportJob and AsyncImportJob in order to take advantage of the new asynchronous operations.
parent 82952470
package eu.simstadt.nf4j.async;
import java.util.EventObject;
import java.util.Optional;
import eu.simstadt.nf4j.Job;
import eu.simstadt.nf4j.JobStatus;
/**
* Every time when the status of a job progresses, one of this events will be created and sent
* to all off the registered job status listeners of the job. Job status listeners implement
* the jobStatusChanged() method which takes this JobStatusEvent as its argument. This event will
* contain the job status as the event source and listener can read the source and decide how to
* deal with the new status of its observed job.
*
* Note, the job status, job and additional event messages will be referenced in extra object members of this event,
* because the status of referenced job may change during the notification of the listeners. Therefore, obtaining
* the job status directly from the job is not reliable, if the listener wants to know the actual source of this
* event.
*
* @author Marcel Bruse
*/
public class JobStatusEvent extends EventObject {
private static final long serialVersionUID = -1800246486543538087L;
/** The job for which this event will be sent to the job status listeners. */
private Job job;
/** There might be an additional (error) message provided with the new job status. */
private Optional<String> message;
/**
* Constructor with job status as event source. The source can be read by the job status listeners.
*
* @param source The new job status, which triggers this event.
*/
public JobStatusEvent(JobStatus source, Job job) {
this(source, job, null);
}
/**
* Constructor with job status as event source and an additional (error) message. The source can be read
* by the job status listeners.
*
* @param source The new job status, which triggers this event.
* @param message an additional (error) message for this event and job status.
*/
public JobStatusEvent(JobStatus source, Job job, Optional<String> message) {
super(source);
this.job = job;
this.message = message;
}
/**
* @return Returns the job for which this event will be sent to the job status listeners.
*/
public Job getJob() {
return job;
}
/**
* @return Returns an additional (error) message, if present.
*/
public Optional<String> getMessage() {
return message;
}
}
package eu.simstadt.nf4j.async;
import java.util.EventListener;
/**
* Your main application may become a job status listener in order to get updates about the status changes of its
* ongoing jobs. Job listeners of asynchronous export and import jobs will receive event objects for most of the
* job status' listed in the job status enumeration.
*
* @author Marcel Bruse
*/
public interface JobStatusListener extends EventListener {
/**
* This callback method will be called by your asynchronous export and import jobs during their send, poll
* and download operations in order to keep you updated about job status changes.
*
* @param event The latest job status event for one of your export or import jobs.
*/
public void jobStatusChanged(JobStatusEvent event);
}
package eu.simstadt.nf4j; package eu.simstadt.nf4j.async;
/** /**
* A layer describes an aspect of a nF product and the type of its data. For instance, a layer could contain * A layer describes an aspect of a nF product and the type of its data. For instance, a layer could contain
......
package eu.simstadt.nf4j.async;
import java.io.File;
import eu.simstadt.nf4j.FailedTransmissionException;
import eu.simstadt.nf4j.InvalidJobDescriptorException;
import eu.simstadt.nf4j.JobStatus;
/**
* This is a class for development and test purposes. It is not needed for production. You may want to delete it.
*
* @author Marcel Bruse
*/
public class Main implements JobStatusListener {
public AsyncExportJob job;
public void doit() {
ExportJobDescription jobDescriptor = ExportJobDescription.getDefaultDescriptor();
jobDescriptor.setProduct("WU3");
Unit unit = Unit.getDefaultUnit();
unit.setValue("821");
jobDescriptor.addUnit(unit);
Layer layer = Layer.getDefaultLayer();
layer.setProduct("WU3");
layer.setName("GML");
jobDescriptor.addLayer(layer);
job = new AsyncExportJob(jobDescriptor, new HTTPConnection("193.196.136.164"));
job.addJobStatusListener(this);
try {
job.send();
} catch (FailedTransmissionException ex) {
ex.printStackTrace();
} catch (InvalidJobDescriptorException ex) {
ex.printStackTrace();
}
System.out.println("Main thread is back again");
}
public static void main(String[] args) {
Main m = new Main();
m.doit();
}
@Override
public void jobStatusChanged(JobStatusEvent event) {
System.out.println(event.getSource() + ": " + event.getMessage().orElse("(no message)"));
if (event.getSource() == JobStatus.FINISHED) {
try {
job.downloadResult();
} catch (FailedTransmissionException ex) {
// TODO Auto-generated catch block
ex.printStackTrace();
}
} else if (event.getSource() == JobStatus.DOWNLOAD) {
try {
File f = job.getResult();
System.out.println("File can be read for job "+ event.getJob().getId() +": " + f.getAbsolutePath());
} catch (FailedTransmissionException ex) {
// TODO Auto-generated catch block
ex.printStackTrace();
}
}
}
}
\ No newline at end of file
package eu.simstadt.nf4j; package eu.simstadt.nf4j.async;
/** /**
* These are the known operation modes of the nF import servlet. * These are the known operation modes of the nF import servlet.
......
package eu.simstadt.nf4j.async;
import eu.simstadt.nf4j.FailedTransmissionException;
/**
* This task frequently polls the status of an asynchronous job within a separate poll thread. Changes of the
* jobs status will be signaled to all of the job status listeners registered at the job.
* You can cancel this task by calling job.cancel().
*
* @author Marcel Bruse
*/
public class PollJobStatusTask implements Runnable {
/** The job for which you want to poll status changes for. */
private AsyncJob job;
/**
* Don't flood your nF server with status request. This interval ensures that your server will receive
* a status request within every time interval.
*/
private long interval;
/**
* Constructor with asynchronous job and the poll interval.
*
* @param job The job to update frequently.
* @param interval The time interval for one request.
*/
public PollJobStatusTask(AsyncJob job, long interval) {
this.job = job;
this.interval = interval;
}
/**
* This method performs the poll operation asynchronously in the jobs separate poll thread.
* Job status listeners will be notified upon status changes.
*/
@Override
public void run() {
try {
while (!job.hasFinished() && !job.hasFailed() && job.keepPolling()) {
job.triggerStatusUpdate();
Thread.sleep(interval * 1000l);
}
// At this line the job may have finished or failed before the job listeners could be notified.
// Therefore, we have to ensure that all listeners know the current status.
job.notifyJobStatusListeners();
} catch (FailedTransmissionException ex) {
job.cancel();
} catch (InterruptedException ex) {
// Canceled by the main thread
}
}
}
\ No newline at end of file
package eu.simstadt.nf4j; package eu.simstadt.nf4j.async;
import org.xml.sax.Attributes; import org.xml.sax.Attributes;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.DefaultHandler;
/** /**
* This SAX handler scans a nF XML status report instance and searches for the nF job id and the status of a * This SAX handler scans nF XML status reports and exception reports and searches for the nF job id, the status of a
* nF job. * nF job and service exception messages.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
......
package eu.simstadt.nf4j.async;
import java.util.Optional;
import eu.simstadt.nf4j.FailedTransmissionException;
import eu.simstadt.nf4j.InvalidJobDescriptorException;
import eu.simstadt.nf4j.JobStatus;
/**
* This task sends an export job to your nF server asynchronously within a separate send thread. Once the send
* operation has finished all of the jobs status listeners will be notified. You can cancel this task by calling
* job.cancel().
*
* @author Marcel Bruse
*/
public class SendExportJobTask implements Runnable {
/** The job to be sent to your nF server. */
private AsyncExportJob job;
/**
* Constructor with the export job to be sent.
*
* @param job The export job to be sent.
*/
public SendExportJobTask(AsyncExportJob job) {
this.job = job;
}
/**
* This methods performs the actual send operation asynchronously in a separate send thread.
* Job status listeners will be notified once the operation finishes or fails.
*/
@Override
public void run() {
try {
HTTPConnection connector = (HTTPConnection) job.getConnector();
connector.sendAndUpdateExportJob(job);
job.setStatus(JobStatus.SENT, Optional.empty());
job.poll();
} catch (InvalidJobDescriptorException ex) {
signalError("Job cancel because of an invalid job description!");
} catch (FailedTransmissionException ex) {
signalError("The job transmission failed. There seams to be a problem with the connector!");
}
}
/**
* This method is superfluous I guess? Please refactor it.
*/
private void signalError(String errorMessage) {
job.setStatus(JobStatus.UNKOWN, Optional.of(errorMessage));
}
}
package eu.simstadt.nf4j.async;
import java.util.Optional;
import eu.simstadt.nf4j.FailedTransmissionException;
import eu.simstadt.nf4j.InvalidJobDescriptorException;
import eu.simstadt.nf4j.JobStatus;
/**
* This task sends an import job to your nF server asynchronously within a separate send thread. Once the send
* operation has finished all of the jobs status listeners will be notified. You can cancel this task by calling
* job.cancel().
*
* @author Marcel Bruse
*/
public class SendImportJobTask implements Runnable {
/** The job to be sent to your nF server. */
private AsyncImportJob job;
/**
* Constructor with the import job to be sent.
*
* @param job The import job to be sent.
*/
public SendImportJobTask(AsyncImportJob job) {
this.job = job;
}
/**
* This methods performs the actual send operation asynchronously in a separate send thread.
* Job status listeners will be notified once the operation finishes or fails.
*/
@Override
public void run() {
try {
HTTPConnection connector = (HTTPConnection) job.getConnector();
connector.sendAndUpdateImportJob(job);
job.setStatus(JobStatus.SENT, Optional.empty());
job.poll();
} catch (InvalidJobDescriptorException ex) {
signalError("Job cancel because of an invalid job description!");
} catch (FailedTransmissionException ex) {
signalError("The job transmission failed. There seams to be a problem with the connector!");
}
}
private void signalError(String errorMessage) {
job.setStatus(JobStatus.UNKOWN, Optional.of(errorMessage));
}
}
package eu.simstadt.nf4j; package eu.simstadt.nf4j.async;
/** /**
* Units (Blattschnitte) divide regions into sections. For instance, the city of Stuttgart could have the units * Units (Blattschnitte) divide regions into sections. For instance, the city of Stuttgart could have the units
......
...@@ -22,11 +22,11 @@ ...@@ -22,11 +22,11 @@
import com.lynden.gmapsfx.javascript.object.Marker; import com.lynden.gmapsfx.javascript.object.Marker;
import com.lynden.gmapsfx.javascript.object.MarkerOptions; import com.lynden.gmapsfx.javascript.object.MarkerOptions;
import eu.simstadt.nf4j.Coord;
import eu.simstadt.nf4j.ExportJobDescriptorImpl;
import eu.simstadt.nf4j.JobFileBuilderImpl;
import eu.simstadt.nf4j.JobDescriptor; import eu.simstadt.nf4j.JobDescriptor;
import eu.simstadt.nf4j.Layer; import eu.simstadt.nf4j.async.Coord;
import eu.simstadt.nf4j.async.ExportJobDescription;
import eu.simstadt.nf4j.async.JobFileBuilderImpl;
import eu.simstadt.nf4j.async.Layer;
public class RegionChooserController implements MapComponentInitializedListener public class RegionChooserController implements MapComponentInitializedListener
...@@ -116,7 +116,7 @@ protected String call() throws Exception { ...@@ -116,7 +116,7 @@ protected String call() throws Exception {
layer.setName("GML"); layer.setName("GML");
layer.setProduct("LBTEST"); layer.setProduct("LBTEST");
layer.setStyle("#000000"); layer.setStyle("#000000");
ExportJobDescriptorImpl jobDescriptor = ExportJobDescriptorImpl.getDefaultDescriptor(); ExportJobDescription jobDescriptor = ExportJobDescription.getDefaultDescriptor();
jobDescriptor.addLayer(layer); jobDescriptor.addLayer(layer);
jobDescriptor.setRegionPolygon(regionPolygon); jobDescriptor.setRegionPolygon(regionPolygon);
JobFileBuilderImpl jobBuilder = new JobFileBuilderImpl(); JobFileBuilderImpl jobBuilder = new JobFileBuilderImpl();
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment