Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Eric Duminil
RegionChooser
Commits
5a0bec3d
Commit
5a0bec3d
authored
Dec 04, 2017
by
eric.duminil
Browse files
Removing CRLF from repository.
parent
3f54801c
Changes
37
Hide whitespace changes
Inline
Side-by-side
deploy.xml
View file @
5a0bec3d
<?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
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".
-->
<project
default=
"deploy"
name=
"RegionChooser"
basedir=
".."
>
<description>
Create a Jar file with RegionChooser libraries and executables
</description>
<property
name=
"target.path"
value=
"lib/region-chooser"
/>
<property
name=
"projects"
value=
"RegionChooser,GeoLibs"
/>
<import
file=
"../SimStadt/deploy-common.xml"
/>
<target
name=
"deploy"
depends=
"unit-test"
>
<echo
file=
"${deploy.dir}/RegionChooser.bat"
>
java -classpath lib/* -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX
pause > nul
</echo>
<echo
file=
"${deploy.dir}/RegionChooser.sh"
>
java -classpath 'lib/*' -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX
</echo>
<chmod
file=
"${deploy.dir}/RegionChooser.sh"
perm=
"u+x"
/>
<echo
file=
"${deploy.dir}/RegionChooser.command"
>
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
</echo>
<chmod
file=
"${deploy.dir}/RegionChooser.command"
perm=
"u+x"
/>
</target>
<target
name=
"unit-test"
depends=
"test-common"
unless=
"doNotTest"
>
<jacoco:coverage
destfile=
"${reports.dir}/${ant.project.name}.exec"
xmlns:jacoco=
"antlib:org.jacoco.ant"
>
<junit
printsummary=
"yes"
haltonfailure=
"yes"
fork=
"true"
>
<classpath
refid=
"test-classpath"
/>
<formatter
type=
"xml"
usefile=
"true"
/>
<formatter
type=
"plain"
usefile=
"true"
/>
<!-- RegionExtractor -->
<test
name=
"eu.simstadt.regionchooser.RegionExtractorTests"
haltonfailure=
"no"
todir=
"${reports.dir}"
/>
</junit>
</jacoco:coverage>
</target>
<?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
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".
-->
<project
default=
"deploy"
name=
"RegionChooser"
basedir=
".."
>
<description>
Create a Jar file with RegionChooser libraries and executables
</description>
<property
name=
"target.path"
value=
"lib/region-chooser"
/>
<property
name=
"projects"
value=
"RegionChooser,GeoLibs"
/>
<import
file=
"../SimStadt/deploy-common.xml"
/>
<target
name=
"deploy"
depends=
"unit-test"
>
<echo
file=
"${deploy.dir}/RegionChooser.bat"
>
java -classpath lib/* -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX
pause > nul
</echo>
<echo
file=
"${deploy.dir}/RegionChooser.sh"
>
java -classpath 'lib/*' -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX
</echo>
<chmod
file=
"${deploy.dir}/RegionChooser.sh"
perm=
"u+x"
/>
<echo
file=
"${deploy.dir}/RegionChooser.command"
>
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
</echo>
<chmod
file=
"${deploy.dir}/RegionChooser.command"
perm=
"u+x"
/>
</target>
<target
name=
"unit-test"
depends=
"test-common"
unless=
"doNotTest"
>
<jacoco:coverage
destfile=
"${reports.dir}/${ant.project.name}.exec"
xmlns:jacoco=
"antlib:org.jacoco.ant"
>
<junit
printsummary=
"yes"
haltonfailure=
"yes"
fork=
"true"
>
<classpath
refid=
"test-classpath"
/>
<formatter
type=
"xml"
usefile=
"true"
/>
<formatter
type=
"plain"
usefile=
"true"
/>
<!-- RegionExtractor -->
<test
name=
"eu.simstadt.regionchooser.RegionExtractorTests"
haltonfailure=
"no"
todir=
"${reports.dir}"
/>
</junit>
</jacoco:coverage>
</target>
</project>
\ No newline at end of file
src/eu/simstadt/nf4j/Connector.java
View file @
5a0bec3d
package
eu.simstadt.nf4j
;
import
java.io.File
;
/**
* NFConnector lets you communicate with your novaFACTORY (nF) server instance.
*
* @param <I> The import job implementation which can be handled by the connector.
* @param <E> The export job implementation which can be handled by the connector.
*
* @author Marcel Bruse
*/
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
* 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
* implement different versions of this NFConnector interface while keeping old implementations due to
* backward compatibility.
*
* @return The supported version of the novaFACTORY.
*/
public
String
supportsNFVersion
();
/**
* 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 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.
*
* @param job The nF export job with description. If the job description is invalid, you will receive an
* InvalidJobDescriptorException.
*/
public
void
sendAndUpdateExportJob
(
E
exportJob
)
throws
InvalidJobDescriptorException
,
FailedTransmissionException
;
/**
* 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
* 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 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
*
* <Product>_<Tile>.start
*
* 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:
*
* <Product>_<Tile>_<Level>_<Operation>.gml
*
* 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 the geometry of a particular LOD,
* - 'DELALL': Deletes a whole building
*
* @param job The nF import job to be sent. It has to contain a valid description.
*/
public
void
sendAndUpdateImportJob
(
I
importJob
)
throws
InvalidJobDescriptorException
,
FailedTransmissionException
;
/**
* 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.
* @return The status of any existing export nF job.
*/
public
E
requestExportJob
(
int
jobId
)
throws
FailedTransmissionException
;
/**
* 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.
* @return The status of any existing import nF job.
*/
public
I
requestImportJob
(
int
jobId
)
throws
FailedTransmissionException
;
/**
* 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.
* @return A file handle to the result of the nF export job.
*/
public
File
requestExportJobResult
(
E
exportJob
)
throws
FailedTransmissionException
;
}
package
eu.simstadt.nf4j
;
import
java.io.File
;
/**
* NFConnector lets you communicate with your novaFACTORY (nF) server instance.
*
* @param <I> The import job implementation which can be handled by the connector.
* @param <E> The export job implementation which can be handled by the connector.
*
* @author Marcel Bruse
*/
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
* 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
* implement different versions of this NFConnector interface while keeping old implementations due to
* backward compatibility.
*
* @return The supported version of the novaFACTORY.
*/
public
String
supportsNFVersion
();
/**
* 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 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.
*
* @param job The nF export job with description. If the job description is invalid, you will receive an
* InvalidJobDescriptorException.
*/
public
void
sendAndUpdateExportJob
(
E
exportJob
)
throws
InvalidJobDescriptorException
,
FailedTransmissionException
;
/**
* 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
* 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 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
*
* <Product>_<Tile>.start
*
* 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:
*
* <Product>_<Tile>_<Level>_<Operation>.gml
*
* 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 the geometry of a particular LOD,
* - 'DELALL': Deletes a whole building
*
* @param job The nF import job to be sent. It has to contain a valid description.
*/
public
void
sendAndUpdateImportJob
(
I
importJob
)
throws
InvalidJobDescriptorException
,
FailedTransmissionException
;
/**
* 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.
* @return The status of any existing export nF job.
*/
public
E
requestExportJob
(
int
jobId
)
throws
FailedTransmissionException
;
/**
* 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.
* @return The status of any existing import nF job.
*/
public
I
requestImportJob
(
int
jobId
)
throws
FailedTransmissionException
;
/**
* 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.
* @return A file handle to the result of the nF export job.
*/
public
File
requestExportJobResult
(
E
exportJob
)
throws
FailedTransmissionException
;
}
src/eu/simstadt/nf4j/ExportJob.java
View file @
5a0bec3d
package
eu.simstadt.nf4j
;
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.
* Every export job has to have a valid job descriptor and/or a job id.
*
* @author Marcel Bruse
*
* @param <D> The descriptor type for the export job implementation.
*/
public
abstract
class
ExportJob
<
D
extends
ExportJobDescriptor
>
extends
Job
{
/** Every job should have a (valid) job descriptor. */
protected
D
descriptor
;
/**
* This constructor forces the job to have a description and a connector instance.
*
* @param connector The job will use this connector to synchronize itself with the nF.
* @param descriptor The description of this job.
*/
public
ExportJob
(
D
descriptor
,
Connector
<?,
?>
connector
)
{
this
.
descriptor
=
descriptor
;
this
.
connector
=
connector
;
}
/**
* This constructor forces the job to have a id and a connector instance.
*
* @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status
* will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then
* @param connector The job will use this connector to synchronize itself with the nF.
*/
public
ExportJob
(
int
id
,
Connector
<?,
?>
connector
)
{
this
.
id
=
id
;
this
.
connector
=
connector
;
}
/**
* @return Returns the description of this job.
*/
public
D
getDescriptor
()
{
return
descriptor
;
}
/**
* 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.
* @throws FailedTransmissionException There could be a problem while accessing or downloading the file.
*/
public
abstract
File
getResult
()
throws
FailedTransmissionException
;
}
package
eu.simstadt.nf4j
;
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.
* Every export job has to have a valid job descriptor and/or a job id.
*
* @author Marcel Bruse
*
* @param <D> The descriptor type for the export job implementation.
*/
public
abstract
class
ExportJob
<
D
extends
ExportJobDescriptor
>
extends
Job
{
/** Every job should have a (valid) job descriptor. */
protected
D
descriptor
;
/**
* This constructor forces the job to have a description and a connector instance.
*
* @param connector The job will use this connector to synchronize itself with the nF.
* @param descriptor The description of this job.
*/
public
ExportJob
(
D
descriptor
,
Connector
<?,
?>
connector
)
{
this
.
descriptor
=
descriptor
;
this
.
connector
=
connector
;
}
/**
* This constructor forces the job to have a id and a connector instance.
*
* @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status
* will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then
* @param connector The job will use this connector to synchronize itself with the nF.
*/
public
ExportJob
(
int
id
,
Connector
<?,
?>
connector
)
{
this
.
id
=
id
;
this
.
connector
=
connector
;
}
/**
* @return Returns the description of this job.
*/
public
D
getDescriptor
()
{
return
descriptor
;
}
/**
* 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.
* @throws FailedTransmissionException There could be a problem while accessing or downloading the file.
*/
public
abstract
File
getResult
()
throws
FailedTransmissionException
;
}
src/eu/simstadt/nf4j/ExportJobDescriptor.java
View file @
5a0bec3d
package
eu.simstadt.nf4j
;
/**
* Implementations of this interface are known to be export job descriptions.
*
* @author Marcel Bruse
*/
public
interface
ExportJobDescriptor
extends
JobDescriptor
{}
package
eu.simstadt.nf4j
;
/**
* Implementations of this interface are known to be export job descriptions.
*
* @author Marcel Bruse
*/
public
interface
ExportJobDescriptor
extends
JobDescriptor
{}
src/eu/simstadt/nf4j/ExportJobFromJavaFXRegionChooser.java
View file @
5a0bec3d
package
eu.simstadt.nf4j
;
import
java.io.File
;
import
java.util.ArrayList
;
import
javafx.application.Platform
;
import
netscape.javascript.JSObject
;
import
com.vividsolutions.jts.geom.Coordinate
;
import
com.vividsolutions.jts.geom.Geometry
;
import
eu.simstadt.nf4j.async.AsyncExportJob
;
import
eu.simstadt.nf4j.async.Coord
;
import
eu.simstadt.nf4j.async.ExportJobDescription
;
import
eu.simstadt.nf4j.async.HTTPConnection
;
import
eu.simstadt.nf4j.async.JobStatusEvent
;
import
eu.simstadt.nf4j.async.JobStatusListener
;
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
* of the subsequent status' LOCAL, SENT, PENDING, RUNNING, FINISHED and DOWNLOAD have to be signaled to this test
* class.
*
* @author Marcel Bruse
*/
//TODO: DRY with SuccessfulExportJob
public
class
ExportJobFromJavaFXRegionChooser
implements
JobStatusListener
{
public
AsyncExportJob
job
;
private
JSObject
novaFactoryOpenLayer
;
public
void
processJob
(
Geometry
poly
,
String
productName
,
JSObject
novaFactoryOpenLayer
)
throws
InterruptedException
{
this
.
novaFactoryOpenLayer
=
novaFactoryOpenLayer
;
ExportJobDescription
description
=
ExportJobDescription
.
getDefaultDescriptor
();
description
.
setInitiator
(
String
.
valueOf
((
int
)
(
Math
.
random
()
*
9999
)));
String
userName
=
System
.
getProperty
(
"user.name"
);
description
.
setAccount
(
userName
);
// description.setProduct("WU3");
description
.
setProduct
(
productName
);
description
.
setJobnumber
(
userName
);
ArrayList
<
Coord
>
regionPolygon
=
new
ArrayList
<>();
for
(
Coordinate
point
:
poly
.
getCoordinates
())
{
regionPolygon
.
add
(
new
Coord
(
point
.
y
,
point
.
x
));
}
description
.
setRegionPolygon
(
regionPolygon
);
Layer
layer
=
Layer
.
getDefaultLayer
();
layer
.
setProduct
(
productName
);
layer
.
setName
(
"GML"
);
description
.
addLayer
(
layer
);
job
=
new
AsyncExportJob
(
description
,
new
HTTPConnection
(
"193.196.136.164"
));
job
.
addJobStatusListener
(
this
);
try
{
job
.
send
();
}
catch
(
FailedTransmissionException
ex
)
{
ex
.
printStackTrace
();
}
catch
(
InvalidJobDescriptorException
ex
)
{
ex
.
printStackTrace
();
}
}
@Override
public
void
jobStatusChanged
(
JobStatusEvent
event
)
{
JobStatus
status
=
(
JobStatus
)
event
.
getSource
();
// System.out.println(status);
if
(
status
==
JobStatus
.
LOCAL
)
{
novaFactoryOpenLayer
.
call
(
"updateStatus"
,
"REQUEST HAS BEEN PREPARED"
);
}
else
if
(
status
==
JobStatus
.
SENT
)
{
novaFactoryOpenLayer
.
call
(
"updateStatus"
,
"REQUEST HAS BEEN SENT"
);
}
else
if
(
status
==
JobStatus
.
PENDING
)
{
novaFactoryOpenLayer
.
call
(
"updateStatus"
,
"PENDING"
);
}
else
if
(
status
==
JobStatus
.
RUNNING
)
{
novaFactoryOpenLayer
.
call
(
"updateStatus"
,
"SERVER IS BUSY"
);
}
else
if
(
status
==
JobStatus
.
FINISHED
)
{
try
{
novaFactoryOpenLayer
.
call
(
"updateStatus"
,
"SERVER IS DONE"
);
job
.
downloadResult
();
}
catch
(
FailedTransmissionException
ex
)
{
ex
.
printStackTrace
();
}
}
else
if
(
status
==
JobStatus
.
DOWNLOAD
)
{
try
{
File
file
=
job
.
getResult
();
novaFactoryOpenLayer
.
call
(
"updateStatus"
,
"DOWNLOADED AS ZIP"
);
//TODO: Call downloadFinished if FAILED
Platform
.
runLater
(
new
Runnable
()
{
@Override
public
void
run
()
{
novaFactoryOpenLayer
.
call
(
"selectSaveFile"
,
file
.
toString
());
}
});
novaFactoryOpenLayer
.
call
(
"downloadFinished"
);
}
catch
(
FailedTransmissionException
ex
)
{
ex
.
printStackTrace
();
}
}
}
package
eu.simstadt.nf4j
;
import
java.io.File
;
import
java.util.ArrayList
;
import
javafx.application.Platform
;
import
netscape.javascript.JSObject
;
import
com.vividsolutions.jts.geom.Coordinate
;
import
com.vividsolutions.jts.geom.Geometry
;
import
eu.simstadt.nf4j.async.AsyncExportJob
;
import
eu.simstadt.nf4j.async.Coord
;
import
eu.simstadt.nf4j.async.ExportJobDescription
;
import
eu.simstadt.nf4j.async.HTTPConnection
;
import
eu.simstadt.nf4j.async.JobStatusEvent
;
import
eu.simstadt.nf4j.async.JobStatusListener
;
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
* of the subsequent status' LOCAL, SENT, PENDING, RUNNING, FINISHED and DOWNLOAD have to be signaled to this test
* class.
*
* @author Marcel Bruse
*/
//TODO: DRY with SuccessfulExportJob
public
class
ExportJobFromJavaFXRegionChooser
implements
JobStatusListener
{
public
AsyncExportJob
job
;
private
JSObject
novaFactoryOpenLayer
;
public
void
processJob
(
Geometry
poly
,
String
productName
,
JSObject
novaFactoryOpenLayer
)
throws
InterruptedException
{
this
.
novaFactoryOpenLayer
=
novaFactoryOpenLayer
;
ExportJobDescription
description
=
ExportJobDescription
.
getDefaultDescriptor
();
description
.
setInitiator
(
String
.
valueOf
((
int
)
(
Math
.
random
()
*
9999
)));
String
userName
=
System
.
getProperty
(
"user.name"
);
description
.
setAccount
(
userName
);
// description.setProduct("WU3");
description
.
setProduct
(
productName
);
description
.
setJobnumber
(
userName
);
ArrayList
<
Coord
>
regionPolygon
=
new
ArrayList
<>();
for
(
Coordinate
point
:
poly
.
getCoordinates
())
{
regionPolygon
.
add
(
new
Coord
(
point
.
y
,
point
.
x
));
}
description
.
setRegionPolygon
(
regionPolygon
);
Layer
layer
=
Layer
.
getDefaultLayer
();
layer
.
setProduct
(
productName
);
layer
.
setName
(
"GML"
);
description
.
addLayer
(
layer
);
job
=
new
AsyncExportJob
(
description
,
new
HTTPConnection
(
"193.196.136.164"
));
job
.
addJobStatusListener
(
this
);
try
{
job
.
send
();
}
catch
(
FailedTransmissionException
ex
)
{
ex
.
printStackTrace
();
}
catch
(
InvalidJobDescriptorException
ex
)
{
ex
.
printStackTrace
();
}
}
@Override
public
void
jobStatusChanged
(
JobStatusEvent
event
)
{
JobStatus
status
=
(
JobStatus
)
event
.
getSource
();
// System.out.println(status);
if
(
status
==
JobStatus
.
LOCAL
)
{
novaFactoryOpenLayer
.
call
(
"updateStatus"
,
"REQUEST HAS BEEN PREPARED"
);
}
else
if
(
status
==
JobStatus
.
SENT
)
{
novaFactoryOpenLayer
.
call
(
"updateStatus"
,
"REQUEST HAS BEEN SENT"
);
}
else
if
(
status
==
JobStatus
.
PENDING
)
{
novaFactoryOpenLayer
.
call
(
"updateStatus"
,
"PENDING"
);
}
else
if
(
status
==
JobStatus
.
RUNNING
)
{
novaFactoryOpenLayer
.
call
(
"updateStatus"
,
"SERVER IS BUSY"
);
}
else
if
(
status
==
JobStatus
.
FINISHED
)
{
try
{
novaFactoryOpenLayer
.
call
(
"updateStatus"
,
"SERVER IS DONE"
);
job
.
downloadResult
();
}
catch
(
FailedTransmissionException
ex
)
{
ex
.
printStackTrace
();
}
}
else
if
(
status
==
JobStatus
.
DOWNLOAD
)
{
try
{
File
file
=
job
.
getResult
();
novaFactoryOpenLayer
.
call
(
"updateStatus"
,
"DOWNLOADED AS ZIP"
);
//TODO: Call downloadFinished if FAILED
Platform
.
runLater
(
new
Runnable
()
{
@Override
public
void
run
()
{
novaFactoryOpenLayer
.
call
(
"selectSaveFile"
,
file
.
toString
());
}
});
novaFactoryOpenLayer
.
call
(
"downloadFinished"
);
}
catch
(
FailedTransmissionException
ex
)
{
ex
.
printStackTrace
();
}
}
}
}
\ No newline at end of file
src/eu/simstadt/nf4j/FailedTransmissionException.java
View file @
5a0bec3d
package
eu.simstadt.nf4j
;
/**
* This exception may be thrown by classes of the nf4j package if/on ...
*
* - the connector is null
* - malformed URLs
* - missing or malformed XML reports
* - HTTP failures
*
* @author Marcel Bruse
*/
public
class
FailedTransmissionException
extends
Exception
{
private
static
final
long
serialVersionUID
=
-
3530932388888249528L
;
/** An textual description of the error. */
private
String
message
;
/** Standard constructor. */
public
FailedTransmissionException
()
{}
/**
* Constructor with error message and without nested cause.
*
* @param message The error message.
*/
public
FailedTransmissionException
(
String
message
)
{
this
(
message
,
null
);
}
/**
* Constructor with cause and without error message.
*
* @param cause The nested cause of this exception.
*/
public
FailedTransmissionException
(
Throwable
cause
)
{
this
(
null
,
cause
);
}
/**
* Constructor with error message and nested cause.
*
* @param message The error message.
* @param cause The nested cause of this exception.
*/
public
FailedTransmissionException
(
String
message
,
Throwable
cause
)
{
this
.
message
=
message
;
initCause
(
cause
);
}
/**
* @return Returns the error message, if present.
*/
@Override
public
String
getMessage
()
{
return
message
;
}
}
package
eu.simstadt.nf4j
;
/**
* This exception may be thrown by classes of the nf4j package if/on ...
*
* - the connector is null
* - malformed URLs
* - missing or malformed XML reports
* - HTTP failures
*
* @author Marcel Bruse
*/
public
class
FailedTransmissionException
extends
Exception
{
private
static
final
long
serialVersionUID
=
-
3530932388888249528L
;
/** An textual description of the error. */
private
String
message
;
/** Standard constructor. */
public
FailedTransmissionException
()
{}
/**
* Constructor with error message and without nested cause.
*
* @param message The error message.
*/
public
FailedTransmissionException
(
String
message
)
{
this
(
message
,
null
);
}
/**
* Constructor with cause and without error message.
*
* @param cause The nested cause of this exception.
*/
public
FailedTransmissionException
(
Throwable
cause
)
{
this
(
null
,
cause
);
}
/**
* Constructor with error message and nested cause.
*
* @param message The error message.
* @param cause The nested cause of this exception.
*/
public
FailedTransmissionException
(
String
message
,
Throwable
cause
)
{
this
.
message
=
message
;
initCause
(
cause
);
}
/**
* @return Returns the error message, if present.
*/
@Override
public
String
getMessage
()
{
return
message
;
}
}
src/eu/simstadt/nf4j/ImportJob.java
View file @
5a0bec3d
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.
* Every import job has to have a valid job descriptor and/or a job id.
*
* @author Marcel Bruse
*
* @param <D> The descriptor type for the import job implementation.
*/
public
abstract
class
ImportJob
<
D
extends
ImportJobDescriptor
>
extends
Job
{
/** Every job should have a (valid) job descriptor. */
protected
D
descriptor
;
/**
* This constructor forces the job to have a description and a connector instance.
*
* @param connector The job will use this connector to synchronize itself with the nF.
* @param descriptor The description of this job.
*/
public
ImportJob
(
D
descriptor
,
Connector
<?,
?>
connector
)
{
this
.
descriptor
=
descriptor
;
this
.
connector
=
connector
;
}
/**
* This constructor forces the job to have a id and a connector instance.
*
* @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status
* will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then
* @param connector The job will use this connector to synchronize itself with the nF.
*/
public
ImportJob
(
int
id
,
Connector
<?,
?>
connector
)
{
this
.
id
=
id
;
this
.
connector
=
connector
;
}
/**
* @return Returns the description of this job.
*/
public
D
getDescriptor
()
{
return
descriptor
;
}
}
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.
* Every import job has to have a valid job descriptor and/or a job id.
*
* @author Marcel Bruse
*
* @param <D> The descriptor type for the import job implementation.
*/
public
abstract
class
ImportJob
<
D
extends
ImportJobDescriptor
>
extends
Job
{
/** Every job should have a (valid) job descriptor. */
protected
D
descriptor
;
/**
* This constructor forces the job to have a description and a connector instance.
*
* @param connector The job will use this connector to synchronize itself with the nF.
* @param descriptor The description of this job.
*/
public
ImportJob
(
D
descriptor
,
Connector
<?,
?>
connector
)
{
this
.
descriptor
=
descriptor
;
this
.
connector
=
connector
;
}
/**
* This constructor forces the job to have a id and a connector instance.
*
* @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status
* will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then
* @param connector The job will use this connector to synchronize itself with the nF.
*/
public
ImportJob
(
int
id
,
Connector
<?,
?>
connector
)
{
this
.
id
=
id
;
this
.
connector
=
connector
;
}
/**
* @return Returns the description of this job.
*/
public
D
getDescriptor
()
{
return
descriptor
;
}
}
src/eu/simstadt/nf4j/ImportJobDescriptor.java
View file @
5a0bec3d
package
eu.simstadt.nf4j
;
import
java.io.File
;
/**
* Implementations of this interface are known to implement import job descriptions.
*
* @author Marcel Bruse
*/
public
interface
ImportJobDescriptor
extends
JobDescriptor
{
/**
* 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.
*/
public
void
setCityGMLFile
(
File
file
);
/**
* @return Returns the CityGML file which is supposed to be imported by the nF.
*/
public
File
getCityGMLFile
();
}
package
eu.simstadt.nf4j
;
import
java.io.File
;
/**
* Implementations of this interface are known to implement import job descriptions.
*
* @author Marcel Bruse
*/
public
interface
ImportJobDescriptor
extends
JobDescriptor
{
/**
* 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.
*/
public
void
setCityGMLFile
(
File
file
);
/**
* @return Returns the CityGML file which is supposed to be imported by the nF.
*/
public
File
getCityGMLFile
();
}
src/eu/simstadt/nf4j/InvalidJobDescriptorException.java
View file @
5a0bec3d
package
eu.simstadt.nf4j
;
/**
* If your export and import job descriptions are invalid due to the job.isValid() method, then it is very likely
* that you will get this exception.
*
* @author Marcel Bruse
*/
public
class
InvalidJobDescriptorException
extends
Exception
{
private
static
final
long
serialVersionUID
=
2710340003578550634L
;
}
package
eu.simstadt.nf4j
;
/**
* If your export and import job descriptions are invalid due to the job.isValid() method, then it is very likely
* that you will get this exception.
*
* @author Marcel Bruse
*/
public
class
InvalidJobDescriptorException
extends
Exception
{
private
static
final
long
serialVersionUID
=
2710340003578550634L
;
}
src/eu/simstadt/nf4j/Job.java
View file @
5a0bec3d
package
eu.simstadt.nf4j
;
/**
* This job class bundles the three attributes of every nF job: Id, status and last job related nF (error) message.
*
* @author Marcel Bruse
*/
public
abstract
class
Job
{
/** The status of the job. There are different states for export and import jobs. */
protected
JobStatus
status
;
/** The id of the job. */
protected
int
id
;
/** The connection to the nF. */
protected
Connector
<?,
?>
connector
;
/**
* Every job has a status. This method returns it. Look up the different possible values in the JobStatus
* enumeration.
*
* @return Returns the status of this job.
*/
public
JobStatus
getStatus
()
{
return
status
;
}
/**
* Lets you set the status of this job.
*
* @param status The status of this job.
*/
protected
void
setStatus
(
JobStatus
status
)
{
this
.
status
=
status
;
}
/**
* @return Returns the id of this job.
*/
public
int
getId
()
{
return
id
;
}
/**
* Sets the id of this job.
*
* @param jobId The job id about to be set.
*/
public
void
setId
(
int
jobId
)
{
this
.
id
=
jobId
;
}
/**
* @return Returns the nF connector of this job.
*/
public
Connector
<?,
?>
getConnector
()
{
return
connector
;
}
/**
* Sets the nF connector of this job.
*
* @param nFConnector The connector of this job.
*/
public
void
setConnector
(
Connector
<?,
?>
connector
)
{
this
.
connector
=
connector
;
}
/**
* 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
* connector in order to reuse them here.
*
* 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
* 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.
*/
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
* 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.
*/
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
* to you in http responses. Export jobs and import jobs have different states. Consider this fact
* in your implementation.
*
* @param statusCode The nF status code for this job.
*/
public
abstract
void
setStatusForCode
(
int
statusCode
);
}
package
eu.simstadt.nf4j
;
/**
* This job class bundles the three attributes of every nF job: Id, status and last job related nF (error) message.
*
* @author Marcel Bruse
*/
public
abstract
class
Job
{
/** The status of the job. There are different states for export and import jobs. */
protected
JobStatus
status
;
/** The id of the job. */
protected
int
id
;
/** The connection to the nF. */
protected
Connector
<?,
?>
connector
;
/**
* Every job has a status. This method returns it. Look up the different possible values in the JobStatus
* enumeration.
*
* @return Returns the status of this job.
*/
public
JobStatus
getStatus
()
{
return
status
;
}
/**
* Lets you set the status of this job.
*
* @param status The status of this job.
*/
protected
void
setStatus
(
JobStatus
status
)
{
this
.
status
=
status
;
}
/**
* @return Returns the id of this job.
*/
public
int
getId
()
{
return
id
;
}
/**
* Sets the id of this job.
*
* @param jobId The job id about to be set.
*/
public
void
setId
(
int
jobId
)
{
this
.
id
=
jobId
;
}
/**
* @return Returns the nF connector of this job.
*/
public
Connector
<?,
?>
getConnector
()
{
return
connector
;
}
/**
* Sets the nF connector of this job.
*
* @param nFConnector The connector of this job.
*/
public
void
setConnector
(
Connector
<?,
?>
connector
)
{
this
.
connector
=
connector
;
}
/**
* 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
* connector in order to reuse them here.
*
* 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
* 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.
*/
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
* 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.
*/
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
* to you in http responses. Export jobs and import jobs have different states. Consider this fact
* in your implementation.
*
* @param statusCode The nF status code for this job.
*/
public
abstract
void
setStatusForCode
(
int
statusCode
);
}
src/eu/simstadt/nf4j/JobDescriptor.java
View file @
5a0bec3d
package
eu.simstadt.nf4j
;
/**
* 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.
*
* @author Marcel Bruse
*/
public
interface
JobDescriptor
{
/**
* @return The supported version of the XML import or export job format.
*/
public
String
supportsJobVersion
();
/**
* @return Returns true, if the job description is complete and correct. False, otherwise.
*/
public
boolean
isValid
();
}
package
eu.simstadt.nf4j
;
/**
* 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.
*
* @author Marcel Bruse
*/
public
interface
JobDescriptor
{
/**
* @return The supported version of the XML import or export job format.
*/
public
String
supportsJobVersion
();
/**
* @return Returns true, if the job description is complete and correct. False, otherwise.
*/
public
boolean
isValid
();
}
src/eu/simstadt/nf4j/JobStatus.java
View file @
5a0bec3d
package
eu.simstadt.nf4j
;
/**
* The list of all possible export and import job states.
*
* @author Marcel Bruse
*/
public
enum
JobStatus
{
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.
SENT
(
20
),
// Job has been sent or it is assumed that it has been set before.
PENDING
(
30
),
// Export job has been enqueued and waits for execution
READY_TO_RUN
(
31
),
// Same as PENDING, but for import jobs
APPROVE
(
32
),
// (?) For import jobs only. Read the nF documentation, seams to be never used.
RUNNING
(
40
),
// Job is running
APPROVE_RUNNING
(
41
),
// (?) For import jobs only. Read the nF documentation, seams to be never used.
FAILED
(
50
),
// Export job failed
ERROR
(
51
),
// Same as FAILED, but for import jobs
WARNING
(
52
),
// For import jobs only. There has been a minor problem
REJECT
(
53
),
// (?) For import jobs only. Read the nF documentation, seams to be never used.
REJECT_RUNNING
(
54
),
// (?) For import jobs only. Read the nF documentation, seams to be never used.
APPROVE_REJECT_ERROR
(
55
),
// (?) For import jobs only. Read the nF documentation, seams to be never used.
APPROVE_REJECT_OK
(
56
),
// (?) For import jobs only. Read the nF documentation, seams to be never used.
IMPORTED_WARNING
(
57
),
// (?) For import jobs only. Read the nF documentation, seams to be never used.
FINISHED
(
60
),
// Job finished
DOWNLOAD
(
70
);
// Export jobs only. CityGML has been download to the local file system
public
static
final
String
UNKNOWN_MESSAGE
=
"The state of the job is unknown."
;
public
static
final
String
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
PENDING_MESSAGE
=
"Job is pending."
;
public
static
final
String
RUNNING_MESSAGE
=
"Job is running."
;
public
static
final
String
FAILED_MESSAGE
=
"Job failed."
;
public
static
final
String
FINISHED_MESSAGE
=
"Job is finished."
;
public
static
final
String
WAITING_MESSAGE
=
"Job is waiting for a response from the remote nF instance."
;
/**
* This constructor sets messages for some states.
*
* @param internalCode Our numerical SimStadt-internal code for the nF status codes.
*/
private
JobStatus
(
int
internalCode
)
{
switch
(
internalCode
)
{
case
0
:
message
=
UNKNOWN_MESSAGE
;
break
;
case
10
:
message
=
LOCAL_MESSAGE
;
break
;
case
20
:
message
=
SENT_MESSAGE
;
break
;
case
30
:
case
31
:
message
=
PENDING_MESSAGE
;
break
;
case
40
:
message
=
RUNNING_MESSAGE
;
break
;
case
50
:
case
51
:
message
=
FAILED_MESSAGE
;
break
;
case
60
:
message
=
FINISHED_MESSAGE
;
break
;
default
:
message
=
""
;
}
}
/**
* This message describes the status or gives hints about error states. This message may come from a
* nF response.
*/
private
String
message
;
/**
* Sets a message for this status in order to describe the status.
*
* @param message A message to describe this status.
*/
public
void
setMessage
(
String
message
)
{
this
.
message
=
message
;
}
/**
* @return Returns a message which describes this status.
*/
public
String
getMessage
()
{
return
message
;
}
}
package
eu.simstadt.nf4j
;
/**
* The list of all possible export and import job states.
*
* @author Marcel Bruse
*/
public
enum
JobStatus
{
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.
SENT
(
20
),
// Job has been sent or it is assumed that it has been set before.
PENDING
(
30
),
// Export job has been enqueued and waits for execution
READY_TO_RUN
(
31
),
// Same as PENDING, but for import jobs
APPROVE
(
32
),
// (?) For import jobs only. Read the nF documentation, seams to be never used.
RUNNING
(
40
),
// Job is running
APPROVE_RUNNING
(
41
),
// (?) For import jobs only. Read the nF documentation, seams to be never used.
FAILED
(
50
),
// Export job failed
ERROR
(
51
),
// Same as FAILED, but for import jobs
WARNING
(
52
),
// For import jobs only. There has been a minor problem
REJECT
(
53
),
// (?) For import jobs only. Read the nF documentation, seams to be never used.
REJECT_RUNNING
(
54
),
// (?) For import jobs only. Read the nF documentation, seams to be never used.
APPROVE_REJECT_ERROR
(
55
),
// (?) For import jobs only. Read the nF documentation, seams to be never used.
APPROVE_REJECT_OK
(
56
),
// (?) For import jobs only. Read the nF documentation, seams to be never used.
IMPORTED_WARNING
(
57
),
// (?) For import jobs only. Read the nF documentation, seams to be never used.
FINISHED
(
60
),
// Job finished
DOWNLOAD
(
70
);
// Export jobs only. CityGML has been download to the local file system
public
static
final
String
UNKNOWN_MESSAGE
=
"The state of the job is unknown."
;
public
static
final
String
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
PENDING_MESSAGE
=
"Job is pending."
;
public
static
final
String
RUNNING_MESSAGE
=
"Job is running."
;
public
static
final
String
FAILED_MESSAGE
=
"Job failed."
;
public
static
final
String
FINISHED_MESSAGE
=
"Job is finished."
;
public
static
final
String
WAITING_MESSAGE
=
"Job is waiting for a response from the remote nF instance."
;
/**
* This constructor sets messages for some states.
*
* @param internalCode Our numerical SimStadt-internal code for the nF status codes.
*/
private
JobStatus
(
int
internalCode
)
{
switch
(
internalCode
)
{
case
0
:
message
=
UNKNOWN_MESSAGE
;
break
;
case
10
:
message
=
LOCAL_MESSAGE
;
break
;
case
20
:
message
=
SENT_MESSAGE
;
break
;
case
30
:
case
31
:
message
=
PENDING_MESSAGE
;
break
;
case
40
:
message
=
RUNNING_MESSAGE
;
break
;
case
50
:
case
51
:
message
=
FAILED_MESSAGE
;
break
;
case
60
:
message
=
FINISHED_MESSAGE
;
break
;
default
:
message
=
""
;
}
}
/**
* This message describes the status or gives hints about error states. This message may come from a
* nF response.
*/
private
String
message
;
/**
* Sets a message for this status in order to describe the status.
*
* @param message A message to describe this status.
*/
public
void
setMessage
(
String
message
)
{
this
.
message
=
message
;
}
/**
* @return Returns a message which describes this status.
*/
public
String
getMessage
()
{
return
message
;
}
}
src/eu/simstadt/nf4j/async/AsyncExportJob.java
View file @
5a0bec3d
package
eu.simstadt.nf4j.async
;
import
java.io.File
;
import
java.util.LinkedList
;
import
java.util.Objects
;
import
java.util.Optional
;
import
eu.simstadt.nf4j.Connector
;
import
eu.simstadt.nf4j.ExportJob
;
import
eu.simstadt.nf4j.FailedTransmissionException
;
import
eu.simstadt.nf4j.InvalidJobDescriptorException
;
import
eu.simstadt.nf4j.JobStatus
;
/**
* Export jobs are requests for CityGML models. Every valid export job has an id and a status. This implementation
* offers non-blocking asynchronous send, poll and download operations, so that your main application has not to wait
* for the results. You may want to register your main application as a job status listeners at this job to get status
* updates from the asynchronous operations.
*
* @author Marcel Bruse
*/
public
class
AsyncExportJob
extends
ExportJob
<
ExportJobDescription
>
implements
AsyncJob
{
/**
* While polling for the current job status, the polling thread will sleep for this amount of seconds before each
* status update request.
*/
private
final
int
DEFAULT_POLLING_INTERVAL
=
5
;
// seconds
/** There can only be one sending thread for each job at a time. */
private
Thread
sendThread
;
/** There can only be one polling thread for each job at a time. */
private
Thread
pollThread
;
/** There can only be one download thread for each job at a time. */
private
Thread
downloadThread
;
/**
* Once the send() operation has been triggered, this member will be true. No subsequent invocations of send() will
* be possible then.
*/
private
boolean
jobTransmissionTriggered
=
false
;
/** As long as this variable is true, the polling thread will be kept alive. */
private
boolean
keepPolling
=
true
;
/**
* List of all registered job status listeners. Whenever the state of this job changes, these listeners will get
* informed.
*/
private
LinkedList
<
JobStatusListener
>
jobListenerList
=
new
LinkedList
<>();
/**
* This job will be send and observed asynchronously. Its results will be downloaded asynchronously also. If an
* asynchronous operation breaks, then the last encountered problem will be described here.
*/
private
Optional
<
String
>
lastEncounteredProblem
=
Optional
.
empty
();
/**
* The last job status which has been sent to all registered job status listeners.
*/
private
JobStatus
lastPublishedJobStatus
;
/** Once the CityGML file has been download, it should be referenced here. */
private
File
result
;
/**
* This constructor forces the job to have a description and a connector instance. Every job which is created by this
* constructor will have the status "local", because it is assumed that it has an unsent description and no job id
* yet.
*
* @param connector The job will use this connector to synchronize itself with the nF.
* @param descriptor The description of this job.
*/
public
AsyncExportJob
(
ExportJobDescription
descriptor
,
Connector
<
AsyncImportJob
,
AsyncExportJob
>
connector
)
{
super
(
descriptor
,
connector
);
status
=
JobStatus
.
LOCAL
;
}
/**
* This constructor forces the job to have a id and a connector instance. Every job which is created by this
* constructor will have the status "sent", because it is assumed that the job is already enqueued at the nF job
* queue.
*
* @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status will be
* updated. If you call updateStatus() and the job id is "unkown" on the server side, then
* @param connector The job will use this connector to synchronize itself with the nF.
*/
public
AsyncExportJob
(
int
id
,
Connector
<
AsyncImportJob
,
AsyncExportJob
>
connector
)
{
super
(
id
,
connector
);
status
=
JobStatus
.
SENT
;
}
/**
* Builds an XML job file from the job description and sends it to the nF server which has been configured in your
* connector instance. This will be done asynchronously within a SendExportJobTask.
*
* @throws FailedTransmissionException If this method has been called before, then you will receive this.
* @throws InvalidJobDescriptorException If the job description is invalid or null, then you will receive this.
*/
@Override
public
synchronized
void
send
()
throws
FailedTransmissionException
,
InvalidJobDescriptorException
{
if
(
jobTransmissionTriggered
)
{
throw
new
FailedTransmissionException
(
"Jobs cannot be sent twice!"
);
}
if
(
Objects
.
isNull
(
descriptor
)
||
!
descriptor
.
isValid
())
{
throw
new
InvalidJobDescriptorException
();
}
jobTransmissionTriggered
=
true
;
notifyJobStatusListeners
();
// Force the job to signal the LOCAL status
sendThread
=
new
Thread
(
new
SendExportJobTask
(
this
));
sendThread
.
start
();
}
/**
* Frequently queries the 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
* change.
*
* Note, there can only be one polling thread at a time. Subsequent calls of poll() will stop the previously started
* poll threads.
*
* @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.
*/
@Override
public
synchronized
void
poll
(
int
interval
)
throws
FailedTransmissionException
{
if
(
status
.
compareTo
(
JobStatus
.
SENT
)
<
0
)
{
throw
new
FailedTransmissionException
(
"The job has not been sent to the nF yet!"
);
}
if
(
Objects
.
nonNull
(
pollThread
))
{
pollThread
.
interrupt
();
}
keepPolling
=
true
;
pollThread
=
new
Thread
(
new
PollJobStatusTask
(
this
,
interval
));
pollThread
.
start
();
}
/**
* 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
* will be performed asynchronously in a separate thread. Job status listeners will be notified upon every new status
* change.
*
* Note, there can only be one polling thread at a time. Subsequent calls of poll() will stop the previously started
* poll threads.
*
* @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.
* @see poll(int)
*/
public
synchronized
void
poll
()
throws
FailedTransmissionException
{
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
* throw a FailedTransmissionException.
*
* @throws FailedTransmissionException You will receive this exception if no connector is present, the connection to
* the nF is broken, the job has not been sent to the nF yet, or another update request is ongoing. In the
* two latter cases, job will either have the status "LOCAL" or "WAITING".
*/
@Override
public
synchronized
void
updateStatus
()
throws
FailedTransmissionException
{
if
(
Objects
.
isNull
(
connector
))
{
throw
new
FailedTransmissionException
(
"No connector set for this job!"
);
}
if
(
status
.
compareTo
(
JobStatus
.
SENT
)
<
0
)
{
throw
new
FailedTransmissionException
(
"The job has not been sent to the nF yet!"
);
}
AsyncExportJob
job
=
((
HTTPConnection
)
connector
).
requestExportJob
(
id
);
JobStatus
newStatus
=
job
.
getStatus
();
if
(
newStatus
.
compareTo
(
JobStatus
.
SENT
)
>
0
)
{
setStatus
(
job
.
getStatus
(),
Optional
.
empty
());
}
}
/**
* Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the asynchronous
* PollJobStatusTask class.
*/
@Override
public
void
triggerStatusUpdate
()
throws
FailedTransmissionException
{
updateStatus
();
}
/**
* Sets the status of this job depending on the given nF status code. nF status codes will be sent to you in http
* responses.
*
* @param statusCode The nF status code for this job.
*/
@Override
public
synchronized
void
setStatusForCode
(
int
statusCode
)
{
switch
(
statusCode
)
{
case
0
:
setStatus
(
JobStatus
.
PENDING
);
break
;
case
10
:
setStatus
(
JobStatus
.
RUNNING
);
break
;
case
20
:
setStatus
(
JobStatus
.
FAILED
);
break
;
case
30
:
setStatus
(
JobStatus
.
FINISHED
);
break
;
default
:
setStatus
(
JobStatus
.
UNKNOWN
);
}
}
/**
* @return Returns true, if the job is definitely done. This is also the case, if the resulting CityGML file has been
* download. False, otherwise.
*/
@Override
public
boolean
hasFinished
()
{
return
status
==
JobStatus
.
FINISHED
||
status
==
JobStatus
.
DOWNLOAD
;
}
/**
* @return Returns true, if the job has been failed. You may want to look up the "last encountered problem" string.
*/
@Override
public
boolean
hasFailed
()
{
return
status
==
JobStatus
.
FAILED
;
}
/**
* Registers a job status listener.
*
* @param jobListener The job status listener to be registered. This listener will receive updates about every
* progressing change of the job status. Meaning, the change to a particular status will only be signaled
* once to the listener.
*/
@Override
public
void
addJobStatusListener
(
JobStatusListener
jobListener
)
{
jobListenerList
.
add
(
jobListener
);
}
/**
* Unregisters a job status listener.
*
* @param jobListener The job status listener to be unregistered.
*/
@Override
public
void
removeJobStatusListener
(
JobStatusListener
jobListener
)
{
jobListenerList
.
remove
(
jobListener
);
}
/**
* Once the status of this job changes, all registered job status listeners will be notified. Listeners will only be
* notified of the status updates where the status of the job progresses and they will only be notified once about
* every singular status. If this status has been signaled already, then the listeners will not be notified again.
*/
@Override
public
synchronized
void
notifyJobStatusListeners
()
{
if
(
Objects
.
isNull
(
lastPublishedJobStatus
)
||
status
.
compareTo
(
lastPublishedJobStatus
)
>
0
)
{
JobStatusEvent
event
=
new
JobStatusEvent
(
status
,
this
,
lastEncounteredProblem
);
for
(
JobStatusListener
listener
:
jobListenerList
)
{
listener
.
jobStatusChanged
(
event
);
}
lastPublishedJobStatus
=
status
;
}
}
/**
* Cancels all ongoing send, poll and download operations as soon as possible.
*/
@Override
public
void
cancel
()
{
keepPolling
=
false
;
if
(
Objects
.
nonNull
(
sendThread
))
{
sendThread
.
interrupt
();
}
if
(
Objects
.
nonNull
(
pollThread
))
{
pollThread
.
interrupt
();
}
if
(
Objects
.
nonNull
(
downloadThread
))
{
downloadThread
.
interrupt
();
}
}
/**
* @return Returns true, if the polling thread should go on with its polling job. Otherwise, false.
*/
@Override
public
boolean
keepPolling
()
{
return
keepPolling
;
}
/**
* Starts downloading the export job result, if there is any. As soon as the download has been finished, the job
* status will be set to DOWNLOADED. All registered job status listeners will get notified about the it. Afterwards,
* you may want to obtain a handle to the download CityGML file with getResult().
*
* @throws FailedTransmissionException If the job has not been finished yet, then you will get some of this.
*/
public
void
downloadResult
()
throws
FailedTransmissionException
{
if
(!
hasFinished
())
{
throw
new
FailedTransmissionException
(
"Job has not been finished!"
);
}
downloadThread
=
new
Thread
(
new
DownloadTask
(
this
));
downloadThread
.
start
();
}
/**
* This method is used by the download task to set the CityGML file handle as soon as the file has been download.
*
* @param result The file handle of the download CityGML file.
*/
protected
void
setResult
(
File
result
)
{
this
.
result
=
result
;
}
/**
* This method will return the download CityGML file for this export job, but only if the export job has actually
* been finished before.
*
* @return Returns a file handle to the download CityGML file.
*
* @throws FailedTransmissionException If the job result has not been download yet, then you will get some of this.
*/
@Override
public
File
getResult
()
throws
FailedTransmissionException
{
if
(!
hasFinished
())
{
throw
new
FailedTransmissionException
(
"Job has not been finished!"
);
}
if
(
Objects
.
isNull
(
result
))
{
throw
new
FailedTransmissionException
(
"Job result has not been downloaded!"
);
}
return
result
;
}
/**
* The asynchronous send, poll and download tasks cannot throw exceptions. If something goes wrong during the save,
* poll or download operation, then you may want to submit at lease a textual description of the problem here. This
* message will be sent to all registered job status listeners on the next status update. This is why you can use the
* convenience method setStatus(jobStatus, message), to do both at the same time.
*
* @param errorMessage The description of the encountered problem. This could be the exception message or a more user
* friendly message.
*/
protected
synchronized
void
setLastEncounteredProblem
(
Optional
<
String
>
errorMessage
)
{
this
.
lastEncounteredProblem
=
errorMessage
;
}
/**
* A convenience method to set a new job status and a status message at the same time. Status messages will be passed
* by asynchronous tasks instead of exception, because they cannot throw exceptions.
*
* @param jobStatus The new status of this job.
* @param message A status message. This message may describe a problem which occurred during an asynchronous task.
*/
protected
synchronized
void
setStatus
(
JobStatus
jobStatus
,
Optional
<
String
>
message
)
{
super
.
setStatus
(
jobStatus
);
lastEncounteredProblem
=
message
;
notifyJobStatusListeners
();
}
package
eu.simstadt.nf4j.async
;
import
java.io.File
;
import
java.util.LinkedList
;
import
java.util.Objects
;
import
java.util.Optional
;
import
eu.simstadt.nf4j.Connector
;
import
eu.simstadt.nf4j.ExportJob
;
import
eu.simstadt.nf4j.FailedTransmissionException
;
import
eu.simstadt.nf4j.InvalidJobDescriptorException
;
import
eu.simstadt.nf4j.JobStatus
;
/**
* Export jobs are requests for CityGML models. Every valid export job has an id and a status. This implementation
* offers non-blocking asynchronous send, poll and download operations, so that your main application has not to wait
* for the results. You may want to register your main application as a job status listeners at this job to get status
* updates from the asynchronous operations.
*
* @author Marcel Bruse
*/
public
class
AsyncExportJob
extends
ExportJob
<
ExportJobDescription
>
implements
AsyncJob
{
/**
* While polling for the current job status, the polling thread will sleep for this amount of seconds before each
* status update request.
*/
private
final
int
DEFAULT_POLLING_INTERVAL
=
5
;
// seconds
/** There can only be one sending thread for each job at a time. */
private
Thread
sendThread
;
/** There can only be one polling thread for each job at a time. */
private
Thread
pollThread
;
/** There can only be one download thread for each job at a time. */
private
Thread
downloadThread
;
/**
* Once the send() operation has been triggered, this member will be true. No subsequent invocations of send() will
* be possible then.
*/
private
boolean
jobTransmissionTriggered
=
false
;
/** As long as this variable is true, the polling thread will be kept alive. */
private
boolean
keepPolling
=
true
;
/**
* List of all registered job status listeners. Whenever the state of this job changes, these listeners will get
* informed.
*/
private
LinkedList
<
JobStatusListener
>
jobListenerList
=
new
LinkedList
<>();
/**
* This job will be send and observed asynchronously. Its results will be downloaded asynchronously also. If an
* asynchronous operation breaks, then the last encountered problem will be described here.
*/
private
Optional
<
String
>
lastEncounteredProblem
=
Optional
.
empty
();
/**
* The last job status which has been sent to all registered job status listeners.
*/
private
JobStatus
lastPublishedJobStatus
;
/** Once the CityGML file has been download, it should be referenced here. */
private
File
result
;
/**
* This constructor forces the job to have a description and a connector instance. Every job which is created by this
* constructor will have the status "local", because it is assumed that it has an unsent description and no job id
* yet.
*
* @param connector The job will use this connector to synchronize itself with the nF.
* @param descriptor The description of this job.
*/
public
AsyncExportJob
(
ExportJobDescription
descriptor
,
Connector
<
AsyncImportJob
,
AsyncExportJob
>
connector
)
{
super
(
descriptor
,
connector
);
status
=
JobStatus
.
LOCAL
;
}
/**
* This constructor forces the job to have a id and a connector instance. Every job which is created by this
* constructor will have the status "sent", because it is assumed that the job is already enqueued at the nF job
* queue.
*
* @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status will be
* updated. If you call updateStatus() and the job id is "unkown" on the server side, then
* @param connector The job will use this connector to synchronize itself with the nF.
*/
public
AsyncExportJob
(
int
id
,
Connector
<
AsyncImportJob
,
AsyncExportJob
>
connector
)
{
super
(
id
,
connector
);
status
=
JobStatus
.
SENT
;
}
/**
* Builds an XML job file from the job description and sends it to the nF server which has been configured in your
* connector instance. This will be done asynchronously within a SendExportJobTask.
*
* @throws FailedTransmissionException If this method has been called before, then you will receive this.
* @throws InvalidJobDescriptorException If the job description is invalid or null, then you will receive this.
*/
@Override
public
synchronized
void
send
()
throws
FailedTransmissionException
,
InvalidJobDescriptorException
{
if
(
jobTransmissionTriggered
)
{
throw
new
FailedTransmissionException
(
"Jobs cannot be sent twice!"
);
}
if
(
Objects
.
isNull
(
descriptor
)
||
!
descriptor
.
isValid
())
{
throw
new
InvalidJobDescriptorException
();
}
jobTransmissionTriggered
=
true
;
notifyJobStatusListeners
();
// Force the job to signal the LOCAL status
sendThread
=
new
Thread
(
new
SendExportJobTask
(
this
));
sendThread
.
start
();
}
/**
* Frequently queries the 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
* change.
*
* Note, there can only be one polling thread at a time. Subsequent calls of poll() will stop the previously started
* poll threads.
*
* @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.
*/
@Override
public
synchronized
void
poll
(
int
interval
)
throws
FailedTransmissionException
{
if
(
status
.
compareTo
(
JobStatus
.
SENT
)
<
0
)
{
throw
new
FailedTransmissionException
(
"The job has not been sent to the nF yet!"
);
}
if
(
Objects
.
nonNull
(
pollThread
))
{
pollThread
.
interrupt
();
}
keepPolling
=
true
;
pollThread
=
new
Thread
(
new
PollJobStatusTask
(
this
,
interval
));
pollThread
.
start
();
}
/**
* 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
* will be performed asynchronously in a separate thread. Job status listeners will be notified upon every new status
* change.
*
* Note, there can only be one polling thread at a time. Subsequent calls of poll() will stop the previously started
* poll threads.
*
* @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.
* @see poll(int)
*/
public
synchronized
void
poll
()
throws
FailedTransmissionException
{
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
* throw a FailedTransmissionException.
*
* @throws FailedTransmissionException You will receive this exception if no connector is present, the connection to
* the nF is broken, the job has not been sent to the nF yet, or another update request is ongoing. In the
* two latter cases, job will either have the status "LOCAL" or "WAITING".
*/
@Override
public
synchronized
void
updateStatus
()
throws
FailedTransmissionException
{
if
(
Objects
.
isNull
(
connector
))
{
throw
new
FailedTransmissionException
(
"No connector set for this job!"
);
}
if
(
status
.
compareTo
(
JobStatus
.
SENT
)
<
0
)
{
throw
new
FailedTransmissionException
(
"The job has not been sent to the nF yet!"
);
}
AsyncExportJob
job
=
((
HTTPConnection
)
connector
).
requestExportJob
(
id
);
JobStatus
newStatus
=
job
.
getStatus
();
if
(
newStatus
.
compareTo
(
JobStatus
.
SENT
)
>
0
)
{
setStatus
(
job
.
getStatus
(),
Optional
.
empty
());
}
}
/**
* Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the asynchronous
* PollJobStatusTask class.
*/
@Override
public
void
triggerStatusUpdate
()
throws
FailedTransmissionException
{
updateStatus
();
}
/**
* Sets the status of this job depending on the given nF status code. nF status codes will be sent to you in http
* responses.
*
* @param statusCode The nF status code for this job.
*/
@Override
public
synchronized
void
setStatusForCode
(
int
statusCode
)
{
switch
(
statusCode
)
{
case
0
:
setStatus
(
JobStatus
.
PENDING
);
break
;
case
10
:
setStatus
(
JobStatus
.
RUNNING
);
break
;
case
20
:
setStatus
(
JobStatus
.
FAILED
);
break
;
case
30
:
setStatus
(
JobStatus
.
FINISHED
);
break
;
default
:
setStatus
(
JobStatus
.
UNKNOWN
);
}
}
/**
* @return Returns true, if the job is definitely done. This is also the case, if the resulting CityGML file has been
* download. False, otherwise.
*/
@Override
public
boolean
hasFinished
()
{
return
status
==
JobStatus
.
FINISHED
||
status
==
JobStatus
.
DOWNLOAD
;
}
/**
* @return Returns true, if the job has been failed. You may want to look up the "last encountered problem" string.
*/
@Override
public
boolean
hasFailed
()
{
return
status
==
JobStatus
.
FAILED
;
}
/**
* Registers a job status listener.
*
* @param jobListener The job status listener to be registered. This listener will receive updates about every
* progressing change of the job status. Meaning, the change to a particular status will only be signaled
* once to the listener.
*/
@Override
public
void
addJobStatusListener
(
JobStatusListener
jobListener
)
{
jobListenerList
.
add
(
jobListener
);
}
/**
* Unregisters a job status listener.
*
* @param jobListener The job status listener to be unregistered.
*/
@Override
public
void
removeJobStatusListener
(
JobStatusListener
jobListener
)
{
jobListenerList
.
remove
(
jobListener
);
}
/**
* Once the status of this job changes, all registered job status listeners will be notified. Listeners will only be
* notified of the status updates where the status of the job progresses and they will only be notified once about
* every singular status. If this status has been signaled already, then the listeners will not be notified again.
*/
@Override
public
synchronized
void
notifyJobStatusListeners
()
{
if
(
Objects
.
isNull
(
lastPublishedJobStatus
)
||
status
.
compareTo
(
lastPublishedJobStatus
)
>
0
)
{
JobStatusEvent
event
=
new
JobStatusEvent
(
status
,
this
,
lastEncounteredProblem
);
for
(
JobStatusListener
listener
:
jobListenerList
)
{
listener
.
jobStatusChanged
(
event
);
}
lastPublishedJobStatus
=
status
;
}
}
/**
* Cancels all ongoing send, poll and download operations as soon as possible.
*/
@Override
public
void
cancel
()
{
keepPolling
=
false
;
if
(
Objects
.
nonNull
(
sendThread
))
{
sendThread
.
interrupt
();
}
if
(
Objects
.
nonNull
(
pollThread
))
{
pollThread
.
interrupt
();
}
if
(
Objects
.
nonNull
(
downloadThread
))
{
downloadThread
.
interrupt
();
}
}
/**
* @return Returns true, if the polling thread should go on with its polling job. Otherwise, false.
*/
@Override
public
boolean
keepPolling
()
{
return
keepPolling
;
}
/**
* Starts downloading the export job result, if there is any. As soon as the download has been finished, the job
* status will be set to DOWNLOADED. All registered job status listeners will get notified about the it. Afterwards,
* you may want to obtain a handle to the download CityGML file with getResult().
*
* @throws FailedTransmissionException If the job has not been finished yet, then you will get some of this.
*/
public
void
downloadResult
()
throws
FailedTransmissionException
{
if
(!
hasFinished
())
{
throw
new
FailedTransmissionException
(
"Job has not been finished!"
);
}
downloadThread
=
new
Thread
(
new
DownloadTask
(
this
));
downloadThread
.
start
();
}
/**
* This method is used by the download task to set the CityGML file handle as soon as the file has been download.
*
* @param result The file handle of the download CityGML file.
*/
protected
void
setResult
(
File
result
)
{
this
.
result
=
result
;
}
/**
* This method will return the download CityGML file for this export job, but only if the export job has actually
* been finished before.
*
* @return Returns a file handle to the download CityGML file.
*
* @throws FailedTransmissionException If the job result has not been download yet, then you will get some of this.
*/
@Override
public
File
getResult
()
throws
FailedTransmissionException
{
if
(!
hasFinished
())
{
throw
new
FailedTransmissionException
(
"Job has not been finished!"
);
}
if
(
Objects
.
isNull
(
result
))
{
throw
new
FailedTransmissionException
(
"Job result has not been downloaded!"
);
}
return
result
;
}
/**
* The asynchronous send, poll and download tasks cannot throw exceptions. If something goes wrong during the save,
* poll or download operation, then you may want to submit at lease a textual description of the problem here. This
* message will be sent to all registered job status listeners on the next status update. This is why you can use the
* convenience method setStatus(jobStatus, message), to do both at the same time.
*
* @param errorMessage The description of the encountered problem. This could be the exception message or a more user
* friendly message.
*/
protected
synchronized
void
setLastEncounteredProblem
(
Optional
<
String
>
errorMessage
)
{
this
.
lastEncounteredProblem
=
errorMessage
;
}
/**
* A convenience method to set a new job status and a status message at the same time. Status messages will be passed
* by asynchronous tasks instead of exception, because they cannot throw exceptions.
*
* @param jobStatus The new status of this job.
* @param message A status message. This message may describe a problem which occurred during an asynchronous task.
*/
protected
synchronized
void
setStatus
(
JobStatus
jobStatus
,
Optional
<
String
>
message
)
{
super
.
setStatus
(
jobStatus
);
lastEncounteredProblem
=
message
;
notifyJobStatusListeners
();
}
}
\ No newline at end of file
src/eu/simstadt/nf4j/async/AsyncImportJob.java
View file @
5a0bec3d
package
eu.simstadt.nf4j.async
;
import
java.util.LinkedList
;
import
java.util.Objects
;
import
java.util.Optional
;
import
eu.simstadt.nf4j.ImportJob
;
import
eu.simstadt.nf4j.FailedTransmissionException
;
import
eu.simstadt.nf4j.InvalidJobDescriptorException
;
import
eu.simstadt.nf4j.JobStatus
;
/**
* Import jobs are requests to store, change or delete CityGML models. Every valid import job has an id and a status.
* This implementation offers non-blocking asynchronous send and poll operations, so that your main application has
* not to wait for the results. You may want to register your main application as a job status listeners at this job
* to get status updates from the asynchronous operations.
*
* @author Marcel Bruse
*/
public
class
AsyncImportJob
extends
ImportJob
<
ImportJobDescription
>
implements
AsyncJob
{
/**
* While polling for the current job status, the polling thread will sleep for this amount of seconds
* before each status update request.
*/
private
final
int
DEFAULT_POLLING_INTERVAL
=
5
;
// seconds
private
boolean
jobTransmissionTriggered
=
false
;
private
JobStatus
lastPublishedJobStatus
;
private
Optional
<
String
>
lastEncounteredProblem
=
Optional
.
empty
();
private
Thread
sendThread
;
private
Thread
pollThread
;
/** As long as this variable is true, the polling thread will be kept alive. */
private
boolean
keepPolling
=
true
;
/**
* List of all registered job status listeners. Whenever the state of this job changes, these listeners
* will get informed.
*/
private
LinkedList
<
JobStatusListener
>
jobListenerList
=
new
LinkedList
<>();
/**
* This constructor forces the job to have a description and a connector instance. Every job which
* is created by this constructor will have the status "local", because it is assumed that it has an unsent
* description and no job id yet.
*
* @param connector The job will use this connector to synchronize itself with the nF.
* @param descriptor The description of this job.
*/
public
AsyncImportJob
(
ImportJobDescription
descriptor
,
HTTPConnection
connector
)
{
super
(
descriptor
,
connector
);
status
=
JobStatus
.
LOCAL
;
}
/**
* This constructor forces the job to have a id and a connector instance. Every job which is created by this
* constructor will have the status "sent", because it is assumed that the job is already enqueued at the
* nF job queue.
*
* @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status
* will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then
* @param connector The job will use this connector to synchronize itself with the nF.
*/
public
AsyncImportJob
(
int
id
,
HTTPConnection
connector
)
{
super
(
id
,
connector
);
status
=
JobStatus
.
SENT
;
}
/**
* This method zips up an archive which includes the CityGML file to be imported as well as a nF start
* file. Both files will be preprocessed according to the set attributes of the job description.
*/
@Override
public
synchronized
void
send
()
throws
InvalidJobDescriptorException
,
FailedTransmissionException
{
if
(
jobTransmissionTriggered
)
{
throw
new
FailedTransmissionException
(
"Jobs cannot be sent twice!"
);
}
if
(
Objects
.
isNull
(
descriptor
)
||
!
descriptor
.
isValid
())
{
throw
new
InvalidJobDescriptorException
();
}
jobTransmissionTriggered
=
true
;
notifyJobStatusListeners
();
// Force the job to signal the LOCAL status
sendThread
=
new
Thread
(
new
SendImportJobTask
(
this
));
sendThread
.
start
();
}
/**
* Frequently queries the 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
* upon every new status change.
*
* @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.
*/
@Override
public
synchronized
void
poll
(
int
interval
)
throws
FailedTransmissionException
{
if
(
status
.
compareTo
(
JobStatus
.
SENT
)
<
0
)
{
throw
new
FailedTransmissionException
(
"The job has not been sent to the nF yet!"
);
}
if
(
Objects
.
nonNull
(
pollThread
))
{
pollThread
.
interrupt
();
}
keepPolling
=
true
;
pollThread
=
new
Thread
(
new
PollJobStatusTask
(
this
,
interval
));
pollThread
.
start
();
}
/**
* Convenience method for polling with a predefined default interval.
*
* @see poll(int)
*/
public
synchronized
void
poll
()
throws
FailedTransmissionException
{
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 throw a FailedTransmissionException.
*
* @throws FailedTransmissionException If the connection to the nF is broken you will get some of this.
*/
@Override
public
synchronized
void
updateStatus
()
throws
FailedTransmissionException
{
if
(
Objects
.
nonNull
(
connector
))
{
AsyncImportJob
job
=
((
HTTPConnection
)
connector
).
requestImportJob
(
id
);
JobStatus
newStatus
=
job
.
getStatus
();
if
(
newStatus
.
compareTo
(
JobStatus
.
SENT
)
>
0
)
{
setStatus
(
job
.
getStatus
(),
Optional
.
empty
());
}
}
else
{
throw
new
FailedTransmissionException
();
}
}
/**
* Sets the status of this job depending on the given nF status code. nF status codes will be sent
* to you in HTTP responses. Note, the nF status code differ from the internal job status codes of
* this library. Read the nF documentation for more information.
*
* @param statusCode The nF status code for this job.
*/
@Override
public
synchronized
void
setStatusForCode
(
int
statusCode
)
{
switch
(
statusCode
)
{
case
0
:
setStatus
(
JobStatus
.
PENDING
);
break
;
case
10
:
setStatus
(
JobStatus
.
RUNNING
);
break
;
case
20
:
setStatus
(
JobStatus
.
ERROR
);
break
;
case
25
:
setStatus
(
JobStatus
.
WARNING
);
break
;
case
30
:
setStatus
(
JobStatus
.
FINISHED
);
break
;
case
40
:
setStatus
(
JobStatus
.
APPROVE
);
break
;
case
45
:
setStatus
(
JobStatus
.
REJECT
);
break
;
case
50
:
setStatus
(
JobStatus
.
APPROVE_RUNNING
);
break
;
case
55
:
setStatus
(
JobStatus
.
REJECT_RUNNING
);
break
;
case
60
:
setStatus
(
JobStatus
.
APPROVE_REJECT_ERROR
);
break
;
case
70
:
setStatus
(
JobStatus
.
APPROVE_REJECT_OK
);
break
;
case
80
:
setStatus
(
JobStatus
.
IMPORTED_WARNING
);
break
;
default
:
setStatus
(
JobStatus
.
UNKNOWN
);
}
}
/**
* Registers a job status listener.
*
* @param jobListener The job status listener to be registered. This listener will receive updates about every
* progressing change of the job status. Meaning, the change to a particular status will only be signaled once
* to the listener.
*/
@Override
public
void
addJobStatusListener
(
JobStatusListener
jobListener
)
{
jobListenerList
.
add
(
jobListener
);
}
/**
* Unregisters a job status listener.
*
* @param jobListener The job status listener to be unregistered.
*/
@Override
public
void
removeJobStatusListener
(
JobStatusListener
jobListener
)
{
jobListenerList
.
remove
(
jobListener
);
}
/**
* Once the status of this job changes, all registered job status listeners will be notified.
* Listeners will only be notified of the status updates where the status of the job progresses and they will
* only be notified once about every singular status. If this status has been signaled already, then the
* listeners will not be notified again.
*/
@Override
public
synchronized
void
notifyJobStatusListeners
()
{
if
(
Objects
.
isNull
(
lastPublishedJobStatus
)
||
status
.
compareTo
(
lastPublishedJobStatus
)
>
0
)
{
JobStatusEvent
event
=
new
JobStatusEvent
(
status
,
this
,
lastEncounteredProblem
);
for
(
JobStatusListener
listener
:
jobListenerList
)
{
listener
.
jobStatusChanged
(
event
);
}
lastPublishedJobStatus
=
status
;
}
}
/**
* A convenience method to set a new job status and a status message at the same time. Status messages will be passed
* by asynchronous tasks instead of exception, because they cannot throw exceptions.
*
* @param jobStatus The new status of this job.
* @param message A status message. This message may describe a problem which occurred during an asynchronous task.
*/
protected
synchronized
void
setStatus
(
JobStatus
jobStatus
,
Optional
<
String
>
message
)
{
super
.
setStatus
(
jobStatus
);
lastEncounteredProblem
=
message
;
notifyJobStatusListeners
();
}
/**
* Cancels all ongoing send and poll operations as soon as possible.
*/
@Override
public
void
cancel
()
{
keepPolling
=
false
;
if
(
Objects
.
nonNull
(
sendThread
))
{
sendThread
.
interrupt
();
}
if
(
Objects
.
nonNull
(
pollThread
))
{
pollThread
.
interrupt
();
}
}
/**
* @return Returns true, if the polling thread should go on with its polling job. Otherwise, false.
*/
@Override
public
boolean
keepPolling
()
{
return
keepPolling
;
}
/**
* @return Returns true, if the job is definitely done. This is also the case, if the resulting CityGML
* file has been download. False, otherwise.
*/
@Override
public
boolean
hasFinished
()
{
return
status
==
JobStatus
.
FINISHED
;
}
/**
* @return Returns true, if the job has been failed. You may want to look up the "last encountered problem"
* string.
*/
@Override
public
boolean
hasFailed
()
{
return
status
==
JobStatus
.
FAILED
||
status
==
JobStatus
.
ERROR
;
}
/**
* Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the
* asynchronous PollJobStatusTask class.
*/
@Override
public
void
triggerStatusUpdate
()
throws
FailedTransmissionException
{
updateStatus
();
}
package
eu.simstadt.nf4j.async
;
import
java.util.LinkedList
;
import
java.util.Objects
;
import
java.util.Optional
;
import
eu.simstadt.nf4j.ImportJob
;
import
eu.simstadt.nf4j.FailedTransmissionException
;
import
eu.simstadt.nf4j.InvalidJobDescriptorException
;
import
eu.simstadt.nf4j.JobStatus
;
/**
* Import jobs are requests to store, change or delete CityGML models. Every valid import job has an id and a status.
* This implementation offers non-blocking asynchronous send and poll operations, so that your main application has
* not to wait for the results. You may want to register your main application as a job status listeners at this job
* to get status updates from the asynchronous operations.
*
* @author Marcel Bruse
*/
public
class
AsyncImportJob
extends
ImportJob
<
ImportJobDescription
>
implements
AsyncJob
{
/**
* While polling for the current job status, the polling thread will sleep for this amount of seconds
* before each status update request.
*/
private
final
int
DEFAULT_POLLING_INTERVAL
=
5
;
// seconds
private
boolean
jobTransmissionTriggered
=
false
;
private
JobStatus
lastPublishedJobStatus
;
private
Optional
<
String
>
lastEncounteredProblem
=
Optional
.
empty
();
private
Thread
sendThread
;
private
Thread
pollThread
;
/** As long as this variable is true, the polling thread will be kept alive. */
private
boolean
keepPolling
=
true
;
/**
* List of all registered job status listeners. Whenever the state of this job changes, these listeners
* will get informed.
*/
private
LinkedList
<
JobStatusListener
>
jobListenerList
=
new
LinkedList
<>();
/**
* This constructor forces the job to have a description and a connector instance. Every job which
* is created by this constructor will have the status "local", because it is assumed that it has an unsent
* description and no job id yet.
*
* @param connector The job will use this connector to synchronize itself with the nF.
* @param descriptor The description of this job.
*/
public
AsyncImportJob
(
ImportJobDescription
descriptor
,
HTTPConnection
connector
)
{
super
(
descriptor
,
connector
);
status
=
JobStatus
.
LOCAL
;
}
/**
* This constructor forces the job to have a id and a connector instance. Every job which is created by this
* constructor will have the status "sent", because it is assumed that the job is already enqueued at the
* nF job queue.
*
* @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status
* will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then
* @param connector The job will use this connector to synchronize itself with the nF.
*/
public
AsyncImportJob
(
int
id
,
HTTPConnection
connector
)
{
super
(
id
,
connector
);
status
=
JobStatus
.
SENT
;
}
/**
* This method zips up an archive which includes the CityGML file to be imported as well as a nF start
* file. Both files will be preprocessed according to the set attributes of the job description.
*/
@Override
public
synchronized
void
send
()
throws
InvalidJobDescriptorException
,
FailedTransmissionException
{
if
(
jobTransmissionTriggered
)
{
throw
new
FailedTransmissionException
(
"Jobs cannot be sent twice!"
);
}
if
(
Objects
.
isNull
(
descriptor
)
||
!
descriptor
.
isValid
())
{
throw
new
InvalidJobDescriptorException
();
}
jobTransmissionTriggered
=
true
;
notifyJobStatusListeners
();
// Force the job to signal the LOCAL status
sendThread
=
new
Thread
(
new
SendImportJobTask
(
this
));
sendThread
.
start
();
}
/**
* Frequently queries the 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
* upon every new status change.
*
* @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.
*/
@Override
public
synchronized
void
poll
(
int
interval
)
throws
FailedTransmissionException
{
if
(
status
.
compareTo
(
JobStatus
.
SENT
)
<
0
)
{
throw
new
FailedTransmissionException
(
"The job has not been sent to the nF yet!"
);
}
if
(
Objects
.
nonNull
(
pollThread
))
{
pollThread
.
interrupt
();
}
keepPolling
=
true
;
pollThread
=
new
Thread
(
new
PollJobStatusTask
(
this
,
interval
));
pollThread
.
start
();
}
/**
* Convenience method for polling with a predefined default interval.
*
* @see poll(int)
*/
public
synchronized
void
poll
()
throws
FailedTransmissionException
{
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 throw a FailedTransmissionException.
*
* @throws FailedTransmissionException If the connection to the nF is broken you will get some of this.
*/
@Override
public
synchronized
void
updateStatus
()
throws
FailedTransmissionException
{
if
(
Objects
.
nonNull
(
connector
))
{
AsyncImportJob
job
=
((
HTTPConnection
)
connector
).
requestImportJob
(
id
);
JobStatus
newStatus
=
job
.
getStatus
();
if
(
newStatus
.
compareTo
(
JobStatus
.
SENT
)
>
0
)
{
setStatus
(
job
.
getStatus
(),
Optional
.
empty
());
}
}
else
{
throw
new
FailedTransmissionException
();
}
}
/**
* Sets the status of this job depending on the given nF status code. nF status codes will be sent
* to you in HTTP responses. Note, the nF status code differ from the internal job status codes of
* this library. Read the nF documentation for more information.
*
* @param statusCode The nF status code for this job.
*/
@Override
public
synchronized
void
setStatusForCode
(
int
statusCode
)
{
switch
(
statusCode
)
{
case
0
:
setStatus
(
JobStatus
.
PENDING
);
break
;
case
10
:
setStatus
(
JobStatus
.
RUNNING
);
break
;
case
20
:
setStatus
(
JobStatus
.
ERROR
);
break
;
case
25
:
setStatus
(
JobStatus
.
WARNING
);
break
;
case
30
:
setStatus
(
JobStatus
.
FINISHED
);
break
;
case
40
:
setStatus
(
JobStatus
.
APPROVE
);
break
;
case
45
:
setStatus
(
JobStatus
.
REJECT
);
break
;
case
50
:
setStatus
(
JobStatus
.
APPROVE_RUNNING
);
break
;
case
55
:
setStatus
(
JobStatus
.
REJECT_RUNNING
);
break
;
case
60
:
setStatus
(
JobStatus
.
APPROVE_REJECT_ERROR
);
break
;
case
70
:
setStatus
(
JobStatus
.
APPROVE_REJECT_OK
);
break
;
case
80
:
setStatus
(
JobStatus
.
IMPORTED_WARNING
);
break
;
default
:
setStatus
(
JobStatus
.
UNKNOWN
);
}
}
/**
* Registers a job status listener.
*
* @param jobListener The job status listener to be registered. This listener will receive updates about every
* progressing change of the job status. Meaning, the change to a particular status will only be signaled once
* to the listener.
*/
@Override
public
void
addJobStatusListener
(
JobStatusListener
jobListener
)
{
jobListenerList
.
add
(
jobListener
);
}
/**
* Unregisters a job status listener.
*
* @param jobListener The job status listener to be unregistered.
*/
@Override
public
void
removeJobStatusListener
(
JobStatusListener
jobListener
)
{
jobListenerList
.
remove
(
jobListener
);
}
/**
* Once the status of this job changes, all registered job status listeners will be notified.
* Listeners will only be notified of the status updates where the status of the job progresses and they will
* only be notified once about every singular status. If this status has been signaled already, then the
* listeners will not be notified again.
*/
@Override
public
synchronized
void
notifyJobStatusListeners
()
{
if
(
Objects
.
isNull
(
lastPublishedJobStatus
)
||
status
.
compareTo
(
lastPublishedJobStatus
)
>
0
)
{
JobStatusEvent
event
=
new
JobStatusEvent
(
status
,
this
,
lastEncounteredProblem
);
for
(
JobStatusListener
listener
:
jobListenerList
)
{
listener
.
jobStatusChanged
(
event
);
}
lastPublishedJobStatus
=
status
;
}
}
/**
* A convenience method to set a new job status and a status message at the same time. Status messages will be passed
* by asynchronous tasks instead of exception, because they cannot throw exceptions.
*
* @param jobStatus The new status of this job.
* @param message A status message. This message may describe a problem which occurred during an asynchronous task.
*/
protected
synchronized
void
setStatus
(
JobStatus
jobStatus
,
Optional
<
String
>
message
)
{
super
.
setStatus
(
jobStatus
);
lastEncounteredProblem
=
message
;
notifyJobStatusListeners
();
}
/**
* Cancels all ongoing send and poll operations as soon as possible.
*/
@Override
public
void
cancel
()
{
keepPolling
=
false
;
if
(
Objects
.
nonNull
(
sendThread
))
{
sendThread
.
interrupt
();
}
if
(
Objects
.
nonNull
(
pollThread
))
{
pollThread
.
interrupt
();
}
}
/**
* @return Returns true, if the polling thread should go on with its polling job. Otherwise, false.
*/
@Override
public
boolean
keepPolling
()
{
return
keepPolling
;
}
/**
* @return Returns true, if the job is definitely done. This is also the case, if the resulting CityGML
* file has been download. False, otherwise.
*/
@Override
public
boolean
hasFinished
()
{
return
status
==
JobStatus
.
FINISHED
;
}
/**
* @return Returns true, if the job has been failed. You may want to look up the "last encountered problem"
* string.
*/
@Override
public
boolean
hasFailed
()
{
return
status
==
JobStatus
.
FAILED
||
status
==
JobStatus
.
ERROR
;
}
/**
* Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the
* asynchronous PollJobStatusTask class.
*/
@Override
public
void
triggerStatusUpdate
()
throws
FailedTransmissionException
{
updateStatus
();
}
}
\ No newline at end of file
src/eu/simstadt/nf4j/async/AsyncJob.java
View file @
5a0bec3d
package
eu.simstadt.nf4j.async
;
import
eu.simstadt.nf4j.FailedTransmissionException
;
/**
* An asynchronous job will be sent in a non-blocking fashion, so that the main thread can proceed after calling
* the send() operation. It queries the status of its remote nF counterpart frequently in a separate thread, so that
* the job operations don't have to wait. Every asynchronous job should maintain a list of job status listeners.
* Every registered job status listener should be notified upon significant and new job status changes.
*
* @author Marcel Bruse
*/
public
interface
AsyncJob
{
/**
* Frequently queries the 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
* upon every new status change.
*
* @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.
*/
public
void
poll
(
int
interval
)
throws
FailedTransmissionException
;
/**
* Cancels all ongoing asynchronous operations of this job as soon as possible. Operations to be canceled
* might be send, poll and download operations.
*/
public
void
cancel
();
/**
* Registers a job status listener at this job. The listener will then be notified upon job status changes.
*
* @param jobListener The job status listener to be registered at this job.
*/
public
void
addJobStatusListener
(
JobStatusListener
jobListener
);
/**
* Unregisters a job status listener for this job. The listener will not be notified about job status changes
* anymore.
*
* @param jobListener The job status listener to be unregistered.
*/
public
void
removeJobStatusListener
(
JobStatusListener
jobListener
);
/**
* Once the status of this job changes, all registered job status listeners will be notified.
*/
public
void
notifyJobStatusListeners
();
/**
* Convenience method.
*
* @return Returns true, if the job is definitely done.
*/
public
boolean
hasFinished
();
/**
* Convenience method.
*
* @return Returns true, if the job was unable to recover from a serious problem.
*/
public
boolean
hasFailed
();
/**
* Asynchronous polling tasks may query this flag frequently in order to decide to proceed or not.
*
* @return Returns true, if the polling task should continue polling for status updates.
*/
public
boolean
keepPolling
();
/**
* Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the
* asynchronous PollJobStatusTask class.
*
* @throws FailedTransmissionException If something goes wrong during the update process, you will get some
* of this.
*/
public
void
triggerStatusUpdate
()
throws
FailedTransmissionException
;
}
package
eu.simstadt.nf4j.async
;
import
eu.simstadt.nf4j.FailedTransmissionException
;
/**
* An asynchronous job will be sent in a non-blocking fashion, so that the main thread can proceed after calling
* the send() operation. It queries the status of its remote nF counterpart frequently in a separate thread, so that
* the job operations don't have to wait. Every asynchronous job should maintain a list of job status listeners.
* Every registered job status listener should be notified upon significant and new job status changes.
*
* @author Marcel Bruse
*/
public
interface
AsyncJob
{
/**
* Frequently queries the 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
* upon every new status change.
*
* @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.
*/
public
void
poll
(
int
interval
)
throws
FailedTransmissionException
;
/**
* Cancels all ongoing asynchronous operations of this job as soon as possible. Operations to be canceled
* might be send, poll and download operations.
*/
public
void
cancel
();
/**
* Registers a job status listener at this job. The listener will then be notified upon job status changes.
*
* @param jobListener The job status listener to be registered at this job.
*/
public
void
addJobStatusListener
(
JobStatusListener
jobListener
);
/**
* Unregisters a job status listener for this job. The listener will not be notified about job status changes
* anymore.
*
* @param jobListener The job status listener to be unregistered.
*/
public
void
removeJobStatusListener
(
JobStatusListener
jobListener
);
/**
* Once the status of this job changes, all registered job status listeners will be notified.
*/
public
void
notifyJobStatusListeners
();
/**
* Convenience method.
*
* @return Returns true, if the job is definitely done.
*/
public
boolean
hasFinished
();
/**
* Convenience method.
*
* @return Returns true, if the job was unable to recover from a serious problem.
*/
public
boolean
hasFailed
();
/**
* Asynchronous polling tasks may query this flag frequently in order to decide to proceed or not.
*
* @return Returns true, if the polling task should continue polling for status updates.
*/
public
boolean
keepPolling
();
/**
* Calls updateStatus() for you, since updateStatus() is a protected method. This method is used by the
* asynchronous PollJobStatusTask class.
*
* @throws FailedTransmissionException If something goes wrong during the update process, you will get some
* of this.
*/
public
void
triggerStatusUpdate
()
throws
FailedTransmissionException
;
}
src/eu/simstadt/nf4j/async/CRSWKT.java
View file @
5a0bec3d
package
eu.simstadt.nf4j.async
;
/**
* This enumeration lists some of the "well known texts" (WKT) which are used to identify
* coordinate reference systems (CRS).
*
* @author Marcel Bruse
*
*/
public
enum
CRSWKT
{
EPSG_31467
(
"EPSG:31467"
),
// DHDN Gauss-Kruger zone 3
EPSG_4326
(
"EPSG:4326"
);
// WGS 84
/** The well known texts for a CRS. */
public
String
wkt
;
/** Constructor for well known texts. */
private
CRSWKT
(
String
wkt
)
{
this
.
wkt
=
wkt
;
}
}
package
eu.simstadt.nf4j.async
;
/**
* This enumeration lists some of the "well known texts" (WKT) which are used to identify
* coordinate reference systems (CRS).
*
* @author Marcel Bruse
*
*/
public
enum
CRSWKT
{
EPSG_31467
(
"EPSG:31467"
),
// DHDN Gauss-Kruger zone 3
EPSG_4326
(
"EPSG:4326"
);
// WGS 84
/** The well known texts for a CRS. */
public
String
wkt
;
/** Constructor for well known texts. */
private
CRSWKT
(
String
wkt
)
{
this
.
wkt
=
wkt
;
}
}
src/eu/simstadt/nf4j/async/Coord.java
View file @
5a0bec3d
package
eu.simstadt.nf4j.async
;
/**
* Another class for coordinates. Is there a general purpose class for this kind of applications?
* This might be an intermediate representation for WGS 84 coordinates.
*
* @author Marcel Bruse
*/
public
class
Coord
{
/** The latitude of the geographic position. */
public
double
latitude
;
/** The longitude of the geographic position. */
public
double
longitude
;
/** Standard constructor.
*
* @param latitude The latitude of the WGS 84 coordinate.
* @param longitude The longitude of the WGS 84 coordinate.
*/
public
Coord
(
double
latitude
,
double
longitude
)
{
this
.
latitude
=
latitude
;
this
.
longitude
=
longitude
;
}
}
package
eu.simstadt.nf4j.async
;
/**
* Another class for coordinates. Is there a general purpose class for this kind of applications?
* This might be an intermediate representation for WGS 84 coordinates.
*
* @author Marcel Bruse
*/
public
class
Coord
{
/** The latitude of the geographic position. */
public
double
latitude
;
/** The longitude of the geographic position. */
public
double
longitude
;
/** Standard constructor.
*
* @param latitude The latitude of the WGS 84 coordinate.
* @param longitude The longitude of the WGS 84 coordinate.
*/
public
Coord
(
double
latitude
,
double
longitude
)
{
this
.
latitude
=
latitude
;
this
.
longitude
=
longitude
;
}
}
src/eu/simstadt/nf4j/async/DownloadTask.java
View file @
5a0bec3d
package
eu.simstadt.nf4j.async
;
import
java.io.File
;
import
java.util.Optional
;
import
eu.simstadt.nf4j.FailedTransmissionException
;
import
eu.simstadt.nf4j.JobStatus
;
/**
* This task downloads export job results asynchronously within its jobs separate download thread.
* You can cancel this task by calling job.cancel().
*
* @author Marcel Bruse
*/
public
class
DownloadTask
implements
Runnable
{
/** The finished export job for which you want to download a CityGML result. */
private
AsyncExportJob
job
;
/**
* Constructor with finished(!) asynchronous export job.
*
* @param job The export job for which you want to download the CityGML result.
*/
public
DownloadTask
(
AsyncExportJob
job
)
{
this
.
job
=
job
;
}
/**
* This method performs the download operation asynchronously in the export jobs download thread.
* Job status listeners will be notified upon the finished download.
*/
@Override
public
void
run
()
{
HTTPConnection
connector
=
(
HTTPConnection
)
job
.
getConnector
();
try
{
File
file
=
connector
.
requestExportJobResult
(
job
);
job
.
setResult
(
file
);
job
.
setStatus
(
JobStatus
.
DOWNLOAD
,
Optional
.
empty
());
}
catch
(
FailedTransmissionException
ex
)
{
// Conditions have been checked by main thread. No exception handling needed.
}
}
}
package
eu.simstadt.nf4j.async
;
import
java.io.File
;
import
java.util.Optional
;
import
eu.simstadt.nf4j.FailedTransmissionException
;
import
eu.simstadt.nf4j.JobStatus
;
/**
* This task downloads export job results asynchronously within its jobs separate download thread.
* You can cancel this task by calling job.cancel().
*
* @author Marcel Bruse
*/
public
class
DownloadTask
implements
Runnable
{
/** The finished export job for which you want to download a CityGML result. */
private
AsyncExportJob
job
;
/**
* Constructor with finished(!) asynchronous export job.
*
* @param job The export job for which you want to download the CityGML result.
*/
public
DownloadTask
(
AsyncExportJob
job
)
{
this
.
job
=
job
;
}
/**
* This method performs the download operation asynchronously in the export jobs download thread.
* Job status listeners will be notified upon the finished download.
*/
@Override
public
void
run
()
{
HTTPConnection
connector
=
(
HTTPConnection
)
job
.
getConnector
();
try
{
File
file
=
connector
.
requestExportJobResult
(
job
);
job
.
setResult
(
file
);
job
.
setStatus
(
JobStatus
.
DOWNLOAD
,
Optional
.
empty
());
}
catch
(
FailedTransmissionException
ex
)
{
// Conditions have been checked by main thread. No exception handling needed.
}
}
}
src/eu/simstadt/nf4j/async/ExportJobDescription.java
View file @
5a0bec3d
package
eu.simstadt.nf4j.async
;
import
java.util.ArrayList
;
import
java.util.List
;
import
eu.simstadt.nf4j.ExportJobDescriptor
;
/**
* 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.
*
* @author Marcel Bruse
*/
public
class
ExportJobDescription
implements
ExportJobDescriptor
{
private
static
final
String
DEFAULT_ACCOUNT
=
"Marcel"
;
private
static
final
String
DEFAULT_XMETA_DATA
=
"0"
;
private
static
final
String
DEFAULT_CALIBRATION
=
"0"
;
private
static
final
String
DEFAULT_PRODUCT
=
"WUDEV"
;
private
static
final
String
DEFAULT_ZIP_RESULT
=
"0"
;
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_NO_DATA_MASK
=
"0"
;
private
static
final
String
DEFAULT_ECK
=
"0"
;
private
static
final
String
DEFAULT_COL
=
"0"
;
private
static
final
String
DEFAULT_EXPORT_META_DATA
=
"1"
;
private
static
final
String
DEFAULT_FORMAT
=
"CityGML"
;
private
static
final
String
DEFAULT_SCALE
=
"10000.0"
;
private
static
final
String
DEFAULT_RESOLUTION
=
"100.0"
;
private
static
final
String
DEFAULT_MERGE_MAP_SHEETS
=
"off"
;
private
static
final
String
DEFAULT_TILE1ASGN
=
"false"
;
private
static
final
String
DEFAULT_SRS
=
"31467"
;
private
static
final
String
DEFAULT_SINGLE
=
"1"
;
private
static
final
String
DEFAULT_PLOTFRAME
=
"0"
;
private
static
final
String
DEFAULT_PLOT_LABEL_SRS
=
"-1"
;
private
static
final
String
DEFAULT_MONO
=
"0"
;
private
static
final
String
DEFAULT_COLOR
=
"0"
;
private
static
final
String
DEFAULT_JOBNUMBER
=
"GR_EXPORT_CITYGML_LOD1"
;
private
static
final
String
DEFAULT_INITIATOR
=
"2758"
;
private
static
final
String
DEFAULT_LODS
=
"12"
;
/** The version of the novaFACTORY XML export job format. */
public
static
final
String
EXPORT_JOB_VERSION
=
"1.0.0"
;
/** TODO: Who or what is this initiator? */
private
String
initiator
;
/** The number of the job which can actually be a string. */
private
String
jobnumber
;
/** The account or user for which a job should be executed. */
private
String
account
;
/** The product for which a job should be executed. */
private
String
product
;
/** Color attribute of the layers tag. */
private
String
color
;
/** The mono attribute of the layers tag. */
private
String
mono
;
/** The plotLabelSrs flag of the layers tag. */
private
String
plotLabelSrs
;
/** The plotframe flag of the layers tag. */
private
String
plotframe
;
/** The single flag of the layers tag. */
private
String
single
;
/** The layers which will be involved in a job. */
private
ArrayList
<
Layer
>
layerList
=
new
ArrayList
<>();
/** The SRS for the job. */
private
String
srs
;
/** The merge_mapsheets attribute of the extent tag. */
private
String
mergeMapsheets
;
/** The tile1asgn attribute of the extent tag. */
private
String
tile1asgn
;
/** The unit tag of the extent. */
private
ArrayList
<
Unit
>
unitList
=
new
ArrayList
<>();
/** The polygon of the selected region which should be exported. */
ArrayList
<
Coord
>
regionPolygon
=
new
ArrayList
<>();
/** The resolution for the job. */
private
String
resolution
;
/** The scale for the job. */
private
String
scale
;
/** The format for the job. E.g. CityGML. */
private
String
format
;
/** The exportmetadata flag for the job. */
private
String
exportmetadata
;
/** The col attribute of the addfile tag. */
private
String
col
;
/** The eck attribute of the addfile tag. */
private
String
eck
;
/** The usenodatamask flag for the job. */
private
String
usenodatamask
;
/** The usepdctborderpoly flag for the job. */
private
String
usepdctborderpoly
;
/** The dhkresolvereferences flag for the job. */
private
String
dhkresolvereferences
;
/** Flag for zipped results. */
private
String
zipresult
;
/** The calibration attribute of the exportmetadata tag. */
private
String
calibration
;
/** The xmetadata attribute of the exportmetadata tag. */
private
String
xmetadata
;
/** 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.
*/
private
String
lods
;
public
String
getInitiator
()
{
return
initiator
;
}
public
void
setInitiator
(
String
initiator
)
{
this
.
initiator
=
initiator
;
}
public
String
getJobnumber
()
{
return
jobnumber
;
}
public
void
setJobnumber
(
String
jobnumber
)
{
this
.
jobnumber
=
jobnumber
;
}
public
String
getAccount
()
{
return
account
;
}
public
void
setAccount
(
String
account
)
{
this
.
account
=
account
;
}
public
String
getProduct
()
{
return
product
;
}
public
void
setProduct
(
String
product
)
{
this
.
product
=
product
;
}
public
String
getColor
()
{
return
color
;
}
public
void
setColor
(
String
color
)
{
this
.
color
=
color
;
}
public
String
getMono
()
{
return
mono
;
}
public
void
setMono
(
String
mono
)
{
this
.
mono
=
mono
;
}
public
String
getPlotLabelSrs
()
{
return
plotLabelSrs
;
}
public
void
setPlotLabelSrs
(
String
plotLabelSrs
)
{
this
.
plotLabelSrs
=
plotLabelSrs
;
}
public
String
getPlotframe
()
{
return
plotframe
;
}
public
void
setPlotframe
(
String
plotframe
)
{
this
.
plotframe
=
plotframe
;
}
public
String
getSingle
()
{
return
single
;
}
public
void
setSingle
(
String
single
)
{
this
.
single
=
single
;
}
public
ArrayList
<
Layer
>
getLayerList
()
{
return
layerList
;
}
public
void
setLayerList
(
ArrayList
<
Layer
>
layerList
)
{
this
.
layerList
=
layerList
;
}
public
void
addLayer
(
Layer
layer
)
{
layerList
.
add
(
layer
);
}
public
String
getSrs
()
{
return
srs
;
}
public
void
setSrs
(
String
srs
)
{
this
.
srs
=
srs
;
}
public
String
getMergeMapsheets
()
{
return
mergeMapsheets
;
}
public
void
setMergeMapsheets
(
String
mergeMapsheets
)
{
this
.
mergeMapsheets
=
mergeMapsheets
;
}
public
ArrayList
<
Unit
>
getUnitList
()
{
return
unitList
;
}
public
void
addUnit
(
Unit
unit
)
{
unitList
.
add
(
unit
);
}
public
String
getTile1asgn
()
{
return
tile1asgn
;
}
public
void
setTile1asgn
(
String
tile1asgn
)
{
this
.
tile1asgn
=
tile1asgn
;
}
public
String
getResolution
()
{
return
resolution
;
}
public
void
setResolution
(
String
resolution
)
{
this
.
resolution
=
resolution
;
}
public
String
getScale
()
{
return
scale
;
}
public
void
setScale
(
String
scale
)
{
this
.
scale
=
scale
;
}
public
String
getFormat
()
{
return
format
;
}
public
void
setFormat
(
String
format
)
{
this
.
format
=
format
;
}
public
String
getExportmetadata
()
{
return
exportmetadata
;
}
public
void
setExportmetadata
(
String
exportmetadata
)
{
this
.
exportmetadata
=
exportmetadata
;
}
public
String
getCol
()
{
return
col
;
}
public
void
setCol
(
String
col
)
{
this
.
col
=
col
;
}
public
String
getEck
()
{
return
eck
;
}
public
void
setEck
(
String
eck
)
{
this
.
eck
=
eck
;
}
public
String
getUsenodatamask
()
{
return
usenodatamask
;
}
public
void
setUsenodatamask
(
String
usenodatamask
)
{
this
.
usenodatamask
=
usenodatamask
;
}
public
String
getUsepdctborderpoly
()
{
return
usepdctborderpoly
;
}
public
void
setUsepdctborderpoly
(
String
usepdctborderpoly
)
{
this
.
usepdctborderpoly
=
usepdctborderpoly
;
}
public
String
getDhkresolvereferences
()
{
return
dhkresolvereferences
;
}
public
void
setDhkresolvereferences
(
String
dhkresolvereferences
)
{
this
.
dhkresolvereferences
=
dhkresolvereferences
;
}
public
String
getZipresult
()
{
return
zipresult
;
}
public
void
setZipresult
(
String
zipresult
)
{
this
.
zipresult
=
zipresult
;
}
public
String
getCalibration
()
{
return
calibration
;
}
public
void
setCalibration
(
String
calibration
)
{
this
.
calibration
=
calibration
;
}
public
String
getXmetadata
()
{
return
xmetadata
;
}
public
void
setXmetadata
(
String
xmetadata
)
{
this
.
xmetadata
=
xmetadata
;
}
public
String
getLODs
()
{
return
lods
;
}
public
void
setLODs
(
String
lods
)
{
this
.
lods
=
lods
;
}
@Override
public
String
supportsJobVersion
()
{
return
EXPORT_JOB_VERSION
;
}
/**
* 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.
*/
public
void
addCoordinateToRegionPolygon
(
Coord
coordinate
)
{
regionPolygon
.
add
(
coordinate
);
}
/**
* Sets the whole region polygon for an export job.
*
* @param regionPolygon The region polygon to be used for an export job.
*/
public
void
setRegionPolygon
(
List
<
Coord
>
regionPolygon
)
{
this
.
regionPolygon
.
clear
();
this
.
regionPolygon
.
addAll
(
regionPolygon
);
}
/**
* @return Returns the region polygon.
*/
public
ArrayList
<
Coord
>
getRegionPolygon
()
{
return
regionPolygon
;
}
/**
* @return Returns true, if all mandatory elements and attributes are set. Otherwise, false.
*/
@Override
public
boolean
isValid
()
{
if
((
regionPolygon
.
isEmpty
()
&&
(
unitList
.
isEmpty
()))
||
layerList
.
isEmpty
()
||
initiator
.
isEmpty
()
||
account
.
isEmpty
()
||
jobnumber
.
isEmpty
()
||
product
.
isEmpty
()
||
color
.
isEmpty
()
||
mono
.
isEmpty
()
||
plotLabelSrs
.
isEmpty
()
||
plotframe
.
isEmpty
()
||
single
.
isEmpty
()
||
srs
.
isEmpty
()
||
resolution
.
isEmpty
()
||
scale
.
isEmpty
()
||
format
.
isEmpty
()
||
exportmetadata
.
isEmpty
()
||
col
.
isEmpty
()
||
eck
.
isEmpty
()
||
lods
.
isEmpty
())
{
return
false
;
}
else
{
return
true
;
}
}
public
static
ExportJobDescription
getDefaultDescriptor
()
{
ExportJobDescription
descriptor
=
new
ExportJobDescription
();
descriptor
.
setInitiator
(
DEFAULT_INITIATOR
);
descriptor
.
setJobnumber
(
DEFAULT_JOBNUMBER
);
descriptor
.
setAccount
(
DEFAULT_ACCOUNT
);
descriptor
.
setProduct
(
DEFAULT_PRODUCT
);
descriptor
.
setColor
(
DEFAULT_COLOR
);
descriptor
.
setMono
(
DEFAULT_MONO
);
descriptor
.
setPlotLabelSrs
(
DEFAULT_PLOT_LABEL_SRS
);
descriptor
.
setPlotframe
(
DEFAULT_PLOTFRAME
);
descriptor
.
setSingle
(
DEFAULT_SINGLE
);
descriptor
.
setSrs
(
DEFAULT_SRS
);
descriptor
.
setMergeMapsheets
(
DEFAULT_MERGE_MAP_SHEETS
);
descriptor
.
setTile1asgn
(
DEFAULT_TILE1ASGN
);
descriptor
.
setResolution
(
DEFAULT_RESOLUTION
);
descriptor
.
setScale
(
DEFAULT_SCALE
);
descriptor
.
setFormat
(
DEFAULT_FORMAT
);
descriptor
.
setExportmetadata
(
DEFAULT_EXPORT_META_DATA
);
descriptor
.
setCol
(
DEFAULT_COL
);
descriptor
.
setEck
(
DEFAULT_ECK
);
descriptor
.
setUsenodatamask
(
DEFAULT_USE_NO_DATA_MASK
);
descriptor
.
setUsepdctborderpoly
(
DEFAULT_USE_PDCT_BORDER_POLY
);
descriptor
.
setDhkresolvereferences
(
DEFAULT_DHK_RESOLVE_REFERENCES
);
descriptor
.
setZipresult
(
DEFAULT_ZIP_RESULT
);
descriptor
.
setCalibration
(
DEFAULT_CALIBRATION
);
descriptor
.
setXmetadata
(
DEFAULT_XMETA_DATA
);
descriptor
.
setLODs
(
DEFAULT_LODS
);
return
descriptor
;
}
}
package
eu.simstadt.nf4j.async
;
import
java.util.ArrayList
;
import
java.util.List
;
import
eu.simstadt.nf4j.ExportJobDescriptor
;
/**
* 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.
*
* @author Marcel Bruse
*/
public
class
ExportJobDescription
implements
ExportJobDescriptor
{
private
static
final
String
DEFAULT_ACCOUNT
=
"Marcel"
;
private
static
final
String
DEFAULT_XMETA_DATA
=
"0"
;
private
static
final
String
DEFAULT_CALIBRATION
=
"0"
;
private
static
final
String
DEFAULT_PRODUCT
=
"WUDEV"
;
private
static
final
String
DEFAULT_ZIP_RESULT
=
"0"
;
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_NO_DATA_MASK
=
"0"
;
private
static
final
String
DEFAULT_ECK
=
"0"
;
private
static
final
String
DEFAULT_COL
=
"0"
;
private
static
final
String
DEFAULT_EXPORT_META_DATA
=
"1"
;
private
static
final
String
DEFAULT_FORMAT
=
"CityGML"
;
private
static
final
String
DEFAULT_SCALE
=
"10000.0"
;
private
static
final
String
DEFAULT_RESOLUTION
=
"100.0"
;
private
static
final
String
DEFAULT_MERGE_MAP_SHEETS
=
"off"
;
private
static
final
String
DEFAULT_TILE1ASGN
=
"false"
;
private
static
final
String
DEFAULT_SRS
=
"31467"
;
private
static
final
String
DEFAULT_SINGLE
=
"1"
;
private
static
final
String
DEFAULT_PLOTFRAME
=
"0"
;
private
static
final
String
DEFAULT_PLOT_LABEL_SRS
=
"-1"
;
private
static
final
String
DEFAULT_MONO
=
"0"
;
private
static
final
String
DEFAULT_COLOR
=
"0"
;
private
static
final
String
DEFAULT_JOBNUMBER
=
"GR_EXPORT_CITYGML_LOD1"
;
private
static
final
String
DEFAULT_INITIATOR
=
"2758"
;
private
static
final
String
DEFAULT_LODS
=
"12"
;
/** The version of the novaFACTORY XML export job format. */
public
static
final
String
EXPORT_JOB_VERSION
=
"1.0.0"
;
/** TODO: Who or what is this initiator? */
private
String
initiator
;
/** The number of the job which can actually be a string. */
private
String
jobnumber
;
/** The account or user for which a job should be executed. */
private
String
account
;
/** The product for which a job should be executed. */
private
String
product
;
/** Color attribute of the layers tag. */
private
String
color
;
/** The mono attribute of the layers tag. */
private
String
mono
;
/** The plotLabelSrs flag of the layers tag. */
private
String
plotLabelSrs
;
/** The plotframe flag of the layers tag. */
private
String
plotframe
;
/** The single flag of the layers tag. */
private
String
single
;
/** The layers which will be involved in a job. */
private
ArrayList
<
Layer
>
layerList
=
new
ArrayList
<>();
/** The SRS for the job. */
private
String
srs
;
/** The merge_mapsheets attribute of the extent tag. */
private
String
mergeMapsheets
;
/** The tile1asgn attribute of the extent tag. */
private
String
tile1asgn
;
/** The unit tag of the extent. */
private
ArrayList
<
Unit
>
unitList
=
new
ArrayList
<>();
/** The polygon of the selected region which should be exported. */
ArrayList
<
Coord
>
regionPolygon
=
new
ArrayList
<>();
/** The resolution for the job. */
private
String
resolution
;
/** The scale for the job. */
private
String
scale
;
/** The format for the job. E.g. CityGML. */
private
String
format
;
/** The exportmetadata flag for the job. */
private
String
exportmetadata
;
/** The col attribute of the addfile tag. */
private
String
col
;
/** The eck attribute of the addfile tag. */
private
String
eck
;
/** The usenodatamask flag for the job. */
private
String
usenodatamask
;
/** The usepdctborderpoly flag for the job. */
private
String
usepdctborderpoly
;
/** The dhkresolvereferences flag for the job. */
private
String
dhkresolvereferences
;
/** Flag for zipped results. */
private
String
zipresult
;
/** The calibration attribute of the exportmetadata tag. */
private
String
calibration
;
/** The xmetadata attribute of the exportmetadata tag. */
private
String
xmetadata
;
/** 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.
*/
private
String
lods
;
public
String
getInitiator
()
{
return
initiator
;
}
public
void
setInitiator
(
String
initiator
)
{
this
.
initiator
=
initiator
;
}
public
String
getJobnumber
()
{
return
jobnumber
;
}
public
void
setJobnumber
(
String
jobnumber
)
{
this
.
jobnumber
=
jobnumber
;
}
public
String
getAccount
()
{
return
account
;
}
public
void
setAccount
(
String
account
)
{
this
.
account
=
account
;
}
public
String
getProduct
()
{
return
product
;
}
public
void
setProduct
(
String
product
)
{
this
.
product
=
product
;
}
public
String
getColor
()
{
return
color
;
}
public
void
setColor
(
String
color
)
{
this
.
color
=
color
;
}
public
String
getMono
()
{
return
mono
;
}
public
void
setMono
(
String
mono
)
{
this
.
mono
=
mono
;
}
public
String
getPlotLabelSrs
()
{
return
plotLabelSrs
;
}
public
void
setPlotLabelSrs
(
String
plotLabelSrs
)
{
this
.
plotLabelSrs
=
plotLabelSrs
;
}
public
String
getPlotframe
()
{
return
plotframe
;
}
public
void
setPlotframe
(
String
plotframe
)
{
this
.
plotframe
=
plotframe
;
}
public
String
getSingle
()
{
return
single
;
}
public
void
setSingle
(
String
single
)
{
this
.
single
=
single
;
}
public
ArrayList
<
Layer
>
getLayerList
()
{
return
layerList
;
}
public
void
setLayerList
(
ArrayList
<
Layer
>
layerList
)
{
this
.
layerList
=
layerList
;
}
public
void
addLayer
(
Layer
layer
)
{
layerList
.
add
(
layer
);
}
public
String
getSrs
()
{
return
srs
;
}
public
void
setSrs
(
String
srs
)
{
this
.
srs
=
srs
;
}
public
String
getMergeMapsheets
()
{
return
mergeMapsheets
;
}
public
void
setMergeMapsheets
(
String
mergeMapsheets
)
{
this
.
mergeMapsheets
=
mergeMapsheets
;
}
public
ArrayList
<
Unit
>
getUnitList
()
{
return
unitList
;
}
public
void
addUnit
(
Unit
unit
)
{
unitList
.
add
(
unit
);
}
public
String
getTile1asgn
()
{
return
tile1asgn
;
}
public
void
setTile1asgn
(
String
tile1asgn
)
{
this
.
tile1asgn
=
tile1asgn
;
}
public
String
getResolution
()
{
return
resolution
;
}
public
void
setResolution
(
String
resolution
)
{
this
.
resolution
=
resolution
;
}
public
String
getScale
()
{
return
scale
;
}
public
void
setScale
(
String
scale
)
{
this
.
scale
=
scale
;
}
public
String
getFormat
()
{
return
format
;
}
public
void
setFormat
(
String
format
)
{
this
.
format
=
format
;
}
public
String
getExportmetadata
()
{
return
exportmetadata
;
}
public
void
setExportmetadata
(
String
exportmetadata
)
{
this
.
exportmetadata
=
exportmetadata
;
}
public
String
getCol
()
{
return
col
;
}
public
void
setCol
(
String
col
)
{
this
.
col
=
col
;
}
public
String
getEck
()
{
return
eck
;
}
public
void
setEck
(
String
eck
)
{
this
.
eck
=
eck
;
}
public
String
getUsenodatamask
()
{
return
usenodatamask
;
}
public
void
setUsenodatamask
(
String
usenodatamask
)
{
this
.
usenodatamask
=
usenodatamask
;
}
public
String
getUsepdctborderpoly
()
{
return
usepdctborderpoly
;
}
public
void
setUsepdctborderpoly
(
String
usepdctborderpoly
)
{
this
.
usepdctborderpoly
=
usepdctborderpoly
;
}
public
String
getDhkresolvereferences
()
{
return
dhkresolvereferences
;
}
public
void
setDhkresolvereferences
(
String
dhkresolvereferences
)
{
this
.
dhkresolvereferences
=
dhkresolvereferences
;
}
public
String
getZipresult
()
{
return
zipresult
;
}
public
void
setZipresult
(
String
zipresult
)
{
this
.
zipresult
=
zipresult
;
}
public
String
getCalibration
()
{
return
calibration
;
}
public
void
setCalibration
(
String
calibration
)
{
this
.
calibration
=
calibration
;
}
public
String
getXmetadata
()
{
return
xmetadata
;
}
public
void
setXmetadata
(
String
xmetadata
)
{
this
.
xmetadata
=
xmetadata
;
}
public
String
getLODs
()
{
return
lods
;
}
public
void
setLODs
(
String
lods
)
{
this
.
lods
=
lods
;
}
@Override
public
String
supportsJobVersion
()
{
return
EXPORT_JOB_VERSION
;
}
/**
* 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.
*/
public
void
addCoordinateToRegionPolygon
(
Coord
coordinate
)
{
regionPolygon
.
add
(
coordinate
);
}
/**
* Sets the whole region polygon for an export job.
*
* @param regionPolygon The region polygon to be used for an export job.
*/
public
void
setRegionPolygon
(
List
<
Coord
>
regionPolygon
)
{
this
.
regionPolygon
.
clear
();
this
.
regionPolygon
.
addAll
(
regionPolygon
);
}
/**
* @return Returns the region polygon.
*/
public
ArrayList
<
Coord
>
getRegionPolygon
()
{
return
regionPolygon
;
}
/**
* @return Returns true, if all mandatory elements and attributes are set. Otherwise, false.
*/
@Override
public
boolean
isValid
()
{
if
((
regionPolygon
.
isEmpty
()
&&
(
unitList
.
isEmpty
()))
||
layerList
.
isEmpty
()
||
initiator
.
isEmpty
()
||
account
.
isEmpty
()
||
jobnumber
.
isEmpty
()
||
product
.
isEmpty
()
||
color
.
isEmpty
()
||
mono
.
isEmpty
()
||
plotLabelSrs
.
isEmpty
()
||
plotframe
.
isEmpty
()
||
single
.
isEmpty
()
||
srs
.
isEmpty
()
||
resolution
.
isEmpty
()
||
scale
.
isEmpty
()
||
format
.
isEmpty
()
||
exportmetadata
.
isEmpty
()
||
col
.
isEmpty
()
||
eck
.
isEmpty
()
||
lods
.
isEmpty
())
{
return
false
;
}
else
{
return
true
;
}
}
public
static
ExportJobDescription
getDefaultDescriptor
()
{
ExportJobDescription
descriptor
=
new
ExportJobDescription
();
descriptor
.
setInitiator
(
DEFAULT_INITIATOR
);
descriptor
.
setJobnumber
(
DEFAULT_JOBNUMBER
);
descriptor
.
setAccount
(
DEFAULT_ACCOUNT
);
descriptor
.
setProduct
(
DEFAULT_PRODUCT
);
descriptor
.
setColor
(
DEFAULT_COLOR
);
descriptor
.
setMono
(
DEFAULT_MONO
);
descriptor
.
setPlotLabelSrs
(
DEFAULT_PLOT_LABEL_SRS
);
descriptor
.
setPlotframe
(
DEFAULT_PLOTFRAME
);
descriptor
.
setSingle
(
DEFAULT_SINGLE
);
descriptor
.
setSrs
(
DEFAULT_SRS
);
descriptor
.
setMergeMapsheets
(
DEFAULT_MERGE_MAP_SHEETS
);
descriptor
.
setTile1asgn
(
DEFAULT_TILE1ASGN
);
descriptor
.
setResolution
(
DEFAULT_RESOLUTION
);
descriptor
.
setScale
(
DEFAULT_SCALE
);
descriptor
.
setFormat
(
DEFAULT_FORMAT
);
descriptor
.
setExportmetadata
(
DEFAULT_EXPORT_META_DATA
);
descriptor
.
setCol
(
DEFAULT_COL
);
descriptor
.
setEck
(
DEFAULT_ECK
);
descriptor
.
setUsenodatamask
(
DEFAULT_USE_NO_DATA_MASK
);
descriptor
.
setUsepdctborderpoly
(
DEFAULT_USE_PDCT_BORDER_POLY
);
descriptor
.
setDhkresolvereferences
(
DEFAULT_DHK_RESOLVE_REFERENCES
);
descriptor
.
setZipresult
(
DEFAULT_ZIP_RESULT
);
descriptor
.
setCalibration
(
DEFAULT_CALIBRATION
);
descriptor
.
setXmetadata
(
DEFAULT_XMETA_DATA
);
descriptor
.
setLODs
(
DEFAULT_LODS
);
return
descriptor
;
}
}
src/eu/simstadt/nf4j/async/HTTPConnection.java
View file @
5a0bec3d
package
eu.simstadt.nf4j.async
;
import
java.io.BufferedReader
;
import
java.io.File
;
import
java.io.FileOutputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStreamReader
;
import
java.io.OutputStream
;
import
java.io.OutputStreamWriter
;
import
java.io.PrintWriter
;
import
java.io.StringReader
;
import
java.io.UnsupportedEncodingException
;
import
java.net.HttpURLConnection
;
import
java.net.MalformedURLException
;
import
java.net.URL
;
import
java.nio.file.Files
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.Objects
;
import
java.util.Optional
;
import
javax.xml.parsers.ParserConfigurationException
;
import
javax.xml.parsers.SAXParser
;
import
javax.xml.parsers.SAXParserFactory
;
import
org.xml.sax.InputSource
;
import
org.xml.sax.SAXException
;
import
eu.simstadt.nf4j.Connector
;
import
eu.simstadt.nf4j.FailedTransmissionException
;
import
eu.simstadt.nf4j.InvalidJobDescriptorException
;
import
eu.simstadt.nf4j.Job
;
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
* technical details about the NFConnector interface @see NFConnector.
*
* Please note, that this connector doesn't act asynchronously. This connector is safe, but will block your main
* application. You may rather want to use the asynchronous job implementations.
*
* @author Marcel Bruse
*
* @param <I> The import job descriptor implementation for this connector.
* @param <E> The export job descriptor implementation for this connector.
*/
public
class
HTTPConnection
implements
Connector
<
AsyncImportJob
,
AsyncExportJob
>
{
/** Supported version of the novaFACTORY. */
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. */
public
static
final
String
DEFAULT_CONTEXT
=
"novaFACTORY"
;
/** The default port number of the nF web application. */
public
static
final
int
DEFAULT_PORT
=
80
;
/** The default protocol for requests. */
public
static
final
String
DEFAULT_PROTOCOL
=
"http"
;
/** The standard char set for requests. */
public
static
final
String
DEFAULT_CHARSET
=
"UTF-8"
;
/** The standard line separator required for file transmission via multipart/form-data. */
public
static
final
String
CRLF
=
"\r\n"
;
/** The name of nF's remote order servlet. */
public
static
final
String
REMOTE_ORDER_SERVLET
=
"RemoteOrder"
;
/** The name of nF's remote status servlet. */
public
static
final
String
REMOTE_STATUS_SERVLET
=
"RemoteStatus"
;
/** The name of nF's remote import servlet. */
public
static
final
String
REMOTE_IMPORT_SERVLET
=
"RemoteImport"
;
/** Default parser error message. */
public
static
final
String
XML_PARSER_ERROR
=
"Request failed due to an unknown XML parser error."
;
/** Default IO exception occurred message. */
public
static
final
String
IO_EXCEPTION_OCCURRED
=
"Request failed due to an IO exception. Check the host and port."
;
/** Default malformed URL message. */
public
static
final
String
MALFORMED_URL
=
"Request failed due to a malformed URL."
;
/** Default unknown error message. */
public
static
final
String
UNKNOWN_ERROR_OCCURRED
=
"An unknown error occurred."
;
/** The server or host name of the nF server. */
private
String
server
;
/** The port of the nF server. */
private
int
port
;
/** The protocol of the data connection. */
private
String
protocol
;
/** The context of the nF web application. It is part of any request URL directed at the nF server. */
private
String
context
;
/**
* Constructs your Connector instance.
*
* @param server The host name of the nF server with which you want to establish a data connection.
*/
public
HTTPConnection
(
String
server
)
{
this
(
server
,
DEFAULT_PORT
,
DEFAULT_CONTEXT
,
DEFAULT_PROTOCOL
);
}
/**
* Constructs your Connector instance.
*
* @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 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
)
{
this
.
server
=
server
;
this
.
port
=
port
;
this
.
context
=
context
;
this
.
protocol
=
protocol
;
}
/**
* 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.
*
* 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
* compatibility.
*
* @return The supported version of the novaFACTORY.
*/
@Override
public
String
supportsNFVersion
()
{
return
NOVA_FACTORY_VERSION
;
}
/**
* 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.
* @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.
*/
@Override
public
AsyncExportJob
requestExportJob
(
int
jobId
)
throws
FailedTransmissionException
{
AsyncExportJob
result
=
new
AsyncExportJob
(
jobId
,
this
);
try
{
List
<
String
>
parameters
=
Arrays
.
asList
(
buildParameter
(
"jobid"
,
jobId
));
getJobFromResponse
(
result
,
getResponse
(
buildURL
(
REMOTE_STATUS_SERVLET
,
parameters
)));
}
catch
(
MalformedURLException
ex
)
{
result
.
getStatus
().
setMessage
(
MALFORMED_URL
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
IOException
ex
)
{
result
.
getStatus
().
setMessage
(
IO_EXCEPTION_OCCURRED
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
ParserConfigurationException
|
SAXException
ex
)
{
result
.
getStatus
().
setMessage
(
XML_PARSER_ERROR
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
return
result
;
}
/**
* 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.
* @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.
*/
@Override
public
AsyncImportJob
requestImportJob
(
int
jobId
)
throws
FailedTransmissionException
{
AsyncImportJob
result
=
new
AsyncImportJob
(
jobId
,
this
);
try
{
List
<
String
>
parameters
=
Arrays
.
asList
(
buildParameter
(
"jobid"
,
jobId
),
buildParameter
(
"request"
,
"status"
));
getJobFromResponse
(
result
,
getResponse
(
buildURL
(
REMOTE_IMPORT_SERVLET
,
parameters
)));
}
catch
(
MalformedURLException
ex
)
{
result
.
getStatus
().
setMessage
(
MALFORMED_URL
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
IOException
ex
)
{
result
.
getStatus
().
setMessage
(
IO_EXCEPTION_OCCURRED
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
ParserConfigurationException
|
SAXException
ex
)
{
result
.
getStatus
().
setMessage
(
XML_PARSER_ERROR
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
return
result
;
}
/**
* 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.
* @return A file handle to the result of the nF export job.
*/
@Override
public
File
requestExportJobResult
(
AsyncExportJob
job
)
throws
FailedTransmissionException
{
if
(!
job
.
hasFinished
())
{
throw
new
FailedTransmissionException
(
"Job is not finished yet!"
);
}
File
result
=
null
;
try
{
List
<
String
>
parameters
=
Arrays
.
asList
(
buildParameter
(
"request"
,
"downloadJob"
),
buildParameter
(
"mode"
,
0
),
buildParameter
(
"jobId"
,
job
.
getId
()));
result
=
downloadFile
(
buildURL
(
REMOTE_ORDER_SERVLET
,
parameters
));
}
catch
(
MalformedURLException
ex
)
{
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
IOException
ex
)
{
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
return
result
;
}
/**
* Builds a simple parameter string for a HTTP GET request string.
*
* @param key The parameter name.
* @param value The value of the parameter.
* @return The HTTP GET request parameter as concatenated key and value.
*/
private
String
buildParameter
(
String
key
,
Object
value
)
{
return
key
+
"="
+
value
;
}
/**
* 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 parameters List of parameters to be send to the servlet.
* @return The built URL instance.
* @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
{
String
url
=
String
.
format
(
"%s://%s:%s/%s/%s"
,
protocol
,
server
,
port
,
context
,
servlet
);
if
(
Objects
.
nonNull
(
parameters
)
&&
!
parameters
.
isEmpty
())
{
url
+=
"?"
+
String
.
join
(
"&"
,
parameters
);
}
return
new
URL
(
url
);
}
/**
* 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
* about the usage of nF servlets.
* @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
* instance.
*/
private
String
getResponse
(
URL
url
)
throws
UnsupportedEncodingException
,
IOException
{
HttpURLConnection
httpConnection
=
(
HttpURLConnection
)
url
.
openConnection
();
httpConnection
.
setRequestProperty
(
"Accept-Charset"
,
DEFAULT_CHARSET
);
return
getResponse
(
httpConnection
);
}
/**
* 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
* of the nF servlets.
* @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 IOException You will get some of this, if anything goes wrong with your data connection to your nF
* instance.
*/
private
String
getResponse
(
HttpURLConnection
httpConnection
)
throws
UnsupportedEncodingException
,
IOException
{
String
xml
=
""
;
if
(
httpConnection
.
getResponseCode
()
==
HttpURLConnection
.
HTTP_OK
)
{
String
contentType
=
httpConnection
.
getHeaderField
(
"Content-Type"
);
String
charset
=
null
;
for
(
String
param
:
contentType
.
replace
(
" "
,
""
).
split
(
";"
))
{
if
(
param
.
startsWith
(
"charset="
))
{
charset
=
param
.
split
(
"="
,
2
)[
1
];
break
;
}
}
InputStream
response
=
httpConnection
.
getInputStream
();
if
(
Objects
.
nonNull
(
charset
))
{
BufferedReader
reader
=
new
BufferedReader
(
new
InputStreamReader
(
response
,
charset
));
String
line
;
while
((
line
=
reader
.
readLine
())
!=
null
)
{
xml
+=
line
;
}
}
response
.
close
();
}
else
{
throw
new
IOException
();
}
return
xml
;
}
/**
* 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
* 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.
*
* @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
* report in the response.
* @throws ParserConfigurationException Something went wrong.
* @throws SAXException Some parse error.
* @throws IOException Some parse error.
*/
private
void
getJobFromResponse
(
Job
job
,
String
xml
)
throws
ParserConfigurationException
,
SAXException
,
IOException
,
FailedTransmissionException
{
SAXParserFactory
saxFactory
=
SAXParserFactory
.
newInstance
();
SAXParser
parser
=
saxFactory
.
newSAXParser
();
StringReader
reader
=
new
StringReader
(
xml
);
ReportHandler
handler
=
new
ReportHandler
();
parser
.
parse
(
new
InputSource
(
reader
),
handler
);
if
(
Objects
.
nonNull
(
handler
.
statusId
))
{
job
.
setStatusForCode
(
handler
.
statusId
);
}
if
(
Objects
.
nonNull
(
handler
.
jobId
))
{
job
.
setId
(
handler
.
jobId
);
}
if
(
Objects
.
nonNull
(
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.
*
* @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
* to errors).
* @throws IOException Something went wrong with your data connection. Check your server and the port.
*/
private
File
downloadFile
(
URL
url
)
throws
IOException
{
File
handle
=
null
;
HttpURLConnection
httpConnection
=
(
HttpURLConnection
)
url
.
openConnection
();
httpConnection
.
setRequestProperty
(
"Accept-Charset"
,
DEFAULT_CHARSET
);
if
(
httpConnection
.
getResponseCode
()
==
HttpURLConnection
.
HTTP_OK
)
{
String
filename
=
"result.gml"
;
String
disposition
=
httpConnection
.
getHeaderField
(
"Content-Disposition"
);
if
(
disposition
!=
null
)
{
int
index
=
disposition
.
indexOf
(
"filename="
);
if
(
index
>
-
1
)
{
filename
=
disposition
.
substring
(
index
+
9
,
disposition
.
length
()).
replaceAll
(
"\""
,
""
).
replaceAll
(
";"
,
""
);
}
}
InputStream
inputStream
=
httpConnection
.
getInputStream
();
handle
=
Files
.
createTempDirectory
(
"nfDownload"
).
resolve
(
filename
).
toFile
();
FileOutputStream
outputStream
=
new
FileOutputStream
(
handle
);
int
bytesRead
=
-
1
;
byte
[]
buffer
=
new
byte
[
4096
];
while
((
bytesRead
=
inputStream
.
read
(
buffer
))
!=
-
1
)
{
outputStream
.
write
(
buffer
,
0
,
bytesRead
);
}
outputStream
.
close
();
inputStream
.
close
();
}
else
{
throw
new
IOException
();
}
return
handle
;
}
/**
* 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.
* @return Returns the status of the export job you just sent.
* @throws InvalidJobDescriptorException
*/
@Override
public
void
sendAndUpdateExportJob
(
AsyncExportJob
job
)
throws
InvalidJobDescriptorException
,
FailedTransmissionException
{
JobFileBuilderImpl
jobFileBuilder
=
new
JobFileBuilderImpl
();
File
exportJobFile
=
jobFileBuilder
.
buildExportJobFile
(
job
.
getDescriptor
());
try
{
URL
url
=
buildURL
(
REMOTE_ORDER_SERVLET
,
null
);
HttpURLConnection
connection
=
(
HttpURLConnection
)
url
.
openConnection
();
connection
.
setRequestMethod
(
"POST"
);
connection
.
setRequestProperty
(
"Connection"
,
"Keep-Alive"
);
connection
.
setRequestProperty
(
"Content-Type"
,
"application/x-www-form-urlencoded"
);
connection
.
setDoOutput
(
true
);
// Sends export job file
connection
.
setDoInput
(
true
);
// Expects XML response
connection
.
connect
();
OutputStream
os
=
connection
.
getOutputStream
();
PrintWriter
writer
=
new
PrintWriter
(
new
OutputStreamWriter
(
os
,
DEFAULT_CHARSET
),
true
);
Files
.
copy
(
exportJobFile
.
toPath
(),
os
);
os
.
flush
();
writer
.
append
(
CRLF
).
flush
();
getJobFromResponse
(
job
,
getResponse
(
connection
));
if
(
job
.
getId
()
>
0
)
{
job
.
setStatus
(
JobStatus
.
SENT
,
Optional
.
empty
());
}
else
{
throw
new
FailedTransmissionException
(
"Job didn't receive an id from the nF server."
);
}
}
catch
(
MalformedURLException
ex
)
{
job
.
getStatus
().
setMessage
(
MALFORMED_URL
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
IOException
ex
)
{
job
.
getStatus
().
setMessage
(
IO_EXCEPTION_OCCURRED
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
ParserConfigurationException
|
SAXException
ex
)
{
job
.
getStatus
().
setMessage
(
XML_PARSER_ERROR
);
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
* 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
* 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
*
* <Product>_<Tile>.start
*
* 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:
*
* <Product>_<Tile>_<Level>_<Operation>.gml
*
* 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
* 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.
* @return Returns the job status of your import job.
* @throws FailedTransmissionException
*/
@Override
public
void
sendAndUpdateImportJob
(
AsyncImportJob
job
)
throws
InvalidJobDescriptorException
,
FailedTransmissionException
{
if
(
job
.
getStatus
()
!=
JobStatus
.
LOCAL
)
{
throw
new
FailedTransmissionException
(
"Job cannot be sent twice!"
);
}
JobFileBuilderImpl
jobFileBuilder
=
new
JobFileBuilderImpl
();
File
importJobFile
=
jobFileBuilder
.
buildImportJobFile
(
job
.
getDescriptor
());
try
{
String
product
=
job
.
getDescriptor
().
getProduct
();
List
<
String
>
parameters
=
Arrays
.
asList
(
buildParameter
(
"request"
,
"imp"
),
// trigger import
buildParameter
(
"pdctKrz"
,
product
),
// the nF product to import to
buildParameter
(
"createjob"
,
"1"
));
// force nF to create an import job with a job number
URL
url
=
buildURL
(
REMOTE_IMPORT_SERVLET
,
parameters
);
HttpURLConnection
connection
=
(
HttpURLConnection
)
url
.
openConnection
();
connection
.
setRequestMethod
(
"POST"
);
connection
.
setRequestProperty
(
"Content-Type"
,
"application/zip"
);
connection
.
setRequestProperty
(
"Accept"
,
"*/*"
);
connection
.
setRequestProperty
(
""
,
""
);
connection
.
setDoOutput
(
true
);
// Sends export job file
connection
.
setDoInput
(
true
);
// Expects XML response
connection
.
connect
();
OutputStream
os
=
connection
.
getOutputStream
();
PrintWriter
writer
=
new
PrintWriter
(
new
OutputStreamWriter
(
os
,
DEFAULT_CHARSET
),
true
);
Files
.
copy
(
importJobFile
.
toPath
(),
os
);
os
.
flush
();
writer
.
append
(
CRLF
).
flush
();
getJobFromResponse
(
job
,
getResponse
(
connection
));
if
(
job
.
getId
()
>
0
)
{
job
.
setStatus
(
JobStatus
.
SENT
,
Optional
.
empty
());
}
else
{
throw
new
FailedTransmissionException
(
"Job didn't receive an id from the nF server."
);
}
}
catch
(
MalformedURLException
ex
)
{
job
.
getStatus
().
setMessage
(
MALFORMED_URL
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
IOException
ex
)
{
job
.
getStatus
().
setMessage
(
IO_EXCEPTION_OCCURRED
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
SAXException
|
ParserConfigurationException
ex
)
{
job
.
getStatus
().
setMessage
(
XML_PARSER_ERROR
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
}
package
eu.simstadt.nf4j.async
;
import
java.io.BufferedReader
;
import
java.io.File
;
import
java.io.FileOutputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStreamReader
;
import
java.io.OutputStream
;
import
java.io.OutputStreamWriter
;
import
java.io.PrintWriter
;
import
java.io.StringReader
;
import
java.io.UnsupportedEncodingException
;
import
java.net.HttpURLConnection
;
import
java.net.MalformedURLException
;
import
java.net.URL
;
import
java.nio.file.Files
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.Objects
;
import
java.util.Optional
;
import
javax.xml.parsers.ParserConfigurationException
;
import
javax.xml.parsers.SAXParser
;
import
javax.xml.parsers.SAXParserFactory
;
import
org.xml.sax.InputSource
;
import
org.xml.sax.SAXException
;
import
eu.simstadt.nf4j.Connector
;
import
eu.simstadt.nf4j.FailedTransmissionException
;
import
eu.simstadt.nf4j.InvalidJobDescriptorException
;
import
eu.simstadt.nf4j.Job
;
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
* technical details about the NFConnector interface @see NFConnector.
*
* Please note, that this connector doesn't act asynchronously. This connector is safe, but will block your main
* application. You may rather want to use the asynchronous job implementations.
*
* @author Marcel Bruse
*
* @param <I> The import job descriptor implementation for this connector.
* @param <E> The export job descriptor implementation for this connector.
*/
public
class
HTTPConnection
implements
Connector
<
AsyncImportJob
,
AsyncExportJob
>
{
/** Supported version of the novaFACTORY. */
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. */
public
static
final
String
DEFAULT_CONTEXT
=
"novaFACTORY"
;
/** The default port number of the nF web application. */
public
static
final
int
DEFAULT_PORT
=
80
;
/** The default protocol for requests. */
public
static
final
String
DEFAULT_PROTOCOL
=
"http"
;
/** The standard char set for requests. */
public
static
final
String
DEFAULT_CHARSET
=
"UTF-8"
;
/** The standard line separator required for file transmission via multipart/form-data. */
public
static
final
String
CRLF
=
"\r\n"
;
/** The name of nF's remote order servlet. */
public
static
final
String
REMOTE_ORDER_SERVLET
=
"RemoteOrder"
;
/** The name of nF's remote status servlet. */
public
static
final
String
REMOTE_STATUS_SERVLET
=
"RemoteStatus"
;
/** The name of nF's remote import servlet. */
public
static
final
String
REMOTE_IMPORT_SERVLET
=
"RemoteImport"
;
/** Default parser error message. */
public
static
final
String
XML_PARSER_ERROR
=
"Request failed due to an unknown XML parser error."
;
/** Default IO exception occurred message. */
public
static
final
String
IO_EXCEPTION_OCCURRED
=
"Request failed due to an IO exception. Check the host and port."
;
/** Default malformed URL message. */
public
static
final
String
MALFORMED_URL
=
"Request failed due to a malformed URL."
;
/** Default unknown error message. */
public
static
final
String
UNKNOWN_ERROR_OCCURRED
=
"An unknown error occurred."
;
/** The server or host name of the nF server. */
private
String
server
;
/** The port of the nF server. */
private
int
port
;
/** The protocol of the data connection. */
private
String
protocol
;
/** The context of the nF web application. It is part of any request URL directed at the nF server. */
private
String
context
;
/**
* Constructs your Connector instance.
*
* @param server The host name of the nF server with which you want to establish a data connection.
*/
public
HTTPConnection
(
String
server
)
{
this
(
server
,
DEFAULT_PORT
,
DEFAULT_CONTEXT
,
DEFAULT_PROTOCOL
);
}
/**
* Constructs your Connector instance.
*
* @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 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
)
{
this
.
server
=
server
;
this
.
port
=
port
;
this
.
context
=
context
;
this
.
protocol
=
protocol
;
}
/**
* 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.
*
* 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
* compatibility.
*
* @return The supported version of the novaFACTORY.
*/
@Override
public
String
supportsNFVersion
()
{
return
NOVA_FACTORY_VERSION
;
}
/**
* 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.
* @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.
*/
@Override
public
AsyncExportJob
requestExportJob
(
int
jobId
)
throws
FailedTransmissionException
{
AsyncExportJob
result
=
new
AsyncExportJob
(
jobId
,
this
);
try
{
List
<
String
>
parameters
=
Arrays
.
asList
(
buildParameter
(
"jobid"
,
jobId
));
getJobFromResponse
(
result
,
getResponse
(
buildURL
(
REMOTE_STATUS_SERVLET
,
parameters
)));
}
catch
(
MalformedURLException
ex
)
{
result
.
getStatus
().
setMessage
(
MALFORMED_URL
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
IOException
ex
)
{
result
.
getStatus
().
setMessage
(
IO_EXCEPTION_OCCURRED
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
ParserConfigurationException
|
SAXException
ex
)
{
result
.
getStatus
().
setMessage
(
XML_PARSER_ERROR
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
return
result
;
}
/**
* 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.
* @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.
*/
@Override
public
AsyncImportJob
requestImportJob
(
int
jobId
)
throws
FailedTransmissionException
{
AsyncImportJob
result
=
new
AsyncImportJob
(
jobId
,
this
);
try
{
List
<
String
>
parameters
=
Arrays
.
asList
(
buildParameter
(
"jobid"
,
jobId
),
buildParameter
(
"request"
,
"status"
));
getJobFromResponse
(
result
,
getResponse
(
buildURL
(
REMOTE_IMPORT_SERVLET
,
parameters
)));
}
catch
(
MalformedURLException
ex
)
{
result
.
getStatus
().
setMessage
(
MALFORMED_URL
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
IOException
ex
)
{
result
.
getStatus
().
setMessage
(
IO_EXCEPTION_OCCURRED
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
ParserConfigurationException
|
SAXException
ex
)
{
result
.
getStatus
().
setMessage
(
XML_PARSER_ERROR
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
return
result
;
}
/**
* 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.
* @return A file handle to the result of the nF export job.
*/
@Override
public
File
requestExportJobResult
(
AsyncExportJob
job
)
throws
FailedTransmissionException
{
if
(!
job
.
hasFinished
())
{
throw
new
FailedTransmissionException
(
"Job is not finished yet!"
);
}
File
result
=
null
;
try
{
List
<
String
>
parameters
=
Arrays
.
asList
(
buildParameter
(
"request"
,
"downloadJob"
),
buildParameter
(
"mode"
,
0
),
buildParameter
(
"jobId"
,
job
.
getId
()));
result
=
downloadFile
(
buildURL
(
REMOTE_ORDER_SERVLET
,
parameters
));
}
catch
(
MalformedURLException
ex
)
{
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
IOException
ex
)
{
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
return
result
;
}
/**
* Builds a simple parameter string for a HTTP GET request string.
*
* @param key The parameter name.
* @param value The value of the parameter.
* @return The HTTP GET request parameter as concatenated key and value.
*/
private
String
buildParameter
(
String
key
,
Object
value
)
{
return
key
+
"="
+
value
;
}
/**
* 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 parameters List of parameters to be send to the servlet.
* @return The built URL instance.
* @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
{
String
url
=
String
.
format
(
"%s://%s:%s/%s/%s"
,
protocol
,
server
,
port
,
context
,
servlet
);
if
(
Objects
.
nonNull
(
parameters
)
&&
!
parameters
.
isEmpty
())
{
url
+=
"?"
+
String
.
join
(
"&"
,
parameters
);
}
return
new
URL
(
url
);
}
/**
* 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
* about the usage of nF servlets.
* @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
* instance.
*/
private
String
getResponse
(
URL
url
)
throws
UnsupportedEncodingException
,
IOException
{
HttpURLConnection
httpConnection
=
(
HttpURLConnection
)
url
.
openConnection
();
httpConnection
.
setRequestProperty
(
"Accept-Charset"
,
DEFAULT_CHARSET
);
return
getResponse
(
httpConnection
);
}
/**
* 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
* of the nF servlets.
* @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 IOException You will get some of this, if anything goes wrong with your data connection to your nF
* instance.
*/
private
String
getResponse
(
HttpURLConnection
httpConnection
)
throws
UnsupportedEncodingException
,
IOException
{
String
xml
=
""
;
if
(
httpConnection
.
getResponseCode
()
==
HttpURLConnection
.
HTTP_OK
)
{
String
contentType
=
httpConnection
.
getHeaderField
(
"Content-Type"
);
String
charset
=
null
;
for
(
String
param
:
contentType
.
replace
(
" "
,
""
).
split
(
";"
))
{
if
(
param
.
startsWith
(
"charset="
))
{
charset
=
param
.
split
(
"="
,
2
)[
1
];
break
;
}
}
InputStream
response
=
httpConnection
.
getInputStream
();
if
(
Objects
.
nonNull
(
charset
))
{
BufferedReader
reader
=
new
BufferedReader
(
new
InputStreamReader
(
response
,
charset
));
String
line
;
while
((
line
=
reader
.
readLine
())
!=
null
)
{
xml
+=
line
;
}
}
response
.
close
();
}
else
{
throw
new
IOException
();
}
return
xml
;
}
/**
* 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
* 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.
*
* @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
* report in the response.
* @throws ParserConfigurationException Something went wrong.
* @throws SAXException Some parse error.
* @throws IOException Some parse error.
*/
private
void
getJobFromResponse
(
Job
job
,
String
xml
)
throws
ParserConfigurationException
,
SAXException
,
IOException
,
FailedTransmissionException
{
SAXParserFactory
saxFactory
=
SAXParserFactory
.
newInstance
();
SAXParser
parser
=
saxFactory
.
newSAXParser
();
StringReader
reader
=
new
StringReader
(
xml
);
ReportHandler
handler
=
new
ReportHandler
();
parser
.
parse
(
new
InputSource
(
reader
),
handler
);
if
(
Objects
.
nonNull
(
handler
.
statusId
))
{
job
.
setStatusForCode
(
handler
.
statusId
);
}
if
(
Objects
.
nonNull
(
handler
.
jobId
))
{
job
.
setId
(
handler
.
jobId
);
}
if
(
Objects
.
nonNull
(
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.
*
* @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
* to errors).
* @throws IOException Something went wrong with your data connection. Check your server and the port.
*/
private
File
downloadFile
(
URL
url
)
throws
IOException
{
File
handle
=
null
;
HttpURLConnection
httpConnection
=
(
HttpURLConnection
)
url
.
openConnection
();
httpConnection
.
setRequestProperty
(
"Accept-Charset"
,
DEFAULT_CHARSET
);
if
(
httpConnection
.
getResponseCode
()
==
HttpURLConnection
.
HTTP_OK
)
{
String
filename
=
"result.gml"
;
String
disposition
=
httpConnection
.
getHeaderField
(
"Content-Disposition"
);
if
(
disposition
!=
null
)
{
int
index
=
disposition
.
indexOf
(
"filename="
);
if
(
index
>
-
1
)
{
filename
=
disposition
.
substring
(
index
+
9
,
disposition
.
length
()).
replaceAll
(
"\""
,
""
).
replaceAll
(
";"
,
""
);
}
}
InputStream
inputStream
=
httpConnection
.
getInputStream
();
handle
=
Files
.
createTempDirectory
(
"nfDownload"
).
resolve
(
filename
).
toFile
();
FileOutputStream
outputStream
=
new
FileOutputStream
(
handle
);
int
bytesRead
=
-
1
;
byte
[]
buffer
=
new
byte
[
4096
];
while
((
bytesRead
=
inputStream
.
read
(
buffer
))
!=
-
1
)
{
outputStream
.
write
(
buffer
,
0
,
bytesRead
);
}
outputStream
.
close
();
inputStream
.
close
();
}
else
{
throw
new
IOException
();
}
return
handle
;
}
/**
* 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.
* @return Returns the status of the export job you just sent.
* @throws InvalidJobDescriptorException
*/
@Override
public
void
sendAndUpdateExportJob
(
AsyncExportJob
job
)
throws
InvalidJobDescriptorException
,
FailedTransmissionException
{
JobFileBuilderImpl
jobFileBuilder
=
new
JobFileBuilderImpl
();
File
exportJobFile
=
jobFileBuilder
.
buildExportJobFile
(
job
.
getDescriptor
());
try
{
URL
url
=
buildURL
(
REMOTE_ORDER_SERVLET
,
null
);
HttpURLConnection
connection
=
(
HttpURLConnection
)
url
.
openConnection
();
connection
.
setRequestMethod
(
"POST"
);
connection
.
setRequestProperty
(
"Connection"
,
"Keep-Alive"
);
connection
.
setRequestProperty
(
"Content-Type"
,
"application/x-www-form-urlencoded"
);
connection
.
setDoOutput
(
true
);
// Sends export job file
connection
.
setDoInput
(
true
);
// Expects XML response
connection
.
connect
();
OutputStream
os
=
connection
.
getOutputStream
();
PrintWriter
writer
=
new
PrintWriter
(
new
OutputStreamWriter
(
os
,
DEFAULT_CHARSET
),
true
);
Files
.
copy
(
exportJobFile
.
toPath
(),
os
);
os
.
flush
();
writer
.
append
(
CRLF
).
flush
();
getJobFromResponse
(
job
,
getResponse
(
connection
));
if
(
job
.
getId
()
>
0
)
{
job
.
setStatus
(
JobStatus
.
SENT
,
Optional
.
empty
());
}
else
{
throw
new
FailedTransmissionException
(
"Job didn't receive an id from the nF server."
);
}
}
catch
(
MalformedURLException
ex
)
{
job
.
getStatus
().
setMessage
(
MALFORMED_URL
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
IOException
ex
)
{
job
.
getStatus
().
setMessage
(
IO_EXCEPTION_OCCURRED
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
ParserConfigurationException
|
SAXException
ex
)
{
job
.
getStatus
().
setMessage
(
XML_PARSER_ERROR
);
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
* 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
* 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
*
* <Product>_<Tile>.start
*
* 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:
*
* <Product>_<Tile>_<Level>_<Operation>.gml
*
* 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
* 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.
* @return Returns the job status of your import job.
* @throws FailedTransmissionException
*/
@Override
public
void
sendAndUpdateImportJob
(
AsyncImportJob
job
)
throws
InvalidJobDescriptorException
,
FailedTransmissionException
{
if
(
job
.
getStatus
()
!=
JobStatus
.
LOCAL
)
{
throw
new
FailedTransmissionException
(
"Job cannot be sent twice!"
);
}
JobFileBuilderImpl
jobFileBuilder
=
new
JobFileBuilderImpl
();
File
importJobFile
=
jobFileBuilder
.
buildImportJobFile
(
job
.
getDescriptor
());
try
{
String
product
=
job
.
getDescriptor
().
getProduct
();
List
<
String
>
parameters
=
Arrays
.
asList
(
buildParameter
(
"request"
,
"imp"
),
// trigger import
buildParameter
(
"pdctKrz"
,
product
),
// the nF product to import to
buildParameter
(
"createjob"
,
"1"
));
// force nF to create an import job with a job number
URL
url
=
buildURL
(
REMOTE_IMPORT_SERVLET
,
parameters
);
HttpURLConnection
connection
=
(
HttpURLConnection
)
url
.
openConnection
();
connection
.
setRequestMethod
(
"POST"
);
connection
.
setRequestProperty
(
"Content-Type"
,
"application/zip"
);
connection
.
setRequestProperty
(
"Accept"
,
"*/*"
);
connection
.
setRequestProperty
(
""
,
""
);
connection
.
setDoOutput
(
true
);
// Sends export job file
connection
.
setDoInput
(
true
);
// Expects XML response
connection
.
connect
();
OutputStream
os
=
connection
.
getOutputStream
();
PrintWriter
writer
=
new
PrintWriter
(
new
OutputStreamWriter
(
os
,
DEFAULT_CHARSET
),
true
);
Files
.
copy
(
importJobFile
.
toPath
(),
os
);
os
.
flush
();
writer
.
append
(
CRLF
).
flush
();
getJobFromResponse
(
job
,
getResponse
(
connection
));
if
(
job
.
getId
()
>
0
)
{
job
.
setStatus
(
JobStatus
.
SENT
,
Optional
.
empty
());
}
else
{
throw
new
FailedTransmissionException
(
"Job didn't receive an id from the nF server."
);
}
}
catch
(
MalformedURLException
ex
)
{
job
.
getStatus
().
setMessage
(
MALFORMED_URL
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
IOException
ex
)
{
job
.
getStatus
().
setMessage
(
IO_EXCEPTION_OCCURRED
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
catch
(
SAXException
|
ParserConfigurationException
ex
)
{
job
.
getStatus
().
setMessage
(
XML_PARSER_ERROR
);
throw
new
FailedTransmissionException
(
ex
.
getMessage
());
}
}
}
\ No newline at end of file
Prev
1
2
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment