From 82952470c87f3f8bbb5eb5742f29adb6b2291e76 Mon Sep 17 00:00:00 2001
From: bruse <bruse@2c044af0-2e85-064f-a0c3-7471430cffcd>
Date: Wed, 10 Dec 2014 16:50:00 +0000
Subject: [PATCH] Introduced some convenience methods for the Job class.

---
 src/eu/simstadt/nf4j/ExportJob.java           | 57 ++++++-----
 .../nf4j/ExportJobDescriptorImpl.java         | 74 ++-------------
 src/eu/simstadt/nf4j/ImportJob.java           | 48 +++++-----
 src/eu/simstadt/nf4j/Job.java                 | 74 +++++++++++----
 src/eu/simstadt/nf4j/JobFileBuilderImpl.java  | 22 +++--
 src/eu/simstadt/nf4j/JobStatus.java           | 26 +++--
 src/eu/simstadt/nf4j/Layer.java               | 14 +++
 src/eu/simstadt/nf4j/Main.java                | 60 ++++++++----
 src/eu/simstadt/nf4j/NFConnector.java         | 21 ++---
 src/eu/simstadt/nf4j/NFConnectorImpl.java     | 50 +++++-----
 src/eu/simstadt/nf4j/Unit.java                | 94 +++++++++++++++++++
 11 files changed, 324 insertions(+), 216 deletions(-)
 create mode 100644 src/eu/simstadt/nf4j/Unit.java

diff --git a/src/eu/simstadt/nf4j/ExportJob.java b/src/eu/simstadt/nf4j/ExportJob.java
index 4275c62..018d2f7 100644
--- a/src/eu/simstadt/nf4j/ExportJob.java
+++ b/src/eu/simstadt/nf4j/ExportJob.java
@@ -1,5 +1,6 @@
 package eu.simstadt.nf4j;
 
+import java.io.File;
 import java.util.Objects;
 
 /**
@@ -7,37 +8,33 @@
  * 
  * @author Marcel Bruse
  */
