You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2015/02/17 12:29:45 UTC

[41/50] [abbrv] incubator-taverna-server git commit: Rewrite code, pass back parsed URs.

Rewrite code, pass back parsed URs.

Project: http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/commit/a1c27cd9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/tree/a1c27cd9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/diff/a1c27cd9

Branch: refs/heads/master
Commit: a1c27cd94e45bde58693df30472e2b34fcf1e07b
Parents: e5bc8be
Author: Donal Fellows <do...@manchester.ac.uk>
Authored: Tue Jul 1 14:18:21 2014 +0100
Committer: Donal Fellows <do...@manchester.ac.uk>
Committed: Tue Jul 1 14:18:21 2014 +0100

----------------------------------------------------------------------
 server-client/pom.xml                           |   5 +
 .../uk/org/taverna/server/client/Connected.java |  20 +
 .../uk/org/taverna/server/client/DirEntry.java  |  39 ++
 .../uk/org/taverna/server/client/Directory.java |  94 ++++
 .../java/uk/org/taverna/server/client/File.java |  95 ++++
 .../uk/org/taverna/server/client/Property.java  |  18 +
 .../java/uk/org/taverna/server/client/Run.java  | 215 ++++++++
 .../uk/org/taverna/server/client/Status.java    |  36 ++
 .../taverna/server/client/TavernaServer.java    | 493 ++-----------------
 .../client/TavernaServerConnectionFactory.java  |  23 +
 .../main/java/org/ogf/usage/JobUsageRecord.java |  23 +-
 11 files changed, 617 insertions(+), 444 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/a1c27cd9/server-client/pom.xml
----------------------------------------------------------------------
diff --git a/server-client/pom.xml b/server-client/pom.xml
index 397f102..bac3ea5 100644
--- a/server-client/pom.xml
+++ b/server-client/pom.xml
@@ -30,6 +30,11 @@
 			<artifactId>tika-core</artifactId>
 			<version>1.5</version>
 		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.server</groupId>
+			<artifactId>server-usagerecord</artifactId>
+			<version>${project.version}</version>
+		</dependency>
 	</dependencies>
 
 	<build>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/a1c27cd9/server-client/src/main/java/uk/org/taverna/server/client/Connected.java
