Commit 5a0bec3d authored by eric.duminil's avatar eric.duminil
Browse files

Removing CRLF from repository.

parent 3f54801c
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- <!--
This script compiles and deploys SimStadt Platform (GUI and Building Library Editors) and all required modules This script compiles and deploys SimStadt Platform (GUI and Building Library Editors) and all required modules
to the directory specified in property "deploy.dir.path". It can be used to perform a headless build. to the directory specified in property "deploy.dir.path". It can be used to perform a headless build.
Before executing check that all required modules/projects are enumerated in property "projects". Before executing check that all required modules/projects are enumerated in property "projects".
--> -->
<project default="deploy" name="RegionChooser" basedir=".."> <project default="deploy" name="RegionChooser" basedir="..">
<description> <description>
Create a Jar file with RegionChooser libraries and executables Create a Jar file with RegionChooser libraries and executables
</description> </description>
<property name="target.path" value="lib/region-chooser" /> <property name="target.path" value="lib/region-chooser" />
<property name="projects" value="RegionChooser,GeoLibs" /> <property name="projects" value="RegionChooser,GeoLibs" />
<import file="../SimStadt/deploy-common.xml" /> <import file="../SimStadt/deploy-common.xml" />
<target name="deploy" depends="unit-test"> <target name="deploy" depends="unit-test">
<echo file="${deploy.dir}/RegionChooser.bat"> <echo file="${deploy.dir}/RegionChooser.bat">
java -classpath lib/* -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX java -classpath lib/* -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX
pause > nul pause > nul
</echo> </echo>
<echo file="${deploy.dir}/RegionChooser.sh"> <echo file="${deploy.dir}/RegionChooser.sh">
java -classpath 'lib/*' -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX java -classpath 'lib/*' -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX
</echo> </echo>
<chmod file="${deploy.dir}/RegionChooser.sh" perm="u+x" /> <chmod file="${deploy.dir}/RegionChooser.sh" perm="u+x" />
<echo file="${deploy.dir}/RegionChooser.command"> <echo file="${deploy.dir}/RegionChooser.command">
cd "$(dirname "$0")" # set the current working directory to the directory this script is in cd "$(dirname "$0")" # set the current working directory to the directory this script is in
java -classpath lib/* -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX java -classpath lib/* -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX
</echo> </echo>
<chmod file="${deploy.dir}/RegionChooser.command" perm="u+x" /> <chmod file="${deploy.dir}/RegionChooser.command" perm="u+x" />
</target> </target>
<target name="unit-test" depends="test-common" unless="doNotTest"> <target name="unit-test" depends="test-common" unless="doNotTest">
<jacoco:coverage destfile="${reports.dir}/${ant.project.name}.exec" xmlns:jacoco="antlib:org.jacoco.ant"> <jacoco:coverage destfile="${reports.dir}/${ant.project.name}.exec" xmlns:jacoco="antlib:org.jacoco.ant">
<junit printsummary="yes" haltonfailure="yes" fork="true"> <junit printsummary="yes" haltonfailure="yes" fork="true">
<classpath refid="test-classpath" /> <classpath refid="test-classpath" />
<formatter type="xml" usefile="true" /> <formatter type="xml" usefile="true" />
<formatter type="plain" usefile="true" /> <formatter type="plain" usefile="true" />
<!-- RegionExtractor --> <!-- RegionExtractor -->
<test name="eu.simstadt.regionchooser.RegionExtractorTests" haltonfailure="no" todir="${reports.dir}" /> <test name="eu.simstadt.regionchooser.RegionExtractorTests" haltonfailure="no" todir="${reports.dir}" />
</junit> </junit>
</jacoco:coverage> </jacoco:coverage>
</target> </target>
</project> </project>
\ No newline at end of file
package eu.simstadt.nf4j; package eu.simstadt.nf4j;
import java.io.File; import java.io.File;
/** /**
* 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 <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. * @param <E> The export job implementation which can be handled by the connector.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public interface Connector<I extends ImportJob<?>, E extends ExportJob<?>> { 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
* HTTP/FTP/WPS/etc. interfaces against which this interface has been implemented. * HTTP/FTP/WPS/etc. interfaces against which this interface has been implemented.
* *
* The various NovaFACTORY interfaces may change over time. Such changes force the SimStadt programmers to * The various NovaFACTORY interfaces may change over time. Such changes force the SimStadt programmers to
* implement different versions of this NFConnector interface while keeping old implementations due to * implement different versions of this NFConnector interface while keeping old implementations due to
* backward compatibility. * backward compatibility.
* *
* @return The supported version of the novaFACTORY. * @return The supported version of the novaFACTORY.
*/ */
public String supportsNFVersion(); public String supportsNFVersion();
/** /**
* Sends an export job to nF. The job has to be passed to the nF in an appropriate XML job file. * Sends an export job to nF. The job has to be passed to the nF in an appropriate XML job file.
* The DTD of the XML format can be obtained from the nF installation or documentation. * The DTD of the XML format can be obtained from the nF installation or documentation.
* The contents of the XML job file are detailed in the job's descriptor instance. This method is supposed to * The contents of the XML job file are detailed in the job's descriptor instance. This method is supposed to
* update the status of the job and the job id. * update the status of the job and the job id.
* *
* @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(E exportJob) public void sendAndUpdateExportJob(E exportJob)
throws InvalidJobDescriptorException, FailedTransmissionException; throws InvalidJobDescriptorException, FailedTransmissionException;
/** /**
* Sends an import job to nF. A given import job descriptor has to describe the import job file. This * Sends an import job to nF. A given import job descriptor has to describe the import job file. This
* method has to build the file and to zip it. The zip file has to obey the internal structure defined * method has to build the file and to zip it. The zip file has to obey the internal structure defined
* in the nF manuals. Short description: * in the nF manuals. Short description:
* *
* Import jobs enable you to add, alter and delete CityGML top level objects (like buildings) in the nF. * Import jobs enable you to add, alter and delete CityGML top level objects (like buildings) in the nF.
* Every import job file has to contain a start file. Start files control the import process through their * Every import job file has to contain a start file. Start files control the import process through their
* file names and their contents. The name of a start file has the following structure * file names and their contents. The name of a start file has the following structure
* *
* <Product>_<Tile>.start * <Product>_<Tile>.start
* *
* The start file should contain the level to which your manipulated CityGML should be imported. Your CityGML * The start file should contain the level to which your manipulated CityGML should be imported. Your CityGML
* file and the ZIP archive to be sent has to be named after the following naming convention: * file and the ZIP archive to be sent has to be named after the following naming convention:
* *
* <Product>_<Tile>_<Level>_<Operation>.gml * <Product>_<Tile>_<Level>_<Operation>.gml
* *
* Here operation can be ... * Here operation can be ...
* - 'REP': Replaces whole existing buildings only, * - 'REP': Replaces whole existing buildings only,
* - 'REPUPD': Replaces whole existing buildings and adds new buildings, * - 'REPUPD': Replaces whole existing buildings and adds new buildings,
* - 'UPD': Update, same as REP, * - 'UPD': Update, same as REP,
* - 'CHG': Change, same as REPUPD, * - 'CHG': Change, same as REPUPD,
* - 'DEL': Deletes the geometry of a particular LOD, * - 'DEL': Deletes the geometry of a particular LOD,
* - 'DELALL': Deletes a whole building * - 'DELALL': Deletes a whole building
* *
* @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(I importJob) public void sendAndUpdateImportJob(I importJob)
throws InvalidJobDescriptorException, FailedTransmissionException; throws InvalidJobDescriptorException, FailedTransmissionException;
/** /**
* Returns the status of any existing export nF job. * Returns the status of any existing export nF 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 E 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.
* *
* @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 I 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.
* *
* @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(E exportJob) 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;
/** /**
* An export job is a proxy object for an actual nF export job. Export jobs are used to get data from your nF server. * 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. * 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. * @param <D> The descriptor type for the export job implementation.
*/ */
public abstract class ExportJob<D extends ExportJobDescriptor> extends Job { public abstract class ExportJob<D extends ExportJobDescriptor> extends Job {
/** Every job should have a (valid) job descriptor. */ /** Every job should have a (valid) job descriptor. */
protected D descriptor; protected D descriptor;
/** /**
* This constructor forces the job to have a description and a connector instance. * 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 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(D descriptor, Connector<?, ?> connector) { public ExportJob(D descriptor, Connector<?, ?> connector) {
this.descriptor = descriptor; this.descriptor = descriptor;
this.connector = connector; this.connector = connector;
} }
/** /**
* This constructor forces the job to have a id and a connector instance. * 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 * @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, Connector<?, ?> connector) { public ExportJob(int id, Connector<?, ?> connector) {
this.id = id; this.id = id;
this.connector = connector; this.connector = connector;
} }
/** /**
* @return Returns the description of this job. * @return Returns the description of this job.
*/ */
public D getDescriptor() { public D getDescriptor() {
return descriptor; return descriptor;
} }
/** /**
* Once an export job has been finished, the caller should use this method to obtain the actual CityGML file. * Once an export job has been finished, the caller should use this method to obtain the actual CityGML file.
* *
* @return A file handle to the resulting CityGML file. * @return A file handle to the resulting CityGML file.
* @throws FailedTransmissionException There could be a problem while accessing or downloading the file. * @throws FailedTransmissionException There could be a problem while accessing or downloading the file.
*/ */
public abstract File getResult() throws FailedTransmissionException; public abstract File getResult() throws FailedTransmissionException;
} }
package eu.simstadt.nf4j; package eu.simstadt.nf4j;
/** /**
* Implementations of this interface are known to be 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 {}
package eu.simstadt.nf4j; package eu.simstadt.nf4j;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import javafx.application.Platform; import javafx.application.Platform;
import netscape.javascript.JSObject; import netscape.javascript.JSObject;
import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.Geometry;
import eu.simstadt.nf4j.async.AsyncExportJob; import eu.simstadt.nf4j.async.AsyncExportJob;
import eu.simstadt.nf4j.async.Coord; import eu.simstadt.nf4j.async.Coord;
import eu.simstadt.nf4j.async.ExportJobDescription; import eu.simstadt.nf4j.async.ExportJobDescription;
import eu.simstadt.nf4j.async.HTTPConnection; import eu.simstadt.nf4j.async.HTTPConnection;
import eu.simstadt.nf4j.async.JobStatusEvent; import eu.simstadt.nf4j.async.JobStatusEvent;
import eu.simstadt.nf4j.async.JobStatusListener; import eu.simstadt.nf4j.async.JobStatusListener;
import eu.simstadt.nf4j.async.Layer; import eu.simstadt.nf4j.async.Layer;
/** /**
* This class contains client oriented export job tests. It will send an export job and listens to status updates. Every * This class contains client oriented export job tests. It will send an export job and listens to status updates. Every
* of the subsequent status' LOCAL, SENT, PENDING, RUNNING, FINISHED and DOWNLOAD have to be signaled to this test * of the subsequent status' LOCAL, SENT, PENDING, RUNNING, FINISHED and DOWNLOAD have to be signaled to this test
* class. * class.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
//TODO: DRY with SuccessfulExportJob //TODO: DRY with SuccessfulExportJob
public class ExportJobFromJavaFXRegionChooser implements JobStatusListener public class ExportJobFromJavaFXRegionChooser implements JobStatusListener
{ {
public AsyncExportJob job; public AsyncExportJob job;
private JSObject novaFactoryOpenLayer; private JSObject novaFactoryOpenLayer;
public void processJob(Geometry poly, String productName, JSObject novaFactoryOpenLayer) throws InterruptedException { public void processJob(Geometry poly, String productName, JSObject novaFactoryOpenLayer) throws InterruptedException {
this.novaFactoryOpenLayer = novaFactoryOpenLayer; this.novaFactoryOpenLayer = novaFactoryOpenLayer;
ExportJobDescription description = ExportJobDescription.getDefaultDescriptor(); ExportJobDescription description = ExportJobDescription.getDefaultDescriptor();
description.setInitiator(String.valueOf((int) (Math.random() * 9999))); description.setInitiator(String.valueOf((int) (Math.random() * 9999)));
String userName = System.getProperty("user.name"); String userName = System.getProperty("user.name");
description.setAccount(userName); description.setAccount(userName);
// description.setProduct("WU3"); // description.setProduct("WU3");
description.setProduct(productName); description.setProduct(productName);
description.setJobnumber(userName); description.setJobnumber(userName);
ArrayList<Coord> regionPolygon = new ArrayList<>(); ArrayList<Coord> regionPolygon = new ArrayList<>();
for (Coordinate point : poly.getCoordinates()) { for (Coordinate point : poly.getCoordinates()) {
regionPolygon.add(new Coord(point.y, point.x)); regionPolygon.add(new Coord(point.y, point.x));
} }
description.setRegionPolygon(regionPolygon); description.setRegionPolygon(regionPolygon);
Layer layer = Layer.getDefaultLayer(); Layer layer = Layer.getDefaultLayer();
layer.setProduct(productName); layer.setProduct(productName);
layer.setName("GML"); layer.setName("GML");
description.addLayer(layer); description.addLayer(layer);
job = new AsyncExportJob(description, new HTTPConnection("193.196.136.164")); job = new AsyncExportJob(description, new HTTPConnection("193.196.136.164"));
job.addJobStatusListener(this); job.addJobStatusListener(this);
try { try {
job.send(); job.send();
} catch (FailedTransmissionException ex) { } catch (FailedTransmissionException ex) {
ex.printStackTrace(); ex.printStackTrace();
} catch (InvalidJobDescriptorException ex) { } catch (InvalidJobDescriptorException ex) {
ex.printStackTrace(); ex.printStackTrace();
} }
} }
@Override @Override
public void jobStatusChanged(JobStatusEvent event) { public void jobStatusChanged(JobStatusEvent event) {
JobStatus status = (JobStatus) event.getSource(); JobStatus status = (JobStatus) event.getSource();
// System.out.println(status); // System.out.println(status);
if (status == JobStatus.LOCAL) { if (status == JobStatus.LOCAL) {
novaFactoryOpenLayer.call("updateStatus", "REQUEST HAS BEEN PREPARED"); novaFactoryOpenLayer.call("updateStatus", "REQUEST HAS BEEN PREPARED");
} else if (status == JobStatus.SENT) { } else if (status == JobStatus.SENT) {
novaFactoryOpenLayer.call("updateStatus", "REQUEST HAS BEEN SENT"); novaFactoryOpenLayer.call("updateStatus", "REQUEST HAS BEEN SENT");
} else if (status == JobStatus.PENDING) { } else if (status == JobStatus.PENDING) {
novaFactoryOpenLayer.call("updateStatus", "PENDING"); novaFactoryOpenLayer.call("updateStatus", "PENDING");
} else if (status == JobStatus.RUNNING) { } else if (status == JobStatus.RUNNING) {
novaFactoryOpenLayer.call("updateStatus", "SERVER IS BUSY"); novaFactoryOpenLayer.call("updateStatus", "SERVER IS BUSY");
} else if (status == JobStatus.FINISHED) { } else if (status == JobStatus.FINISHED) {
try { try {
novaFactoryOpenLayer.call("updateStatus", "SERVER IS DONE"); novaFactoryOpenLayer.call("updateStatus", "SERVER IS DONE");
job.downloadResult(); job.downloadResult();
} catch (FailedTransmissionException ex) { } catch (FailedTransmissionException ex) {
ex.printStackTrace(); ex.printStackTrace();
} }
} else if (status == JobStatus.DOWNLOAD) { } else if (status == JobStatus.DOWNLOAD) {
try { try {
File file = job.getResult(); File file = job.getResult();
novaFactoryOpenLayer.call("updateStatus", "DOWNLOADED AS ZIP"); novaFactoryOpenLayer.call("updateStatus", "DOWNLOADED AS ZIP");
//TODO: Call downloadFinished if FAILED //TODO: Call downloadFinished if FAILED
Platform.runLater(new Runnable() { Platform.runLater(new Runnable() {
@Override @Override
public void run() { public void run() {
novaFactoryOpenLayer.call("selectSaveFile", file.toString()); novaFactoryOpenLayer.call("selectSaveFile", file.toString());
} }
}); });
novaFactoryOpenLayer.call("downloadFinished"); novaFactoryOpenLayer.call("downloadFinished");
} catch (FailedTransmissionException ex) { } catch (FailedTransmissionException ex) {
ex.printStackTrace(); ex.printStackTrace();
} }
} }
} }
} }
\ No newline at end of file
package eu.simstadt.nf4j; package eu.simstadt.nf4j;
/** /**
* 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
* - HTTP failures * - HTTP failures
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public class FailedTransmissionException extends Exception { public class FailedTransmissionException extends Exception {
private static final long serialVersionUID = -3530932388888249528L; private static final long serialVersionUID = -3530932388888249528L;
/** An textual description of the error. */ /** An textual description of the error. */
private String message; private String message;
/** Standard constructor. */ /** Standard constructor. */
public FailedTransmissionException() {} public FailedTransmissionException() {}
/** /**
* Constructor with error message and without nested cause. * Constructor with error message and without nested cause.
* *
* @param message The error message. * @param message The error message.
*/ */
public FailedTransmissionException(String message) { public FailedTransmissionException(String message) {
this(message, null); this(message, null);
} }
/** /**
* Constructor with cause and without error message. * Constructor with cause and without error message.
* *
* @param cause The nested cause of this exception. * @param cause The nested cause of this exception.
*/ */
public FailedTransmissionException(Throwable cause) { public FailedTransmissionException(Throwable cause) {
this(null, cause); this(null, cause);
} }
/** /**
* Constructor with error message and nested cause. * Constructor with error message and nested cause.
* *
* @param message The error message. * @param message The error message.
* @param cause The nested cause of this exception. * @param cause The nested cause of this exception.
*/ */
public FailedTransmissionException(String message, Throwable cause) { public FailedTransmissionException(String message, Throwable cause) {
this.message = message; this.message = message;
initCause(cause); initCause(cause);
} }
/** /**
* @return Returns the error message, if present. * @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;
/** /**
* An import job is a proxy object for an actual nF import job. Import jobs are used to store data in your nF database. * 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. * Every import 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 import job implementation. * @param <D> The descriptor type for the import job implementation.
*/ */
public abstract class ImportJob<D extends ImportJobDescriptor> extends Job { public abstract class ImportJob<D extends ImportJobDescriptor> extends Job {
/** Every job should have a (valid) job descriptor. */ /** Every job should have a (valid) job descriptor. */
protected D descriptor; protected D descriptor;
/** /**
* This constructor forces the job to have a description and a connector instance. * 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 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(D descriptor, Connector<?, ?> connector) { public ImportJob(D descriptor, Connector<?, ?> connector) {
this.descriptor = descriptor; this.descriptor = descriptor;
this.connector = connector; this.connector = connector;
} }
/** /**
* This constructor forces the job to have a id and a connector instance. * 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 * @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, Connector<?, ?> connector) { public ImportJob(int id, Connector<?, ?> connector) {
this.id = id; this.id = id;
this.connector = connector; this.connector = connector;
} }
/** /**
* @return Returns the description of this job. * @return Returns the description of this job.
*/ */
public D getDescriptor() { public D getDescriptor() {
return descriptor; return descriptor;
} }
} }
package eu.simstadt.nf4j; package eu.simstadt.nf4j;
import java.io.File; import java.io.File;
/** /**
* Implementations of this interface are known to implement import job descriptions. * Implementations of this interface are known to implement import job descriptions.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public interface ImportJobDescriptor extends JobDescriptor { public interface ImportJobDescriptor extends JobDescriptor {
/** /**
* Sets the CityGML file which is supposed to be imported by the nF. * Sets the CityGML file which is supposed to be imported by the nF.
* *
* @param file The CityGML file which is supposed to be imported by the nF. * @param file The CityGML file which is supposed to be imported by the nF.
*/ */
public void setCityGMLFile(File file); public void setCityGMLFile(File file);
/** /**
* @return Returns the CityGML file which is supposed to be imported by the nF. * @return Returns the CityGML file which is supposed to be imported by the nF.
*/ */
public File getCityGMLFile(); public File getCityGMLFile();
} }
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 * 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. * that you will get this exception.
* *
* @author Marcel Bruse * @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;
} }
package eu.simstadt.nf4j; package eu.simstadt.nf4j;
/** /**
* This job class bundles the three attributes of every nF job: Id, status and last job related nF (error) message. * This job class bundles the three attributes of every nF job: Id, status and last job related nF (error) message.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public abstract class Job { 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;
/** 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 Connector<?, ?> connector; protected Connector<?, ?> connector;
/** /**
* Every job has a status. This method returns it. Look up the different possible values in the JobStatus * Every job has a status. This method returns it. Look up the different possible values in the JobStatus
* enumeration. * enumeration.
* *
* @return Returns the status of this job. * @return Returns the status of this job.
*/ */
public JobStatus getStatus() { public JobStatus getStatus() {
return status; return status;
} }
/** /**
* Lets you set the status of this job. * Lets you set the status of this job.
* *
* @param status The status of this job. * @param status The status of this job.
*/ */
protected void setStatus(JobStatus status) { protected void setStatus(JobStatus status) {
this.status = status; this.status = status;
} }
/** /**
* @return Returns the id of this job. * @return Returns the id of this job.
*/ */
public int getId() { public int getId() {
return id; return id;
} }
/** /**
* Sets the id of this job. * Sets the id of this job.
* *
* @param jobId The job id about to be set. * @param jobId The job id about to be set.
*/ */
public void setId(int jobId) { public void setId(int jobId) {
this.id = jobId; this.id = jobId;
} }
/** /**
* @return Returns the nF connector of this job. * @return Returns the nF connector of this job.
*/ */
public Connector<?, ?> getConnector() { public Connector<?, ?> getConnector() {
return connector; return connector;
} }
/** /**
* Sets the nF connector of this job. * Sets the nF connector of this job.
* *
* @param nFConnector The connector of this job. * @param nFConnector The connector of this job.
*/ */
public void setConnector(Connector<?, ?> connector) { public void setConnector(Connector<?, ?> connector) {
this.connector = connector; this.connector = connector;
} }
/** /**
* This method reads the job description, builds a job file from it and uses the connector to send the * This method reads the job description, builds a job file from it and uses the connector to send the
* job file to nF. You may want to implement the file building and file sending parts in the * job file to nF. You may want to implement the file building and file sending parts in the
* connector in order to reuse them here. * connector in order to reuse them here.
* *
* This method is a object oriented convenience method for NFConnector.sendXXXJobFile(). It is sensible * This method is a object oriented convenience method for NFConnector.sendXXXJobFile(). It is sensible
* to have this convenience method, because it offers the API user centralized controls over the jobs * to have this convenience method, because it offers the API user centralized controls over the jobs
* status chain without switching between job instances and connector instances. In the end, the user is * status chain without switching between job instances and connector instances. In the end, the user is
* not interested in the connection to the nF, but in the job itself. * not interested in the connection to the nF, but in the job itself.
*/ */
public abstract void send() throws InvalidJobDescriptorException, FailedTransmissionException; public abstract void send() throws InvalidJobDescriptorException, FailedTransmissionException;
/** /**
* Connects to the nF and refreshes the status of this job. If there is no nF connector set, or the job id * Connects to the nF and refreshes the status of this job. If there is no nF connector set, or the job id
* is unkown by the nF, then this operation will throw a FailedTransmissionException. * is unkown by the nF, then this operation will throw a FailedTransmissionException.
* *
* @throws FailedTransmissionException If the connection to the nF is broken you will get some of this. * @throws FailedTransmissionException If the connection to the nF is broken you will get some of this.
*/ */
public abstract void updateStatus() throws FailedTransmissionException; public abstract void updateStatus() throws FailedTransmissionException;
/** /**
* Sets the status of this job depending on the given nF status code. nF status codes will be sent * Sets the status of this job depending on the given nF status code. nF status codes will be sent
* to you in http responses. Export jobs and import jobs have different states. Consider this fact * to you in http responses. Export jobs and import jobs have different states. Consider this fact
* in your implementation. * in your implementation.
* *
* @param statusCode The nF status code for this job. * @param statusCode The nF status code for this job.
*/ */
public abstract void setStatusForCode(int statusCode); public abstract void setStatusForCode(int statusCode);
} }
package eu.simstadt.nf4j; package eu.simstadt.nf4j;
/** /**
* Every instance of this class describes an export or import job for the novaFACTORY. Instances of JobBuilder * Every instance of this class describes an export or import job for the novaFACTORY. Instances of JobBuilder
* take JobDescriptions and build XML export and import job files out of it. * take JobDescriptions and build XML export and import job files out of it.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public interface JobDescriptor { public interface JobDescriptor {
/** /**
* @return The supported version of the XML import or export job format. * @return The supported version of the XML import or export job format.
*/ */
public String supportsJobVersion(); public String supportsJobVersion();
/** /**
* @return Returns true, if the job description is complete and correct. False, otherwise. * @return Returns true, if the job description is complete and correct. False, otherwise.
*/ */
public boolean isValid(); public boolean isValid();
} }
package eu.simstadt.nf4j; package eu.simstadt.nf4j;
/** /**
* The list of all possible export and import job states. * The list of all possible export and import job states.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public enum JobStatus { public enum JobStatus {
UNKNOWN(0), // A local NF4J code, may be set in rare cases, if synchronization with nF fails. UNKNOWN(0), // A local NF4J code, may be set in rare cases, if synchronization with nF fails.
LOCAL(10), // Job has not been sent yet and is known by the local system only. LOCAL(10), // Job has not been sent yet and is known by the local system only.
SENT(20), // Job has been sent or it is assumed that it has been set before. 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 PENDING(30), // Export job has been enqueued and waits for execution
READY_TO_RUN(31), // Same as PENDING, but for import jobs 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. APPROVE(32), // (?) For import jobs only. Read the nF documentation, seams to be never used.
RUNNING(40), // Job is running RUNNING(40), // Job is running
APPROVE_RUNNING(41), // (?) For import jobs only. Read the nF documentation, seams to be never used. APPROVE_RUNNING(41), // (?) For import jobs only. Read the nF documentation, seams to be never used.
FAILED(50), // Export job failed FAILED(50), // Export job failed
ERROR(51), // Same as FAILED, but for import jobs ERROR(51), // Same as FAILED, but for import jobs
WARNING(52), // For import jobs only. There has been a minor problem 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(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. 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_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. 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. IMPORTED_WARNING(57), // (?) For import jobs only. Read the nF documentation, seams to be never used.
FINISHED(60), // Job finished FINISHED(60), // Job finished
DOWNLOAD(70); // Export jobs only. CityGML has been download to the local file system 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.";
public static final String SENT_MESSAGE = "The job has been sent, is enqueued at the nF and has a job id."; public static final String SENT_MESSAGE = "The job has been sent, is enqueued at the nF and has a job id.";
public static final String PENDING_MESSAGE = "Job is pending."; public static final String PENDING_MESSAGE = "Job is pending.";
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."; 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.
* *
* @param internalCode Our numerical SimStadt-internal code for the nF status codes. * @param internalCode Our numerical SimStadt-internal code for the nF status codes.
*/ */
private JobStatus(int internalCode) { private JobStatus(int internalCode) {
switch (internalCode) { switch (internalCode) {
case 0: case 0:
message = UNKNOWN_MESSAGE; break; message = UNKNOWN_MESSAGE; break;
case 10: case 10:
message = LOCAL_MESSAGE; break; message = LOCAL_MESSAGE; break;
case 20: case 20:
message = SENT_MESSAGE; break; message = SENT_MESSAGE; break;
case 30: case 30:
case 31: case 31:
message = PENDING_MESSAGE; break; message = PENDING_MESSAGE; break;
case 40: case 40:
message = RUNNING_MESSAGE; break; message = RUNNING_MESSAGE; break;
case 50: case 50:
case 51: case 51:
message = FAILED_MESSAGE; break; message = FAILED_MESSAGE; break;
case 60: case 60:
message = FINISHED_MESSAGE; break; message = FINISHED_MESSAGE; break;
default: default:
message = ""; message = "";
} }
} }
/** /**
* This message describes the status or gives hints about error states. This message may come from a * This message describes the status or gives hints about error states. This message may come from a
* nF response. * nF response.
*/ */
private String message; private String message;
/** /**
* Sets a message for this status in order to describe the status. * Sets a message for this status in order to describe the status.
* *
* @param message A message to describe this status. * @param message A message to describe this status.
*/ */
public void setMessage(String message) { public void setMessage(String message) {
this.message = message; this.message = message;
} }
/** /**
* @return Returns a message which describes this status. * @return Returns a message which describes this status.
*/ */
public String getMessage() { public String getMessage() {
return message; return message;
} }
} }
package eu.simstadt.nf4j.async; package eu.simstadt.nf4j.async;
import java.io.File; import java.io.File;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import eu.simstadt.nf4j.Connector; import eu.simstadt.nf4j.Connector;
import eu.simstadt.nf4j.ExportJob; import eu.simstadt.nf4j.ExportJob;
import eu.simstadt.nf4j.FailedTransmissionException; import eu.simstadt.nf4j.FailedTransmissionException;
import eu.simstadt.nf4j.InvalidJobDescriptorException; import eu.simstadt.nf4j.InvalidJobDescriptorException;
import eu.simstadt.nf4j.JobStatus; import eu.simstadt.nf4j.JobStatus;
/** /**
* Export jobs are requests for CityGML models. Every valid export job has an id and a status. This implementation * 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 * 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 * 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. * updates from the asynchronous operations.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public class AsyncExportJob extends ExportJob<ExportJobDescription> implements AsyncJob public class AsyncExportJob extends ExportJob<ExportJobDescription> implements AsyncJob
{ {
/** /**
* While polling for the current job status, the polling thread will sleep for this amount of seconds before each * While polling for the current job status, the polling thread will sleep for this amount of seconds before each
* status update request. * status update request.
*/ */
private final int DEFAULT_POLLING_INTERVAL = 5; // seconds private final int DEFAULT_POLLING_INTERVAL = 5; // seconds
/** There can only be one sending thread for each job at a time. */ /** There can only be one sending thread for each job at a time. */
private Thread sendThread; private Thread sendThread;
/** There can only be one polling thread for each job at a time. */ /** There can only be one polling thread for each job at a time. */
private Thread pollThread; private Thread pollThread;
/** There can only be one download thread for each job at a time. */ /** There can only be one download thread for each job at a time. */
private Thread downloadThread; private Thread downloadThread;
/** /**
* Once the send() operation has been triggered, this member will be true. No subsequent invocations of send() will * Once the send() operation has been triggered, this member will be true. No subsequent invocations of send() will
* be possible then. * be possible then.
*/ */
private boolean jobTransmissionTriggered = false; private boolean jobTransmissionTriggered = false;
/** As long as this variable is true, the polling thread will be kept alive. */ /** As long as this variable is true, the polling thread will be kept alive. */
private boolean keepPolling = true; private boolean keepPolling = true;
/** /**
* List of all registered job status listeners. Whenever the state of this job changes, these listeners will get * List of all registered job status listeners. Whenever the state of this job changes, these listeners will get
* informed. * informed.
*/ */
private LinkedList<JobStatusListener> jobListenerList = new LinkedList<>(); private LinkedList<JobStatusListener> jobListenerList = new LinkedList<>();
/** /**
* This job will be send and observed asynchronously. Its results will be downloaded asynchronously also. If an * This job will be send and observed asynchronously. Its results will be downloaded asynchronously also. If an
* asynchronous operation breaks, then the last encountered problem will be described here. * asynchronous operation breaks, then the last encountered problem will be described here.
*/ */
private Optional<String> lastEncounteredProblem = Optional.empty(); private Optional<String> lastEncounteredProblem = Optional.empty();
/** /**
* The last job status which has been sent to all registered job status listeners. * The last job status which has been sent to all registered job status listeners.
*/ */
private JobStatus lastPublishedJobStatus; private JobStatus lastPublishedJobStatus;
/** Once the CityGML file has been download, it should be referenced here. */ /** Once the CityGML file has been download, it should be referenced here. */
private File result; private File result;
/** /**
* This constructor forces the job to have a description and a connector instance. Every job which is created by this * 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 * constructor will have the status "local", because it is assumed that it has an unsent description and no job id
* yet. * 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 AsyncExportJob(ExportJobDescription descriptor, Connector<AsyncImportJob, AsyncExportJob> connector) { public AsyncExportJob(ExportJobDescription descriptor, Connector<AsyncImportJob, AsyncExportJob> connector) {
super(descriptor, connector); super(descriptor, connector);
status = JobStatus.LOCAL; status = JobStatus.LOCAL;
} }
/** /**
* 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. 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 * constructor will have the status "sent", because it is assumed that the job is already enqueued at the nF job
* queue. * queue.
* *
* @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status will be * @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 * 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 AsyncExportJob(int id, Connector<AsyncImportJob, AsyncExportJob> connector) { public AsyncExportJob(int id, Connector<AsyncImportJob, AsyncExportJob> connector) {
super(id, connector); super(id, connector);
status = JobStatus.SENT; 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 * 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. * 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 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. * @throws InvalidJobDescriptorException If the job description is invalid or null, then you will receive this.
*/ */
@Override @Override
public synchronized void send() throws FailedTransmissionException, InvalidJobDescriptorException { public synchronized void send() throws FailedTransmissionException, InvalidJobDescriptorException {
if (jobTransmissionTriggered) { if (jobTransmissionTriggered) {
throw new FailedTransmissionException("Jobs cannot be sent twice!"); throw new FailedTransmissionException("Jobs cannot be sent twice!");
} }
if (Objects.isNull(descriptor) || !descriptor.isValid()) { if (Objects.isNull(descriptor) || !descriptor.isValid()) {
throw new InvalidJobDescriptorException(); throw new InvalidJobDescriptorException();
} }
jobTransmissionTriggered = true; jobTransmissionTriggered = true;
notifyJobStatusListeners(); // Force the job to signal the LOCAL status notifyJobStatusListeners(); // Force the job to signal the LOCAL status
sendThread = new Thread(new SendExportJobTask(this)); sendThread = new Thread(new SendExportJobTask(this));
sendThread.start(); sendThread.start();
} }
/** /**
* Frequently queries the status of the remote nF export job and updates the local status accordingly. The queries * Frequently queries the status of the remote nF export job and updates the local status accordingly. The queries
* will be performed asynchronously in a separate thread. Job status listeners will be notified upon every new status * will be performed asynchronously in a separate thread. Job status listeners will be notified upon every new status
* change. * change.
* *
* Note, there can only be one polling thread at a time. Subsequent calls of poll() will stop the previously started * Note, there can only be one polling thread at a time. Subsequent calls of poll() will stop the previously started
* poll threads. * poll threads.
* *
* @param interval Amount of seconds to wait before the next status update request will be sent to the nF server. * @param interval Amount of seconds to wait before the next status update request will be sent to the nF server.
* *
* @throws FailedTransmissionException If your job has not been sent yet, then you will get some of this. * @throws FailedTransmissionException If your job has not been sent yet, then you will get some of this.
*/ */
@Override @Override
public synchronized void poll(int interval) throws FailedTransmissionException { public synchronized void poll(int interval) throws FailedTransmissionException {
if (status.compareTo(JobStatus.SENT) < 0) { if (status.compareTo(JobStatus.SENT) < 0) {
throw new FailedTransmissionException("The job has not been sent to the nF yet!"); throw new FailedTransmissionException("The job has not been sent to the nF yet!");
} }
if (Objects.nonNull(pollThread)) { if (Objects.nonNull(pollThread)) {
pollThread.interrupt(); pollThread.interrupt();
} }
keepPolling = true; keepPolling = true;
pollThread = new Thread(new PollJobStatusTask(this, interval)); pollThread = new Thread(new PollJobStatusTask(this, interval));
pollThread.start(); pollThread.start();
} }
/** /**
* Convenience method for polling with a predefined default interval. * Convenience method for polling with a predefined default interval.
* *
* Frequently queries the status of the remote nF export job and updates the local status accordingly. The queries * Frequently queries the status of the remote nF export job and updates the local status accordingly. The queries
* will be performed asynchronously in a separate thread. Job status listeners will be notified upon every new status * will be performed asynchronously in a separate thread. Job status listeners will be notified upon every new status
* change. * change.
* *
* Note, there can only be one polling thread at a time. Subsequent calls of poll() will stop the previously started * Note, there can only be one polling thread at a time. Subsequent calls of poll() will stop the previously started
* poll threads. * poll threads.
* *
* @param interval Amount of seconds to wait before the next status update request will be sent to the nF server. * @param interval Amount of seconds to wait before the next status update request will be sent to the nF server.
* @throws FailedTransmissionException If your job has not been sent yet, then you will get some of this. * @throws FailedTransmissionException If your job has not been sent yet, then you will get some of this.
* @see poll(int) * @see poll(int)
*/ */
public synchronized void poll() throws FailedTransmissionException { public synchronized void poll() throws FailedTransmissionException {
poll(DEFAULT_POLLING_INTERVAL); poll(DEFAULT_POLLING_INTERVAL);
} }
/** /**
* Connects to the nF and refreshes the status of this job. If there is no nF connector set, this operation will * Connects to the nF and refreshes the status of this job. If there is no nF connector set, this operation will
* throw a FailedTransmissionException. * throw a FailedTransmissionException.
* *
* @throws FailedTransmissionException You will receive this exception if no connector is present, the connection to * @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 * 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". * two latter cases, job will either have the status "LOCAL" or "WAITING".
*/ */
@Override @Override
public synchronized void updateStatus() throws FailedTransmissionException { public synchronized void updateStatus() throws FailedTransmissionException {
if (Objects.isNull(connector)) { if (Objects.isNull(connector)) {
throw new FailedTransmissionException("No connector set for this job!"); throw new FailedTransmissionException("No connector set for this job!");
} }
if (status.compareTo(JobStatus.SENT) < 0) { if (status.compareTo(JobStatus.SENT) < 0) {
throw new FailedTransmissionException("The job has not been sent to the nF yet!"); throw new FailedTransmissionException("The job has not been sent to the nF yet!");
} }
AsyncExportJob job = ((HTTPConnection) connector).requestExportJob(id); AsyncExportJob job = ((HTTPConnection) connector).requestExportJob(id);
JobStatus newStatus = job.getStatus(); JobStatus newStatus = job.getStatus();
if (newStatus.compareTo(JobStatus.SENT) > 0) { if (newStatus.compareTo(JobStatus.SENT) > 0) {
setStatus(job.getStatus(), Optional.empty()); setStatus(job.getStatus(), Optional.empty());
} }
} }
/** /**
* Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the asynchronous * Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the asynchronous
* PollJobStatusTask class. * PollJobStatusTask class.
*/ */
@Override @Override
public void triggerStatusUpdate() throws FailedTransmissionException { public void triggerStatusUpdate() throws FailedTransmissionException {
updateStatus(); updateStatus();
} }
/** /**
* Sets the status of this job depending on the given nF status code. nF status codes will be sent to you in http * Sets the status of this job depending on the given nF status code. nF status codes will be sent to you in http
* responses. * responses.
* *
* @param statusCode The nF status code for this job. * @param statusCode The nF status code for this job.
*/ */
@Override @Override
public synchronized void setStatusForCode(int statusCode) { public synchronized void setStatusForCode(int statusCode) {
switch (statusCode) { switch (statusCode) {
case 0: case 0:
setStatus(JobStatus.PENDING); setStatus(JobStatus.PENDING);
break; break;
case 10: case 10:
setStatus(JobStatus.RUNNING); setStatus(JobStatus.RUNNING);
break; break;
case 20: case 20:
setStatus(JobStatus.FAILED); setStatus(JobStatus.FAILED);
break; break;
case 30: case 30:
setStatus(JobStatus.FINISHED); setStatus(JobStatus.FINISHED);
break; break;
default: default:
setStatus(JobStatus.UNKNOWN); setStatus(JobStatus.UNKNOWN);
} }
} }
/** /**
* @return Returns true, if the job is definitely done. This is also the case, if the resulting CityGML file has been * @return Returns true, if the job is definitely done. This is also the case, if the resulting CityGML file has been
* download. False, otherwise. * download. False, otherwise.
*/ */
@Override @Override
public boolean hasFinished() { public boolean hasFinished() {
return status == JobStatus.FINISHED || status == JobStatus.DOWNLOAD; 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. * @return Returns true, if the job has been failed. You may want to look up the "last encountered problem" string.
*/ */
@Override @Override
public boolean hasFailed() { public boolean hasFailed() {
return status == JobStatus.FAILED; return status == JobStatus.FAILED;
} }
/** /**
* Registers a job status listener. * Registers a job status listener.
* *
* @param jobListener The job status listener to be registered. This listener will receive updates about every * @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 * progressing change of the job status. Meaning, the change to a particular status will only be signaled
* once to the listener. * once to the listener.
*/ */
@Override @Override
public void addJobStatusListener(JobStatusListener jobListener) { public void addJobStatusListener(JobStatusListener jobListener) {
jobListenerList.add(jobListener); jobListenerList.add(jobListener);
} }
/** /**
* Unregisters a job status listener. * Unregisters a job status listener.
* *
* @param jobListener The job status listener to be unregistered. * @param jobListener The job status listener to be unregistered.
*/ */
@Override @Override
public void removeJobStatusListener(JobStatusListener jobListener) { public void removeJobStatusListener(JobStatusListener jobListener) {
jobListenerList.remove(jobListener); jobListenerList.remove(jobListener);
} }
/** /**
* Once the status of this job changes, all registered job status listeners will be notified. Listeners will only be * 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 * 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. * every singular status. If this status has been signaled already, then the listeners will not be notified again.
*/ */
@Override @Override
public synchronized void notifyJobStatusListeners() { public synchronized void notifyJobStatusListeners() {
if (Objects.isNull(lastPublishedJobStatus) || status.compareTo(lastPublishedJobStatus) > 0) { if (Objects.isNull(lastPublishedJobStatus) || status.compareTo(lastPublishedJobStatus) > 0) {
JobStatusEvent event = new JobStatusEvent(status, this, lastEncounteredProblem); JobStatusEvent event = new JobStatusEvent(status, this, lastEncounteredProblem);
for (JobStatusListener listener : jobListenerList) { for (JobStatusListener listener : jobListenerList) {
listener.jobStatusChanged(event); listener.jobStatusChanged(event);
} }
lastPublishedJobStatus = status; lastPublishedJobStatus = status;
} }
} }
/** /**
* Cancels all ongoing send, poll and download operations as soon as possible. * Cancels all ongoing send, poll and download operations as soon as possible.
*/ */
@Override @Override
public void cancel() { public void cancel() {
keepPolling = false; keepPolling = false;
if (Objects.nonNull(sendThread)) { if (Objects.nonNull(sendThread)) {
sendThread.interrupt(); sendThread.interrupt();
} }
if (Objects.nonNull(pollThread)) { if (Objects.nonNull(pollThread)) {
pollThread.interrupt(); pollThread.interrupt();
} }
if (Objects.nonNull(downloadThread)) { if (Objects.nonNull(downloadThread)) {
downloadThread.interrupt(); downloadThread.interrupt();
} }
} }
/** /**
* @return Returns true, if the polling thread should go on with its polling job. Otherwise, false. * @return Returns true, if the polling thread should go on with its polling job. Otherwise, false.
*/ */
@Override @Override
public boolean keepPolling() { public boolean keepPolling() {
return keepPolling; return keepPolling;
} }
/** /**
* Starts downloading the export job result, if there is any. As soon as the download has been finished, the job * 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, * 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(). * 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. * @throws FailedTransmissionException If the job has not been finished yet, then you will get some of this.
*/ */
public void downloadResult() throws FailedTransmissionException { public void downloadResult() throws FailedTransmissionException {
if (!hasFinished()) { if (!hasFinished()) {
throw new FailedTransmissionException("Job has not been finished!"); throw new FailedTransmissionException("Job has not been finished!");
} }
downloadThread = new Thread(new DownloadTask(this)); downloadThread = new Thread(new DownloadTask(this));
downloadThread.start(); downloadThread.start();
} }
/** /**
* This method is used by the download task to set the CityGML file handle as soon as the file has been download. * 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. * @param result The file handle of the download CityGML file.
*/ */
protected void setResult(File result) { protected void setResult(File result) {
this.result = result; this.result = result;
} }
/** /**
* This method will return the download CityGML file for this export job, but only if the export job has actually * This method will return the download CityGML file for this export job, but only if the export job has actually
* been finished before. * been finished before.
* *
* @return Returns a file handle to the download CityGML file. * @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. * @throws FailedTransmissionException If the job result has not been download yet, then you will get some of this.
*/ */
@Override @Override
public File getResult() throws FailedTransmissionException { public File getResult() throws FailedTransmissionException {
if (!hasFinished()) { if (!hasFinished()) {
throw new FailedTransmissionException("Job has not been finished!"); throw new FailedTransmissionException("Job has not been finished!");
} }
if (Objects.isNull(result)) { if (Objects.isNull(result)) {
throw new FailedTransmissionException("Job result has not been downloaded!"); throw new FailedTransmissionException("Job result has not been downloaded!");
} }
return result; return result;
} }
/** /**
* The asynchronous send, poll and download tasks cannot throw exceptions. If something goes wrong during the save, * 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 * 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 * 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. * 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 * @param errorMessage The description of the encountered problem. This could be the exception message or a more user
* friendly message. * friendly message.
*/ */
protected synchronized void setLastEncounteredProblem(Optional<String> errorMessage) { protected synchronized void setLastEncounteredProblem(Optional<String> errorMessage) {
this.lastEncounteredProblem = 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 * 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. * by asynchronous tasks instead of exception, because they cannot throw exceptions.
* *
* @param jobStatus The new status of this job. * @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. * @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) { protected synchronized void setStatus(JobStatus jobStatus, Optional<String> message) {
super.setStatus(jobStatus); super.setStatus(jobStatus);
lastEncounteredProblem = message; lastEncounteredProblem = message;
notifyJobStatusListeners(); notifyJobStatusListeners();
} }
} }
\ No newline at end of file
package eu.simstadt.nf4j.async; package eu.simstadt.nf4j.async;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import eu.simstadt.nf4j.ImportJob; import eu.simstadt.nf4j.ImportJob;
import eu.simstadt.nf4j.FailedTransmissionException; import eu.simstadt.nf4j.FailedTransmissionException;
import eu.simstadt.nf4j.InvalidJobDescriptorException; import eu.simstadt.nf4j.InvalidJobDescriptorException;
import eu.simstadt.nf4j.JobStatus; 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. * 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 * 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 * 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. * to get status updates from the asynchronous operations.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public class AsyncImportJob extends ImportJob<ImportJobDescription> implements AsyncJob { public class AsyncImportJob extends ImportJob<ImportJobDescription> implements AsyncJob {
/** /**
* While polling for the current job status, the polling thread will sleep for this amount of seconds * While polling for the current job status, the polling thread will sleep for this amount of seconds
* before each status update request. * before each status update request.
*/ */
private final int DEFAULT_POLLING_INTERVAL = 5; // seconds private final int DEFAULT_POLLING_INTERVAL = 5; // seconds
private boolean jobTransmissionTriggered = false; private boolean jobTransmissionTriggered = false;
private JobStatus lastPublishedJobStatus; private JobStatus lastPublishedJobStatus;
private Optional<String> lastEncounteredProblem = Optional.empty(); private Optional<String> lastEncounteredProblem = Optional.empty();
private Thread sendThread; private Thread sendThread;
private Thread pollThread; private Thread pollThread;
/** As long as this variable is true, the polling thread will be kept alive. */ /** As long as this variable is true, the polling thread will be kept alive. */
private boolean keepPolling = true; private boolean keepPolling = true;
/** /**
* List of all registered job status listeners. Whenever the state of this job changes, these listeners * List of all registered job status listeners. Whenever the state of this job changes, these listeners
* will get informed. * will get informed.
*/ */
private LinkedList<JobStatusListener> jobListenerList = new LinkedList<>(); private LinkedList<JobStatusListener> jobListenerList = new LinkedList<>();
/** /**
* 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. Every job which
* is created by this constructor will have the status "local", because it is assumed that it has an unsent * 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. * 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 AsyncImportJob(ImportJobDescription descriptor, HTTPConnection connector) { public AsyncImportJob(ImportJobDescription descriptor, HTTPConnection connector) {
super(descriptor, connector); super(descriptor, connector);
status = JobStatus.LOCAL; status = JobStatus.LOCAL;
} }
/** /**
* 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. 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 * constructor will have the status "sent", because it is assumed that the job is already enqueued at the
* nF job queue. * 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 AsyncImportJob(int id, HTTPConnection connector) { public AsyncImportJob(int id, HTTPConnection connector) {
super(id, connector); super(id, connector);
status = JobStatus.SENT; status = JobStatus.SENT;
} }
/** /**
* This method zips up an archive which includes the CityGML file to be imported as well as a nF start * 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. * file. Both files will be preprocessed according to the set attributes of the job description.
*/ */
@Override @Override
public synchronized void send() throws InvalidJobDescriptorException, FailedTransmissionException { public synchronized void send() throws InvalidJobDescriptorException, FailedTransmissionException {
if (jobTransmissionTriggered) { if (jobTransmissionTriggered) {
throw new FailedTransmissionException("Jobs cannot be sent twice!"); throw new FailedTransmissionException("Jobs cannot be sent twice!");
} }
if (Objects.isNull(descriptor) || !descriptor.isValid()) { if (Objects.isNull(descriptor) || !descriptor.isValid()) {
throw new InvalidJobDescriptorException(); throw new InvalidJobDescriptorException();
} }
jobTransmissionTriggered = true; jobTransmissionTriggered = true;
notifyJobStatusListeners(); // Force the job to signal the LOCAL status notifyJobStatusListeners(); // Force the job to signal the LOCAL status
sendThread = new Thread(new SendImportJobTask(this)); sendThread = new Thread(new SendImportJobTask(this));
sendThread.start(); sendThread.start();
} }
/** /**
* Frequently queries the status of the remote nF export job and updates the local status accordingly. * Frequently queries the status of the remote 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 * The queries will be performed asynchronously in a separate thread. Job status listener will be notified
* upon every new status change. * upon every new status change.
* *
* @param interval Amount of seconds to wait before the next status update request will be sent to the * @param interval Amount of seconds to wait before the next status update request will be sent to the
* nF server. * nF server.
* *
* @throws FailedTransmissionException If your job has not been sent yet, then you will get some of this. * @throws FailedTransmissionException If your job has not been sent yet, then you will get some of this.
*/ */
@Override @Override
public synchronized void poll(int interval) throws FailedTransmissionException { public synchronized void poll(int interval) throws FailedTransmissionException {
if (status.compareTo(JobStatus.SENT) < 0) { if (status.compareTo(JobStatus.SENT) < 0) {
throw new FailedTransmissionException("The job has not been sent to the nF yet!"); throw new FailedTransmissionException("The job has not been sent to the nF yet!");
} }
if (Objects.nonNull(pollThread)) { if (Objects.nonNull(pollThread)) {
pollThread.interrupt(); pollThread.interrupt();
} }
keepPolling = true; keepPolling = true;
pollThread = new Thread(new PollJobStatusTask(this, interval)); pollThread = new Thread(new PollJobStatusTask(this, interval));
pollThread.start(); pollThread.start();
} }
/** /**
* Convenience method for polling with a predefined default interval. * Convenience method for polling with a predefined default interval.
* *
* @see poll(int) * @see poll(int)
*/ */
public synchronized void poll() throws FailedTransmissionException { public synchronized void poll() throws FailedTransmissionException {
poll(DEFAULT_POLLING_INTERVAL); poll(DEFAULT_POLLING_INTERVAL);
} }
/** /**
* Connects to the nF and refreshes the status of this job. If there is no nF connector set, * Connects to the nF and refreshes the status of this job. If there is no nF connector set,
* this operation will throw a FailedTransmissionException. * this operation will throw a FailedTransmissionException.
* *
* @throws FailedTransmissionException If the connection to the nF is broken you will get some of this. * @throws FailedTransmissionException If the connection to the nF is broken you will get some of this.
*/ */
@Override @Override
public synchronized void updateStatus() throws FailedTransmissionException { public synchronized void updateStatus() throws FailedTransmissionException {
if (Objects.nonNull(connector)) { if (Objects.nonNull(connector)) {
AsyncImportJob job = ((HTTPConnection) connector).requestImportJob(id); AsyncImportJob job = ((HTTPConnection) connector).requestImportJob(id);
JobStatus newStatus = job.getStatus(); JobStatus newStatus = job.getStatus();
if (newStatus.compareTo(JobStatus.SENT) > 0) { if (newStatus.compareTo(JobStatus.SENT) > 0) {
setStatus(job.getStatus(), Optional.empty()); setStatus(job.getStatus(), Optional.empty());
} }
} else { } else {
throw new FailedTransmissionException(); throw new FailedTransmissionException();
} }
} }
/** /**
* Sets the status of this job depending on the given nF status code. nF status codes will be sent * 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 * 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. * this library. Read the nF documentation for more information.
* *
* @param statusCode The nF status code for this job. * @param statusCode The nF status code for this job.
*/ */
@Override @Override
public synchronized void setStatusForCode(int statusCode) { public synchronized void setStatusForCode(int statusCode) {
switch (statusCode) { switch (statusCode) {
case 0: case 0:
setStatus(JobStatus.PENDING); break; setStatus(JobStatus.PENDING); break;
case 10: case 10:
setStatus(JobStatus.RUNNING); break; setStatus(JobStatus.RUNNING); break;
case 20: case 20:
setStatus(JobStatus.ERROR); break; setStatus(JobStatus.ERROR); break;
case 25: case 25:
setStatus(JobStatus.WARNING); break; setStatus(JobStatus.WARNING); break;
case 30: case 30:
setStatus(JobStatus.FINISHED); break; setStatus(JobStatus.FINISHED); break;
case 40: case 40:
setStatus(JobStatus.APPROVE); break; setStatus(JobStatus.APPROVE); break;
case 45: case 45:
setStatus(JobStatus.REJECT); break; setStatus(JobStatus.REJECT); break;
case 50: case 50:
setStatus(JobStatus.APPROVE_RUNNING); break; setStatus(JobStatus.APPROVE_RUNNING); break;
case 55: case 55:
setStatus(JobStatus.REJECT_RUNNING); break; setStatus(JobStatus.REJECT_RUNNING); break;
case 60: case 60:
setStatus(JobStatus.APPROVE_REJECT_ERROR); break; setStatus(JobStatus.APPROVE_REJECT_ERROR); break;
case 70: case 70:
setStatus(JobStatus.APPROVE_REJECT_OK); break; setStatus(JobStatus.APPROVE_REJECT_OK); break;
case 80: case 80:
setStatus(JobStatus.IMPORTED_WARNING); break; setStatus(JobStatus.IMPORTED_WARNING); break;
default: default:
setStatus(JobStatus.UNKNOWN); setStatus(JobStatus.UNKNOWN);
} }
} }
/** /**
* Registers a job status listener. * Registers a job status listener.
* *
* @param jobListener The job status listener to be registered. This listener will receive updates about every * @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 * progressing change of the job status. Meaning, the change to a particular status will only be signaled once
* to the listener. * to the listener.
*/ */
@Override @Override
public void addJobStatusListener(JobStatusListener jobListener) { public void addJobStatusListener(JobStatusListener jobListener) {
jobListenerList.add(jobListener); jobListenerList.add(jobListener);
} }
/** /**
* Unregisters a job status listener. * Unregisters a job status listener.
* *
* @param jobListener The job status listener to be unregistered. * @param jobListener The job status listener to be unregistered.
*/ */
@Override @Override
public void removeJobStatusListener(JobStatusListener jobListener) { public void removeJobStatusListener(JobStatusListener jobListener) {
jobListenerList.remove(jobListener); jobListenerList.remove(jobListener);
} }
/** /**
* Once the status of this job changes, all registered job status listeners will be notified. * 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 * 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 * only be notified once about every singular status. If this status has been signaled already, then the
* listeners will not be notified again. * listeners will not be notified again.
*/ */
@Override @Override
public synchronized void notifyJobStatusListeners() { public synchronized void notifyJobStatusListeners() {
if (Objects.isNull(lastPublishedJobStatus) || status.compareTo(lastPublishedJobStatus) > 0) { if (Objects.isNull(lastPublishedJobStatus) || status.compareTo(lastPublishedJobStatus) > 0) {
JobStatusEvent event = new JobStatusEvent(status, this, lastEncounteredProblem); JobStatusEvent event = new JobStatusEvent(status, this, lastEncounteredProblem);
for (JobStatusListener listener : jobListenerList) { for (JobStatusListener listener : jobListenerList) {
listener.jobStatusChanged(event); listener.jobStatusChanged(event);
} }
lastPublishedJobStatus = status; lastPublishedJobStatus = status;
} }
} }
/** /**
* A convenience method to set a new job status and a status message at the same time. Status messages will be passed * 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. * by asynchronous tasks instead of exception, because they cannot throw exceptions.
* *
* @param jobStatus The new status of this job. * @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. * @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) { protected synchronized void setStatus(JobStatus jobStatus, Optional<String> message) {
super.setStatus(jobStatus); super.setStatus(jobStatus);
lastEncounteredProblem = message; lastEncounteredProblem = message;
notifyJobStatusListeners(); notifyJobStatusListeners();
} }
/** /**
* Cancels all ongoing send and poll operations as soon as possible. * Cancels all ongoing send and poll operations as soon as possible.
*/ */
@Override @Override
public void cancel() { public void cancel() {
keepPolling = false; keepPolling = false;
if (Objects.nonNull(sendThread)) { if (Objects.nonNull(sendThread)) {
sendThread.interrupt(); sendThread.interrupt();
} }
if (Objects.nonNull(pollThread)) { if (Objects.nonNull(pollThread)) {
pollThread.interrupt(); pollThread.interrupt();
} }
} }
/** /**
* @return Returns true, if the polling thread should go on with its polling job. Otherwise, false. * @return Returns true, if the polling thread should go on with its polling job. Otherwise, false.
*/ */
@Override @Override
public boolean keepPolling() { public boolean keepPolling() {
return keepPolling; return keepPolling;
} }
/** /**
* @return Returns true, if the job is definitely done. This is also the case, if the resulting CityGML * @return Returns true, if the job is definitely done. This is also the case, if the resulting CityGML
* file has been download. False, otherwise. * file has been download. False, otherwise.
*/ */
@Override @Override
public boolean hasFinished() { public boolean hasFinished() {
return status == JobStatus.FINISHED; return status == JobStatus.FINISHED;
} }
/** /**
* @return Returns true, if the job has been failed. You may want to look up the "last encountered problem" * @return Returns true, if the job has been failed. You may want to look up the "last encountered problem"
* string. * string.
*/ */
@Override @Override
public boolean hasFailed() { public boolean hasFailed() {
return status == JobStatus.FAILED || status == JobStatus.ERROR; return status == JobStatus.FAILED || status == JobStatus.ERROR;
} }
/** /**
* Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the * Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the
* asynchronous PollJobStatusTask class. * asynchronous PollJobStatusTask class.
*/ */
@Override @Override
public void triggerStatusUpdate() throws FailedTransmissionException { public void triggerStatusUpdate() throws FailedTransmissionException {
updateStatus(); updateStatus();
} }
} }
\ No newline at end of file
package eu.simstadt.nf4j.async; package eu.simstadt.nf4j.async;
import eu.simstadt.nf4j.FailedTransmissionException; 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 * 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 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. * 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. * Every registered job status listener should be notified upon significant and new job status changes.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public interface AsyncJob { public interface AsyncJob {
/** /**
* Frequently queries the status of the remote nF export job and updates the local status accordingly. * Frequently queries the status of the remote 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 * The queries will be performed asynchronously in a separate thread. Job status listener will be notified
* upon every new status change. * upon every new status change.
* *
* @param interval Amount of seconds to wait before the next status update request will be sent to the * @param interval Amount of seconds to wait before the next status update request will be sent to the
* nF server. * nF server.
* *
* @throws FailedTransmissionException If your job has not been sent yet, then you will get some of this. * @throws FailedTransmissionException If your job has not been sent yet, then you will get some of this.
*/ */
public void poll(int interval) throws FailedTransmissionException; public void poll(int interval) throws FailedTransmissionException;
/** /**
* Cancels all ongoing asynchronous operations of this job as soon as possible. Operations to be canceled * Cancels all ongoing asynchronous operations of this job as soon as possible. Operations to be canceled
* might be send, poll and download operations. * might be send, poll and download operations.
*/ */
public void cancel(); public void cancel();
/** /**
* Registers a job status listener at this job. The listener will then be notified upon job status changes. * 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. * @param jobListener The job status listener to be registered at this job.
*/ */
public void addJobStatusListener(JobStatusListener jobListener); public void addJobStatusListener(JobStatusListener jobListener);
/** /**
* Unregisters a job status listener for this job. The listener will not be notified about job status changes * Unregisters a job status listener for this job. The listener will not be notified about job status changes
* anymore. * anymore.
* *
* @param jobListener The job status listener to be unregistered. * @param jobListener The job status listener to be unregistered.
*/ */
public void removeJobStatusListener(JobStatusListener jobListener); public void removeJobStatusListener(JobStatusListener jobListener);
/** /**
* Once the status of this job changes, all registered job status listeners will be notified. * Once the status of this job changes, all registered job status listeners will be notified.
*/ */
public void notifyJobStatusListeners(); public void notifyJobStatusListeners();
/** /**
* Convenience method. * Convenience method.
* *
* @return Returns true, if the job is definitely done. * @return Returns true, if the job is definitely done.
*/ */
public boolean hasFinished(); public boolean hasFinished();
/** /**
* Convenience method. * Convenience method.
* *
* @return Returns true, if the job was unable to recover from a serious problem. * @return Returns true, if the job was unable to recover from a serious problem.
*/ */
public boolean hasFailed(); public boolean hasFailed();
/** /**
* Asynchronous polling tasks may query this flag frequently in order to decide to proceed or not. * 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. * @return Returns true, if the polling task should continue polling for status updates.
*/ */
public boolean keepPolling(); public boolean keepPolling();
/** /**
* Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the * Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the
* asynchronous PollJobStatusTask class. * asynchronous PollJobStatusTask class.
* *
* @throws FailedTransmissionException If something goes wrong during the update process, you will get some * @throws FailedTransmissionException If something goes wrong during the update process, you will get some
* of this. * of this.
*/ */
public void triggerStatusUpdate() throws FailedTransmissionException; public void triggerStatusUpdate() throws FailedTransmissionException;
} }
package eu.simstadt.nf4j.async; 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
* coordinate reference systems (CRS). * coordinate reference systems (CRS).
* *
* @author Marcel Bruse * @author Marcel Bruse
* *
*/ */
public enum CRSWKT { public enum CRSWKT {
EPSG_31467("EPSG:31467"), // DHDN Gauss-Kruger zone 3 EPSG_31467("EPSG:31467"), // DHDN Gauss-Kruger zone 3
EPSG_4326("EPSG:4326"); // WGS 84 EPSG_4326("EPSG:4326"); // WGS 84
/** 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. */ /** Constructor for well known texts. */
private CRSWKT(String wkt) { private CRSWKT(String wkt) {
this.wkt = wkt; this.wkt = wkt;
} }
} }
package eu.simstadt.nf4j.async; package eu.simstadt.nf4j.async;
/** /**
* Another class for coordinates. Is there a general purpose class for this kind of applications? * 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. * This might be an intermediate representation for WGS 84 coordinates.
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public class Coord { public class Coord {
/** The latitude of the geographic position. */ /** The latitude of the geographic position. */
public double latitude; public double latitude;
/** The longitude of the geographic position. */ /** The longitude of the geographic position. */
public double longitude; public double longitude;
/** Standard constructor. /** Standard constructor.
* *
* @param latitude The latitude of the WGS 84 coordinate. * @param latitude The latitude of the WGS 84 coordinate.
* @param longitude The longitude of the WGS 84 coordinate. * @param longitude The longitude of the WGS 84 coordinate.
*/ */
public Coord(double latitude, double longitude) { public Coord(double latitude, double longitude) {
this.latitude = latitude; this.latitude = latitude;
this.longitude = longitude; this.longitude = longitude;
} }
} }
package eu.simstadt.nf4j.async; package eu.simstadt.nf4j.async;
import java.io.File; import java.io.File;
import java.util.Optional; import java.util.Optional;
import eu.simstadt.nf4j.FailedTransmissionException; import eu.simstadt.nf4j.FailedTransmissionException;
import eu.simstadt.nf4j.JobStatus; import eu.simstadt.nf4j.JobStatus;
/** /**
* This task downloads export job results asynchronously within its jobs separate download thread. * This task downloads export job results asynchronously within its jobs separate download thread.
* You can cancel this task by calling job.cancel(). * You can cancel this task by calling job.cancel().
* *
* @author Marcel Bruse * @author Marcel Bruse
*/ */
public class DownloadTask implements Runnable { public class DownloadTask implements Runnable {
/** The finished export job for which you want to download a CityGML result. */ /** The finished export job for which you want to download a CityGML result. */
private AsyncExportJob job; private AsyncExportJob job;
/** /**
* Constructor with finished(!) asynchronous export job. * Constructor with finished(!) asynchronous export job.
* *
* @param job The export job for which you want to download the CityGML result. * @param job The export job for which you want to download the CityGML result.
*/ */
public DownloadTask(AsyncExportJob job) { public DownloadTask(AsyncExportJob job) {
this.job = job; this.job = job;
} }
/** /**
* This method performs the download operation asynchronously in the export jobs download thread. * This method performs the download operation asynchronously in the export jobs download thread.
* Job status listeners will be notified upon the finished download. * Job status listeners will be notified upon the finished download.
*/ */
@Override @Override
public void run() { public void run() {
HTTPConnection connector = (HTTPConnection) job.getConnector(); HTTPConnection connector = (HTTPConnection) job.getConnector();
try { try {
File file = connector.requestExportJobResult(job); File file = connector.requestExportJobResult(job);
job.setResult(file); job.setResult(file);
job.setStatus(JobStatus.DOWNLOAD, Optional.empty()); job.setStatus(JobStatus.DOWNLOAD, Optional.empty());
} catch (FailedTransmissionException ex) { } catch (FailedTransmissionException ex) {
// Conditions have been checked by main thread. No exception handling needed. // Conditions have been checked by main thread. No exception handling needed.
} }
} }
} }
package eu.simstadt.nf4j.async; 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; 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 ExportJobDescription implements ExportJobDescriptor { public class ExportJobDescription implements ExportJobDescriptor {
private static final String DEFAULT_ACCOUNT = "Marcel"; private static final String DEFAULT_ACCOUNT = "Marcel";
private static final String DEFAULT_XMETA_DATA = "0"; private static final String DEFAULT_XMETA_DATA = "0";
private static final String DEFAULT_CALIBRATION = "0"; private static final String DEFAULT_CALIBRATION = "0";
private static final String DEFAULT_PRODUCT = "WUDEV"; private static final String DEFAULT_PRODUCT = "WUDEV";
private static final String DEFAULT_ZIP_RESULT = "0"; private static final String DEFAULT_ZIP_RESULT = "0";
private static final String DEFAULT_DHK_RESOLVE_REFERENCES = "1"; private static final String DEFAULT_DHK_RESOLVE_REFERENCES = "1";
private static final String DEFAULT_USE_PDCT_BORDER_POLY = "0"; private static final String DEFAULT_USE_PDCT_BORDER_POLY = "0";
private static final String DEFAULT_USE_NO_DATA_MASK = "0"; private static final String DEFAULT_USE_NO_DATA_MASK = "0";
private static final String DEFAULT_ECK = "0"; private static final String DEFAULT_ECK = "0";
private static final String DEFAULT_COL = "0"; private static final String DEFAULT_COL = "0";
private static final String DEFAULT_EXPORT_META_DATA = "1"; private static final String DEFAULT_EXPORT_META_DATA = "1";
private static final String DEFAULT_FORMAT = "CityGML"; private static final String DEFAULT_FORMAT = "CityGML";
private static final String DEFAULT_SCALE = "10000.0"; private static final String DEFAULT_SCALE = "10000.0";
private static final String DEFAULT_RESOLUTION = "100.0"; private static final String DEFAULT_RESOLUTION = "100.0";
private static final String DEFAULT_MERGE_MAP_SHEETS = "off"; private static final String DEFAULT_MERGE_MAP_SHEETS = "off";
private static final String DEFAULT_TILE1ASGN = "false"; private static final String DEFAULT_TILE1ASGN = "false";
private static final String DEFAULT_SRS = "31467"; private static final String DEFAULT_SRS = "31467";
private static final String DEFAULT_SINGLE = "1"; private static final String DEFAULT_SINGLE = "1";
private static final String DEFAULT_PLOTFRAME = "0"; private static final String DEFAULT_PLOTFRAME = "0";
private static final String DEFAULT_PLOT_LABEL_SRS = "-1"; private static final String DEFAULT_PLOT_LABEL_SRS = "-1";
private static final String DEFAULT_MONO = "0"; private static final String DEFAULT_MONO = "0";
private static final String DEFAULT_COLOR = "0"; private static final String DEFAULT_COLOR = "0";
private static final String DEFAULT_JOBNUMBER = "GR_EXPORT_CITYGML_LOD1"; private static final String DEFAULT_JOBNUMBER = "GR_EXPORT_CITYGML_LOD1";
private static final String DEFAULT_INITIATOR = "2758"; private static final String DEFAULT_INITIATOR = "2758";
private static final String DEFAULT_LODS = "12"; private static final String DEFAULT_LODS = "12";
/** The version of the novaFACTORY XML export job format. */ /** The version of the novaFACTORY XML export job format. */
public static final String EXPORT_JOB_VERSION = "1.0.0"; public static final String EXPORT_JOB_VERSION = "1.0.0";
/** TODO: Who or what is this initiator? */ /** TODO: Who or what is this initiator? */
private String initiator; private String initiator;
/** The number of the job which can actually be a string. */ /** The number of the job which can actually be a string. */
private String jobnumber; private String jobnumber;
/** The account or user for which a job should be executed. */ /** The account or user for which a job should be executed. */
private String account; private String account;
/** The product for which a job should be executed. */ /** The product for which a job should be executed. */
private String product; private String product;
/** Color attribute of the layers tag. */ /** Color attribute of the layers tag. */
private String color; private String color;
/** The mono attribute of the layers tag. */ /** The mono attribute of the layers tag. */
private String mono; private String mono;
/** The plotLabelSrs flag of the layers tag. */ /** The plotLabelSrs flag of the layers tag. */
private String plotLabelSrs; private String plotLabelSrs;
/** The plotframe flag of the layers tag. */ /** The plotframe flag of the layers tag. */
private String plotframe; private String plotframe;
/** The single flag of the layers tag. */ /** The single flag of the layers tag. */
private String single; private String single;
/** The layers which will be involved in a job. */ /** The layers which will be involved in a job. */
private ArrayList<Layer> layerList = new ArrayList<>(); private ArrayList<Layer> layerList = new ArrayList<>();
/** The SRS for the job. */ /** The SRS for the job. */
private String srs; private String srs;
/** The merge_mapsheets attribute of the extent tag. */ /** The merge_mapsheets attribute of the extent tag. */
private String mergeMapsheets; private String mergeMapsheets;
/** The tile1asgn attribute of the extent tag. */ /** The tile1asgn attribute of the extent tag. */
private String tile1asgn; private String tile1asgn;
/** The unit tag of the extent. */ /** The unit tag of the extent. */
private ArrayList<Unit> unitList = new ArrayList<>(); private ArrayList<Unit> unitList = new ArrayList<>();
/** The polygon of the selected region which should be exported. */ /** The polygon of the selected region which should be exported. */
ArrayList<Coord> regionPolygon = new ArrayList<>(); ArrayList<Coord> regionPolygon = new ArrayList<>();
/** The resolution for the job. */ /** The resolution for the job. */
private String resolution; private String resolution;
/** The scale for the job. */ /** The scale for the job. */
private String scale; private String scale;
/** The format for the job. E.g. CityGML. */ /** The format for the job. E.g. CityGML. */
private String format; private String format;
/** The exportmetadata flag for the job. */ /** The exportmetadata flag for the job. */
private String exportmetadata; private String exportmetadata;
/** The col attribute of the addfile tag. */ /** The col attribute of the addfile tag. */
private String col; private String col;
/** The eck attribute of the addfile tag. */ /** The eck attribute of the addfile tag. */
private String eck; private String eck;
/** The usenodatamask flag for the job. */ /** The usenodatamask flag for the job. */
private String usenodatamask; private String usenodatamask;
/** The usepdctborderpoly flag for the job. */ /** The usepdctborderpoly flag for the job. */
private String usepdctborderpoly; private String usepdctborderpoly;
/** The dhkresolvereferences flag for the job. */ /** The dhkresolvereferences flag for the job. */
private String dhkresolvereferences; private String dhkresolvereferences;
/** Flag for zipped results. */ /** Flag for zipped results. */
private String zipresult; private String zipresult;
/** The calibration attribute of the exportmetadata tag. */ /** The calibration attribute of the exportmetadata tag. */
private String calibration; private String calibration;
/** The xmetadata attribute of the exportmetadata tag. */ /** The xmetadata attribute of the exportmetadata tag. */
private String xmetadata; private String xmetadata;
/** The LOD or LODs which should be exported. Set it to "1", if you want LOD1. /** The LOD or LODs which should be exported. Set it to "1", if you want LOD1.
* Set it to "12", if you want the LOD1 and LOD2. * Set it to "12", if you want the LOD1 and LOD2.
*/ */
private String lods; private String lods;
public String getInitiator() { public String getInitiator() {
return initiator; return initiator;
} }
public void setInitiator(String initiator) { public void setInitiator(String initiator) {
this.initiator = initiator; this.initiator = initiator;
} }
public String getJobnumber() { public String getJobnumber() {
return jobnumber; return jobnumber;
} }
public void setJobnumber(String jobnumber) { public void setJobnumber(String jobnumber) {
this.jobnumber = jobnumber; this.jobnumber = jobnumber;
} }
public String getAccount() { public String getAccount() {
return account; return account;
} }
public void setAccount(String account) { public void setAccount(String account) {
this.account = account; this.account = account;
} }
public String getProduct() { public String getProduct() {
return product; return product;
} }
public void setProduct(String product) { public void setProduct(String product) {
this.product = product; this.product = product;
} }
public String getColor() { public String getColor() {
return color; return color;
} }
public void setColor(String color) { public void setColor(String color) {
this.color = color; this.color = color;
} }
public String getMono() { public String getMono() {
return mono; return mono;
} }
public void setMono(String mono) { public void setMono(String mono) {
this.mono = mono; this.mono = mono;
} }
public String getPlotLabelSrs() { public String getPlotLabelSrs() {
return plotLabelSrs; return plotLabelSrs;
} }
public void setPlotLabelSrs(String plotLabelSrs) { public void setPlotLabelSrs(String plotLabelSrs) {
this.plotLabelSrs = plotLabelSrs; this.plotLabelSrs = plotLabelSrs;
} }
public String getPlotframe() { public String getPlotframe() {
return plotframe; return plotframe;
} }
public void setPlotframe(String plotframe) { public void setPlotframe(String plotframe) {
this.plotframe = plotframe; this.plotframe = plotframe;
} }
public String getSingle() { public String getSingle() {
return single; return single;
} }
public void setSingle(String single) { public void setSingle(String single) {
this.single = single; this.single = single;
} }
public ArrayList<Layer> getLayerList() { public ArrayList<Layer> getLayerList() {
return layerList; return layerList;
} }
public void setLayerList(ArrayList<Layer> layerList) { public void setLayerList(ArrayList<Layer> layerList) {
this.layerList = layerList; this.layerList = layerList;
} }
public void addLayer(Layer layer) { public void addLayer(Layer layer) {
layerList.add(layer); layerList.add(layer);
} }
public String getSrs() { public String getSrs() {
return srs; return srs;
} }
public void setSrs(String srs) { public void setSrs(String srs) {
this.srs = srs; this.srs = srs;
} }
public String getMergeMapsheets() { public String getMergeMapsheets() {
return mergeMapsheets; return mergeMapsheets;
} }
public void setMergeMapsheets(String mergeMapsheets) { public void setMergeMapsheets(String mergeMapsheets) {
this.mergeMapsheets = mergeMapsheets; this.mergeMapsheets = mergeMapsheets;
} }
public ArrayList<Unit> getUnitList() { public ArrayList<Unit> getUnitList() {
return unitList; return unitList;
} }
public void addUnit(Unit unit) { public void addUnit(Unit unit) {
unitList.add(unit); unitList.add(unit);
} }
public String getTile1asgn() { public String getTile1asgn() {
return tile1asgn; return tile1asgn;
} }
public void setTile1asgn(String tile1asgn) { public void setTile1asgn(String tile1asgn) {
this.tile1asgn = tile1asgn; this.tile1asgn = tile1asgn;
} }
public String getResolution() { public String getResolution() {
return resolution; return resolution;
} }
public void setResolution(String resolution) { public void setResolution(String resolution) {
this.resolution = resolution; this.resolution = resolution;
} }
public String getScale() { public String getScale() {
return scale; return scale;
} }
public void setScale(String scale) { public void setScale(String scale) {
this.scale = scale; this.scale = scale;
} }
public String getFormat() { public String getFormat() {
return format; return format;
} }
public void setFormat(String format) { public void setFormat(String format) {
this.format = format; this.format = format;
} }
public String getExportmetadata() { public String getExportmetadata() {
return exportmetadata; return exportmetadata;
} }
public void setExportmetadata(String exportmetadata) { public void setExportmetadata(String exportmetadata) {
this.exportmetadata = exportmetadata; this.exportmetadata = exportmetadata;
} }
public String getCol() { public String getCol() {
return col; return col;
} }
public void setCol(String col) { public void setCol(String col) {
this.col = col; this.col = col;
} }
public String getEck() { public String getEck() {
return eck; return eck;
} }
public void setEck(String eck) { public void setEck(String eck) {
this.eck = eck; this.eck = eck;
} }
public String getUsenodatamask() { public String getUsenodatamask() {
return usenodatamask; return usenodatamask;
} }
public void setUsenodatamask(String usenodatamask) { public void setUsenodatamask(String usenodatamask) {
this.usenodatamask = usenodatamask; this.usenodatamask = usenodatamask;
} }
public String getUsepdctborderpoly() { public String getUsepdctborderpoly() {
return usepdctborderpoly; return usepdctborderpoly;
} }
public void setUsepdctborderpoly(String usepdctborderpoly) { public void setUsepdctborderpoly(String usepdctborderpoly) {
this.usepdctborderpoly = usepdctborderpoly; this.usepdctborderpoly = usepdctborderpoly;
} }
public String getDhkresolvereferences() { public String getDhkresolvereferences() {
return dhkresolvereferences; return dhkresolvereferences;
} }
public void setDhkresolvereferences(String dhkresolvereferences) { public void setDhkresolvereferences(String dhkresolvereferences) {
this.dhkresolvereferences = dhkresolvereferences; this.dhkresolvereferences = dhkresolvereferences;
} }
public String getZipresult() { public String getZipresult() {
return zipresult; return zipresult;
} }
public void setZipresult(String zipresult) { public void setZipresult(String zipresult) {
this.zipresult = zipresult; this.zipresult = zipresult;
} }
public String getCalibration() { public String getCalibration() {
return calibration; return calibration;
} }
public void setCalibration(String calibration) { public void setCalibration(String calibration) {
this.calibration = calibration; this.calibration = calibration;
} }
public String getXmetadata() { public String getXmetadata() {
return xmetadata; return xmetadata;
} }
public void setXmetadata(String xmetadata) { public void setXmetadata(String xmetadata) {
this.xmetadata = xmetadata; this.xmetadata = xmetadata;
} }
public String getLODs() { public String getLODs() {
return lods; return lods;
} }
public void setLODs(String lods) { public void setLODs(String lods) {
this.lods = lods; this.lods = lods;
} }
@Override @Override
public String supportsJobVersion() { public String supportsJobVersion() {
return EXPORT_JOB_VERSION; return EXPORT_JOB_VERSION;
} }
/** /**
* Adds a new coordinate to the region polygon. A region polygon is only used for export jobs. * Adds a new coordinate to the region polygon. A region polygon is only used for export jobs.
* *
* @param coordinate The new coordiante of the region polygon of a export job. * @param coordinate The new coordiante of the region polygon of a export job.
*/ */
public void addCoordinateToRegionPolygon(Coord coordinate) { public void addCoordinateToRegionPolygon(Coord coordinate) {
regionPolygon.add(coordinate); regionPolygon.add(coordinate);
} }
/** /**
* Sets the whole region polygon for an export job. * Sets the whole region polygon for an export job.
* *
* @param regionPolygon The region polygon to be used for an export job. * @param regionPolygon The region polygon to be used for an export job.
*/ */
public void setRegionPolygon(List<Coord> regionPolygon) { public void setRegionPolygon(List<Coord> regionPolygon) {
this.regionPolygon.clear(); this.regionPolygon.clear();
this.regionPolygon.addAll(regionPolygon); this.regionPolygon.addAll(regionPolygon);
} }
/** /**
* @return Returns the region polygon. * @return Returns the region polygon.
*/ */
public ArrayList<Coord> getRegionPolygon() { public ArrayList<Coord> getRegionPolygon() {
return regionPolygon; return regionPolygon;
} }
/** /**
* @return Returns true, if all mandatory elements and attributes are set. Otherwise, false. * @return Returns true, if all mandatory elements and attributes are set. Otherwise, false.
*/ */
@Override @Override
public boolean isValid() { public boolean isValid() {
if ((regionPolygon.isEmpty() && (unitList.isEmpty())) if ((regionPolygon.isEmpty() && (unitList.isEmpty()))
|| layerList.isEmpty() || layerList.isEmpty()
|| initiator.isEmpty() || initiator.isEmpty()
|| account.isEmpty() || account.isEmpty()
|| jobnumber.isEmpty() || jobnumber.isEmpty()
|| product.isEmpty() || product.isEmpty()
|| color.isEmpty() || color.isEmpty()
|| mono.isEmpty() || mono.isEmpty()
|| plotLabelSrs.isEmpty() || plotLabelSrs.isEmpty()
|| plotframe.isEmpty() || plotframe.isEmpty()
|| single.isEmpty() || single.isEmpty()
|| srs.isEmpty() || srs.isEmpty()
|| resolution.isEmpty() || resolution.isEmpty()
|| scale.isEmpty() || scale.isEmpty()
|| format.isEmpty() || format.isEmpty()
|| exportmetadata.isEmpty() || exportmetadata.isEmpty()
|| col.isEmpty() || col.isEmpty()
|| eck.isEmpty() || eck.isEmpty()
|| lods.isEmpty()) { || lods.isEmpty()) {
return false; return false;
} else { } else {
return true; return true;
} }
} }
public static ExportJobDescription getDefaultDescriptor() { public static ExportJobDescription getDefaultDescriptor() {
ExportJobDescription descriptor = new ExportJobDescription(); 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);
descriptor.setProduct(DEFAULT_PRODUCT); descriptor.setProduct(DEFAULT_PRODUCT);
descriptor.setColor(DEFAULT_COLOR); descriptor.setColor(DEFAULT_COLOR);
descriptor.setMono(DEFAULT_MONO); descriptor.setMono(DEFAULT_MONO);
descriptor.setPlotLabelSrs(DEFAULT_PLOT_LABEL_SRS); descriptor.setPlotLabelSrs(DEFAULT_PLOT_LABEL_SRS);
descriptor.setPlotframe(DEFAULT_PLOTFRAME); descriptor.setPlotframe(DEFAULT_PLOTFRAME);
descriptor.setSingle(DEFAULT_SINGLE); descriptor.setSingle(DEFAULT_SINGLE);
descriptor.setSrs(DEFAULT_SRS); descriptor.setSrs(DEFAULT_SRS);
descriptor.setMergeMapsheets(DEFAULT_MERGE_MAP_SHEETS); descriptor.setMergeMapsheets(DEFAULT_MERGE_MAP_SHEETS);
descriptor.setTile1asgn(DEFAULT_TILE1ASGN); descriptor.setTile1asgn(DEFAULT_TILE1ASGN);
descriptor.setResolution(DEFAULT_RESOLUTION); descriptor.setResolution(DEFAULT_RESOLUTION);
descriptor.setScale(DEFAULT_SCALE); descriptor.setScale(DEFAULT_SCALE);
descriptor.setFormat(DEFAULT_FORMAT); descriptor.setFormat(DEFAULT_FORMAT);
descriptor.setExportmetadata(DEFAULT_EXPORT_META_DATA); descriptor.setExportmetadata(DEFAULT_EXPORT_META_DATA);
descriptor.setCol(DEFAULT_COL); descriptor.setCol(DEFAULT_COL);
descriptor.setEck(DEFAULT_ECK); descriptor.setEck(DEFAULT_ECK);
descriptor.setUsenodatamask(DEFAULT_USE_NO_DATA_MASK); descriptor.setUsenodatamask(DEFAULT_USE_NO_DATA_MASK);
descriptor.setUsepdctborderpoly(DEFAULT_USE_PDCT_BORDER_POLY); descriptor.setUsepdctborderpoly(DEFAULT_USE_PDCT_BORDER_POLY);
descriptor.setDhkresolvereferences(DEFAULT_DHK_RESOLVE_REFERENCES); descriptor.setDhkresolvereferences(DEFAULT_DHK_RESOLVE_REFERENCES);
descriptor.setZipresult(DEFAULT_ZIP_RESULT); descriptor.setZipresult(DEFAULT_ZIP_RESULT);
descriptor.setCalibration(DEFAULT_CALIBRATION); descriptor.setCalibration(DEFAULT_CALIBRATION);
descriptor.setXmetadata(DEFAULT_XMETA_DATA); descriptor.setXmetadata(DEFAULT_XMETA_DATA);
descriptor.setLODs(DEFAULT_LODS); descriptor.setLODs(DEFAULT_LODS);
return descriptor; return descriptor;
} }
} }
package eu.simstadt.nf4j.async; package eu.simstadt.nf4j.async;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringReader; import java.io.StringReader;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.file.Files; import java.nio.file.Files;
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 java.util.Optional;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import eu.simstadt.nf4j.Connector; import eu.simstadt.nf4j.Connector;
import eu.simstadt.nf4j.FailedTransmissionException; import eu.simstadt.nf4j.FailedTransmissionException;
import eu.simstadt.nf4j.InvalidJobDescriptorException; import eu.simstadt.nf4j.InvalidJobDescriptorException;
import eu.simstadt.nf4j.Job; import eu.simstadt.nf4j.Job;
import eu.simstadt.nf4j.JobStatus; import eu.simstadt.nf4j.JobStatus;
/** /**
* NFConnector lets you communicate with your novaFACTORY (nF) server instance. It supports nF version 6.3.1.1. For more * 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. * 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 * 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. * 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 HTTPConnection implements Connector<AsyncImportJob, AsyncExportJob> 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";
/** The default context of the nF web application. It is part of any request URL directed at the nF server. */ /** The default context of the nF web application. It is part of any request URL directed at the nF server. */
public static final String DEFAULT_CONTEXT = "novaFACTORY"; public static final String DEFAULT_CONTEXT = "novaFACTORY";
/** The default port number of the nF web application. */ /** The default port number of the nF web application. */
public static final int DEFAULT_PORT = 80; public static final int DEFAULT_PORT = 80;
/** The default protocol for requests. */ /** The default protocol for requests. */
public static final String DEFAULT_PROTOCOL = "http"; public static final String DEFAULT_PROTOCOL = "http";
/** The standard char set for requests. */ /** The standard char set for requests. */
public static final String DEFAULT_CHARSET = "UTF-8"; public static final String DEFAULT_CHARSET = "UTF-8";
/** The standard line separator required for file transmission via multipart/form-data. */ /** The standard line separator required for file transmission via multipart/form-data. */
public static final String CRLF = "\r\n"; public static final String CRLF = "\r\n";
/** The name of nF's remote order servlet. */ /** The name of nF's remote order servlet. */
public static final String REMOTE_ORDER_SERVLET = "RemoteOrder"; public static final String REMOTE_ORDER_SERVLET = "RemoteOrder";
/** The name of nF's remote status servlet. */ /** The name of nF's remote status servlet. */
public static final String REMOTE_STATUS_SERVLET = "RemoteStatus"; public static final String REMOTE_STATUS_SERVLET = "RemoteStatus";
/** The name of nF's remote import servlet. */ /** The name of nF's remote import servlet. */
public static final String REMOTE_IMPORT_SERVLET = "RemoteImport"; public static final String REMOTE_IMPORT_SERVLET = "RemoteImport";
/** Default parser error message. */ /** Default parser error message. */
public static final String XML_PARSER_ERROR = "Request failed due to an unknown XML parser error."; public static final String XML_PARSER_ERROR = "Request failed due to an unknown XML parser error.";
/** Default IO exception occurred message. */ /** Default IO exception occurred message. */
public static final String IO_EXCEPTION_OCCURRED = public static final String IO_EXCEPTION_OCCURRED =
"Request failed due to an IO exception. Check the host and port."; "Request failed due to an IO exception. Check the host and port.";
/** Default malformed URL message. */ /** Default malformed URL message. */
public static final String MALFORMED_URL = "Request failed due to a malformed URL."; public static final String MALFORMED_URL = "Request failed due to a malformed URL.";
/** Default unknown error message. */ /** Default unknown error message. */
public static final String UNKNOWN_ERROR_OCCURRED = "An unknown error occurred."; public static final String UNKNOWN_ERROR_OCCURRED = "An unknown error occurred.";
/** The server or host name of the nF server. */ /** The server or host name of the nF server. */
private String server; private String server;
/** The port of the nF server. */ /** The port of the nF server. */
private int port; private int port;
/** The protocol of the data connection. */ /** The protocol of the data connection. */
private String protocol; private String protocol;
/** The context of the nF web application. It is part of any request URL directed at the nF server. */ /** The context of the nF web application. It is part of any request URL directed at the nF server. */
private String context; private String context;
/** /**
* Constructs your Connector instance. * Constructs your Connector instance.
* *
* @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 HTTPConnection(String server) { public HTTPConnection(String server) {
this(server, DEFAULT_PORT, DEFAULT_CONTEXT, DEFAULT_PROTOCOL); this(server, DEFAULT_PORT, DEFAULT_CONTEXT, DEFAULT_PROTOCOL);
} }
/** /**
* Constructs your Connector instance. * Constructs your Connector instance.
* *
* @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.
* @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 HTTPConnection(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;
this.protocol = protocol; this.protocol = protocol;
} }
/** /**
* 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
* HTTP/FTP/WPS/etc. interfaces against which this interface has been implemented. * HTTP/FTP/WPS/etc. interfaces against which this interface has been implemented.
* *
* The NovaFACTORY interfaces may change over time. Such changes force the SimStadt programmers to implement * The NovaFACTORY interfaces may change over time. Such changes force the SimStadt programmers to implement
* different versions of this NFConnector interface while keeping old implementations in order to ensure backward * different versions of this NFConnector interface while keeping old implementations in order to ensure backward
* compatibility. * compatibility.
* *
* @return The supported version of the novaFACTORY. * @return The supported version of the novaFACTORY.
*/ */
@Override @Override
public String supportsNFVersion() { public String supportsNFVersion() {
return NOVA_FACTORY_VERSION; return NOVA_FACTORY_VERSION;
} }
/** /**
* Returns the status of any existing nF export job. * Returns the status of any existing nF export 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 nF export job. * @return The status of any existing nF export job.
* @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 AsyncExportJob requestExportJob(int jobId) throws FailedTransmissionException { public AsyncExportJob requestExportJob(int jobId) throws FailedTransmissionException {
AsyncExportJob result = new AsyncExportJob(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)));
} catch (MalformedURLException ex) { } catch (MalformedURLException ex) {
result.getStatus().setMessage(MALFORMED_URL); result.getStatus().setMessage(MALFORMED_URL);
throw new FailedTransmissionException(ex.getMessage()); throw new FailedTransmissionException(ex.getMessage());
} catch (IOException ex) { } catch (IOException ex) {
result.getStatus().setMessage(IO_EXCEPTION_OCCURRED); result.getStatus().setMessage(IO_EXCEPTION_OCCURRED);
throw new FailedTransmissionException(ex.getMessage()); throw new FailedTransmissionException(ex.getMessage());
} catch (ParserConfigurationException | SAXException ex) { } catch (ParserConfigurationException | SAXException ex) {
result.getStatus().setMessage(XML_PARSER_ERROR); result.getStatus().setMessage(XML_PARSER_ERROR);
throw new FailedTransmissionException(ex.getMessage()); throw new FailedTransmissionException(ex.getMessage());
} }
return result; return result;
} }
/** /**
* Returns the status of any existing nF import job. * Returns the status of any existing nF import 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 nF import job. * @return The status of any existing nF import job.
* @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 AsyncImportJob requestImportJob(int jobId) throws FailedTransmissionException { public AsyncImportJob requestImportJob(int jobId) throws FailedTransmissionException {
AsyncImportJob result = new AsyncImportJob(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),
buildParameter("request", "status")); buildParameter("request", "status"));
getJobFromResponse(result, getResponse(buildURL(REMOTE_IMPORT_SERVLET, parameters))); getJobFromResponse(result, getResponse(buildURL(REMOTE_IMPORT_SERVLET, parameters)));
} catch (MalformedURLException ex) { } catch (MalformedURLException ex) {
result.getStatus().setMessage(MALFORMED_URL); result.getStatus().setMessage(MALFORMED_URL);
throw new FailedTransmissionException(ex.getMessage()); throw new FailedTransmissionException(ex.getMessage());
} catch (IOException ex) { } catch (IOException ex) {
result.getStatus().setMessage(IO_EXCEPTION_OCCURRED); result.getStatus().setMessage(IO_EXCEPTION_OCCURRED);
throw new FailedTransmissionException(ex.getMessage()); throw new FailedTransmissionException(ex.getMessage());
} catch (ParserConfigurationException | SAXException ex) { } catch (ParserConfigurationException | SAXException ex) {
result.getStatus().setMessage(XML_PARSER_ERROR); result.getStatus().setMessage(XML_PARSER_ERROR);
throw new FailedTransmissionException(ex.getMessage()); throw new FailedTransmissionException(ex.getMessage());
} }
return result; return result;
} }
/** /**
* 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.
* *
* @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.
*/ */
@Override @Override
public File requestExportJobResult(AsyncExportJob job) throws FailedTransmissionException { public File requestExportJobResult(AsyncExportJob job) throws FailedTransmissionException {
if (!job.hasFinished()) { if (!job.hasFinished()) {
throw new FailedTransmissionException("Job is not finished yet!"); 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", job.getId())); 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());
} catch (IOException ex) { } catch (IOException ex) {
throw new FailedTransmissionException(ex.getMessage()); throw new FailedTransmissionException(ex.getMessage());
} }
return result; return result;
} }
/** /**
* Builds a simple parameter string for a HTTP GET request string. * Builds a simple parameter string for a HTTP GET request string.
* *
* @param key The parameter name. * @param key The parameter name.
* @param value The value of the parameter. * @param value The value of the parameter.
* @return The HTTP GET request parameter as concatenated key and value. * @return The HTTP GET request parameter as concatenated key and value.
*/ */
private String buildParameter(String key, Object value) { private String buildParameter(String key, Object value) {
return key + "=" + value; return key + "=" + value;
} }
/** /**
* Builds a HTTP GET request URL out of the used protocol, nF server, port, context, servlet and existing parameters. * Builds a HTTP GET request URL out of the used protocol, nF server, port, context, servlet and existing parameters.
* *
* @param servlet One of the supported nF servlets RemoteOrder, RemoteStatus or RemoteImport. * @param servlet One of the supported nF servlets RemoteOrder, RemoteStatus or RemoteImport.
* @param parameters List of parameters to be send to the servlet. * @param parameters List of parameters to be send to the servlet.
* @return The built URL instance. * @return The built URL instance.
* @throws MalformedURLException You will get some of this, if you mess up any part of the URL. * @throws MalformedURLException You will get some of this, if you mess up any part of the URL.
*/ */
private URL buildURL(String servlet, List<String> parameters) throws MalformedURLException { private URL buildURL(String servlet, List<String> parameters) throws MalformedURLException {
String url = String.format("%s://%s:%s/%s/%s", protocol, server, port, context, servlet); String url = String.format("%s://%s:%s/%s/%s", protocol, server, port, context, servlet);
if (Objects.nonNull(parameters) && !parameters.isEmpty()) { if (Objects.nonNull(parameters) && !parameters.isEmpty()) {
url += "?" + String.join("&", parameters); url += "?" + String.join("&", parameters);
} }
return new URL(url); return new URL(url);
} }
/** /**
* Requests a response from the given URL. The response is expected to be returned as nF XML report. * Requests a response from the given URL. The response is expected to be returned as nF XML report.
* *
* @param url The URL of the nF servlet with all its necessary parameters. Read the nF handbook for more details * @param url The URL of the nF servlet with all its necessary parameters. Read the nF handbook for more details
* about the usage of nF servlets. * about the usage of nF servlets.
* @return The response is expected to be a XML report, which will then be returned as a String. * @return The response is expected to be a XML report, which will then be returned as a String.
* @throws IOException You will get some of this, if anything goes wrong with your data connection to your nF * @throws IOException You will get some of this, if anything goes wrong with your data connection to your nF
* instance. * instance.
*/ */
private String getResponse(URL url) throws UnsupportedEncodingException, IOException { private String getResponse(URL url) throws UnsupportedEncodingException, IOException {
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection(); HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.setRequestProperty("Accept-Charset", DEFAULT_CHARSET); httpConnection.setRequestProperty("Accept-Charset", DEFAULT_CHARSET);
return getResponse(httpConnection); return getResponse(httpConnection);
} }
/** /**
* Requests a response from the given HTTP connection. The response is expected to be a XML document. * Requests a response from the given HTTP connection. The response is expected to be a XML document.
* *
* @param httpConnection The HTTP Connection to the nF servlet. Read the nF handbook for more details about the usage * @param httpConnection The HTTP Connection to the nF servlet. Read the nF handbook for more details about the usage
* of the nF servlets. * of the nF servlets.
* @return The response is expected to be a XML report, which will then be returned as a String. * @return The response is expected to be a XML report, which will then be returned as a String.
* @throws UnsupportedEncodingException You will get some of this, if your connection uses wrong encodings. * @throws UnsupportedEncodingException You will get some of this, if your connection uses wrong encodings.
* @throws IOException You will get some of this, if anything goes wrong with your data connection to your nF * @throws IOException You will get some of this, if anything goes wrong with your data connection to your nF
* instance. * instance.
*/ */
private String getResponse(HttpURLConnection httpConnection) throws UnsupportedEncodingException, IOException { private String getResponse(HttpURLConnection httpConnection) throws UnsupportedEncodingException, IOException {
String xml = ""; String xml = "";
if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
String contentType = httpConnection.getHeaderField("Content-Type"); String contentType = httpConnection.getHeaderField("Content-Type");
String charset = null; String charset = null;
for (String param : contentType.replace(" ", "").split(";")) { for (String param : contentType.replace(" ", "").split(";")) {
if (param.startsWith("charset=")) { if (param.startsWith("charset=")) {
charset = param.split("=", 2)[1]; charset = param.split("=", 2)[1];
break; break;
} }
} }
InputStream response = httpConnection.getInputStream(); InputStream response = httpConnection.getInputStream();
if (Objects.nonNull(charset)) { if (Objects.nonNull(charset)) {
BufferedReader reader = new BufferedReader(new InputStreamReader(response, charset)); BufferedReader reader = new BufferedReader(new InputStreamReader(response, charset));
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
xml += line; xml += line;
} }
} }
response.close(); response.close();
} else { } else {
throw new IOException(); throw new IOException();
} }
return xml; return xml;
} }
/** /**
* If everything works as expected, then nF's servlets will respond with XML reports to your requests. The job id and * If everything works as expected, then nF's servlets will respond with XML reports to your requests. The job id and
* status will be extracted from the response string. The id of the passed job instance will be updated. The XML * status will be extracted from the response string. The id of the passed job instance will be updated. The XML
* reports have a certain structure. Read the nF manual for more information about the XML reports and have a look at * reports have a certain structure. Read the nF manual for more information about the XML reports and have a look at
* the DTD of the XML reports. * the DTD of the XML reports.
* *
* @param xml A XML string that is supposed to be a XML document. * @param xml A XML string that is supposed to be a XML document.
* @param An empty job status object about to be filled with the actual result. It will be UNKOWN if there was no XML * @param An empty job status object about to be filled with the actual result. It will be UNKOWN if there was no XML
* report in the response. * report in the response.
* @throws ParserConfigurationException Something went wrong. * @throws ParserConfigurationException Something went wrong.
* @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, FailedTransmissionException { 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);
ReportHandler handler = new ReportHandler(); ReportHandler handler = new ReportHandler();
parser.parse(new InputSource(reader), handler); parser.parse(new InputSource(reader), handler);
if (Objects.nonNull(handler.statusId)) { if (Objects.nonNull(handler.statusId)) {
job.setStatusForCode(handler.statusId); job.setStatusForCode(handler.statusId);
} }
if (Objects.nonNull(handler.jobId)) { if (Objects.nonNull(handler.jobId)) {
job.setId(handler.jobId); job.setId(handler.jobId);
} }
if (Objects.nonNull(handler.serviceException)) { if (Objects.nonNull(handler.serviceException)) {
throw new FailedTransmissionException(handler.serviceException); throw new FailedTransmissionException(handler.serviceException);
} }
} }
/** /**
* Downloads a (zipped) CityGML file from the nF server. The file will be the result of an export job. * Downloads a (zipped) CityGML file from the nF server. The file will be the result of an export job.
* *
* @param url The URL of the remote order servlet that points at the CityGML file on the nF server. * @param url The URL of the remote order servlet that points at the CityGML file on the nF server.
* @return Returns a handle to the downloaded (zipped) CityGML file. Will be null if no file has been downloaded (due * @return Returns a handle to the downloaded (zipped) CityGML file. Will be null if no file has been downloaded (due
* to errors). * to errors).
* @throws IOException Something went wrong with your data connection. Check your server and the port. * @throws IOException Something went wrong with your data connection. Check your server and the port.
*/ */
private File downloadFile(URL url) throws IOException { private File downloadFile(URL url) throws IOException {
File handle = null; File handle = null;
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection(); HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.setRequestProperty("Accept-Charset", DEFAULT_CHARSET); httpConnection.setRequestProperty("Accept-Charset", DEFAULT_CHARSET);
if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
String filename = "result.gml"; String filename = "result.gml";
String disposition = httpConnection.getHeaderField("Content-Disposition"); String disposition = httpConnection.getHeaderField("Content-Disposition");
if (disposition != null) { if (disposition != null) {
int index = disposition.indexOf("filename="); int index = disposition.indexOf("filename=");
if (index > -1) { if (index > -1) {
filename = disposition. filename = disposition.
substring(index + 9, disposition.length()). substring(index + 9, disposition.length()).
replaceAll("\"", ""). replaceAll("\"", "").
replaceAll(";", ""); replaceAll(";", "");
} }
} }
InputStream inputStream = httpConnection.getInputStream(); InputStream inputStream = httpConnection.getInputStream();
handle = Files.createTempDirectory("nfDownload").resolve(filename).toFile(); handle = Files.createTempDirectory("nfDownload").resolve(filename).toFile();
FileOutputStream outputStream = new FileOutputStream(handle); FileOutputStream outputStream = new FileOutputStream(handle);
int bytesRead = -1; int bytesRead = -1;
byte[] buffer = new byte[4096]; byte[] buffer = new byte[4096];
while ((bytesRead = inputStream.read(buffer)) != -1) { while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead); outputStream.write(buffer, 0, bytesRead);
} }
outputStream.close(); outputStream.close();
inputStream.close(); inputStream.close();
} else { } else {
throw new IOException(); throw new IOException();
} }
return handle; return handle;
} }
/** /**
* Sends an export job to the nF. This job will be enqueued in the queue of export jobs. * Sends an export job to the nF. This job will be enqueued in the queue of export jobs.
* *
* @param file The XML file which describes the nF export job. * @param file The XML file which describes the nF export job.
* @return Returns the status of the export job you just sent. * @return Returns the status of the export job you just sent.
* @throws InvalidJobDescriptorException * @throws InvalidJobDescriptorException
*/ */
@Override @Override
public void sendAndUpdateExportJob(AsyncExportJob 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());
try { try {
URL url = buildURL(REMOTE_ORDER_SERVLET, null); URL url = buildURL(REMOTE_ORDER_SERVLET, null);
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST"); connection.setRequestMethod("POST");
connection.setRequestProperty("Connection", "Keep-Alive"); connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setDoOutput(true); // Sends export job file connection.setDoOutput(true); // Sends export job file
connection.setDoInput(true); // Expects XML response connection.setDoInput(true); // Expects XML response
connection.connect(); connection.connect();
OutputStream os = connection.getOutputStream(); OutputStream os = connection.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, DEFAULT_CHARSET), true); PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, DEFAULT_CHARSET), true);
Files.copy(exportJobFile.toPath(), os); Files.copy(exportJobFile.toPath(), os);
os.flush(); os.flush();
writer.append(CRLF).flush(); writer.append(CRLF).flush();
getJobFromResponse(job, getResponse(connection)); getJobFromResponse(job, getResponse(connection));
if (job.getId() > 0) { if (job.getId() > 0) {
job.setStatus(JobStatus.SENT, Optional.empty()); job.setStatus(JobStatus.SENT, Optional.empty());
} else { } else {
throw new FailedTransmissionException("Job didn't receive an id from the nF server."); 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());
} catch (IOException ex) { } catch (IOException ex) {
job.getStatus().setMessage(IO_EXCEPTION_OCCURRED); job.getStatus().setMessage(IO_EXCEPTION_OCCURRED);
throw new FailedTransmissionException(ex.getMessage()); throw new FailedTransmissionException(ex.getMessage());
} catch (ParserConfigurationException | SAXException ex) { } catch (ParserConfigurationException | SAXException ex) {
job.getStatus().setMessage(XML_PARSER_ERROR); job.getStatus().setMessage(XML_PARSER_ERROR);
throw new FailedTransmissionException(ex.getMessage()); throw new FailedTransmissionException(ex.getMessage());
} }
} }
/** /**
* Sends an import job to nF. The given import job file has to be a zip file and has to obey the internal structure * Sends an import job to nF. The given import job file has to be a zip file and has to obey the internal structure
* defined in the nF manuals. Short description: * defined in the nF manuals. Short description:
* *
* Import jobs enable you to add, alter and delete CityGML top level objects (like buildings) in the nF. Every import * Import jobs enable you to add, alter and delete CityGML top level objects (like buildings) in the nF. Every import
* job file has to contain a start file. Start files control the import process through their file names and their * job file has to contain a start file. Start files control the import process through their file names and their
* contents. The name of a start file has the following structure * contents. The name of a start file has the following structure
* *
* <Product>_<Tile>.start * <Product>_<Tile>.start
* *
* The start file should contain the level to which your manipulated CityGML should be imported. Your CityGML file * The start file should contain the level to which your manipulated CityGML should be imported. Your CityGML file
* and the ZIP archive has to be named after the following naming convention: * and the ZIP archive has to be named after the following naming convention:
* *
* <Product>_<Tile>_<Level>_<Operation>.gml * <Product>_<Tile>_<Level>_<Operation>.gml
* *
* Here operation can be ... - 'REP': Replaces whole existing buildings only, - 'REPUPD': Replaces whole existing * Here operation can be ... - 'REP': Replaces whole existing buildings only, - 'REPUPD': Replaces whole existing
* buildings and adds new buildings, - 'UPD': Update, same as REP, - 'CHG': Change, same as REPUPD, - 'DEL': Deletes * buildings and adds new buildings, - 'UPD': Update, same as REP, - 'CHG': Change, same as REPUPD, - 'DEL': Deletes
* the geometry of a particular LOD, - 'DELALL': Deletes a whole building * the geometry of a particular LOD, - 'DELALL': Deletes a whole building
* *
* @param file The nF import job as a prepared ZIP file. It has to contain the CityGML file and the start file. * @param file The nF import job as a prepared ZIP file. It has to contain the CityGML file and the start file.
* @return Returns the job status of your import job. * @return Returns the job status of your import job.
* @throws FailedTransmissionException * @throws FailedTransmissionException
*/ */
@Override @Override
public void sendAndUpdateImportJob(AsyncImportJob job) public void sendAndUpdateImportJob(AsyncImportJob job)
throws InvalidJobDescriptorException, FailedTransmissionException { throws InvalidJobDescriptorException, FailedTransmissionException {
if (job.getStatus() != JobStatus.LOCAL) { if (job.getStatus() != JobStatus.LOCAL) {
throw new FailedTransmissionException("Job cannot be sent twice!"); 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(); 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", product), // 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();
connection.setRequestMethod("POST"); connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/zip"); connection.setRequestProperty("Content-Type", "application/zip");
connection.setRequestProperty("Accept", "*/*"); connection.setRequestProperty("Accept", "*/*");
connection.setRequestProperty("", ""); connection.setRequestProperty("", "");
connection.setDoOutput(true); // Sends export job file connection.setDoOutput(true); // Sends export job file
connection.setDoInput(true); // Expects XML response connection.setDoInput(true); // Expects XML response
connection.connect(); connection.connect();
OutputStream os = connection.getOutputStream(); OutputStream os = connection.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, DEFAULT_CHARSET), true); PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, DEFAULT_CHARSET), true);
Files.copy(importJobFile.toPath(), os); Files.copy(importJobFile.toPath(), os);
os.flush(); os.flush();
writer.append(CRLF).flush(); writer.append(CRLF).flush();
getJobFromResponse(job, getResponse(connection)); getJobFromResponse(job, getResponse(connection));
if (job.getId() > 0) { if (job.getId() > 0) {
job.setStatus(JobStatus.SENT, Optional.empty()); job.setStatus(JobStatus.SENT, Optional.empty());
} else { } else {
throw new FailedTransmissionException("Job didn't receive an id from the nF server."); 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());
} catch (IOException ex) { } catch (IOException ex) {
job.getStatus().setMessage(IO_EXCEPTION_OCCURRED); job.getStatus().setMessage(IO_EXCEPTION_OCCURRED);
throw new FailedTransmissionException(ex.getMessage()); throw new FailedTransmissionException(ex.getMessage());
} catch (SAXException | ParserConfigurationException ex) { } catch (SAXException | ParserConfigurationException ex) {
job.getStatus().setMessage(XML_PARSER_ERROR); job.getStatus().setMessage(XML_PARSER_ERROR);
throw new FailedTransmissionException(ex.getMessage()); throw new FailedTransmissionException(ex.getMessage());
} }
} }
} }
\ No newline at end of file
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