-public class ExportJob extends Job {
-
-	/**
-	 * Initializes a new export job instance with unknown state. This is a convenient method.
-	 * 
-	 * @return Returns a new export job instance with unknown state.
-	 */
-	public static ExportJob getNewInstance() {
-		return new ExportJob(JobStatus.UNKOWN);
-	}
+public class ExportJob extends Job<ExportJobDescriptorImpl> {
 	
 	/**
-	 * Initializes a new export job instance with unknown state and connector instance. This is a 
-	 * convenient method.
+	 * 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 The nF connector which answers status requests for this job.
-	 * @return Returns a new export job instance with unknown state.
+	 * @param connector The job will use this connector to synchronize itself with the nF.
+	 * @param descriptor The description of this job.
 	 */
-	public static ExportJob getNewInstance(NFConnector<?, ?> nFConnector) {
-		ExportJob result = getNewInstance();
-		result.setNFConnector(nFConnector);
-		return result;
+	public ExportJob(ExportJobDescriptorImpl descriptor, NFConnector connector) {
+		super(descriptor, connector);
+		status = JobStatus.LOCAL;
 	}
-	
+
 	/**
-	 * This constructor forces the job to have a defined state.
+	 * 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 status
+	 * @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status
+	 * will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then     
+	 * @param connector The job will use this connector to synchronize itself with the nF.
 	 */
-	public ExportJob(JobStatus status) {
-		super(status);
+	public ExportJob(int id, NFConnector connector) {
+		super(id, connector);
+		status = JobStatus.SENT;
 	}
 	
 	/**
@@ -48,7 +45,7 @@ public ExportJob(JobStatus status) {
 	 */
 	public void updateStatus() throws FailedTransmissionException {
 		if (Objects.nonNull(getNFConnector())) {
-			Job job = getNFConnector().requestExportJob(getJobId());
+			Job<ExportJobDescriptorImpl> job = getNFConnector().requestExportJob(getId());
 			setStatus(job.getStatus());
 		} else {
 			throw new FailedTransmissionException();
@@ -83,5 +80,17 @@ public void setStatusForCode(int statusCode) {
 	public boolean isFinished() {
 		return Objects.nonNull(getStatus()) && getStatus().equals(JobStatus.FINISHED);
 	}
+
+	@Override
+	public void send() throws InvalidJobDescriptorException, FailedTransmissionException {
+		connector.sendAndUpdateExportJob(this);
+	}
+	
+	public File requestExportJobResult() throws FailedTransmissionException {
+		if (!isFinished()) {
+			throw new FailedTransmissionException("Job is not finished yet!");
+		}
+		return connector.requestExportJobResult(id);
+	}
 	
 }
diff --git a/src/eu/simstadt/nf4j/ExportJobDescriptorImpl.java b/src/eu/simstadt/nf4j/ExportJobDescriptorImpl.java
index c80f635..cee5bee 100644
--- a/src/eu/simstadt/nf4j/ExportJobDescriptorImpl.java
+++ b/src/eu/simstadt/nf4j/ExportJobDescriptorImpl.java
@@ -42,14 +42,6 @@ public class ExportJobDescriptorImpl implements ExportJobDescriptor {
 	private static final String DEFAULT_MERGE_MAP_SHEETS = "off";
 	
 	private static final String DEFAULT_TILE1ASGN = "false";
-	
-	private static final String DEFAULT_EXTERIOR = "0";
-	
-	private static final String DEFAULT_FRAME = "0";
-	
-	private static final String DEFAULT_SELECT_MAP_SHEETS = "0";
-	
-	private static final String DEFAULT_SUBDIVISION = "4";
 
 	private static final String DEFAULT_SRS = "31467";
 
@@ -109,20 +101,8 @@ public class ExportJobDescriptorImpl implements ExportJobDescriptor {
 	/** The tile1asgn attribute of the extent tag. */
 	private String tile1asgn;
 	
-	/** The exterior attribute of the unit tag. */	
-	private String exterior;
-	
-	/** The frame attribute of the unit tag. */
-	private String frame;
-	
-	/** The select mapsheet attribute of the unit tag. */
-	private String selectMapsheets;
-	
-	/** The subdivision attribute of the unit tag. */
-	private String subdivision;
-	
 	/** The unit tag of the extent. */
-	private String unit;
+	private ArrayList<Unit> unitList = new ArrayList<>();
 		
 	/** The polygon of the selected region which should be exported. */
 	ArrayList<Coord> regionPolygon = new ArrayList<>();
@@ -263,20 +243,12 @@ public void setMergeMapsheets(String mergeMapsheets) {
 		this.mergeMapsheets = mergeMapsheets;
 	}
 	
-	public String getUnit() {
-		return unit;
+	public ArrayList<Unit> getUnitList() {
+		return unitList;
 	}
 	
-	public void setUnit(String unit) {
-		this.unit = unit;
-	}
-
-	public String getExterior() {
-		return exterior;
-	}
-
-	public void setExterior(String exterior) {
-		this.exterior = exterior;
+	public void addUnit(Unit unit) {
+		unitList.add(unit);
 	}
 
 	public String getTile1asgn() {
@@ -286,30 +258,6 @@ public String getTile1asgn() {
 	public void setTile1asgn(String tile1asgn) {
 		this.tile1asgn = tile1asgn;
 	}
-	
-	public String getFrame() {
-		return frame;
-	}
-
-	public void setFrame(String frame) {
-		this.frame = frame;
-	}
-
-	public String getSelectMapsheets() {
-		return selectMapsheets;
-	}
-
-	public void setSelectMapsheets(String selectMapsheets) {
-		this.selectMapsheets = selectMapsheets;
-	}
-
-	public String getSubdivision() {
-		return subdivision;
-	}
-
-	public void setSubdivision(String subdivision) {
-		this.subdivision = subdivision;
-	}
 
 	public String getResolution() {
 		return resolution;
@@ -443,13 +391,7 @@ public ArrayList<Coord> getRegionPolygon() {
 	 */
 	@Override
 	public boolean isValid() {
-		if ((regionPolygon.isEmpty()
-					&& (unit.isEmpty()
-						|| exterior.isEmpty()
-						|| frame.isEmpty()
-						|| selectMapsheets.isEmpty()
-						|| subdivision.isEmpty()
-					))
+		if ((regionPolygon.isEmpty() && (unitList.isEmpty()))
 				|| layerList.isEmpty()
 				|| initiator.isEmpty()
 				|| account.isEmpty()
@@ -486,11 +428,7 @@ public static ExportJobDescriptorImpl getDefaultDescriptor() {
 		descriptor.setSingle(DEFAULT_SINGLE);
 		descriptor.setSrs(DEFAULT_SRS);
 		descriptor.setMergeMapsheets(DEFAULT_MERGE_MAP_SHEETS);
-		descriptor.setExterior(DEFAULT_EXTERIOR);
 		descriptor.setTile1asgn(DEFAULT_TILE1ASGN);
-		descriptor.setFrame(DEFAULT_FRAME);
-		descriptor.setSelectMapsheets(DEFAULT_SELECT_MAP_SHEETS);
-		descriptor.setSubdivision(DEFAULT_SUBDIVISION);
 		descriptor.setResolution(DEFAULT_RESOLUTION);
 		descriptor.setScale(DEFAULT_SCALE);
 		descriptor.setFormat(DEFAULT_FORMAT);
diff --git a/src/eu/simstadt/nf4j/ImportJob.java b/src/eu/simstadt/nf4j/ImportJob.java
index c216034..696706d 100644
--- a/src/eu/simstadt/nf4j/ImportJob.java
+++ b/src/eu/simstadt/nf4j/ImportJob.java
@@ -7,37 +7,33 @@
  * 
  * @author Marcel Bruse
  */
-public class ImportJob extends Job {
+public class ImportJob extends Job<ImportJobDescriptorImpl> {
 	
 	/**
-	 * Initializes a new import job instance with unknown state. This is a convenient method.
+	 * 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.
 	 * 
-	 * @return Returns a new import job instance with unknown state.
+	 * @param connector The job will use this connector to synchronize itself with the nF.
+	 * @param descriptor The description of this job.
 	 */
-	public static ImportJob getNewInstance() {
-		return new ImportJob(JobStatus.UNKOWN);
+	public ImportJob(ImportJobDescriptorImpl descriptor, NFConnector connector) {
+		super(descriptor, connector);
+		status = JobStatus.LOCAL;
 	}
 	
 	/**
-	 * Initializes a new import job instance with unknown state and connector instance. This is a
-	 * convenient method.
+	 * 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 The nF connector which answers status requests for this job.
-	 * @return Returns a new import job instance with unknown state.
+	 * @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 static ImportJob getNewInstance(NFConnector<?, ?> nFConnector) {
-		ImportJob result = getNewInstance();
-		result.setNFConnector(nFConnector);
-		return result;
-	}
-	
-	/**
-	 * This constructor forces the job to have a defined state.
-	 * 
-	 * @param status
-	 */
-	public ImportJob(JobStatus status) {
-		super(status);
+	public ImportJob(int id, NFConnector connector) {
+		super(id, connector);
+		status = JobStatus.SENT;
 	}
 	
 	/**
@@ -48,7 +44,7 @@ public ImportJob(JobStatus status) {
 	 */
 	public void updateStatus() throws FailedTransmissionException {
 		if (Objects.nonNull(getNFConnector())) {
-			Job job = getNFConnector().requestImportJob(getJobId());
+			Job<ImportJobDescriptorImpl> job = getNFConnector().requestImportJob(getId());
 			setStatus(job.getStatus());
 		} else {
 			throw new FailedTransmissionException();
@@ -92,5 +88,11 @@ public void setStatusForCode(int statusCode) {
 			setStatus(JobStatus.UNKOWN);
 		}
 	}
+
+	@Override
+	public void send() throws InvalidJobDescriptorException,
+			FailedTransmissionException {
+		connector.sendAndUpdateImportJob(this);
+	}
 	
 }
diff --git a/src/eu/simstadt/nf4j/Job.java b/src/eu/simstadt/nf4j/Job.java
index 8138def..72c6213 100644
--- a/src/eu/simstadt/nf4j/Job.java
+++ b/src/eu/simstadt/nf4j/Job.java
@@ -1,30 +1,45 @@
 package eu.simstadt.nf4j;
 
-import java.util.Objects;
-
 /**
  * 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 {
+public abstract class Job<D extends JobDescriptor> {
 	
 	/** The status of the job. There are different states for export and import jobs. */
-	private JobStatus status;
+	protected JobStatus status;
+	
+	/** Every job should have a (valid) job descriptor. */
+	protected D descriptor;
 	
 	/** The id of the job. */
-	private int jobId;
+	protected int id;
 	
 	/** The connection to the nF. */
-	private NFConnector<?, ?> nFConnector;
+	protected NFConnector connector;
 	
 	/**
-	 * This constructor forces the job to have a defined state.
+	 * This constructor forces the job to have a description and a connector instance.
 	 * 
-	 * @param status The initial status of this job.
+	 * @param connector The job will use this connector to synchronize itself with the nF.
+	 * @param descriptor The description of this job.
 	 */
-	public Job(JobStatus status) {
-		this.status = status;
+	public Job(D descriptor, NFConnector connector) {
+		this.descriptor = descriptor;
+		this.connector = connector;
+	}
+	
+	/**
+	 * This constructor forces the job to have a id and a connector instance.
+	 * 
+	 * @param id The job id. If you call updateStatus() and the nF "knows" the job id, then the job status
+	 * will be updated. If you call updateStatus() and the job id is "unkown" on the server side, then     
+	 * @param connector The job will use this connector to synchronize itself with the nF.
+	 */
+	public Job(int id, NFConnector connector) {
+		this.id = id;
+		this.connector = connector;
 	}
 	
 	/**
@@ -45,11 +60,18 @@ protected void setStatus(JobStatus status) {
 		this.status = status;
 	}
 	
+	/**
+	 * @return Returns the description of this job.
+	 */
+	public D getDescriptor() {
+		return descriptor;
+	}
+	
 	/**
 	 * @return Returns the id of this job.
 	 */
-	public int getJobId() {
-		return jobId;
+	public int getId() {
+		return id;
 	}
 	
 	/**
@@ -57,15 +79,15 @@ public int getJobId() {
 	 * 
 	 * @param jobId The job id about to be set.
 	 */
-	public void setJobId(int jobId) {
-		this.jobId = jobId;
+	public void setId(int jobId) {
+		this.id = jobId;
 	}
 	
 	/**
 	 * @return Returns the nF connector of this job.
 	 */
-	public NFConnector<?, ?> getNFConnector() {
-		return nFConnector;
+	public NFConnector getNFConnector() {
+		return connector;
 	}
 
 	/**
@@ -73,13 +95,25 @@ public void setJobId(int jobId) {
 	 * 
 	 * @param nFConnector The connector of this job.
 	 */
-	public void setNFConnector(NFConnector<?, ?> nFConnector) {
-		this.nFConnector = nFConnector;
+	public void setNFConnector(NFConnector nFConnector) {
+		this.connector = nFConnector;
 	}
 	
 	/**
-	 * Connects to the nF and refreshes the status of this job. If there is no nF connector set,
-	 * this operation will throw a FailedTransmissionException.
+	 * This 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. 
 	 */
diff --git a/src/eu/simstadt/nf4j/JobFileBuilderImpl.java b/src/eu/simstadt/nf4j/JobFileBuilderImpl.java
index 7a40117..c298752 100644
--- a/src/eu/simstadt/nf4j/JobFileBuilderImpl.java
+++ b/src/eu/simstadt/nf4j/JobFileBuilderImpl.java
@@ -117,17 +117,19 @@ public File buildExportJobFile(ExportJobDescriptorImpl jobDescriptor) throws Inv
 				extent.setAttribute("merge_mapsheets", jobDescriptor.getMergeMapsheets());
 				root.appendChild(extent);
 				
-				if (!jobDescriptor.getUnit().isEmpty()) {
+				if (!jobDescriptor.getUnitList().isEmpty()) {
 					extent.setAttribute("tile1asgn", jobDescriptor.getTile1asgn());
-					Element unit = doc.createElement("unit");
-					unit.setAttribute("exterior", jobDescriptor.getExterior());
-					unit.setAttribute("frame", jobDescriptor.getFrame());
-					unit.setAttribute("select_mapsheets", jobDescriptor.getSelectMapsheets());
-					unit.setAttribute("subdivision", jobDescriptor.getSubdivision());
-					unit.appendChild(doc.createTextNode(jobDescriptor.getUnit()));
-					extent.appendChild(unit);
+					for (Unit unit : jobDescriptor.getUnitList()) {
+						Element unitElement = doc.createElement("unit");
+						unitElement.setAttribute("exterior", unit.getExterior());
+						unitElement.setAttribute("frame", unit.getFrame());
+						unitElement.setAttribute("select_mapsheets", unit.getSelectMapsheets());
+						unitElement.setAttribute("subdivision", unit.getSubdivision());
+						unitElement.appendChild(doc.createTextNode(unit.getValue()));
+						extent.appendChild(unitElement);						
+					}
 				} else {
-					Element polygon = appendRegionPolygon(doc, jobDescriptor.regionPolygon);
+					Element polygon = createRegionPolygonElement(doc, jobDescriptor.regionPolygon);
 					extent.appendChild(polygon);					
 				}
 				
@@ -276,7 +278,7 @@ public static ProjCoordinate transformCoordinate(ProjCoordinate wgs84Position,
 	 * @param regionPolygon The polygon of the region which has been selected to be exported.
 	 * @return The w3c.dom.Element of the XML export job which describes the region polygon.
 	 */
-	private static Element appendRegionPolygon(Document doc, List<Coord> regionPolygon) {
+	private static Element createRegionPolygonElement(Document doc, List<Coord> regionPolygon) {
 		Element polygon = doc.createElement("polygon");
 		polygon.setAttribute("srs", "31467");
 		CRSFactory f = new CRSFactory();
diff --git a/src/eu/simstadt/nf4j/JobStatus.java b/src/eu/simstadt/nf4j/JobStatus.java
index 49941cd..3725da3 100644
--- a/src/eu/simstadt/nf4j/JobStatus.java
+++ b/src/eu/simstadt/nf4j/JobStatus.java
@@ -6,11 +6,13 @@
  * @author Marcel Bruse
  */
 public enum JobStatus {
-	UNKOWN(0), PENDING(1), RUNNING(2), FAILED(3), FINISHED(4), READY_TO_RUN(5), ERROR(6), WARNING(7), APPROVE(8),
-	REJECT(9),	APPROVE_RUNNING(10), REJECT_RUNNING(11), APPROVE_REJECT_ERROR(12), APPROVE_REJECT_OK(13),
-	IMPORTED_WARNING(14);
+	UNKOWN(0), LOCAL(1), SENT(2), PENDING(3), RUNNING(4), FAILED(5), FINISHED(6), READY_TO_RUN(7), ERROR(8),
+	WARNING(9),	APPROVE(10), REJECT(11), APPROVE_RUNNING(12), REJECT_RUNNING(13), APPROVE_REJECT_ERROR(14),
+	APPROVE_REJECT_OK(15), IMPORTED_WARNING(16);
 	
-	public static final String UNKNOWN_STATUS_MESSAGE = "The state of the job is unknown.";
+	public static final String UNKNOWN_MESSAGE = "The state of the job is unknown.";
+	public static final String LOCAL_MESSAGE = "The job is known locally only. It has not been sent yet.";
+	public static final String 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.";
@@ -24,16 +26,20 @@ public enum JobStatus {
 	private JobStatus(int internalCode) {
 		switch (internalCode) {
 		case 0:
-			message = UNKNOWN_STATUS_MESSAGE; break;
+			message = UNKNOWN_MESSAGE; break;
 		case 1:
-		case 5:
-			message = PENDING_MESSAGE; break;
+			message = LOCAL_MESSAGE; break;
 		case 2:
-			message = RUNNING_MESSAGE; break;
+			message = SENT_MESSAGE; break;
 		case 3:
-		case 6:
-			message = FAILED_MESSAGE; break;
+		case 7:
+			message = PENDING_MESSAGE; break;
 		case 4:
+			message = RUNNING_MESSAGE; break;
+		case 5:
+		case 8:
+			message = FAILED_MESSAGE; break;
+		case 6:
 			message = FINISHED_MESSAGE; break;
 		default:
 			message = "";
diff --git a/src/eu/simstadt/nf4j/Layer.java b/src/eu/simstadt/nf4j/Layer.java
index 51af5d5..b940cb6 100644
--- a/src/eu/simstadt/nf4j/Layer.java
+++ b/src/eu/simstadt/nf4j/Layer.java
@@ -7,6 +7,12 @@
  * @author Marcel Bruse
  */
 public class Layer {
+	
+	private static final String DEFAULT_NAME = "GML";
+	
+	private static final String DEFAULT_PRODUCT = "WU3";
+	
+	private static final String DEFAULT_STYLE = "#000000";
 
 	/** The name of the layer. */
 	private String name;
@@ -83,4 +89,12 @@ public void setStyle(String style) {
 		this.style = style;
 	}
 	
+	public static Layer getDefaultLayer() {
+		Layer layer = new Layer();
+		layer.setName(DEFAULT_NAME);
+		layer.setProduct(DEFAULT_PRODUCT);
+		layer.setStyle(DEFAULT_STYLE);
+		return layer;
+	}
+	
 }
diff --git a/src/eu/simstadt/nf4j/Main.java b/src/eu/simstadt/nf4j/Main.java
index c4ec662..29ebaa2 100644
--- a/src/eu/simstadt/nf4j/Main.java
+++ b/src/eu/simstadt/nf4j/Main.java
@@ -1,27 +1,47 @@
 package eu.simstadt.nf4j;
 
-import java.io.File;
+import java.util.Arrays;
+import java.util.function.Consumer;
 
 public class Main {
-	
-	public static void main(String[] args) {
-////		ImportJobDescriptorImpl desc = ImportJobDescriptorImpl.getDefaultDescriptor();
-////		desc.setProduct("WUDEV");
-////		desc.setLeaf("820");
-////		desc.setCityGMLFile(new File("WUDEV_820_GML.gml"));
-////		desc.addADESchemaFile(new File("energy.xsd"));
-////		System.out.println(desc.getCityGMLFile().canRead());
-//		
-//		NFConnectorImpl connector = new NFConnectorImpl("193.196.136.164");
-//		
-//		try {
-////			ImportJob job = connector.sendImportJobFile(desc);
-//			ImportJob job = connector.requestImportJob(301);
-//			job.updateStatus();
-//			System.out.println(job.getJobId() + ": " + job.getStatus());
-//		}  catch (FailedTransmissionException ex) {
-//			ex.printStackTrace();
-//		}
+
+	public static void main(String[] args) {		
+		
+		ExportJobDescriptorImpl jobDescriptor = ExportJobDescriptorImpl.getDefaultDescriptor();
+		jobDescriptor.setProduct("WU3");
+		
+		Arrays.asList("820", "821", "822", "823", "824").forEach(new Consumer<String>() {
+			@Override
+			public void accept(String unitLabel) {
+				Unit unit = Unit.getDefaultUnit();
+				unit.setValue(unitLabel);
+				jobDescriptor.addUnit(unit);
+			}
+		});
+		
+		Layer layer = Layer.getDefaultLayer();
+		layer.setProduct("WU3");
+		layer.setName("GML");
+		
+		jobDescriptor.addLayer(layer);
+		
+		ExportJob job = new ExportJob(jobDescriptor, new NFConnectorImpl("193.196.136.164"));
+		
+		try {
+			job.send();
+			int i = 1;
+			while(!job.isFinished()) {
+				System.out.println(i++);
+				Thread.sleep(5000l);
+				job.updateStatus();
+				System.out.println(job.getStatus());
+			}
+			job.requestExportJobResult();
+		} catch (FailedTransmissionException | InvalidJobDescriptorException ex) {
+			ex.printStackTrace();
+		} catch (InterruptedException ex) {
+			ex.printStackTrace();
+		}
 	}
 
 }
\ No newline at end of file
diff --git a/src/eu/simstadt/nf4j/NFConnector.java b/src/eu/simstadt/nf4j/NFConnector.java
index bb9ccd6..04d93d8 100644
--- a/src/eu/simstadt/nf4j/NFConnector.java
+++ b/src/eu/simstadt/nf4j/NFConnector.java
@@ -6,11 +6,8 @@
  * NFConnector lets you communicate with your novaFACTORY (nF) server instance.
  * 
  * @author Marcel Bruse
- * 
- * @param <I> The import job descriptor implementation for this connector.
- * @param <E> The export job descriptor implementation for this connector.
  */
-public interface NFConnector<I extends ImportJobDescriptor, E extends ExportJobDescriptor> {
+public interface NFConnector {
 	
 	/**
 	 * Callers of this NFConnector want to know the actual version of the novaFACTORY and the versions of its
@@ -27,12 +24,13 @@
 	/**
 	 * 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 a job descriptor.
-	 * 
-	 * @param jobDescriptor The nF export job description.
-	 * @return The status of the job. Could be pending or failed.
+	 * 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 ExportJob sendExportJobFile(E exportJobDescriptor) 
+	public void sendAndUpdateExportJob(ExportJob job)
 			throws InvalidJobDescriptorException, FailedTransmissionException;
 	
 	/**
@@ -59,10 +57,9 @@ public ExportJob sendExportJobFile(E exportJobDescriptor)
 	 * - '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 id of your import job or -1 if something went wrong.
+	 * @param job The nF import job to be sent. It has to contain a valid description.
 	 */
-	public ImportJob sendImportJobFile(I importJobDescriptor) 
+	public void sendAndUpdateImportJob(ImportJob job) 
 			throws InvalidJobDescriptorException, FailedTransmissionException;
 	
 	/**
diff --git a/src/eu/simstadt/nf4j/NFConnectorImpl.java b/src/eu/simstadt/nf4j/NFConnectorImpl.java
index 275a04e..fc01968 100644
--- a/src/eu/simstadt/nf4j/NFConnectorImpl.java
+++ b/src/eu/simstadt/nf4j/NFConnectorImpl.java
@@ -35,7 +35,7 @@
  * @param <I> The import job descriptor implementation for this connector.
  * @param <E> The export job descriptor implementation for this connector.
  */
-public class NFConnectorImpl implements NFConnector<ImportJobDescriptorImpl, ExportJobDescriptorImpl> {
+public class NFConnectorImpl implements NFConnector {
 
 	/** Supported version of the novaFACTORY. */
 	public static final String NOVA_FACTORY_VERSION = "6.3.1.1";
@@ -136,7 +136,7 @@ public String supportsNFVersion() {
 	 */
 	@Override
 	public ExportJob requestExportJob(int jobId) throws FailedTransmissionException {
-		ExportJob result = ExportJob.getNewInstance(this);
+		ExportJob result = new ExportJob(jobId, this);
 		try {
 			List<String> parameters = Arrays.asList(buildParameter("jobid", jobId));
 			getJobFromResponse(result, getResponse(buildURL(REMOTE_STATUS_SERVLET, parameters)));
@@ -162,7 +162,7 @@ public ExportJob requestExportJob(int jobId) throws FailedTransmissionException
 	 */
 	@Override
 	public ImportJob requestImportJob(int jobId) throws FailedTransmissionException {
-		ImportJob result = ImportJob.getNewInstance(this);
+		ImportJob result = new ImportJob(jobId, this);
 		try {
 			List<String> parameters = Arrays.asList(
 					buildParameter("jobid", jobId),
@@ -295,10 +295,8 @@ private String getResponse(HttpURLConnection httpConnection) throws UnsupportedE
 	 * @throws SAXException Some parse error.
 	 * @throws IOException Some parse error.
 	 */
-	private void getJobFromResponse(Job job, String xml)
+	private void getJobFromResponse(Job<?> job, String xml)
 			throws ParserConfigurationException, SAXException, IOException {
-		JobStatus status = JobStatus.UNKOWN;
-		job.setStatus(status);
 		SAXParserFactory saxFactory = SAXParserFactory.newInstance();
 		SAXParser parser = saxFactory.newSAXParser();
 		StringReader reader = new StringReader(xml);
@@ -308,10 +306,10 @@ private void getJobFromResponse(Job job, String xml)
 			job.setStatusForCode(handler.statusId);
 		}
 		if (Objects.nonNull(handler.serviceException)) {
-			status.setMessage(handler.serviceException);
+			job.getStatus().setMessage(handler.serviceException);
 		}
 		if (Objects.nonNull(handler.jobId)) {
-			job.setJobId(handler.jobId);			
+			job.setId(handler.jobId);
 		}
 	}
 	
@@ -363,11 +361,10 @@ private File downloadFile(URL url) throws IOException {
 	 * @throws InvalidJobDescriptorException 
 	 */
 	@Override
-	public ExportJob sendExportJobFile(ExportJobDescriptorImpl jobDescriptor) 
+	public void sendAndUpdateExportJob(ExportJob job) 
 			throws InvalidJobDescriptorException, FailedTransmissionException {
-		ExportJob result = ExportJob.getNewInstance();
 		JobFileBuilderImpl jobFileBuilder = new JobFileBuilderImpl();
-		File exportJobFile = jobFileBuilder.buildExportJobFile(jobDescriptor);
+		File exportJobFile = jobFileBuilder.buildExportJobFile(job.getDescriptor());
 		try {
 			URL url = buildURL(REMOTE_ORDER_SERVLET, null);
 			HttpURLConnection connection = (HttpURLConnection) url.openConnection();
@@ -382,23 +379,20 @@ public ExportJob sendExportJobFile(ExportJobDescriptorImpl jobDescriptor)
 			Files.copy(exportJobFile.toPath(), os);
 			os.flush();
 			writer.append(CRLF).flush();
-			getJobFromResponse(result, getResponse(connection));
-			// At this line, result will be UNKNOWN, although the job has been enqueued. A separate status request
-			// will be sent in order to query for the actual state of the job. 
-			if (result.getJobId() > 0) {
-				result = requestExportJob(result.getJobId());
-			}
+			getJobFromResponse(job, getResponse(connection));
+			// At this line, the job status will be unknown, although the job has been enqueued by the nF.
+			// A separate status request has to be sent in order to get the actual state of the job. 
+			job.updateStatus();
 		} catch (MalformedURLException ex) {
-			result.getStatus().setMessage(MALFORMED_URL);
+			job.getStatus().setMessage(MALFORMED_URL);
 			throw new FailedTransmissionException(ex.getMessage());
 		} catch (IOException ex) {
-			result.getStatus().setMessage(IO_EXCEPTION_OCCURRED);
+			job.getStatus().setMessage(IO_EXCEPTION_OCCURRED);
 			throw new FailedTransmissionException(ex.getMessage());
 		} catch (ParserConfigurationException | SAXException ex) {
-			result.getStatus().setMessage(XML_PARSER_ERROR);
+			job.getStatus().setMessage(XML_PARSER_ERROR);
 			throw new FailedTransmissionException(ex.getMessage());
 		}
-		return result;
 	}
 
 	/**
@@ -429,11 +423,10 @@ public ExportJob sendExportJobFile(ExportJobDescriptorImpl jobDescriptor)
 	 * @throws FailedTransmissionException 
 	 */
 	@Override
-	public ImportJob sendImportJobFile(ImportJobDescriptorImpl jobDescriptor) 
+	public void sendAndUpdateImportJob(ImportJob job) 
 			throws InvalidJobDescriptorException, FailedTransmissionException {
-		ImportJob result = ImportJob.getNewInstance();
 		JobFileBuilderImpl jobFileBuilder = new JobFileBuilderImpl();
-		File importJobFile = jobFileBuilder.buildImportJobFile(jobDescriptor);
+		File importJobFile = jobFileBuilder.buildImportJobFile(job.getDescriptor());
 		try {
 			List<String> parameters = Arrays.asList(
 					buildParameter("request", "imp"),    // trigger import
@@ -453,18 +446,17 @@ public ImportJob sendImportJobFile(ImportJobDescriptorImpl jobDescriptor)
 			Files.copy(importJobFile.toPath(), os);
 			os.flush();
 			writer.append(CRLF).flush();
-			getJobFromResponse(result, getResponse(connection));
+			getJobFromResponse(job, getResponse(connection));
 		} catch (MalformedURLException ex) {
-			result.getStatus().setMessage(MALFORMED_URL);
+			job.getStatus().setMessage(MALFORMED_URL);
 			throw new FailedTransmissionException(ex.getMessage());
 		} catch (IOException ex) {
-			result.getStatus().setMessage(IO_EXCEPTION_OCCURRED);
+			job.getStatus().setMessage(IO_EXCEPTION_OCCURRED);
 			throw new FailedTransmissionException(ex.getMessage());
 		} catch (SAXException | ParserConfigurationException ex) {
-			result.getStatus().setMessage(XML_PARSER_ERROR);
+			job.getStatus().setMessage(XML_PARSER_ERROR);
 			throw new FailedTransmissionException(ex.getMessage());
 		}
-		return result;
 	}
 	
 }
\ No newline at end of file
diff --git a/src/eu/simstadt/nf4j/Unit.java b/src/eu/simstadt/nf4j/Unit.java
new file mode 100644
index 0000000..19d554b
--- /dev/null
+++ b/src/eu/simstadt/nf4j/Unit.java
@@ -0,0 +1,94 @@
+package eu.simstadt.nf4j;
+
+/**
+ * Units (Blattschnitte) divide regions into sections. For instance, the city of Stuttgart could have the units
+ * "Stg-Mitte", "Stg-West", "Bad Cannstatt", "Heslach", etc.
+ * 
+ * @author Marcel Bruse
+ *
+ */
+public class Unit {
+	
+	private static final String DEFAULT_EXTERIOR = "0";
+	
+	private static final String DEFAULT_FRAME = "0";
+	
+	private static final String DEFAULT_SELECT_MAP_SHEETS = "0";
+	
+	private static final String DEFAULT_SUBDIVISION = "4";
+	
+	/** The exterior attribute of the unit tag. */	
+	private String exterior;
+	
+	/** The frame attribute of the unit tag. */
+	private String frame;
+	
+	/** The select mapsheet attribute of the unit tag. */
+	private String selectMapsheets;
+	
+	/** The subdivision attribute of the unit tag. */
+	private String subdivision;
+	
+	/** The actual value of the unit tag. */
+	private String value;
+
+	public String getExterior() {
+		return exterior;
+	}
+
+	public void setExterior(String exterior) {
+		this.exterior = exterior;
+	}
+
+	public String getFrame() {
+		return frame;
+	}
+
+	public void setFrame(String frame) {
+		this.frame = frame;
+	}
+
+	public String getSelectMapsheets() {
+		return selectMapsheets;
+	}
+
+	public void setSelectMapsheets(String selectMapsheets) {
+		this.selectMapsheets = selectMapsheets;
+	}
+
+	public String getSubdivision() {
+		return subdivision;
+	}
+
+	public void setSubdivision(String subdivision) {
+		this.subdivision = subdivision;
+	}
+
+	public String getValue() {
+		return value;
+	}
+
+	public void setValue(String value) {
+		this.value = value;
+	}
+	
+	/**
+	 * @return Returns true, if the unit is valid.
+	 */
+	public boolean isValid() {
+		return !(exterior.isEmpty()
+				|| frame.isEmpty()
+				|| selectMapsheets.isEmpty()
+				|| subdivision.isEmpty());
+	}
+	
+	public static Unit getDefaultUnit() {
+		Unit unit = new Unit();
+		unit.setExterior(DEFAULT_EXTERIOR);
+		unit.setFrame(DEFAULT_FRAME);
+		unit.setSelectMapsheets(DEFAULT_SELECT_MAP_SHEETS);
+		unit.setSubdivision(DEFAULT_SUBDIVISION);
+		return unit;
+	}
+	
+}
-- 
GitLab