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
...@@ -5,9 +5,12 @@ ...@@ -5,9 +5,12 @@
/** /**
* NFConnector lets you communicate with your novaFACTORY (nF) server instance. * NFConnector lets you communicate with your novaFACTORY (nF) server instance.
* *
* @param <I> The import job implementation which can be handled by the connector.
* @param <E> The export job implementation which can be handled by the connector.
*
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public interface NFConnector { public interface Connector<I extends ImportJob<?>, E extends ExportJob<?>> {
/** /**
* Callers of this NFConnector want to know the actual version of the novaFACTORY and the versions of its * Callers of this NFConnector want to know the actual version of the novaFACTORY and the versions of its
...@@ -30,7 +33,7 @@ public interface NFConnector { ...@@ -30,7 +33,7 @@ public interface NFConnector {
* @param job The nF export job with description. If the job description is invalid, you will receive an * @param job The nF export job with description. If the job description is invalid, you will receive an
* InvalidJobDescriptorException. * InvalidJobDescriptorException.
*/ */
public void sendAndUpdateExportJob(ExportJob job) public void sendAndUpdateExportJob(E exportJob)
throws InvalidJobDescriptorException, FailedTransmissionException; throws InvalidJobDescriptorException, FailedTransmissionException;
/** /**
...@@ -59,7 +62,7 @@ public void sendAndUpdateExportJob(ExportJob job) ...@@ -59,7 +62,7 @@ public void sendAndUpdateExportJob(ExportJob job)
* *
* @param job The nF import job to be sent. It has to contain a valid description. * @param job The nF import job to be sent. It has to contain a valid description.
*/ */
public void sendAndUpdateImportJob(ImportJob job) public void sendAndUpdateImportJob(I importJob)
throws InvalidJobDescriptorException, FailedTransmissionException; throws InvalidJobDescriptorException, FailedTransmissionException;
/** /**
...@@ -68,7 +71,7 @@ public void sendAndUpdateImportJob(ImportJob job) ...@@ -68,7 +71,7 @@ public void sendAndUpdateImportJob(ImportJob job)
* @param jobId The id of the export job for which you want to request the status. * @param jobId The id of the export job for which you want to request the status.
* @return The status of any existing export nF job. * @return The status of any existing export nF job.
*/ */
public ExportJob requestExportJob(int jobId) throws FailedTransmissionException; public E requestExportJob(int jobId) throws FailedTransmissionException;
/** /**
* Returns the status of any existing import nF job. * Returns the status of any existing import nF job.
...@@ -76,7 +79,7 @@ public void sendAndUpdateImportJob(ImportJob job) ...@@ -76,7 +79,7 @@ public void sendAndUpdateImportJob(ImportJob job)
* @param jobId The id of the import job for which you want to request the status. * @param jobId The id of the import job for which you want to request the status.
* @return The status of any existing import nF job. * @return The status of any existing import nF job.
*/ */
public ImportJob requestImportJob(int jobId) throws FailedTransmissionException; public I requestImportJob(int jobId) throws FailedTransmissionException;
/** /**
* Downloads the result for a given nF export job and hands over the corresponding file handle. * Downloads the result for a given nF export job and hands over the corresponding file handle.
...@@ -84,6 +87,6 @@ public void sendAndUpdateImportJob(ImportJob job) ...@@ -84,6 +87,6 @@ public void sendAndUpdateImportJob(ImportJob job)
* @param jobId The id of the export job for which the result should be loaded. * @param jobId The id of the export job for which the result should be loaded.
* @return A file handle to the result of the nF export job. * @return A file handle to the result of the nF export job.
*/ */
public File requestExportJobResult(int jobId) throws FailedTransmissionException; public File requestExportJobResult(E exportJob) throws FailedTransmissionException;
} }
package eu.simstadt.nf4j; package eu.simstadt.nf4j;
import java.io.File; import java.io.File;
import java.util.Objects;
/** /**
* Export jobs are requests for CityGML models. Every valid export job has an id and a status. * An export job is a proxy object for an actual nF export job. Export jobs are used to get data from your nF server.
* Every export job has to have a valid job descriptor and/or a job id.
* *
* @author Marcel Bruse * @author Marcel Bruse
*
* @param <D> The descriptor type for the export job implementation.
*/ */
public class ExportJob extends Job<ExportJobDescriptorImpl> { public abstract class ExportJob<D extends ExportJobDescriptor> extends Job {
/** Every job should have a (valid) job descriptor. */
protected D descriptor;
/** /**
* This constructor forces the job to have a description and a connector instance. Every job which * This constructor forces the job to have a description and a connector instance.
* is created by this constructor will have the status "local", because it is assumed that it has an unsent
* description and no job id yet.
* *
* @param connector The job will use this connector to synchronize itself with the nF. * @param connector The job will use this connector to synchronize itself with the nF.
* @param descriptor The description of this job. * @param descriptor The description of this job.
*/ */
public ExportJob(ExportJobDescriptorImpl descriptor, NFConnector connector) { public ExportJob(D descriptor, Connector<?, ?> connector) {
super(descriptor, connector); this.descriptor = descriptor;
status = JobStatus.LOCAL; this.connector = connector;
} }
/** /**
* This constructor forces the job to have a id and a connector instance. Every job which is created by this * This constructor forces the job to have a id and a connector instance.
* constructor will have the status "sent", because it is assumed that the job is already enqueued at the
* nF job queue.
* *
* @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status * @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status
* will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then * will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then
* @param connector The job will use this connector to synchronize itself with the nF. * @param connector The job will use this connector to synchronize itself with the nF.
*/ */
public ExportJob(int id, NFConnector connector) { public ExportJob(int id, Connector<?, ?> connector) {
super(id, connector); this.id = id;
status = JobStatus.SENT; this.connector = connector;
} }
/** /**
* Connects to the nF and refreshes the status of this job. If there is no nF connector set, * @return Returns the description of this job.
* this operation will throw a FailedTransmissionException.
*
* @throws FailedTransmissionException If the connection to the nF is broken you will get some of this.
*/ */
public void updateStatus() throws FailedTransmissionException { public D getDescriptor() {
if (Objects.nonNull(getNFConnector())) { return descriptor;
Job<ExportJobDescriptorImpl> job = getNFConnector().requestExportJob(getId());
setStatus(job.getStatus());
} else {
throw new FailedTransmissionException();
}
} }
/** /**
* Sets the status of this job depending on the given nF status code. nF status codes will be sent * Once an export job has been finished, the caller should use this method to obtain the actual CityGML file.
* to you in http responses.
* *
* @param statusCode The nF status code for this job. * @return A file handle to the resulting CityGML file.
* @throws FailedTransmissionException There could be a problem while accessing or downloading the file.
*/ */
@Override public abstract File getResult() throws FailedTransmissionException;
public void setStatusForCode(int statusCode) {
switch (statusCode) {
case 0:
setStatus(JobStatus.PENDING); break;
case 10:
setStatus(JobStatus.RUNNING); break;
case 20:
setStatus(JobStatus.FAILED); break;
case 30:
setStatus(JobStatus.FINISHED); break;
default:
setStatus(JobStatus.UNKOWN);
}
}
/**
* @return Returns true, if the job is definitely done. False, otherwise.
*/
public boolean isFinished() {
return Objects.nonNull(getStatus()) && getStatus().equals(JobStatus.FINISHED);
}
@Override
public void send() throws InvalidJobDescriptorException, FailedTransmissionException {
connector.sendAndUpdateExportJob(this);
}
public File requestExportJobResult() throws FailedTransmissionException {
if (!isFinished()) {
throw new FailedTransmissionException("Job is not finished yet!");
}
return connector.requestExportJobResult(id);
}
} }
package eu.simstadt.nf4j; package eu.simstadt.nf4j;
/** /**
* Implementations of this interface are known to implement export job descriptions. * Implementations of this interface are known to be export job descriptions.
* *
* @author Marcel Bruse * @author Marcel Bruse
*
*/ */
public interface ExportJobDescriptor extends JobDescriptor { public interface ExportJobDescriptor extends JobDescriptor {}
}
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
/** /**
* This exception may be thrown by classes of the nf4j package if/on ... * This exception may be thrown by classes of the nf4j package if/on ...
*
* - the connector is null * - the connector is null
* - malformed URLs * - malformed URLs
* - missing or malformed XML reports * - missing or malformed XML reports
...@@ -13,14 +14,44 @@ public class FailedTransmissionException extends Exception { ...@@ -13,14 +14,44 @@ public class FailedTransmissionException extends Exception {
private static final long serialVersionUID = -3530932388888249528L; private static final long serialVersionUID = -3530932388888249528L;
/** An textual description of the error. */
private String message; private String message;
/** Standard constructor. */
public FailedTransmissionException() {} public FailedTransmissionException() {}
/**
* Constructor with error message and without nested cause.
*
* @param message The error message.
*/
public FailedTransmissionException(String message) { public FailedTransmissionException(String message) {
this(message, null);
}
/**
* Constructor with cause and without error message.
*
* @param cause The nested cause of this exception.
*/
public FailedTransmissionException(Throwable cause) {
this(null, cause);
}
/**
* Constructor with error message and nested cause.
*
* @param message The error message.
* @param cause The nested cause of this exception.
*/
public FailedTransmissionException(String message, Throwable cause) {
this.message = message; this.message = message;
initCause(cause);
} }
/**
* @return Returns the error message, if present.
*/
@Override @Override
public String getMessage() { public String getMessage() {
return message; return message;
......
package eu.simstadt.nf4j; package eu.simstadt.nf4j;
import java.util.Objects;
/** /**
* Import jobs are requests to store, change or delete CityGML models. Every valid import job has an id and a status. * An import job is a proxy object for an actual nF import job. Import jobs are used to store data in your nF database.
* * Every import job has to have a valid job descriptor and/or a job id.
* @author Marcel Bruse *
*/ * @author Marcel Bruse
public class ImportJob extends Job<ImportJobDescriptorImpl> { *
* @param <D> The descriptor type for the import job implementation.
*/
public abstract class ImportJob<D extends ImportJobDescriptor> extends Job {
/** Every job should have a (valid) job descriptor. */
protected D descriptor;
/** /**
* This constructor forces the job to have a description and a connector instance. Every job which * This constructor forces the job to have a description and a connector instance.
* is created by this constructor will have the status "local", because it is assumed that it has an unsent
* description and no job id yet.
* *
* @param connector The job will use this connector to synchronize itself with the nF. * @param connector The job will use this connector to synchronize itself with the nF.
* @param descriptor The description of this job. * @param descriptor The description of this job.
*/ */
public ImportJob(ImportJobDescriptorImpl descriptor, NFConnector connector) { public ImportJob(D descriptor, Connector<?, ?> connector) {
super(descriptor, connector); this.descriptor = descriptor;
status = JobStatus.LOCAL; this.connector = connector;
} }
/** /**
* This constructor forces the job to have a id and a connector instance. Every job which is created by this * This constructor forces the job to have a id and a connector instance.
* constructor will have the status "sent", because it is assumed that the job is already enqueued at the
* nF job queue.
* *
* @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status * @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status
* will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then * will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then
* @param connector The job will use this connector to synchronize itself with the nF. * @param connector The job will use this connector to synchronize itself with the nF.
*/ */
public ImportJob(int id, NFConnector connector) { public ImportJob(int id, Connector<?, ?> connector) {
super(id, connector); this.id = id;
status = JobStatus.SENT; this.connector = connector;
}
/**
* Connects to the nF and refreshes the status of this job. If there is no nF connector set,
* this operation will throw a FailedTransmissionException.
*
* @throws FailedTransmissionException If the connection to the nF is broken you will get some of this.
*/
public void updateStatus() throws FailedTransmissionException {
if (Objects.nonNull(getNFConnector())) {
Job<ImportJobDescriptorImpl> job = getNFConnector().requestImportJob(getId());
setStatus(job.getStatus());
} else {
throw new FailedTransmissionException();
}
} }
/** /**
* Sets the status of this job depending on the given nF status code. nF status codes will be sent * @return Returns the description of this job.
* to you in http responses.
*
* @param statusCode The nF status code for this job.
*/ */
@Override public D getDescriptor() {
public void setStatusForCode(int statusCode) { return descriptor;
switch (statusCode) {
case 0:
setStatus(JobStatus.READY_TO_RUN); break;
case 10:
setStatus(JobStatus.RUNNING); break;
case 20:
setStatus(JobStatus.ERROR); break;
case 25:
setStatus(JobStatus.WARNING); break;
case 30:
setStatus(JobStatus.FINISHED); break;
case 40:
setStatus(JobStatus.APPROVE); break;
case 45:
setStatus(JobStatus.REJECT); break;
case 50:
setStatus(JobStatus.APPROVE_RUNNING); break;
case 55:
setStatus(JobStatus.REJECT_RUNNING); break;
case 60:
setStatus(JobStatus.APPROVE_REJECT_ERROR); break;
case 70:
setStatus(JobStatus.APPROVE_REJECT_OK); break;
case 80:
setStatus(JobStatus.IMPORTED_WARNING); break;
default:
setStatus(JobStatus.UNKOWN);
}
}
@Override
public void send() throws InvalidJobDescriptorException,
FailedTransmissionException {
connector.sendAndUpdateImportJob(this);
} }
} }
package eu.simstadt.nf4j; package eu.simstadt.nf4j;
/**
* If your export and import job descriptions are invalid due to the job.isValid() method, then it is very likely
* that you will get this exception.
*
* @author Marcel Bruse
*/
public class InvalidJobDescriptorException extends Exception { public class InvalidJobDescriptorException extends Exception {
private static final long serialVersionUID = 2710340003578550634L; private static final long serialVersionUID = 2710340003578550634L;
......
...@@ -5,42 +5,16 @@ ...@@ -5,42 +5,16 @@
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public abstract class Job<D extends JobDescriptor> { public abstract class Job {
/** The status of the job. There are different states for export and import jobs. */ /** The status of the job. There are different states for export and import jobs. */
protected JobStatus status; protected JobStatus status;
/** Every job should have a (valid) job descriptor. */
protected D descriptor;
/** The id of the job. */ /** The id of the job. */
protected int id; protected int id;
/** The connection to the nF. */ /** The connection to the nF. */
protected NFConnector connector; protected Connector<?, ?> connector;
/**
* This constructor forces the job to have a description and a connector instance.
*
* @param connector The job will use this connector to synchronize itself with the nF.
* @param descriptor The description of this job.
*/
public Job(D descriptor, NFConnector connector) {
this.descriptor = descriptor;
this.connector = connector;
}
/**
* This constructor forces the job to have a id and a connector instance.
*
* @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status
* will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then
* @param connector The job will use this connector to synchronize itself with the nF.
*/
public Job(int id, NFConnector connector) {
this.id = id;
this.connector = connector;
}
/** /**
* Every job has it's status. Look up the different possible values in the JobStatus enumeration. * Every job has it's status. Look up the different possible values in the JobStatus enumeration.
...@@ -60,13 +34,6 @@ protected void setStatus(JobStatus status) { ...@@ -60,13 +34,6 @@ protected void setStatus(JobStatus status) {
this.status = status; this.status = status;
} }
/**
* @return Returns the description of this job.
*/
public D getDescriptor() {
return descriptor;
}
/** /**
* @return Returns the id of this job. * @return Returns the id of this job.
*/ */
...@@ -86,7 +53,7 @@ public void setId(int jobId) { ...@@ -86,7 +53,7 @@ public void setId(int jobId) {
/** /**
* @return Returns the nF connector of this job. * @return Returns the nF connector of this job.
*/ */
public NFConnector getNFConnector() { public Connector<?, ?> getConnector() {
return connector; return connector;
} }
...@@ -95,8 +62,8 @@ public NFConnector getNFConnector() { ...@@ -95,8 +62,8 @@ public NFConnector getNFConnector() {
* *
* @param nFConnector The connector of this job. * @param nFConnector The connector of this job.
*/ */
public void setNFConnector(NFConnector nFConnector) { public void setConnector(Connector<?, ?> connector) {
this.connector = nFConnector; this.connector = connector;
} }
/** /**
......
...@@ -6,9 +6,24 @@ ...@@ -6,9 +6,24 @@
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public enum JobStatus { public enum JobStatus {
UNKOWN(0), LOCAL(1), SENT(2), PENDING(3), RUNNING(4), FAILED(5), FINISHED(6), READY_TO_RUN(7), ERROR(8), UNKOWN(0), // A local NF4J code, may be set in rare cases, if synchronization with nF fails.
WARNING(9), APPROVE(10), REJECT(11), APPROVE_RUNNING(12), REJECT_RUNNING(13), APPROVE_REJECT_ERROR(14), LOCAL(10), // Job has not been sent yet and is known by the local system only.
APPROVE_REJECT_OK(15), IMPORTED_WARNING(16); SENT(20), // Job has been sent or it is assumed that it has been set before.
PENDING(30), // Export job has been enqueued and waits for execution
READY_TO_RUN(31), // Same as PENDING, but for import jobs
APPROVE(32), // (?) For import jobs only. Read the nF documentation, seams to be never used.
RUNNING(40), // Job is running
APPROVE_RUNNING(41), // (?) For import jobs only. Read the nF documentation, seams to be never used.
FAILED(50), // Export job failed
ERROR(51), // Same as FAILED, but for import jobs
WARNING(52), // For import jobs only. There has been a minor problem
REJECT(53), // (?) For import jobs only. Read the nF documentation, seams to be never used.
REJECT_RUNNING(54), // (?) For import jobs only. Read the nF documentation, seams to be never used.
APPROVE_REJECT_ERROR(55), // (?) For import jobs only. Read the nF documentation, seams to be never used.
APPROVE_REJECT_OK(56), // (?) For import jobs only. Read the nF documentation, seams to be never used.
IMPORTED_WARNING(57), // (?) For import jobs only. Read the nF documentation, seams to be never used.
FINISHED(60), // Job finished
DOWNLOAD(70); // Export jobs only. CityGML has been download to the local file system
public static final String UNKNOWN_MESSAGE = "The state of the job is unknown."; public static final String UNKNOWN_MESSAGE = "The state of the job is unknown.";
public static final String LOCAL_MESSAGE = "The job is known locally only. It has not been sent yet."; public static final String LOCAL_MESSAGE = "The job is known locally only. It has not been sent yet.";
...@@ -17,6 +32,7 @@ public enum JobStatus { ...@@ -17,6 +32,7 @@ public enum JobStatus {
public static final String RUNNING_MESSAGE = "Job is running."; public static final String RUNNING_MESSAGE = "Job is running.";
public static final String FAILED_MESSAGE = "Job failed."; public static final String FAILED_MESSAGE = "Job failed.";
public static final String FINISHED_MESSAGE = "Job is finished."; public static final String FINISHED_MESSAGE = "Job is finished.";
public static final String WAITING_MESSAGE = "Job is waiting for a response from the remote nF instance.";
/** /**
* This constructor sets messages for some states. * This constructor sets messages for some states.
...@@ -27,19 +43,19 @@ private JobStatus(int internalCode) { ...@@ -27,19 +43,19 @@ private JobStatus(int internalCode) {
switch (internalCode) { switch (internalCode) {
case 0: case 0:
message = UNKNOWN_MESSAGE; break; message = UNKNOWN_MESSAGE; break;
case 1: case 10:
message = LOCAL_MESSAGE; break; message = LOCAL_MESSAGE; break;
case 2: case 20:
message = SENT_MESSAGE; break; message = SENT_MESSAGE; break;
case 3: case 30:
case 7: case 31:
message = PENDING_MESSAGE; break; message = PENDING_MESSAGE; break;
case 4: case 40:
message = RUNNING_MESSAGE; break; message = RUNNING_MESSAGE; break;
case 5: case 50:
case 8: case 51:
message = FAILED_MESSAGE; break; message = FAILED_MESSAGE; break;
case 6: case 60:
message = FINISHED_MESSAGE; break; message = FINISHED_MESSAGE; break;
default: default:
message = ""; message = "";
......
package eu.simstadt.nf4j;
import java.util.Arrays;
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
ExportJobDescriptorImpl jobDescriptor = ExportJobDescriptorImpl.getDefaultDescriptor();
jobDescriptor.setProduct("WU3");
Arrays.asList("820", "821", "822", "823", "824").forEach(new Consumer<String>() {
@Override
public void accept(String unitLabel) {
Unit unit = Unit.getDefaultUnit();
unit.setValue(unitLabel);
jobDescriptor.addUnit(unit);
}
});
Layer layer = Layer.getDefaultLayer();
layer.setProduct("WU3");
layer.setName("GML");
jobDescriptor.addLayer(layer);
ExportJob job = new ExportJob(jobDescriptor, new NFConnectorImpl("193.196.136.164"));
try {
job.send();
int i = 1;
while(!job.isFinished()) {
System.out.println(i++);
Thread.sleep(5000l);
job.updateStatus();
System.out.println(job.getStatus());
}
job.requestExportJobResult();
} catch (FailedTransmissionException | InvalidJobDescriptorException ex) {
ex.printStackTrace();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
\ No newline at end of file
package eu.simstadt.nf4j.async;
import java.io.File;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Optional;
import eu.simstadt.nf4j.ExportJob;
import eu.simstadt.nf4j.FailedTransmissionException;
import eu.simstadt.nf4j.InvalidJobDescriptorException;
import eu.simstadt.nf4j.JobStatus;
import eu.simstadt.nf4j.Connector;
/**
* Export jobs are requests for CityGML models. Every valid export job has an id and a status. This implementation
* offers non-blocking asynchronous send, poll and download operations, so that your main application has not to
* wait for the results. You may want to register your main application as a job status listeners at this job to
* get status updates from the asynchronous operations.
*
* @author Marcel Bruse
*/
public class AsyncExportJob extends ExportJob<ExportJobDescription> implements AsyncJob {
/**
* While polling for the current job status, the polling thread will sleep for this amount of time within
* each iteration.
*/
private final long DEFAULT_POLLING_INTERVAL = 5; // seconds
/** There can only be one sending thread for each job at a time. */
private Thread sendThread;
/** There can only be one polling thread for each job at a time. */
private Thread pollThread;
/** There can only be one download thread for each job at a time. */
private Thread downloadThread;
/**
* Once the send() operation has been triggered, this member will be true. No subsequent invocations of
* send() will be possible then.
*/
private boolean jobTransmissionTriggered = false;
/** As long as this variable is true, the polling thread will be kept alive. */
private boolean keepPolling = true;
/**
* List of all registered job status listeners. Whenever the state of this job changes, these listeners
* will get informed.
*/
private LinkedList<JobStatusListener> jobListenerList = new LinkedList<>();
/**
* This job will be send and observed asynchronously. It's results will be downloaded asynchronously also.
* If an asynchronous operation breaks, then the last encountered problem will be described here.
*/
private Optional<String> lastEncounteredProblem = Optional.empty();
/**
* The last job status which has been sent to all registered job status listeners.
*/
private JobStatus lastPublishedJobStatus;
/** Once the CityGML file has been download, it should be referenced here. */
private File result;
/**
* This constructor forces the job to have a description and a connector instance. Every job which
* is created by this constructor will have the status "local", because it is assumed that it has an unsent
* description and no job id yet.
*
* @param connector The job will use this connector to synchronize itself with the nF.
* @param descriptor The description of this job.
*/
public AsyncExportJob(ExportJobDescription descriptor, Connector<AsyncImportJob, AsyncExportJob> connector) {
super(descriptor, connector);
status = JobStatus.LOCAL;
}
/**
* This constructor forces the job to have a id and a connector instance. Every job which is created by this
* constructor will have the status "sent", because it is assumed that the job is already enqueued at the
* nF job queue.
*
* @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status
* will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then
* @param connector The job will use this connector to synchronize itself with the nF.
*/
public AsyncExportJob(int id, Connector<AsyncImportJob, AsyncExportJob> connector) {
super(id, connector);
status = JobStatus.SENT;
}
/**
* Builds an XML job file from the job description and sends it to the nF server which has been configured
* in your connector instance. This will be done asynchronously within a SendExportJobTask.
*
* @throws FailedTransmissionException If this method has been called before, then you will receive this.
* @throws InvalidJobDescriptorException If the job description is invalid or null, then you will receive this.
*/
@Override
public synchronized void send() throws FailedTransmissionException, InvalidJobDescriptorException {
if (jobTransmissionTriggered) {
throw new FailedTransmissionException("Jobs cannot be sent twice!");
}
if (Objects.isNull(descriptor) || !descriptor.isValid()) {
throw new InvalidJobDescriptorException();
}
jobTransmissionTriggered = true;
notifyJobStatusListeners(); // Force the job to signal the LOCAL status
sendThread = new Thread(new SendExportJobTask(this));
sendThread.start();
}
/**
* Frequently queries the remote status of the nF export job and updates the local status accordingly.
* The queries will be performed asynchronously in a separate thread. Job status listener will be notified
* upon every new status change.
*
* @throws FailedTransmissionException If your job has not been sent yet, then you will get some of this.
*/
@Override
public synchronized void poll() throws FailedTransmissionException {
if (status.compareTo(JobStatus.SENT) < 0) {
throw new FailedTransmissionException("The job has not been sent to the nF yet!");
}
if (Objects.nonNull(pollThread)) {
pollThread.interrupt();
}
keepPolling = true;
pollThread = new Thread(new PollJobStatusTask(this, DEFAULT_POLLING_INTERVAL));
pollThread.start();
}
/**
* Connects to the nF and refreshes the status of this job. If there is no nF connector set,
* this operation will throw a FailedTransmissionException.
*
* @throws FailedTransmissionException You will receive this exception if no connector is present, the connection
* to the nF is broken, the job has not been sent to the nF yet, or another update request is ongoing. In the two
* latter cases, job will either have the status "LOCAL" or "WAITING".
*/
@Override
public synchronized void updateStatus() throws FailedTransmissionException {
if (Objects.isNull(connector)) {
throw new FailedTransmissionException("No connector set for this job!");
}
if (status.compareTo(JobStatus.SENT) < 0) {
throw new FailedTransmissionException("The job has not been sent to the nF yet!");
}
AsyncExportJob job = ((HTTPConnection) connector).requestExportJob(id);
JobStatus newStatus = job.getStatus();
if (newStatus.compareTo(JobStatus.SENT) > 0) {
setStatus(job.getStatus(), Optional.empty());
}
}
/**
* Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the
* asynchronous PollJobStatusTask class.
*/
@Override
public void triggerStatusUpdate() throws FailedTransmissionException {
updateStatus();
}
/**
* Sets the status of this job depending on the given nF status code. nF status codes will be sent
* to you in http responses.
*
* @param statusCode The nF status code for this job.
*/
@Override
public synchronized void setStatusForCode(int statusCode) {
switch (statusCode) {
case 0:
setStatus(JobStatus.PENDING); break;
case 10:
setStatus(JobStatus.RUNNING); break;
case 20:
setStatus(JobStatus.FAILED); break;
case 30:
setStatus(JobStatus.FINISHED); break;
default:
setStatus(JobStatus.UNKOWN);
}
}
/**
* @return Returns true, if the job is definitely done. This is also the case, if the resulting CityGML
* file has been download. False, otherwise.
*/
@Override
public boolean hasFinished() {
return status == JobStatus.FINISHED || status == JobStatus.DOWNLOAD;
}
/**
* @return Returns true, if the job has been failed. You may want to look up the "last encountered problem"
* string.
*/
@Override
public boolean hasFailed() {
return status == JobStatus.FAILED;
}
/**
* Registers a job status listener.
*
* @param jobListener The job status listener to be registered. This listener will receive updates about every
* progressing change of the job status. Meaning, the change to a particular status will only be signaled once
* to the listener.
*/
@Override
public void addJobStatusListener(JobStatusListener jobListener) {
jobListenerList.add(jobListener);
}
/**
* Unregisters a job status listener.
*
* @param jobListener The job status listener to be unregistered.
*/
@Override
public void removeJobStatusListener(JobStatusListener jobListener) {
jobListenerList.remove(jobListener);
}
/**
* Once the status of this job changes, all registered job status listeners will be notified.
* Listeners will only be notified of the status updates where the status of the job progresses and they will
* only be notified once about every singular status. If this status has been signaled already, then the
* listeners will not be notified again.
*/
@Override
public synchronized void notifyJobStatusListeners() {
if (Objects.isNull(lastPublishedJobStatus) || status.compareTo(lastPublishedJobStatus) > 0) {
JobStatusEvent event = new JobStatusEvent(status, this, lastEncounteredProblem);
for (JobStatusListener listener : jobListenerList) {
listener.jobStatusChanged(event);
}
lastPublishedJobStatus = status;
}
}
/**
* Cancels all ongoing send, poll and download operations as soon as possible.
*/
@Override
public void cancel() {
keepPolling = false;
if (Objects.nonNull(sendThread)) {
sendThread.interrupt();
}
if (Objects.nonNull(pollThread)) {
pollThread.interrupt();
}
if (Objects.nonNull(downloadThread)) {
downloadThread.interrupt();
}
}
/**
* @return Returns true, if the polling thread should go on with its polling job. Otherwise, false.
*/
@Override
public boolean keepPolling() {
return keepPolling;
}
/**
* Starts downloading the export job result, if there is any. As soon as the download has been finished,
* the job status will be set to DOWNLOADED. All registered job status listeners will get notified about
* the it. Afterwards, you may want to obtain a handle to the download CityGML file with getResult().
*
* @throws FailedTransmissionException If the job has not been finished yet, then you will get some
* of this.
*/
public void downloadResult() throws FailedTransmissionException {
if (!hasFinished()) {
throw new FailedTransmissionException("Job has not been finished!");
}
downloadThread = new Thread(new DownloadTask(this));
downloadThread.start();
}
/**
* This method is used by the download task to set the CityGML file handle as soon as the file has
* been download.
*
* @param result The file handle of the download CityGML file.
*/
protected void setResult(File result) {
this.result = result;
}
/**
* This method will return the download CityGML file for this export job, but only if the export job has
* actually been finished before.
*
* @return Returns a file handle to the download CityGML file.
*
* @throws FailedTransmissionException If the job result has not been download yet, then you will get some
* of this.
*/
@Override
public File getResult() throws FailedTransmissionException {
if (!hasFinished()) {
throw new FailedTransmissionException("Job has not been finished!");
}
if (Objects.isNull(result)) {
throw new FailedTransmissionException("Job result has not been downloaded!");
}
return result;
}
/**
* The asynchronous send, poll and download tasks cannot throw exceptions. If something goes wrong during the save,
* poll or download operation, then you may want to submit at lease a textual description of the problem here. This
* message will be sent to all registered job status listeners on the next status update. This is why you can use
* the convenience method setStatus(jobStatus, message), to do both at the same time.
*
* @param errorMessage The description of the encountered problem. This could be the exception message or a more user
* friendly message.
*/
protected synchronized void setLastEncounteredProblem(Optional<String> errorMessage) {
this.lastEncounteredProblem = errorMessage;
}
/**
* A convenience method to set a new job status and a status message at the same time. Status messages will be passed
* by asynchronous tasks instead of exception, because they cannot throw exceptions.
*
* @param jobStatus The new status of this job.
* @param message A status message. This message may describe a problem which occurred during an asynchronous task.
*/
protected synchronized void setStatus(JobStatus jobStatus, Optional<String> message) {
super.setStatus(jobStatus);
lastEncounteredProblem = message;
notifyJobStatusListeners();
}
}
\ No newline at end of file
package eu.simstadt.nf4j.async;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Optional;
import eu.simstadt.nf4j.ImportJob;
import eu.simstadt.nf4j.FailedTransmissionException;
import eu.simstadt.nf4j.InvalidJobDescriptorException;
import eu.simstadt.nf4j.JobStatus;
/**
* Import jobs are requests to store, change or delete CityGML models. Every valid import job has an id and a status.
* This implementation offers non-blocking asynchronous send and poll operations, so that your main application has
* not to wait for the results. You may want to register your main application as a job status listeners at this job
* to get status updates from the asynchronous operations.
*
* @author Marcel Bruse
*/
public class AsyncImportJob extends ImportJob<ImportJobDescription> implements AsyncJob {
/**
* While polling for the current job status, the polling thread will sleep for this amount of time within
* each iteration.
*/
private final long DEFAULT_POLLING_INTERVAL = 5; // seconds
private boolean jobTransmissionTriggered = false;
private JobStatus lastPublishedJobStatus;
private Optional<String> lastEncounteredProblem = Optional.empty();
private Thread sendThread;
private Thread pollThread;
/** As long as this variable is true, the polling thread will be kept alive. */
private boolean keepPolling = true;
/**
* List of all registered job status listeners. Whenever the state of this job changes, these listeners
* will get informed.
*/
private LinkedList<JobStatusListener> jobListenerList = new LinkedList<>();
/**
* This constructor forces the job to have a description and a connector instance. Every job which
* is created by this constructor will have the status "local", because it is assumed that it has an unsent
* description and no job id yet.
*
* @param connector The job will use this connector to synchronize itself with the nF.
* @param descriptor The description of this job.
*/
public AsyncImportJob(ImportJobDescription descriptor, HTTPConnection connector) {
super(descriptor, connector);
status = JobStatus.LOCAL;
}
/**
* This constructor forces the job to have a id and a connector instance. Every job which is created by this
* constructor will have the status "sent", because it is assumed that the job is already enqueued at the
* nF job queue.
*
* @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status
* will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then
* @param connector The job will use this connector to synchronize itself with the nF.
*/
public AsyncImportJob(int id, HTTPConnection connector) {
super(id, connector);
status = JobStatus.SENT;
}
/**
* This method zips up an archive which includes the CityGML file to be imported as well as a nF start
* file. Both files will be preprocessed according to the set attributes of the job description.
*/
@Override
public synchronized void send() throws InvalidJobDescriptorException, FailedTransmissionException {
if (jobTransmissionTriggered) {
throw new FailedTransmissionException("Jobs cannot be sent twice!");
}
if (Objects.isNull(descriptor) || !descriptor.isValid()) {
throw new InvalidJobDescriptorException();
}
jobTransmissionTriggered = true;
notifyJobStatusListeners(); // Force the job to signal the LOCAL status
sendThread = new Thread(new SendImportJobTask(this));
sendThread.start();
}
/**
* Frequently queries the remote status of the nF export job and updates the local status accordingly.
* The queries will be performed asynchronously in a separate thread. Job status listener will be notified
* upon every new status change.
*
* @throws FailedTransmissionException If your job has not been sent yet, then you will get some of this.
*/
@Override
public synchronized void poll() throws FailedTransmissionException {
if (status.compareTo(JobStatus.SENT) < 0) {
throw new FailedTransmissionException("The job has not been sent to the nF yet!");
}
if (Objects.nonNull(pollThread)) {
pollThread.interrupt();
}
keepPolling = true;
pollThread = new Thread(new PollJobStatusTask(this, DEFAULT_POLLING_INTERVAL));
pollThread.start();
}
/**
* Connects to the nF and refreshes the status of this job. If there is no nF connector set,
* this operation will throw a FailedTransmissionException.
*
* @throws FailedTransmissionException If the connection to the nF is broken you will get some of this.
*/
@Override
public synchronized void updateStatus() throws FailedTransmissionException {
if (Objects.nonNull(connector)) {
AsyncImportJob job = ((HTTPConnection) connector).requestImportJob(id);
JobStatus newStatus = job.getStatus();
if (newStatus.compareTo(JobStatus.SENT) > 0) {
setStatus(job.getStatus(), Optional.empty());
}
} else {
throw new FailedTransmissionException();
}
}
/**
* Sets the status of this job depending on the given nF status code. nF status codes will be sent
* to you in HTTP responses. Note, the nF status code differ from the internal job status codes of
* this library. Read the nF documentation for more information.
*
* @param statusCode The nF status code for this job.
*/
@Override
public synchronized void setStatusForCode(int statusCode) {
switch (statusCode) {
case 0:
setStatus(JobStatus.PENDING); break;
case 10:
setStatus(JobStatus.RUNNING); break;
case 20:
setStatus(JobStatus.ERROR); break;
case 25:
setStatus(JobStatus.WARNING); break;
case 30:
setStatus(JobStatus.FINISHED); break;
case 40:
setStatus(JobStatus.APPROVE); break;
case 45:
setStatus(JobStatus.REJECT); break;
case 50:
setStatus(JobStatus.APPROVE_RUNNING); break;
case 55:
setStatus(JobStatus.REJECT_RUNNING); break;
case 60:
setStatus(JobStatus.APPROVE_REJECT_ERROR); break;
case 70:
setStatus(JobStatus.APPROVE_REJECT_OK); break;
case 80:
setStatus(JobStatus.IMPORTED_WARNING); break;
default:
setStatus(JobStatus.UNKOWN);
}
}
/**
* Registers a job status listener.
*
* @param jobListener The job status listener to be registered. This listener will receive updates about every
* progressing change of the job status. Meaning, the change to a particular status will only be signaled once
* to the listener.
*/
@Override
public void addJobStatusListener(JobStatusListener jobListener) {
jobListenerList.add(jobListener);
}
/**
* Unregisters a job status listener.
*
* @param jobListener The job status listener to be unregistered.
*/
@Override
public void removeJobStatusListener(JobStatusListener jobListener) {
jobListenerList.remove(jobListener);
}
/**
* Once the status of this job changes, all registered job status listeners will be notified.
* Listeners will only be notified of the status updates where the status of the job progresses and they will
* only be notified once about every singular status. If this status has been signaled already, then the
* listeners will not be notified again.
*/
@Override
public synchronized void notifyJobStatusListeners() {
if (Objects.isNull(lastPublishedJobStatus) || status.compareTo(lastPublishedJobStatus) > 0) {
JobStatusEvent event = new JobStatusEvent(status, this, lastEncounteredProblem);
for (JobStatusListener listener : jobListenerList) {
listener.jobStatusChanged(event);
}
lastPublishedJobStatus = status;
}
}
/**
* A convenience method to set a new job status and a status message at the same time. Status messages will be passed
* by asynchronous tasks instead of exception, because they cannot throw exceptions.
*
* @param jobStatus The new status of this job.
* @param message A status message. This message may describe a problem which occurred during an asynchronous task.
*/
protected synchronized void setStatus(JobStatus jobStatus, Optional<String> message) {
super.setStatus(jobStatus);
lastEncounteredProblem = message;
notifyJobStatusListeners();
}
/**
* Cancels all ongoing send and poll operations as soon as possible.
*/
@Override
public void cancel() {
keepPolling = false;
if (Objects.nonNull(sendThread)) {
sendThread.interrupt();
}
if (Objects.nonNull(pollThread)) {
pollThread.interrupt();
}
}
/**
* @return Returns true, if the polling thread should go on with its polling job. Otherwise, false.
*/
@Override
public boolean keepPolling() {
return keepPolling;
}
/**
* @return Returns true, if the job is definitely done. This is also the case, if the resulting CityGML
* file has been download. False, otherwise.
*/
@Override
public boolean hasFinished() {
return status == JobStatus.FINISHED;
}
/**
* @return Returns true, if the job has been failed. You may want to look up the "last encountered problem"
* string.
*/
@Override
public boolean hasFailed() {
return status == JobStatus.FAILED || status == JobStatus.ERROR;
}
/**
* Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the
* asynchronous PollJobStatusTask class.
*/
@Override
public void triggerStatusUpdate() throws FailedTransmissionException {
updateStatus();
}
}
\ No newline at end of file
package eu.simstadt.nf4j.async;
import eu.simstadt.nf4j.FailedTransmissionException;
/**
* An asynchronous job will be sent in a non-blocking fashion, so that the main thread can proceed after calling
* the send() operation. It queries the status of its remote nF counterpart frequently in a separate thread, so that
* the job operations don't have to wait. Every asynchronous job should maintain a list of job status listeners.
* Every registered job status listener should be notified upon significant and new job status changes.
*
* @author Marcel Bruse
*/
public interface AsyncJob {
/**
* Frequently queries the remote status of the nF export job and updates the local status accordingly.
* The queries will be performed asynchronously in a separate thread. Job status listener will be notified
* upon every new status change.
*
* @throws FailedTransmissionException If your job has not been sent yet, then you will get some of this.
*/
public void poll() throws FailedTransmissionException;
/**
* Cancels all ongoing asynchronous operations of this job as soon as possible. Operations to be canceled
* might be send, poll and download operations.
*/
public void cancel();
/**
* Registers a job status listener at this job. The listener will then be notified upon job status changes.
*
* @param jobListener The job status listener to be registered at this job.
*/
public void addJobStatusListener(JobStatusListener jobListener);
/**
* Unregisters a job status listener for this job. The listener will not be notified about job status changes
* anymore.
*
* @param jobListener The job status listener to be unregistered.
*/
public void removeJobStatusListener(JobStatusListener jobListener);
/**
* Once the status of this job changes, all registered job status listeners will be notified.
*/
public void notifyJobStatusListeners();
/**
* Convenience method.
*
* @return Returns true, if the job is definitely done.
*/
public boolean hasFinished();
/**
* Convenience method.
*
* @return Returns true, if the job was unable to recover from a serious problem.
*/
public boolean hasFailed();
/**
* Asynchronous polling tasks may query this flag frequently in order to decide to proceed or not.
*
* @return Returns true, if the polling task should continue polling for status updates.
*/
public boolean keepPolling();
/**
* Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the
* asynchronous PollJobStatusTask class.
*
* @throws FailedTransmissionException If something goes wrong during the update process, you will get some
* of this.
*/
public void triggerStatusUpdate() throws FailedTransmissionException;
}
package eu.simstadt.nf4j; package eu.simstadt.nf4j.async;
/** /**
* This enumeration lists some of the "well known texts" (WKT) which are used to identify * This enumeration lists some of the "well known texts" (WKT) which are used to identify
...@@ -15,6 +15,7 @@ public enum CRSWKT { ...@@ -15,6 +15,7 @@ public enum CRSWKT {
/** The well known texts for a CRS. */ /** The well known texts for a CRS. */
public String wkt; public String wkt;
/** Constructor for well known texts. */
private CRSWKT(String wkt) { private CRSWKT(String wkt) {
this.wkt = wkt; this.wkt = wkt;
} }
......
package eu.simstadt.nf4j; package eu.simstadt.nf4j.async;
/** /**
* Another class for coordinates. This is an intermediate representation for WGS 84 coordinates. * Another class for coordinates. Is there a general purpose class for this kind of applications?
* This might be an intermediate representation for WGS 84 coordinates.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
......
package eu.simstadt.nf4j.async;
import java.io.File;
import java.util.Optional;
import eu.simstadt.nf4j.FailedTransmissionException;
import eu.simstadt.nf4j.JobStatus;
/**
* This task downloads export job results asynchronously within its jobs separate download thread.
* You can cancel this task by calling job.cancel().
*
* @author Marcel Bruse
*/
public class DownloadTask implements Runnable {
/** The finished export job for which you want to download a CityGML result. */
private AsyncExportJob job;
/**
* Constructor with finished(!) asynchronous export job.
*
* @param job The export job for which you want to download the CityGML result.
*/
public DownloadTask(AsyncExportJob job) {
this.job = job;
}
/**
* This method performs the download operation asynchronously in the export jobs download thread.
* Job status listeners will be notified upon the finished download.
*/
@Override
public void run() {
HTTPConnection connector = (HTTPConnection) job.getConnector();
try {
File file = connector.requestExportJobResult(job);
job.setResult(file);
job.setStatus(JobStatus.DOWNLOAD, Optional.empty());
} catch (FailedTransmissionException ex) {
// Conditions have been checked by main thread. No exception handling needed.
}
}
}
package eu.simstadt.nf4j; package eu.simstadt.nf4j.async;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import eu.simstadt.nf4j.ExportJobDescriptor;
/** /**
* Every instance of this class describes an export job for the novaFACTORY. Instances of JobBuilder * Every instance of this class describes an export job for the novaFACTORY. Instances of JobBuilder
* take JobDescriptions and build XML export job files out of it. * take JobDescriptions and build XML export job files out of it.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public class ExportJobDescriptorImpl implements ExportJobDescriptor { public class ExportJobDescription implements ExportJobDescriptor {
private static final String DEFAULT_ACCOUNT = "Marcel"; private static final String DEFAULT_ACCOUNT = "Marcel";
...@@ -415,8 +417,8 @@ public boolean isValid() { ...@@ -415,8 +417,8 @@ public boolean isValid() {
} }
} }
public static ExportJobDescriptorImpl getDefaultDescriptor() { public static ExportJobDescription getDefaultDescriptor() {
ExportJobDescriptorImpl descriptor = new ExportJobDescriptorImpl(); ExportJobDescription descriptor = new ExportJobDescription();
descriptor.setInitiator(DEFAULT_INITIATOR); descriptor.setInitiator(DEFAULT_INITIATOR);
descriptor.setJobnumber(DEFAULT_JOBNUMBER); descriptor.setJobnumber(DEFAULT_JOBNUMBER);
descriptor.setAccount(DEFAULT_ACCOUNT); descriptor.setAccount(DEFAULT_ACCOUNT);
......
package eu.simstadt.nf4j; package eu.simstadt.nf4j.async;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParser;
...@@ -26,16 +27,25 @@ ...@@ -26,16 +27,25 @@
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import eu.simstadt.nf4j.Job;
import eu.simstadt.nf4j.FailedTransmissionException;
import eu.simstadt.nf4j.InvalidJobDescriptorException;
import eu.simstadt.nf4j.JobStatus;
import eu.simstadt.nf4j.Connector;
/** /**
* NFConnector lets you communicate with your novaFACTORY (nF) server instance. It supports nF version 6.3.1.1. * NFConnector lets you communicate with your novaFACTORY (nF) server instance. It supports nF version 6.3.1.1.
* For more technical details about the NFConnector interface @see NFConnector. * For more technical details about the NFConnector interface @see NFConnector.
* *
* Please note, that this connector doesn't act asynchronously. This connector is safe, but will block your main
* application. You may rather want to use the asynchronous job implementations.
*
* @author Marcel Bruse * @author Marcel Bruse
* *
* @param <I> The import job descriptor implementation for this connector. * @param <I> The import job descriptor implementation for this connector.
* @param <E> The export job descriptor implementation for this connector. * @param <E> The export job descriptor implementation for this connector.
*/ */
public class NFConnectorImpl implements NFConnector { public class HTTPConnection implements Connector<AsyncImportJob, AsyncExportJob> {
/** Supported version of the novaFACTORY. */ /** Supported version of the novaFACTORY. */
public static final String NOVA_FACTORY_VERSION = "6.3.1.1"; public static final String NOVA_FACTORY_VERSION = "6.3.1.1";
...@@ -94,7 +104,7 @@ public class NFConnectorImpl implements NFConnector { ...@@ -94,7 +104,7 @@ public class NFConnectorImpl implements NFConnector {
* *
* @param server The host name of the nF server with which you want to establish a data connection. * @param server The host name of the nF server with which you want to establish a data connection.
*/ */
public NFConnectorImpl(String server) { public HTTPConnection(String server) {
this(server, DEFAULT_PORT, DEFAULT_CONTEXT, DEFAULT_PROTOCOL); this(server, DEFAULT_PORT, DEFAULT_CONTEXT, DEFAULT_PROTOCOL);
} }
...@@ -105,7 +115,7 @@ public NFConnectorImpl(String server) { ...@@ -105,7 +115,7 @@ public NFConnectorImpl(String server) {
* @param port The port of the nF web application. * @param port The port of the nF web application.
* @param context The context of the nF web application. It is part of any request URL directed at the nF server. * @param context The context of the nF web application. It is part of any request URL directed at the nF server.
*/ */
public NFConnectorImpl(String server, int port, String context, String protocol) { public HTTPConnection(String server, int port, String context, String protocol) {
this.server = server; this.server = server;
this.port = port; this.port = port;
this.context = context; this.context = context;
...@@ -135,8 +145,8 @@ public String supportsNFVersion() { ...@@ -135,8 +145,8 @@ public String supportsNFVersion() {
* @throws IOException An error occurred during the request. This could be a malformed URL or a network failure. * @throws IOException An error occurred during the request. This could be a malformed URL or a network failure.
*/ */
@Override @Override
public ExportJob requestExportJob(int jobId) throws FailedTransmissionException { public AsyncExportJob requestExportJob(int jobId) throws FailedTransmissionException {
ExportJob result = new ExportJob(jobId, this); AsyncExportJob result = new AsyncExportJob(jobId, this);
try { try {
List<String> parameters = Arrays.asList(buildParameter("jobid", jobId)); List<String> parameters = Arrays.asList(buildParameter("jobid", jobId));
getJobFromResponse(result, getResponse(buildURL(REMOTE_STATUS_SERVLET, parameters))); getJobFromResponse(result, getResponse(buildURL(REMOTE_STATUS_SERVLET, parameters)));
...@@ -161,8 +171,8 @@ public ExportJob requestExportJob(int jobId) throws FailedTransmissionException ...@@ -161,8 +171,8 @@ public ExportJob requestExportJob(int jobId) throws FailedTransmissionException
* @throws IOException An error occurred during the request. This could be a malformed URL or a network failure. * @throws IOException An error occurred during the request. This could be a malformed URL or a network failure.
*/ */
@Override @Override
public ImportJob requestImportJob(int jobId) throws FailedTransmissionException { public AsyncImportJob requestImportJob(int jobId) throws FailedTransmissionException {
ImportJob result = new ImportJob(jobId, this); AsyncImportJob result = new AsyncImportJob(jobId, this);
try { try {
List<String> parameters = Arrays.asList( List<String> parameters = Arrays.asList(
buildParameter("jobid", jobId), buildParameter("jobid", jobId),
...@@ -188,13 +198,16 @@ public ImportJob requestImportJob(int jobId) throws FailedTransmissionException ...@@ -188,13 +198,16 @@ public ImportJob requestImportJob(int jobId) throws FailedTransmissionException
* @return A file handle to the result of the nF export job. * @return A file handle to the result of the nF export job.
*/ */
@Override @Override
public File requestExportJobResult(int jobId) throws FailedTransmissionException { public File requestExportJobResult(AsyncExportJob job) throws FailedTransmissionException {
if (!job.hasFinished()) {
throw new FailedTransmissionException("Job is not finished yet!");
}
File result = null; File result = null;
try { try {
List<String> parameters = Arrays.asList( List<String> parameters = Arrays.asList(
buildParameter("request", "downloadJob"), buildParameter("request", "downloadJob"),
buildParameter("mode", 0), buildParameter("mode", 0),
buildParameter("jobId", jobId)); buildParameter("jobId", job.getId()));
result = downloadFile(buildURL(REMOTE_ORDER_SERVLET, parameters)); result = downloadFile(buildURL(REMOTE_ORDER_SERVLET, parameters));
} catch (MalformedURLException ex) { } catch (MalformedURLException ex) {
throw new FailedTransmissionException(ex.getMessage()); throw new FailedTransmissionException(ex.getMessage());
...@@ -295,8 +308,8 @@ private String getResponse(HttpURLConnection httpConnection) throws UnsupportedE ...@@ -295,8 +308,8 @@ private String getResponse(HttpURLConnection httpConnection) throws UnsupportedE
* @throws SAXException Some parse error. * @throws SAXException Some parse error.
* @throws IOException Some parse error. * @throws IOException Some parse error.
*/ */
private void getJobFromResponse(Job<?> job, String xml) private void getJobFromResponse(Job job, String xml)
throws ParserConfigurationException, SAXException, IOException { throws ParserConfigurationException, SAXException, IOException, FailedTransmissionException {
SAXParserFactory saxFactory = SAXParserFactory.newInstance(); SAXParserFactory saxFactory = SAXParserFactory.newInstance();
SAXParser parser = saxFactory.newSAXParser(); SAXParser parser = saxFactory.newSAXParser();
StringReader reader = new StringReader(xml); StringReader reader = new StringReader(xml);
...@@ -305,12 +318,12 @@ private void getJobFromResponse(Job<?> job, String xml) ...@@ -305,12 +318,12 @@ private void getJobFromResponse(Job<?> job, String xml)
if (Objects.nonNull(handler.statusId)) { if (Objects.nonNull(handler.statusId)) {
job.setStatusForCode(handler.statusId); job.setStatusForCode(handler.statusId);
} }
if (Objects.nonNull(handler.serviceException)) {
job.getStatus().setMessage(handler.serviceException);
}
if (Objects.nonNull(handler.jobId)) { if (Objects.nonNull(handler.jobId)) {
job.setId(handler.jobId); job.setId(handler.jobId);
} }
if (Objects.nonNull(handler.serviceException)) {
throw new FailedTransmissionException(handler.serviceException);
}
} }
/** /**
...@@ -361,7 +374,7 @@ private File downloadFile(URL url) throws IOException { ...@@ -361,7 +374,7 @@ private File downloadFile(URL url) throws IOException {
* @throws InvalidJobDescriptorException * @throws InvalidJobDescriptorException
*/ */
@Override @Override
public void sendAndUpdateExportJob(ExportJob job) public void sendAndUpdateExportJob(AsyncExportJob job)
throws InvalidJobDescriptorException, FailedTransmissionException { throws InvalidJobDescriptorException, FailedTransmissionException {
JobFileBuilderImpl jobFileBuilder = new JobFileBuilderImpl(); JobFileBuilderImpl jobFileBuilder = new JobFileBuilderImpl();
File exportJobFile = jobFileBuilder.buildExportJobFile(job.getDescriptor()); File exportJobFile = jobFileBuilder.buildExportJobFile(job.getDescriptor());
...@@ -380,9 +393,11 @@ public void sendAndUpdateExportJob(ExportJob job) ...@@ -380,9 +393,11 @@ public void sendAndUpdateExportJob(ExportJob job)
os.flush(); os.flush();
writer.append(CRLF).flush(); writer.append(CRLF).flush();
getJobFromResponse(job, getResponse(connection)); getJobFromResponse(job, getResponse(connection));
// At this line, the job status will be unknown, although the job has been enqueued by the nF. if (job.getId() > 0) {
// A separate status request has to be sent in order to get the actual state of the job. job.setStatus(JobStatus.SENT, Optional.empty());
job.updateStatus(); } else {
throw new FailedTransmissionException("Job didn't receive an id from the nF server.");
}
} catch (MalformedURLException ex) { } catch (MalformedURLException ex) {
job.getStatus().setMessage(MALFORMED_URL); job.getStatus().setMessage(MALFORMED_URL);
throw new FailedTransmissionException(ex.getMessage()); throw new FailedTransmissionException(ex.getMessage());
...@@ -423,14 +438,18 @@ public void sendAndUpdateExportJob(ExportJob job) ...@@ -423,14 +438,18 @@ public void sendAndUpdateExportJob(ExportJob job)
* @throws FailedTransmissionException * @throws FailedTransmissionException
*/ */
@Override @Override
public void sendAndUpdateImportJob(ImportJob job) public void sendAndUpdateImportJob(AsyncImportJob job)
throws InvalidJobDescriptorException, FailedTransmissionException { throws InvalidJobDescriptorException, FailedTransmissionException {
if (job.getStatus() != JobStatus.LOCAL) {
throw new FailedTransmissionException("Job cannot be sent twice!");
}
JobFileBuilderImpl jobFileBuilder = new JobFileBuilderImpl(); JobFileBuilderImpl jobFileBuilder = new JobFileBuilderImpl();
File importJobFile = jobFileBuilder.buildImportJobFile(job.getDescriptor()); File importJobFile = jobFileBuilder.buildImportJobFile(job.getDescriptor());
try { try {
String product = job.getDescriptor().getProduct();
List<String> parameters = Arrays.asList( List<String> parameters = Arrays.asList(
buildParameter("request", "imp"), // trigger import buildParameter("request", "imp"), // trigger import
buildParameter("pdctKrz", "WUDEV"), // the nF product to import to buildParameter("pdctKrz", product), // the nF product to import to
buildParameter("createjob", "1")); // force nF to create an import job with a job number buildParameter("createjob", "1")); // force nF to create an import job with a job number
URL url = buildURL(REMOTE_IMPORT_SERVLET, parameters); URL url = buildURL(REMOTE_IMPORT_SERVLET, parameters);
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
......
package eu.simstadt.nf4j; package eu.simstadt.nf4j.async;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
import eu.simstadt.nf4j.ImportJobDescriptor;
/** /**
* Every instance of this class describes an import job for the novaFACTORY. Instances of NFConnector and * Every instance of this class describes an import job for the novaFACTORY. Instances of NFConnector and
* JobBuilder take JobDescriptions and build XML import job files out of it. * JobBuilder take JobDescriptions and build XML import job files out of it.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public class ImportJobDescriptorImpl implements ImportJobDescriptor { public class ImportJobDescription implements ImportJobDescriptor {
/** The version of the novaFACTORY XML export job format. */ /** The version of the novaFACTORY XML export job format. */
public static final String IMPORT_JOB_VERSION = "1.0.0"; public static final String IMPORT_JOB_VERSION = "1.0.0";
...@@ -147,8 +149,8 @@ public String supportsJobVersion() { ...@@ -147,8 +149,8 @@ public String supportsJobVersion() {
/** /**
* This is just a prototype for presentation purposes. * This is just a prototype for presentation purposes.
*/ */
public static ImportJobDescriptorImpl getDefaultDescriptor() { public static ImportJobDescription getDefaultDescriptor() {
ImportJobDescriptorImpl descriptor = new ImportJobDescriptorImpl(); ImportJobDescription descriptor = new ImportJobDescription();
descriptor.setLevel(DEFAULT_LEVEL); descriptor.setLevel(DEFAULT_LEVEL);
return descriptor; return descriptor;
} }
......
package eu.simstadt.nf4j; package eu.simstadt.nf4j.async;
import java.io.File; import java.io.File;
import eu.simstadt.nf4j.ExportJobDescriptor;
import eu.simstadt.nf4j.ImportJobDescriptor;
import eu.simstadt.nf4j.InvalidJobDescriptorException;
/** /**
* Implementations of JobBuilder build nF import and export jobs using nF's XML job format. There should be * Implementations of JobBuilder build nF import and export jobs using nF's XML job format. There should be
* one implementation for each version of novaFACTORY. The supported version should be returned by * one implementation for each version of novaFACTORY. The supported version should be returned by
......
package eu.simstadt.nf4j; package eu.simstadt.nf4j.async;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
...@@ -30,13 +30,15 @@ ...@@ -30,13 +30,15 @@
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import eu.simstadt.nf4j.InvalidJobDescriptorException;
/** /**
* Builds nF import and export jobs using nF's XML job format. Please read the nF manual if you want more * Builds nF import and export jobs using nF's XML job format. Please read the nF manual if you want more
* details about the numerous job attributes listed below. * details about the numerous job attributes listed below.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public class JobFileBuilderImpl implements JobFileBuilder<ImportJobDescriptorImpl, ExportJobDescriptorImpl> { public class JobFileBuilderImpl implements JobFileBuilder<ImportJobDescription, ExportJobDescription> {
/** Supported version of the novaFACTORY. */ /** Supported version of the novaFACTORY. */
public static final String NOVA_FACTORY_VERSION = "6.3.1.1"; public static final String NOVA_FACTORY_VERSION = "6.3.1.1";
...@@ -44,16 +46,25 @@ public class JobFileBuilderImpl implements JobFileBuilder<ImportJobDescriptorImp ...@@ -44,16 +46,25 @@ public class JobFileBuilderImpl implements JobFileBuilder<ImportJobDescriptorImp
/** The version of the XML export job format. */ /** The version of the XML export job format. */
public static final String EXPORT_JOB_VERSION = "1.0.0"; public static final String EXPORT_JOB_VERSION = "1.0.0";
/**
* @return Returns the supported novaFACTORY version.
*/
@Override @Override
public String supportsNFVersion() { public String supportsNFVersion() {
return NOVA_FACTORY_VERSION; return NOVA_FACTORY_VERSION;
} }
/**
* @return Returns the supported XML export job version.
*/
@Override @Override
public String supportsExportJobVersion() { public String supportsExportJobVersion() {
return EXPORT_JOB_VERSION; return EXPORT_JOB_VERSION;
} }
/**
* @return Returns the supported XML import job version.
*/
@Override @Override
public String supportsImportJobVersion() { public String supportsImportJobVersion() {
return null; return null;
...@@ -68,7 +79,7 @@ public String supportsImportJobVersion() { ...@@ -68,7 +79,7 @@ public String supportsImportJobVersion() {
* @throws FailedJobTransmissionException * @throws FailedJobTransmissionException
*/ */
@Override @Override
public File buildExportJobFile(ExportJobDescriptorImpl jobDescriptor) throws InvalidJobDescriptorException { public File buildExportJobFile(ExportJobDescription jobDescriptor) throws InvalidJobDescriptorException {
File result = null; File result = null;
if (Objects.nonNull(jobDescriptor) && jobDescriptor.isValid()) { if (Objects.nonNull(jobDescriptor) && jobDescriptor.isValid()) {
try { try {
...@@ -236,6 +247,13 @@ public File buildExportJobFile(ExportJobDescriptorImpl jobDescriptor) throws Inv ...@@ -236,6 +247,13 @@ public File buildExportJobFile(ExportJobDescriptorImpl jobDescriptor) throws Inv
return result; return result;
} }
/**
* Appends layers to the XML job document.
*
* @param doc The XML document.
* @param layers The XML layers element where new layers should be appended.
* @param layerList The user defined list of layers.
*/
private void appendLayers(Document doc, Element layers, private void appendLayers(Document doc, Element layers,
ArrayList<Layer> layerList) { ArrayList<Layer> layerList) {
for (Layer layer : layerList) { for (Layer layer : layerList) {
...@@ -302,7 +320,7 @@ private static Element createRegionPolygonElement(Document doc, List<Coord> regi ...@@ -302,7 +320,7 @@ private static Element createRegionPolygonElement(Document doc, List<Coord> regi
* @return Returns a XML import job document. * @return Returns a XML import job document.
*/ */
@Override @Override
public File buildImportJobFile(ImportJobDescriptorImpl jobDescriptor) public File buildImportJobFile(ImportJobDescription jobDescriptor)
throws InvalidJobDescriptorException { throws InvalidJobDescriptorException {
if (Objects.nonNull(jobDescriptor) && jobDescriptor.isValid()) { if (Objects.nonNull(jobDescriptor) && jobDescriptor.isValid()) {
try { try {
......
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