----------------------------------------------------------------------
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/Connected.java b/server-client/src/main/java/uk/org/taverna/server/client/Connected.java
new file mode 100644
index 0000000..263034c
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/Connected.java
@@ -0,0 +1,20 @@
+package uk.org.taverna.server.client;
+
+import uk.org.taverna.server.client.TavernaServer.ClientException;
+import uk.org.taverna.server.client.TavernaServer.ServerException;
+
+import com.sun.jersey.api.client.ClientResponse;
+
+abstract class Connected {
+	void checkError(ClientResponse response) throws ClientException,
+			ServerException {
+		ClientResponse.Status s = response.getClientResponseStatus();
+		if (s.getStatusCode() == 401)
+			throw new TavernaServer.AuthorizationException("not authorized",
+					null);
+		if (s.getStatusCode() >= 500)
+			throw new TavernaServer.ServerException(s.getReasonPhrase(), null);
+		if (s.getStatusCode() >= 400)
+			throw new TavernaServer.ClientException(s.getReasonPhrase(), null);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/a1c27cd9/server-client/src/main/java/uk/org/taverna/server/client/DirEntry.java
----------------------------------------------------------------------
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/DirEntry.java b/server-client/src/main/java/uk/org/taverna/server/client/DirEntry.java
new file mode 100644
index 0000000..267707d
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/DirEntry.java
@@ -0,0 +1,39 @@
+package uk.org.taverna.server.client;
+
+import org.taverna.server.client.wadl.TavernaServer.Root.RunsRunName.Wd.Path2;
+
+import uk.org.taverna.server.client.TavernaServer.ClientException;
+import uk.org.taverna.server.client.TavernaServer.ServerException;
+
+import com.sun.jersey.api.client.ClientResponse;
+
+public abstract class DirEntry extends Connected {
+	final Path2 handle;
+	final String path;
+	final Run run;
+
+	protected DirEntry(Run run, String path) {
+		this.run = run;
+		this.path = path.replaceFirst("/+$", "");
+		this.handle = run.run.wd().path2(this.path);
+	}
+
+	public void delete() throws ClientException, ServerException {
+		checkError(handle.deleteAsXml(ClientResponse.class));
+	}
+
+	String path(ClientResponse response) throws ClientException, ServerException {
+		checkError(response);
+		String[] bits = response.getLocation().getPath().split("/");
+		return concat(bits[bits.length - 1]);
+	}
+
+	String localName() {
+		String[] bits = path.split("/");
+		return bits[bits.length - 1];
+	}
+
+	String concat(String name) {
+		return path + "/" + name.split("/", 2)[0];
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/a1c27cd9/server-client/src/main/java/uk/org/taverna/server/client/Directory.java
----------------------------------------------------------------------
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/Directory.java b/server-client/src/main/java/uk/org/taverna/server/client/Directory.java
new file mode 100644
index 0000000..38dc394
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/Directory.java
@@ -0,0 +1,94 @@
+package uk.org.taverna.server.client;
+
+import static java.io.File.createTempFile;
+import static javax.ws.rs.client.Entity.entity;
+import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipFile;
+
+import org.taverna.server.client.wadl.TavernaServer.Root.RunsRunName.Wd;
+
+import uk.org.taverna.server.client.TavernaServer.ClientException;
+import uk.org.taverna.server.client.TavernaServer.ServerException;
+import uk.org.taverna.server.client.generic.DirectoryEntry;
+import uk.org.taverna.server.client.generic.DirectoryReference;
+import uk.org.taverna.server.client.generic.FileReference;
+import uk.org.taverna.server.client.rest.DirectoryContents;
+import uk.org.taverna.server.client.rest.MakeDirectory;
+import uk.org.taverna.server.client.rest.UploadFile;
+
+import com.sun.jersey.api.client.ClientResponse;
+
+public class Directory extends DirEntry {
+	private final Wd wd;
+
+	Directory(Run run) {
+		super(run, "");
+		this.wd = run.run.wd();
+	}
+
+	Directory(Run run, String path) {
+		super(run, path);
+		this.wd = run.run.wd();
+	}
+
+	public List<DirEntry> list() {
+		List<DirEntry> result = new ArrayList<>();
+		for (DirectoryEntry de : wd.path3(path)
+				.getAsXml(DirectoryContents.class).getDirOrFile())
+			if (de instanceof DirectoryReference)
+				result.add(new Directory(run, de.getValue()));
+			else if (de instanceof FileReference)
+				result.add(new File(run, de.getValue()));
+		return result;
+	}
+
+	public File createFile(String name, byte[] content) throws ClientException,
+			ServerException {
+		UploadFile uf = new UploadFile();
+		uf.setName(name);
+		uf.setValue(content);
+		return new File(run, path(wd.path(path).putAsXml(uf,
+				ClientResponse.class)));
+	}
+
+	public File createFile(String name, java.io.File content)
+			throws ClientException, ServerException {
+		return new File(run, path(wd.path(concat(name)).putOctetStreamAsXml(
+				entity(content, APPLICATION_OCTET_STREAM_TYPE),
+				ClientResponse.class)));
+	}
+
+	public File createFile(String name, URI source) throws ClientException,
+			ServerException {
+		return new File(run, path(wd.path(concat(name)).postTextUriListAsXml(
+				source.toString(), ClientResponse.class)));
+	}
+
+	public Directory createDirectory(String name) throws ClientException,
+			ServerException {
+		MakeDirectory mkdir = new MakeDirectory();
+		mkdir.setName(name);
+		return new Directory(run, path(wd.path(path).putAsXml(mkdir,
+				ClientResponse.class)));
+	}
+
+	public byte[] getZippedContents() {
+		return wd.path3(path).getAsZip(byte[].class);
+	}
+
+	public ZipFile getZip() throws IOException {
+		byte[] contents = getZippedContents();
+		java.io.File tmp = createTempFile(localName(), ".zip");
+		try (OutputStream os = new FileOutputStream(tmp)) {
+			os.write(contents);
+		}
+		return new ZipFile(tmp);
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/a1c27cd9/server-client/src/main/java/uk/org/taverna/server/client/File.java
----------------------------------------------------------------------
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/File.java b/server-client/src/main/java/uk/org/taverna/server/client/File.java
new file mode 100644
index 0000000..0287afb
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/File.java
@@ -0,0 +1,95 @@
+package uk.org.taverna.server.client;
+
+import static java.io.File.createTempFile;
+import static javax.ws.rs.client.Entity.entity;
+import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
+import static org.apache.commons.io.IOUtils.copy;
+import static org.apache.tika.mime.MimeTypes.getDefaultMimeTypes;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+import org.apache.tika.mime.MimeTypeException;
+import org.taverna.server.client.wadl.TavernaServer.Root.RunsRunName.Wd;
+
+import uk.org.taverna.server.client.TavernaServer.ClientException;
+import uk.org.taverna.server.client.TavernaServer.ServerException;
+
+import com.sun.jersey.api.client.ClientHandlerException;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.UniformInterfaceException;
+
+public class File extends DirEntry {
+	private final Wd wd;
+
+	File(Run run, String path) {
+		super(run, path);
+		wd = run.run.wd();
+	}
+
+	public InputStream getAsStream() {
+		return wd.path3(path).getAsOctetStream(InputStream.class);
+	}
+
+	public byte[] get() {
+		return wd.path3(path).getAsOctetStream(byte[].class);
+	}
+
+	public String get(Charset encoding) {
+		return new String(wd.path3(path).getAsOctetStream(byte[].class),
+				encoding);
+	}
+
+	public java.io.File getAsFile() throws ClientHandlerException,
+			UniformInterfaceException, IOException, MimeTypeException,
+			ClientException, ServerException {
+		ClientResponse cr = wd.path3(path).getAsOctetStream(
+				ClientResponse.class);
+		checkError(cr);
+		String[] bits = localName().split("[.]");
+		String ext = getDefaultMimeTypes().forName(
+				cr.getHeaders().getFirst("Content-Type")).getExtension();
+		if (ext == null)
+			ext = bits[bits.length - 1];
+		java.io.File tmp = createTempFile(bits[0], ext);
+		try (OutputStream os = new FileOutputStream(tmp);
+				InputStream is = cr.getEntity(InputStream.class)) {
+			copy(is, os);
+		}
+		return tmp;
+	}
+
+	public void setContents(byte[] newContents) throws ClientException,
+			ServerException {
+		checkError(wd.path(path).putOctetStreamAsXml(newContents,
+				ClientResponse.class));
+	}
+
+	public void setContents(String newContents) throws ClientException,
+			ServerException {
+		checkError(wd.path(path).putOctetStreamAsXml(newContents,
+				ClientResponse.class));
+	}
+
+	public void setContents(String newContents, Charset encoding)
+			throws ClientException, ServerException {
+		checkError(wd.path(path).putOctetStreamAsXml(
+				newContents.getBytes(encoding), ClientResponse.class));
+	}
+
+	public void setContents(InputStream newContents) throws ClientException,
+			ServerException {
+		checkError(wd.path(path).putOctetStreamAsXml(newContents,
+				ClientResponse.class));
+	}
+
+	public void setContents(java.io.File newContents) throws IOException,
+			ClientException, ServerException {
+		checkError(wd.path(path).putOctetStreamAsXml(
+				entity(newContents, APPLICATION_OCTET_STREAM_TYPE),
+				ClientResponse.class));
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/a1c27cd9/server-client/src/main/java/uk/org/taverna/server/client/Property.java
----------------------------------------------------------------------
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/Property.java b/server-client/src/main/java/uk/org/taverna/server/client/Property.java
new file mode 100644
index 0000000..0e6542f
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/Property.java
@@ -0,0 +1,18 @@
+package uk.org.taverna.server.client;
+
+public enum Property {
+	STDOUT("stdout"), STDERR("stderr"), EXIT_CODE("exitcode"), READY_TO_NOTIFY(
+			"readyToNotify"), EMAIL("notificationAddress"), USAGE(
+			"usageRecord");
+
+	private String s;
+
+	private Property(String s) {
+		this.s = s;
+	}
+
+	@Override
+	public String toString() {
+		return s;
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/a1c27cd9/server-client/src/main/java/uk/org/taverna/server/client/Run.java
----------------------------------------------------------------------
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/Run.java b/server-client/src/main/java/uk/org/taverna/server/client/Run.java
new file mode 100644
index 0000000..5c6875e
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/Run.java
@@ -0,0 +1,215 @@
+package uk.org.taverna.server.client;
+
+import static org.joda.time.format.ISODateTimeFormat.dateTime;
+import static org.joda.time.format.ISODateTimeFormat.dateTimeParser;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Date;
+import java.util.List;
+
+import javax.xml.bind.JAXBException;
+
+import org.apache.commons.io.IOUtils;
+import org.joda.time.DateTime;
+import org.ogf.usage.JobUsageRecord;
+import org.taverna.server.client.wadl.TavernaServer.Root.RunsRunName;
+import org.w3c.dom.Element;
+
+import uk.org.taverna.server.client.TavernaServer.ClientException;
+import uk.org.taverna.server.client.TavernaServer.ServerException;
+import uk.org.taverna.server.client.generic.KeyPairCredential;
+import uk.org.taverna.server.client.generic.PasswordCredential;
+import uk.org.taverna.server.client.generic.port.InputPort;
+import uk.org.taverna.server.client.generic.port.OutputPort;
+import uk.org.taverna.server.client.rest.InputDescription;
+import uk.org.taverna.server.client.rest.InputDescription.Value;
+
+import com.sun.jersey.api.client.ClientResponse;
+
+public class Run extends Connected {
+	RunsRunName run;
+
+	Run(TavernaServer server, String value) {
+		run = server.root.runsRunName(value);
+	}
+
+	public String getName() {
+		return run.name().getAsTextPlain(ClientResponse.class)
+				.getEntity(String.class);
+	}
+
+	public void setName(String name) {
+		run.name().putTextPlain(name, String.class);
+	}
+
+	public Date getExpiry() {
+		return dateTimeParser().parseDateTime(
+				run.expiry().getAsTextPlain(String.class)).toDate();
+	}
+
+	public void setExpiry(Date expiryTimestamp) {
+		run.expiry().putTextPlain(
+				dateTime().print(new DateTime(expiryTimestamp)), String.class);
+	}
+
+	public Date getCreate() {
+		String timestamp = run.createTime().getAsTextPlain(String.class);
+		if (timestamp == null || timestamp.trim().isEmpty())
+			return null;
+		return dateTimeParser().parseDateTime(timestamp).toDate();
+	}
+
+	public Date getStart() {
+		String timestamp = run.startTime().getAsTextPlain(String.class);
+		if (timestamp == null || timestamp.trim().isEmpty())
+			return null;
+		return dateTimeParser().parseDateTime(timestamp).toDate();
+	}
+
+	public Date getFinish() {
+		String timestamp = run.finishTime().getAsTextPlain(String.class);
+		if (timestamp == null || timestamp.trim().isEmpty())
+			return null;
+		return dateTimeParser().parseDateTime(timestamp).toDate();
+	}
+
+	public Status getStatus() {
+		return Status.valueOf(run.status().getAsTextPlain(String.class));
+	}
+
+	public void setStatus(Status status) {
+		run.status().putTextPlain(status, String.class);
+	}
+
+	public void start() {
+		setStatus(Status.Operating);
+	}
+
+	public void kill() {
+		setStatus(Status.Finished);
+	}
+
+	public boolean isRunning() {
+		return getStatus() == Status.Operating;
+	}
+
+	public String getStandardOutput() {
+		return run.stdout().getAsTextPlain(String.class);
+	}
+
+	public String getStandardError() {
+		return run.stderr().getAsTextPlain(String.class);
+	}
+
+	public String getLog() {
+		return run.log().getAsTextPlain(String.class);
+	}
+
+	public Integer getExitCode() {
+		String code = run.listeners().name("io")
+				.propertiesPropertyName("exitCode")
+				.getAsTextPlain(String.class);
+		if (code == null || code.trim().isEmpty())
+			return null;
+		return Integer.parseInt(code);
+	}
+
+	public String getProperty(Property prop) {
+		return run.listeners().name("io")
+				.propertiesPropertyName(prop.toString())
+				.getAsTextPlain(String.class);
+	}
+
+	public void setGenerateRunBundle(boolean generateRunBundle) {
+		run.generateProvenance().putTextPlain(generateRunBundle, String.class);
+	}
+
+	public byte[] getRunBundle() {
+		return run.runBundle().getAsVndWf4everRobundleZip(byte[].class);
+	}
+
+	public List<InputPort> getInputs() {
+		return run.input().expected().getAsInputDescriptionXml().getInput();
+	}
+
+	public List<OutputPort> getOutputs() {
+		return run.output().getAsOutputDescriptionXml().getOutput();
+	}
+
+	public void setInput(String name, String value) {
+		Value v = new Value();
+		v.setValue(value);
+		InputDescription idesc = new InputDescription();
+		idesc.setValue(v);
+		run.input().inputName(name).putXmlAsInputDescription(idesc);
+	}
+
+	public void setInput(String name, String value, char listSeparator) {
+		Value v = new Value();
+		v.setValue(value);
+		InputDescription idesc = new InputDescription();
+		idesc.setValue(v);
+		idesc.setListDelimiter(new String(new char[] { listSeparator }));
+		run.input().inputName(name).putXmlAsInputDescription(idesc);
+	}
+
+	public byte[] getWorkflow() {
+		return run.workflow().getAsVndTavernaT2flowXml(byte[].class);
+	}
+
+	// TODO Consider better ways to do this
+	public Element getInteractionFeed() {
+		return run.interaction().getAsAtomXml(Element.class);
+	}
+
+	public Element getInteractionEntry(String id) {
+		return run.interaction().id(id).getAsAtomXml(Element.class);
+	}
+
+	public JobUsageRecord getUsageRecord() throws JAXBException {
+		return JobUsageRecord.unmarshal(run.usage().getAsXml(Element.class));
+	}
+
+	public Directory getWorkingDirectory() {
+		return new Directory(this);
+	}
+
+	public String getOwner() {
+		return run.security().owner().getAsTextPlain(String.class);
+	}
+
+	// TODO permissions
+
+	public void grantPasswordCredential(URI contextService, String username,
+			String password) throws ClientException, ServerException {
+		PasswordCredential pc = new PasswordCredential();
+		pc.setServiceURI(contextService.toString());
+		pc.setUsername(username);
+		pc.setPassword(password);
+		checkError(run.security().credentials()
+				.postXmlAsOctetStream(pc, ClientResponse.class));
+	}
+
+	public void grantKeyCredential(URI contextService, java.io.File source,
+			String unlockPassword, String aliasEntry) throws IOException,
+			ClientException, ServerException {
+		KeyPairCredential kpc = new KeyPairCredential();
+		kpc.setServiceURI(contextService.toString());
+		try (InputStream in = new FileInputStream(source)) {
+			byte[] buffer = new byte[(int) source.length()];
+			IOUtils.read(in, buffer);
+			kpc.setCredentialBytes(buffer);
+		}
+		if (source.getName().endsWith(".p12"))
+			kpc.setFileType("PKCS12");
+		else
+			kpc.setFileType("JKS");
+		kpc.setCredentialName(aliasEntry);
+		kpc.setUnlockPassword(unlockPassword);
+		checkError(run.security().credentials()
+				.postXmlAsOctetStream(kpc, ClientResponse.class));
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/a1c27cd9/server-client/src/main/java/uk/org/taverna/server/client/Status.java
----------------------------------------------------------------------
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/Status.java b/server-client/src/main/java/uk/org/taverna/server/client/Status.java
new file mode 100644
index 0000000..9c375ad
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/Status.java
@@ -0,0 +1,36 @@
+package uk.org.taverna.server.client;
+
+/**
+ * States of a workflow run. They are {@link #Initialized Initialized},
+ * {@link #Operating Operating}, {@link #Stopped Stopped}, and
+ * {@link #Finished Finished}. Conceptually, there is also a
+ * <tt>Destroyed</tt> state, but the workflow run does not exist (and hence
+ * can't have its state queried or set) in that case.
+ * 
+ * @author Donal Fellows
+ */
+public enum Status {
+	/**
+	 * The workflow run has been created, but is not yet running. The run
+	 * will need to be manually moved to {@link #Operating Operating} when
+	 * ready.
+	 */
+	Initialized,
+	/**
+	 * The workflow run is going, reading input, generating output, etc.
+	 * Will eventually either move automatically to {@link #Finished
+	 * Finished} or can be moved manually to {@link #Stopped Stopped} (where
+	 * supported).
+	 */
+	Operating,
+	/**
+	 * The workflow run is paused, and will need to be moved back to
+	 * {@link #Operating Operating} manually.
+	 */
+	Stopped,
+	/**
+	 * The workflow run has ceased; data files will continue to exist until
+	 * the run is destroyed (which may be manual or automatic).
+	 */
+	Finished
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/a1c27cd9/server-client/src/main/java/uk/org/taverna/server/client/TavernaServer.java
----------------------------------------------------------------------
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/TavernaServer.java b/server-client/src/main/java/uk/org/taverna/server/client/TavernaServer.java
index 3ceb208..7c0dcdd 100644
--- a/server-client/src/main/java/uk/org/taverna/server/client/TavernaServer.java
+++ b/server-client/src/main/java/uk/org/taverna/server/client/TavernaServer.java
@@ -1,467 +1,54 @@
 package uk.org.taverna.server.client;
 
-import static java.io.File.createTempFile;
 import static java.nio.file.Files.readAllBytes;
-import static javax.ws.rs.client.Entity.entity;
-import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
-import static org.apache.commons.io.IOUtils.copy;
-import static org.apache.tika.mime.MimeTypes.getDefaultMimeTypes;
-import static org.joda.time.format.ISODateTimeFormat.dateTime;
-import static org.joda.time.format.ISODateTimeFormat.dateTimeParser;
 import static org.taverna.server.client.wadl.TavernaServer.createClient;
 import static org.taverna.server.client.wadl.TavernaServer.root;
 
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.net.URI;
-import java.nio.charset.Charset;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.List;
-import java.util.zip.ZipFile;
 
-import org.apache.commons.io.IOUtils;
-import org.apache.tika.mime.MimeTypeException;
-import org.joda.time.DateTime;
 import org.taverna.server.client.wadl.TavernaServer.Root;
-import org.taverna.server.client.wadl.TavernaServer.Root.RunsRunName;
-import org.w3c.dom.Element;
 
-import uk.org.taverna.server.client.generic.port.InputPort;
-import uk.org.taverna.server.client.generic.port.OutputPort;
 import uk.org.taverna.server.client.generic.Capability;
-import uk.org.taverna.server.client.generic.DirectoryEntry;
-import uk.org.taverna.server.client.generic.DirectoryReference;
-import uk.org.taverna.server.client.generic.FileReference;
-import uk.org.taverna.server.client.generic.KeyPairCredential;
-import uk.org.taverna.server.client.generic.PasswordCredential;
 import uk.org.taverna.server.client.generic.TavernaRun;
 import uk.org.taverna.server.client.generic.VersionedElement;
-import uk.org.taverna.server.client.rest.DirectoryContents;
-import uk.org.taverna.server.client.rest.InputDescription;
-import uk.org.taverna.server.client.rest.InputDescription.Value;
-import uk.org.taverna.server.client.rest.MakeDirectory;
-import uk.org.taverna.server.client.rest.UploadFile;
 
 import com.sun.jersey.api.client.Client;
-import com.sun.jersey.api.client.ClientHandlerException;
 import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.UniformInterfaceException;
 import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
 
-public class TavernaServer {
-	private Root root;
+public class TavernaServer extends Connected {
+	final Root root;
+	private final URI location;
+	private final boolean authenticated;
 
-	/**
-	 * States of a workflow run. They are {@link #Initialized Initialized},
-	 * {@link #Operating Operating}, {@link #Stopped Stopped}, and
-	 * {@link #Finished Finished}. Conceptually, there is also a
-	 * <tt>Destroyed</tt> state, but the workflow run does not exist (and hence
-	 * can't have its state queried or set) in that case.
-	 * 
-	 * @author Donal Fellows
-	 */
-	public static enum Status {
-		/**
-		 * The workflow run has been created, but is not yet running. The run
-		 * will need to be manually moved to {@link #Operating Operating} when
-		 * ready.
-		 */
-		Initialized,
-		/**
-		 * The workflow run is going, reading input, generating output, etc.
-		 * Will eventually either move automatically to {@link #Finished
-		 * Finished} or can be moved manually to {@link #Stopped Stopped} (where
-		 * supported).
-		 */
-		Operating,
-		/**
-		 * The workflow run is paused, and will need to be moved back to
-		 * {@link #Operating Operating} manually.
-		 */
-		Stopped,
-		/**
-		 * The workflow run has ceased; data files will continue to exist until
-		 * the run is destroyed (which may be manual or automatic).
-		 */
-		Finished
+	TavernaServer(URI serviceRoot) {
+		root = root(createClient(), location = serviceRoot);
+		authenticated = false;
 	}
 
-	public static enum Property {
-		STDOUT("stdout"), STDERR("stderr"), EXIT_CODE("exitcode"), READY_TO_NOTIFY(
-				"readyToNotify"), EMAIL("notificationAddress"), USAGE(
-				"usageRecord");
-
-		private String s;
-
-		private Property(String s) {
-			this.s = s;
-		}
-
-		@Override
-		public String toString() {
-			return s;
-		}
-	}
-
-	public class Run {
-		private RunsRunName run;
-
-		Run(String value) {
-			run = root.runsRunName(value);
-		}
-
-		public String getName() {
-			return run.name().getAsTextPlain(String.class);
-		}
-
-		public void setName(String name) {
-			run.name().putTextPlain(name, String.class);
-		}
-
-		public Date getExpiry() {
-			return dateTimeParser().parseDateTime(
-					run.expiry().getAsTextPlain(String.class)).toDate();
-		}
-
-		public void setExpiry(Date expiryTimestamp) {
-			run.expiry().putTextPlain(
-					dateTime().print(new DateTime(expiryTimestamp)),
-					String.class);
-		}
-
-		public Date getCreate() {
-			String timestamp = run.createTime().getAsTextPlain(String.class);
-			if (timestamp == null || timestamp.trim().isEmpty())
-				return null;
-			return dateTimeParser().parseDateTime(timestamp).toDate();
-		}
-
-		public Date getStart() {
-			String timestamp = run.startTime().getAsTextPlain(String.class);
-			if (timestamp == null || timestamp.trim().isEmpty())
-				return null;
-			return dateTimeParser().parseDateTime(timestamp).toDate();
-		}
-
-		public Date getFinish() {
-			String timestamp = run.finishTime().getAsTextPlain(String.class);
-			if (timestamp == null || timestamp.trim().isEmpty())
-				return null;
-			return dateTimeParser().parseDateTime(timestamp).toDate();
-		}
-
-		public Status getStatus() {
-			return Status.valueOf(run.status().getAsTextPlain(String.class));
-		}
-
-		public void setStatus(Status status) {
-			run.status().putTextPlain(status, String.class);
-		}
-
-		public void start() {
-			setStatus(Status.Operating);
-		}
-
-		public void kill() {
-			setStatus(Status.Finished);
-		}
-
-		public boolean isRunning() {
-			return getStatus() == Status.Operating;
-		}
-
-		public String getStandardOutput() {
-			return run.stdout().getAsTextPlain(String.class);
-		}
-
-		public String getStandardError() {
-			return run.stderr().getAsTextPlain(String.class);
-		}
-
-		public String getLog() {
-			return run.log().getAsTextPlain(String.class);
-		}
-
-		public Integer getExitCode() {
-			String code = run.listeners().name("io")
-					.propertiesPropertyName("exitCode")
-					.getAsTextPlain(String.class);
-			if (code == null || code.trim().isEmpty())
-				return null;
-			return Integer.parseInt(code);
-		}
-
-		public String getProperty(Property prop) {
-			return run.listeners().name("io")
-					.propertiesPropertyName(prop.toString())
-					.getAsTextPlain(String.class);
-		}
-
-		public void setGenerateRunBundle(boolean generateRunBundle) {
-			run.generateProvenance().putTextPlain(generateRunBundle,
-					String.class);
-		}
-
-		public byte[] getRunBundle() {
-			return run.runBundle().getAsVndWf4everRobundleZip(byte[].class);
-		}
-
-		public List<InputPort> getInputs() {
-			return run.input().expected().getAsInputDescriptionXml().getInput();
-		}
-
-		public List<OutputPort> getOutputs() {
-			return run.output().getAsOutputDescriptionXml().getOutput();
-		}
-
-		public void setInput(String name, String value) {
-			Value v = new Value();
-			v.setValue(value);
-			InputDescription idesc = new InputDescription();
-			idesc.setValue(v);
-			run.input().inputName(name).putXmlAsInputDescription(idesc);
-		}
-
-		public void setInput(String name, String value, char listSeparator) {
-			Value v = new Value();
-			v.setValue(value);
-			InputDescription idesc = new InputDescription();
-			idesc.setValue(v);
-			idesc.setListDelimiter(new String(new char[] { listSeparator }));
-			run.input().inputName(name).putXmlAsInputDescription(idesc);
-		}
-
-		public byte[] getWorkflow() {
-			return run.workflow().getAsVndTavernaT2flowXml(byte[].class);
-		}
-
-		// TODO Consider better ways to do this
-		public Element getInteractionFeed() {
-			return run.interaction().getAsAtomXml(Element.class);
-		}
-
-		public Element getInteractionEntry(String id) {
-			return run.interaction().id(id).getAsAtomXml(Element.class);
-		}
-
-		public Element getUsageRecord() {
-			return run.usage().getAsXml(Element.class);
-		}
-
-		public Directory getWorkingDirectory() {
-			return new Directory();
-		}
-
-		public abstract class DirEntry {
-			final String path;
-
-			protected DirEntry(String path) {
-				this.path = path.replaceFirst("/+$", "");
-			}
-
-			public void delete() {
-				run.wd().path2(path).deleteAsXml(ClientResponse.class);
-			}
-
-			String path(ClientResponse response) {
-				String[] bits = response.getLocation().getPath().split("/");
-				return concat(bits[bits.length - 1]);
-			}
-
-			String localName() {
-				String[] bits = path.split("/");
-				return bits[bits.length - 1];
-			}
-
-			String concat(String name) {
-				return path + "/" + name.split("/", 2)[0];
-			}
-		}
-
-		public class Directory extends DirEntry {
-			Directory() {
-				super("");
-			}
-
-			Directory(String path) {
-				super(path);
-			}
-
-			public List<DirEntry> list() {
-				List<DirEntry> result = new ArrayList<>();
-				for (DirectoryEntry de : run.wd().path3(path)
-						.getAsXml(DirectoryContents.class).getDirOrFile())
-					if (de instanceof DirectoryReference)
-						result.add(new Directory(de.getValue()));
-					else if (de instanceof FileReference)
-						result.add(new File(de.getValue()));
-				return result;
-			}
-
-			public File createFile(String name, byte[] content) {
-				UploadFile uf = new UploadFile();
-				uf.setName(name);
-				uf.setValue(content);
-				return new File(path(run.wd().path(path)
-						.putAsXml(uf, ClientResponse.class)));
-			}
-
-			public File createFile(String name, java.io.File content) {
-				return new File(path(run
-						.wd()
-						.path(concat(name))
-						.putOctetStreamAsXml(
-								entity(content, APPLICATION_OCTET_STREAM_TYPE),
-								ClientResponse.class)));
-			}
-
-			public File createFile(String name, URI source) {
-				return new File(path(run
-						.wd()
-						.path(concat(name))
-						.postTextUriListAsXml(source.toString(),
-								ClientResponse.class)));
-			}
-
-			public Directory createDirectory(String name) {
-				MakeDirectory mkdir = new MakeDirectory();
-				mkdir.setName(name);
-				return new Directory(path(run.wd().path(path)
-						.putAsXml(mkdir, ClientResponse.class)));
-			}
-
-			public byte[] getZippedContents() {
-				return run.wd().path3(path).getAsZip(byte[].class);
-			}
-
-			public ZipFile getZip() throws IOException {
-				byte[] contents = getZippedContents();
-				java.io.File tmp = createTempFile(localName(), ".zip");
-				try (OutputStream os = new FileOutputStream(tmp)) {
-					os.write(contents);
-				}
-				return new ZipFile(tmp);
-			}
-		}
-
-		public class File extends DirEntry {
-			File(String path) {
-				super(path);
-			}
-
-			public InputStream getAsStream() {
-				return run.wd().path3(path).getAsOctetStream(InputStream.class);
-			}
-
-			public byte[] get() {
-				return run.wd().path3(path).getAsOctetStream(byte[].class);
-			}
-
-			public String get(Charset encoding) {
-				return new String(run.wd().path3(path)
-						.getAsOctetStream(byte[].class), encoding);
-			}
-
-			public java.io.File getAsFile() throws ClientHandlerException,
-					UniformInterfaceException, IOException, MimeTypeException {
-				ClientResponse cr = run.wd().path3(path)
-						.getAsOctetStream(ClientResponse.class);
-				String[] bits = localName().split("[.]");
-				String ext = getDefaultMimeTypes().forName(
-						cr.getHeaders().getFirst("Content-Type"))
-						.getExtension();
-				if (ext == null)
-					ext = bits[bits.length-1];
-				java.io.File tmp = createTempFile(bits[0], ext);
-				try (OutputStream os = new FileOutputStream(tmp);
-						InputStream is = cr.getEntity(InputStream.class)) {
-					copy(is, os);
-				}
-				return tmp;
-			}
-
-			public void setContents(byte[] newContents) {
-				run.wd().path(path)
-						.putOctetStreamAsXml(newContents, ClientResponse.class);
-			}
-
-			public void setContents(String newContents) {
-				run.wd().path(path)
-						.putOctetStreamAsXml(newContents, ClientResponse.class);
-			}
-
-			public void setContents(String newContents, Charset encoding) {
-				run.wd()
-						.path(path)
-						.putOctetStreamAsXml(newContents.getBytes(encoding),
-								ClientResponse.class);
-			}
-
-			public void setContents(InputStream newContents) {
-				run.wd().path(path)
-						.putOctetStreamAsXml(newContents, ClientResponse.class);
-			}
-
-			public void setContents(java.io.File newContents)
-					throws IOException {
-				run.wd()
-						.path(path)
-						.putOctetStreamAsXml(
-								entity(newContents,
-										APPLICATION_OCTET_STREAM_TYPE),
-								ClientResponse.class);
-			}
-		}
-
-		public String getOwner() {
-			return run.security().owner().getAsTextPlain(String.class);
-		}
-
-		// TODO permissions
-
-		public void grantPasswordCredential(URI contextService,
-				String username, String password) {
-			PasswordCredential pc = new PasswordCredential();
-			pc.setServiceURI(contextService.toString());
-			pc.setUsername(username);
-			pc.setPassword(password);
-			run.security().credentials()
-					.postXmlAsOctetStream(pc, ClientResponse.class);
-		}
-
-		public void grantKeyCredential(URI contextService, java.io.File source,
-				String unlockPassword, String aliasEntry) throws IOException {
-			KeyPairCredential kpc = new KeyPairCredential();
-			kpc.setServiceURI(contextService.toString());
-			try (InputStream in = new FileInputStream(source)) {
-				byte[] buffer = new byte[(int) source.length()];
-				IOUtils.read(in, buffer);
-				kpc.setCredentialBytes(buffer);
-			}
-			if (source.getName().endsWith(".p12"))
-				kpc.setFileType("PKCS12");
-			else
-				kpc.setFileType("JKS");
-			kpc.setCredentialName(aliasEntry);
-			kpc.setUnlockPassword(unlockPassword);
-			run.security().credentials()
-					.postXmlAsOctetStream(kpc, ClientResponse.class);
-		}
-	}
-
-	public TavernaServer(URI serviceRoot) {
-		root = root(createClient(), serviceRoot);
+	TavernaServer(URI serviceRoot, String username, String password) {
+		Client client = createClient();
+		client.addFilter(new HTTPBasicAuthFilter(username, password));
+		authenticated = true;
+		root = root(client, location = serviceRoot);
 	}
 
-	public TavernaServer(URI serviceRoot, String username, String password) {
+	TavernaServer(TavernaServer service, String username, String password) {
 		Client client = createClient();
 		client.addFilter(new HTTPBasicAuthFilter(username, password));
-		root = root(client, serviceRoot);
+		authenticated = true;
+		root = root(client, location = service.location);
+		getServerVersionInfo();
+	}
+
+	public TavernaServer upgradeToAuth(String username, String password) {
+		if (authenticated)
+			throw new IllegalStateException("may only upgrade an unauthenticated connection");
+		return new TavernaServer(this, username, password);
 	}
 
 	public List<Capability> getCapabilities() {
@@ -485,7 +72,7 @@ public class TavernaServer {
 	public List<Run> getExistingRuns() {
 		List<Run> runs = new ArrayList<>();
 		for (TavernaRun run : root.runs().getAsRunListXml().getRun())
-			runs.add(new Run(run.getValue()));
+			runs.add(new Run(this, run.getValue()));
 		return runs;
 	}
 
@@ -493,25 +80,49 @@ public class TavernaServer {
 		return root.getAsServerDescriptionXml();
 	}
 
-	private Run response2run(ClientResponse response) {
+	private Run response2run(ClientResponse response) throws ClientException, ServerException {
+		checkError(response);
 		if (response.getClientResponseStatus().getStatusCode() == 201) {
 			String[] path = response.getLocation().getPath().split("/");
-			return new Run(path[path.length - 1]);
+			return new Run(this, path[path.length - 1]);
 		}
 		return null;
 	}
 
-	public Run createWorkflowRun(byte[] t2flowBytes) {
+	public Run createWorkflowRun(byte[] t2flowBytes) throws ClientException, ServerException {
 		return response2run(root.runs().postVndTavernaT2flowXmlAsOctetStream(
 				t2flowBytes, ClientResponse.class));
 	}
 
-	public Run createWorkflowRun(File t2flowFile) throws IOException {
+	public Run createWorkflowRun(File t2flowFile) throws IOException, ClientException, ServerException {
 		return createWorkflowRun(readAllBytes(t2flowFile.toPath()));
 	}
 
-	public Run createWorkflowRun(URI t2flowUri) {
+	public Run createWorkflowRun(URI t2flowUri) throws ClientException, ServerException {
 		return response2run(root.runs().postTextUriListAsOctetStream(
 				t2flowUri.toString(), ClientResponse.class));
 	}
+
+
+	public static class ClientException extends Exception {
+		private static final long serialVersionUID = 1L;
+
+		ClientException(String msg, Throwable cause) {
+			super(msg, cause);
+		}
+	}
+	public static class AuthorizationException extends ClientException {
+		private static final long serialVersionUID = 1L;
+
+		AuthorizationException(String msg, Throwable cause) {
+			super(msg, cause);
+		}
+	}
+	static class ServerException extends Exception {
+		private static final long serialVersionUID = 1L;
+
+		ServerException(String msg, Throwable cause) {
+			super(msg, cause);
+		}
+	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/a1c27cd9/server-client/src/main/java/uk/org/taverna/server/client/TavernaServerConnectionFactory.java
----------------------------------------------------------------------
diff --git a/server-client/src/main/java/uk/org/taverna/server/client/TavernaServerConnectionFactory.java b/server-client/src/main/java/uk/org/taverna/server/client/TavernaServerConnectionFactory.java
new file mode 100644
index 0000000..b00b075
--- /dev/null
+++ b/server-client/src/main/java/uk/org/taverna/server/client/TavernaServerConnectionFactory.java
@@ -0,0 +1,23 @@
+package uk.org.taverna.server.client;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+public class TavernaServerConnectionFactory {
+	private Map<URI, TavernaServer> cache = new HashMap<>();
+
+	public synchronized TavernaServer connectNoAuth(URI uri) {
+		TavernaServer conn = cache.get(uri);
+		if (conn == null)
+			cache.put(uri, conn = new TavernaServer(uri));
+		return conn;
+	}
+
+	public TavernaServer connectAuth(URI uri, String username, String password) {
+		TavernaServer conn = new TavernaServer(uri, username, password);
+		// Force a check of the credentials by getting the server version
+		conn.getServerVersionInfo();
+		return conn;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/a1c27cd9/server-usagerecord/src/main/java/org/ogf/usage/JobUsageRecord.java
----------------------------------------------------------------------
diff --git a/server-usagerecord/src/main/java/org/ogf/usage/JobUsageRecord.java b/server-usagerecord/src/main/java/org/ogf/usage/JobUsageRecord.java
index cf7799a..d12d3d8 100644
--- a/server-usagerecord/src/main/java/org/ogf/usage/JobUsageRecord.java
+++ b/server-usagerecord/src/main/java/org/ogf/usage/JobUsageRecord.java
@@ -19,6 +19,7 @@ import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlTransient;
 import javax.xml.datatype.DatatypeConfigurationException;
 import javax.xml.datatype.DatatypeFactory;
+import javax.xml.transform.dom.DOMSource;
 
 import org.ogf.usage.v1_0.Charge;
 import org.ogf.usage.v1_0.ConsumableResourceType;
@@ -45,6 +46,7 @@ import org.ogf.usage.v1_0.TimeDuration;
 import org.ogf.usage.v1_0.TimeInstant;
 import org.ogf.usage.v1_0.UserIdentity;
 import org.ogf.usage.v1_0.WallDuration;
+import org.w3c.dom.Element;
 
 @XmlRootElement(name = "UsageRecord", namespace = "http://schema.ogf.org/urf/2003/09/urf")
 public class JobUsageRecord extends org.ogf.usage.v1_0.UsageRecordType {
@@ -277,10 +279,25 @@ public class JobUsageRecord extends org.ogf.usage.v1_0.UsageRecordType {
 		return writer.toString();
 	}
 
+	private static JAXBContext context;
+	static {
+		try {
+			context = JAXBContext.newInstance(JobUsageRecord.class);
+		} catch (JAXBException e) {
+			throw new RuntimeException("failed to handle JAXB annotated class",
+					e);
+		}
+	}
+
 	public static JobUsageRecord unmarshal(String s) throws JAXBException {
-		StringReader reader = new StringReader(s);
-		return (JobUsageRecord) JAXBContext.newInstance(JobUsageRecord.class)
-				.createUnmarshaller().unmarshal(reader);
+		return (JobUsageRecord) context.createUnmarshaller().unmarshal(
+				new StringReader(s));
+	}
+
+	public static JobUsageRecord unmarshal(Element elem) throws JAXBException {
+		return context.createUnmarshaller()
+				.unmarshal(new DOMSource(elem), JobUsageRecord.class)
+				.getValue();
 	}
 
 	// TODO: Add signing support