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/23 11:20:28 UTC
[21/26] incubator-taverna-server git commit: Revert "temporarily
empty repository"
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/DirectoryREST.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/DirectoryREST.java b/server-webapp/src/main/java/org/taverna/server/master/DirectoryREST.java
new file mode 100644
index 0000000..48969fa
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/DirectoryREST.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2010-2012 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM;
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.ok;
+import static javax.ws.rs.core.Response.seeOther;
+import static javax.ws.rs.core.Response.status;
+import static org.apache.commons.logging.LogFactory.getLog;
+import static org.taverna.server.master.api.ContentTypes.APPLICATION_ZIP_TYPE;
+import static org.taverna.server.master.api.ContentTypes.DIRECTORY_VARIANTS;
+import static org.taverna.server.master.api.ContentTypes.INITIAL_FILE_VARIANTS;
+import static org.taverna.server.master.common.Roles.SELF;
+import static org.taverna.server.master.common.Roles.USER;
+import static org.taverna.server.master.common.Uri.secure;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.core.Variant;
+import javax.xml.ws.Holder;
+
+import org.apache.commons.logging.Log;
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.api.DirectoryBean;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.exceptions.NoDirectoryEntryException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.interfaces.Directory;
+import org.taverna.server.master.interfaces.DirectoryEntry;
+import org.taverna.server.master.interfaces.File;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.rest.DirectoryContents;
+import org.taverna.server.master.rest.FileSegment;
+import org.taverna.server.master.rest.MakeOrUpdateDirEntry;
+import org.taverna.server.master.rest.MakeOrUpdateDirEntry.MakeDirectory;
+import org.taverna.server.master.rest.TavernaServerDirectoryREST;
+import org.taverna.server.master.utils.FilenameUtils;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+
+/**
+ * RESTful access to the filesystem.
+ *
+ * @author Donal Fellows
+ */
+class DirectoryREST implements TavernaServerDirectoryREST, DirectoryBean {
+ private Log log = getLog("Taverna.Server.Webapp");
+ private TavernaServerSupport support;
+ private TavernaRun run;
+ private FilenameUtils fileUtils;
+
+ @Override
+ public void setSupport(TavernaServerSupport support) {
+ this.support = support;
+ }
+
+ @Override
+ @Required
+ public void setFileUtils(FilenameUtils fileUtils) {
+ this.fileUtils = fileUtils;
+ }
+
+ @Override
+ public DirectoryREST connect(TavernaRun run) {
+ this.run = run;
+ return this;
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed({ USER, SELF })
+ public Response destroyDirectoryEntry(List<PathSegment> path)
+ throws NoUpdateException, FilesystemAccessException,
+ NoDirectoryEntryException {
+ support.permitUpdate(run);
+ fileUtils.getDirEntry(run, path).destroy();
+ return noContent().build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed({ USER, SELF })
+ public DirectoryContents getDescription(UriInfo ui)
+ throws FilesystemAccessException {
+ return new DirectoryContents(ui, run.getWorkingDirectory()
+ .getContents());
+ }
+
+ @Override
+ @CallCounted
+ public Response options(List<PathSegment> path) {
+ return opt("PUT", "POST", "DELETE");
+ }
+
+ /*
+ * // Nasty! This can have several different responses...
+ *
+ * @Override @CallCounted private Response
+ * getDirectoryOrFileContents(List<PathSegment> path, UriInfo ui, Request
+ * req) throws FilesystemAccessException, NoDirectoryEntryException {
+ *
+ * DirectoryEntry de = fileUtils.getDirEntry(run, path);
+ *
+ * // How did the user want the result?
+ *
+ * List<Variant> variants = getVariants(de); Variant v =
+ * req.selectVariant(variants); if (v == null) return
+ * notAcceptable(variants).type(TEXT_PLAIN)
+ * .entity("Do not know what type of response to produce.") .build();
+ *
+ * // Produce the content to deliver up
+ *
+ * Object result; if
+ * (v.getMediaType().equals(APPLICATION_OCTET_STREAM_TYPE))
+ *
+ * // Only for files...
+ *
+ * result = de; else if (v.getMediaType().equals(APPLICATION_ZIP_TYPE))
+ *
+ * // Only for directories...
+ *
+ * result = ((Directory) de).getContentsAsZip(); else
+ *
+ * // Only for directories... // XML or JSON; let CXF pick what to do
+ *
+ * result = new DirectoryContents(ui, ((Directory) de).getContents());
+ * return ok(result).type(v.getMediaType()).build();
+ *
+ * }
+ */
+
+ private boolean matchType(MediaType a, MediaType b) {
+ if (log.isDebugEnabled())
+ log.debug("comparing " + a.getType() + "/" + a.getSubtype()
+ + " and " + b.getType() + "/" + b.getSubtype());
+ return (a.isWildcardType() || b.isWildcardType() || a.getType().equals(
+ b.getType()))
+ && (a.isWildcardSubtype() || b.isWildcardSubtype() || a
+ .getSubtype().equals(b.getSubtype()));
+ }
+
+ /**
+ * What are we willing to serve up a directory or file as?
+ *
+ * @param de
+ * The reference to the object to serve.
+ * @return The variants we can serve it as.
+ * @throws FilesystemAccessException
+ * If we fail to read data necessary to detection of its media
+ * type.
+ */
+ private List<Variant> getVariants(DirectoryEntry de)
+ throws FilesystemAccessException {
+ if (de instanceof Directory)
+ return DIRECTORY_VARIANTS;
+ else if (!(de instanceof File))
+ throw new FilesystemAccessException("not a directory or file!");
+ File f = (File) de;
+ List<Variant> variants = new ArrayList<>(INITIAL_FILE_VARIANTS);
+ String contentType = support.getEstimatedContentType(f);
+ if (!contentType.equals(APPLICATION_OCTET_STREAM)) {
+ String[] ct = contentType.split("/");
+ variants.add(0,
+ new Variant(new MediaType(ct[0], ct[1]), (String) null, null));
+ }
+ return variants;
+ }
+
+ /** How did the user want the result? */
+ private MediaType pickType(HttpHeaders headers, DirectoryEntry de)
+ throws FilesystemAccessException, NegotiationFailedException {
+ List<Variant> variants = getVariants(de);
+ // Manual content negotiation!!! Ugh!
+ for (MediaType mt : headers.getAcceptableMediaTypes())
+ for (Variant v : variants)
+ if (matchType(mt, v.getMediaType()))
+ return v.getMediaType();
+ throw new NegotiationFailedException(
+ "Do not know what type of response to produce.", variants);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed({ USER, SELF })
+ public Response getDirectoryOrFileContents(List<PathSegment> path,
+ UriInfo ui, HttpHeaders headers) throws FilesystemAccessException,
+ NoDirectoryEntryException, NegotiationFailedException {
+ DirectoryEntry de = fileUtils.getDirEntry(run, path);
+
+ // How did the user want the result?
+ MediaType wanted = pickType(headers, de);
+
+ log.info("producing content of type " + wanted);
+ // Produce the content to deliver up
+ Object result;
+ if (de instanceof File) {
+ // Only for files...
+ result = de;
+ List<String> range = headers.getRequestHeader("Range");
+ if (range != null && range.size() == 1)
+ return new FileSegment((File) de, range.get(0))
+ .toResponse(wanted);
+ } else {
+ // Only for directories...
+ Directory d = (Directory) de;
+ if (wanted.getType().equals(APPLICATION_ZIP_TYPE.getType())
+ && wanted.getSubtype().equals(
+ APPLICATION_ZIP_TYPE.getSubtype()))
+ result = d.getContentsAsZip();
+ else
+ // XML or JSON; let CXF pick what to do
+ result = new DirectoryContents(ui, d.getContents());
+ }
+ return ok(result).type(wanted).build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed({ USER, SELF })
+ public Response makeDirectoryOrUpdateFile(List<PathSegment> parent,
+ MakeOrUpdateDirEntry op, UriInfo ui) throws NoUpdateException,
+ FilesystemAccessException, NoDirectoryEntryException {
+ support.permitUpdate(run);
+ DirectoryEntry container = fileUtils.getDirEntry(run, parent);
+ if (!(container instanceof Directory))
+ throw new FilesystemAccessException("You may not "
+ + ((op instanceof MakeDirectory) ? "make a subdirectory of"
+ : "place a file in") + " a file.");
+ if (op.name == null || op.name.length() == 0)
+ throw new FilesystemAccessException("missing name attribute");
+ Directory d = (Directory) container;
+ UriBuilder ub = secure(ui).path("{name}");
+
+ // Make a directory in the context directory
+
+ if (op instanceof MakeDirectory) {
+ Directory target = d.makeSubdirectory(support.getPrincipal(),
+ op.name);
+ return created(ub.build(target.getName())).build();
+ }
+
+ // Make or set the contents of a file
+
+ File f = null;
+ for (DirectoryEntry e : d.getContents()) {
+ if (e.getName().equals(op.name)) {
+ if (e instanceof Directory)
+ throw new FilesystemAccessException(
+ "You may not overwrite a directory with a file.");
+ f = (File) e;
+ break;
+ }
+ }
+ if (f == null) {
+ f = d.makeEmptyFile(support.getPrincipal(), op.name);
+ f.setContents(op.contents);
+ return created(ub.build(f.getName())).build();
+ }
+ f.setContents(op.contents);
+ return seeOther(ub.build(f.getName())).build();
+ }
+
+ private File getFileForWrite(List<PathSegment> filePath,
+ Holder<Boolean> isNew) throws FilesystemAccessException,
+ NoDirectoryEntryException, NoUpdateException {
+ support.permitUpdate(run);
+ if (filePath == null || filePath.size() == 0)
+ throw new FilesystemAccessException(
+ "Cannot create a file that is not in a directory.");
+
+ List<PathSegment> dirPath = new ArrayList<>(filePath);
+ String name = dirPath.remove(dirPath.size() - 1).getPath();
+ DirectoryEntry de = fileUtils.getDirEntry(run, dirPath);
+ if (!(de instanceof Directory)) {
+ throw new FilesystemAccessException(
+ "Cannot create a file that is not in a directory.");
+ }
+ Directory d = (Directory) de;
+
+ File f = null;
+ isNew.value = false;
+ for (DirectoryEntry e : d.getContents())
+ if (e.getName().equals(name)) {
+ if (e instanceof File) {
+ f = (File) e;
+ break;
+ }
+ throw new FilesystemAccessException(
+ "Cannot create a file that is not in a directory.");
+ }
+
+ if (f == null) {
+ f = d.makeEmptyFile(support.getPrincipal(), name);
+ isNew.value = true;
+ } else
+ f.setContents(new byte[0]);
+ return f;
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed({ USER, SELF })
+ public Response setFileContents(List<PathSegment> filePath,
+ InputStream contents, UriInfo ui) throws NoDirectoryEntryException,
+ NoUpdateException, FilesystemAccessException {
+ Holder<Boolean> isNew = new Holder<>(true);
+ support.copyStreamToFile(contents, getFileForWrite(filePath, isNew));
+
+ if (isNew.value)
+ return created(ui.getAbsolutePath()).build();
+ else
+ return noContent().build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public Response setFileContentsFromURL(List<PathSegment> filePath,
+ List<URI> referenceList, UriInfo ui)
+ throws NoDirectoryEntryException, NoUpdateException,
+ FilesystemAccessException {
+ support.permitUpdate(run);
+ if (referenceList.isEmpty() || referenceList.size() > 1)
+ return status(422).entity("URI list must have single URI in it")
+ .build();
+ URI uri = referenceList.get(0);
+ try {
+ uri.toURL();
+ } catch (MalformedURLException e) {
+ return status(422).entity("URI list must have value URL in it")
+ .build();
+ }
+ Holder<Boolean> isNew = new Holder<>(true);
+ File f = getFileForWrite(filePath, isNew);
+
+ try {
+ support.copyDataToFile(uri, f);
+ } catch (MalformedURLException ex) {
+ // Should not happen; called uri.toURL() successfully above
+ throw new NoUpdateException("failed to parse URI", ex);
+ } catch (IOException ex) {
+ throw new FilesystemAccessException(
+ "failed to transfer data from URI", ex);
+ }
+
+ if (isNew.value)
+ return created(ui.getAbsolutePath()).build();
+ else
+ return noContent().build();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/FileConcatenation.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/FileConcatenation.java b/server-webapp/src/main/java/org/taverna/server/master/FileConcatenation.java
new file mode 100644
index 0000000..3893b3d
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/FileConcatenation.java
@@ -0,0 +1,68 @@
+package org.taverna.server.master;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.interfaces.File;
+
+/**
+ * Simple concatenation of files.
+ *
+ * @author Donal Fellows
+ */
+public class FileConcatenation implements Iterable<File> {
+ private List<File> files = new ArrayList<>();
+
+ public void add(File f) {
+ files.add(f);
+ }
+
+ public boolean isEmpty() {
+ return files.isEmpty();
+ }
+
+ /**
+ * @return The total length of the files, or -1 if this cannot be
+ * determined.
+ */
+ public long size() {
+ long size = 0;
+ for (File f : files)
+ try {
+ size += f.getSize();
+ } catch (FilesystemAccessException e) {
+ // Ignore; shouldn't happen but can't guarantee
+ }
+ return (size == 0 && !files.isEmpty() ? -1 : size);
+ }
+
+ /**
+ * Get the concatenated files.
+ *
+ * @param encoding
+ * The encoding to use.
+ * @return The concatenated files.
+ * @throws UnsupportedEncodingException
+ * If the encoding doesn't exist.
+ */
+ public String get(String encoding) throws UnsupportedEncodingException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ for (File f : files)
+ try {
+ baos.write(f.getContents(0, -1));
+ } catch (FilesystemAccessException | IOException e) {
+ continue;
+ }
+ return baos.toString(encoding);
+ }
+
+ @Override
+ public Iterator<File> iterator() {
+ return files.iterator();
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/InputREST.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/InputREST.java b/server-webapp/src/main/java/org/taverna/server/master/InputREST.java
new file mode 100644
index 0000000..0f48207
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/InputREST.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master;
+
+import static java.util.UUID.randomUUID;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.util.Date;
+
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.jaxrs.model.URITemplate;
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.api.InputBean;
+import org.taverna.server.master.common.DirEntryReference;
+import org.taverna.server.master.exceptions.BadInputPortNameException;
+import org.taverna.server.master.exceptions.BadPropertyValueException;
+import org.taverna.server.master.exceptions.BadStateChangeException;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.exceptions.NoDirectoryEntryException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.exceptions.UnknownRunException;
+import org.taverna.server.master.interfaces.DirectoryEntry;
+import org.taverna.server.master.interfaces.File;
+import org.taverna.server.master.interfaces.Input;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.rest.TavernaServerInputREST;
+import org.taverna.server.master.rest.TavernaServerInputREST.InDesc.AbstractContents;
+import org.taverna.server.master.rest.TavernaServerInputREST.InDesc.Reference;
+import org.taverna.server.master.utils.FilenameUtils;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+import org.taverna.server.port_description.InputDescription;
+
+/**
+ * RESTful interface to the input descriptor of a single workflow run.
+ *
+ * @author Donal Fellows
+ */
+class InputREST implements TavernaServerInputREST, InputBean {
+ private UriInfo ui;
+ private TavernaServerSupport support;
+ private TavernaRun run;
+ private ContentsDescriptorBuilder cdBuilder;
+ private FilenameUtils fileUtils;
+
+ @Override
+ public void setSupport(TavernaServerSupport support) {
+ this.support = support;
+ }
+
+ @Override
+ @Required
+ public void setCdBuilder(ContentsDescriptorBuilder cdBuilder) {
+ this.cdBuilder = cdBuilder;
+ }
+
+ @Override
+ @Required
+ public void setFileUtils(FilenameUtils fileUtils) {
+ this.fileUtils = fileUtils;
+ }
+
+ @Override
+ public InputREST connect(TavernaRun run, UriInfo ui) {
+ this.run = run;
+ this.ui = ui;
+ return this;
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public InputsDescriptor get() {
+ return new InputsDescriptor(ui, run);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public InputDescription getExpected() {
+ return cdBuilder.makeInputDescriptor(run, ui);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public String getBaclavaFile() {
+ String i = run.getInputBaclavaFile();
+ return i == null ? "" : i;
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public InDesc getInput(String name, UriInfo ui) throws BadInputPortNameException {
+ Input i = support.getInput(run, name);
+ if (i == null)
+ throw new BadInputPortNameException("unknown input port name");
+ return new InDesc(i, ui);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public String setBaclavaFile(String filename) throws NoUpdateException,
+ BadStateChangeException, FilesystemAccessException {
+ support.permitUpdate(run);
+ run.setInputBaclavaFile(filename);
+ String i = run.getInputBaclavaFile();
+ return i == null ? "" : i;
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public InDesc setInput(String name, InDesc inputDescriptor, UriInfo ui)
+ throws NoUpdateException, BadStateChangeException,
+ FilesystemAccessException, BadInputPortNameException,
+ BadPropertyValueException {
+ inputDescriptor.descriptorRef = null;
+ AbstractContents ac = inputDescriptor.assignment;
+ if (name == null || name.isEmpty())
+ throw new BadInputPortNameException("bad input name");
+ if (ac == null)
+ throw new BadPropertyValueException("no content!");
+ if (inputDescriptor.delimiter != null
+ && inputDescriptor.delimiter.isEmpty())
+ inputDescriptor.delimiter = null;
+ if (ac instanceof InDesc.Reference)
+ return setRemoteInput(name, (InDesc.Reference) ac,
+ inputDescriptor.delimiter, ui);
+ if (!(ac instanceof InDesc.File || ac instanceof InDesc.Value))
+ throw new BadPropertyValueException("unknown content type");
+ support.permitUpdate(run);
+ Input i = support.getInput(run, name);
+ if (i == null)
+ i = run.makeInput(name);
+ if (ac instanceof InDesc.File)
+ i.setFile(ac.contents);
+ else
+ i.setValue(ac.contents);
+ i.setDelimiter(inputDescriptor.delimiter);
+ return new InDesc(i, ui);
+ }
+
+ private InDesc setRemoteInput(String name, Reference ref, String delimiter,
+ UriInfo ui) throws BadStateChangeException,
+ BadPropertyValueException, FilesystemAccessException {
+ URITemplate tmpl = new URITemplate(ui.getBaseUri()
+ + "/runs/{runName}/wd/{path:.+}");
+ MultivaluedMap<String, String> mvm = new MetadataMap<>();
+ if (!tmpl.match(ref.contents, mvm)) {
+ throw new BadPropertyValueException(
+ "URI in reference does not refer to local disk resource");
+ }
+ try {
+ File from = fileUtils.getFile(
+ support.getRun(mvm.get("runName").get(0)),
+ SyntheticDirectoryEntry.make(mvm.get("path").get(0)));
+ File to = run.getWorkingDirectory().makeEmptyFile(
+ support.getPrincipal(), randomUUID().toString());
+
+ to.copy(from);
+
+ Input i = support.getInput(run, name);
+ if (i == null)
+ i = run.makeInput(name);
+ i.setFile(to.getFullName());
+ i.setDelimiter(delimiter);
+ return new InDesc(i, ui);
+ } catch (UnknownRunException e) {
+ throw new BadStateChangeException("may not copy from that run", e);
+ } catch (NoDirectoryEntryException e) {
+ throw new BadStateChangeException("source does not exist", e);
+ }
+ }
+
+ @Override
+ @CallCounted
+ public Response options() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response expectedOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response baclavaOptions() {
+ return opt("PUT");
+ }
+
+ @Override
+ @CallCounted
+ public Response inputOptions(@PathParam("name") String name) {
+ return opt("PUT");
+ }
+}
+
+/**
+ * A way to create synthetic directory entries, used during deletion.
+ *
+ * @author Donal Fellows
+ */
+class SyntheticDirectoryEntry implements DirectoryEntry {
+ public static DirEntryReference make(String path) {
+ return DirEntryReference.newInstance(new SyntheticDirectoryEntry(path));
+ }
+
+ private SyntheticDirectoryEntry(String p) {
+ this.p = p;
+ this.d = new Date();
+ }
+
+ private String p;
+ private Date d;
+
+ @Override
+ public String getName() {
+ return null;
+ }
+
+ @Override
+ public String getFullName() {
+ return p;
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ @Override
+ public int compareTo(DirectoryEntry o) {
+ return p.compareTo(o.getFullName());
+ }
+
+ @Override
+ public Date getModificationDate() {
+ return d;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/InteractionFeed.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/InteractionFeed.java b/server-webapp/src/main/java/org/taverna/server/master/InteractionFeed.java
new file mode 100644
index 0000000..b686491
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/InteractionFeed.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2013 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master;
+
+import static org.taverna.server.master.common.Roles.SELF;
+import static org.taverna.server.master.common.Roles.USER;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.core.Response;
+
+import org.apache.abdera.model.Entry;
+import org.apache.abdera.model.Feed;
+import org.taverna.server.master.api.FeedBean;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.exceptions.NoDirectoryEntryException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.interaction.InteractionFeedSupport;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.rest.InteractionFeedREST;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+
+/**
+ * How to connect an interaction feed to the webapp.
+ *
+ * @author Donal Fellows
+ */
+public class InteractionFeed implements InteractionFeedREST, FeedBean {
+ private InteractionFeedSupport interactionFeed;
+ private TavernaRun run;
+
+ @Override
+ public void setInteractionFeedSupport(InteractionFeedSupport feed) {
+ this.interactionFeed = feed;
+ }
+
+ InteractionFeed connect(TavernaRun run) {
+ this.run = run;
+ return this;
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed({ USER, SELF })
+ public Feed getFeed() throws FilesystemAccessException,
+ NoDirectoryEntryException {
+ return interactionFeed.getRunFeed(run);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed({ USER, SELF })
+ public Response addEntry(Entry entry) throws MalformedURLException,
+ FilesystemAccessException, NoDirectoryEntryException,
+ NoUpdateException {
+ Entry realEntry = interactionFeed.addRunFeedEntry(run, entry);
+ URI location;
+ try {
+ location = realEntry.getSelfLink().getHref().toURI();
+ } catch (URISyntaxException e) {
+ throw new RuntimeException("failed to make URI from link?!", e);
+ }
+ return Response.created(location).entity(realEntry)
+ .type("application/atom+xml;type=entry").build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed({ USER, SELF })
+ public Entry getEntry(String id) throws FilesystemAccessException,
+ NoDirectoryEntryException {
+ return interactionFeed.getRunFeedEntry(run, id);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed({ USER, SELF })
+ public String deleteEntry(String id) throws FilesystemAccessException,
+ NoDirectoryEntryException, NoUpdateException {
+ interactionFeed.removeRunFeedEntry(run, id);
+ return "entry successfully deleted";
+ }
+
+ @Override
+ @CallCounted
+ public Response feedOptions() {
+ return opt("POST");
+ }
+
+ @Override
+ @CallCounted
+ public Response entryOptions(String id) {
+ return opt("DELETE");
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/ListenerPropertyREST.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/ListenerPropertyREST.java b/server-webapp/src/main/java/org/taverna/server/master/ListenerPropertyREST.java
new file mode 100644
index 0000000..3e983a9
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/ListenerPropertyREST.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master;
+
+import static org.apache.commons.logging.LogFactory.getLog;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import javax.ws.rs.core.Response;
+
+import org.apache.commons.logging.Log;
+import org.taverna.server.master.api.ListenerPropertyBean;
+import org.taverna.server.master.exceptions.NoListenerException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.interfaces.Listener;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.rest.TavernaServerListenersREST;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+
+/**
+ * RESTful interface to a single property of a workflow run.
+ *
+ * @author Donal Fellows
+ */
+class ListenerPropertyREST implements TavernaServerListenersREST.Property,
+ ListenerPropertyBean {
+ private Log log = getLog("Taverna.Server.Webapp");
+ private TavernaServerSupport support;
+ private Listener listen;
+ private String propertyName;
+ private TavernaRun run;
+
+ @Override
+ public void setSupport(TavernaServerSupport support) {
+ this.support = support;
+ }
+
+ @Override
+ public ListenerPropertyREST connect(Listener listen, TavernaRun run,
+ String propertyName) {
+ this.listen = listen;
+ this.propertyName = propertyName;
+ this.run = run;
+ return this;
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public String getValue() {
+ try {
+ return listen.getProperty(propertyName);
+ } catch (NoListenerException e) {
+ log.error("unexpected exception; property \"" + propertyName
+ + "\" should exist", e);
+ return null;
+ }
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public String setValue(String value) throws NoUpdateException,
+ NoListenerException {
+ support.permitUpdate(run);
+ listen.setProperty(propertyName, value);
+ return listen.getProperty(propertyName);
+ }
+
+ @Override
+ @CallCounted
+ public Response options() {
+ return opt("PUT");
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/ListenersREST.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/ListenersREST.java b/server-webapp/src/main/java/org/taverna/server/master/ListenersREST.java
new file mode 100644
index 0000000..4b7d7f3
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/ListenersREST.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.UriBuilder.fromUri;
+import static org.taverna.server.master.common.Uri.secure;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import org.taverna.server.master.api.ListenersBean;
+import org.taverna.server.master.exceptions.NoListenerException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.interfaces.Listener;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.rest.ListenerDefinition;
+import org.taverna.server.master.rest.TavernaServerListenersREST;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+
+/**
+ * RESTful interface to a single workflow run's event listeners.
+ *
+ * @author Donal Fellows
+ */
+abstract class ListenersREST implements TavernaServerListenersREST,
+ ListenersBean {
+ private TavernaRun run;
+ private TavernaServerSupport support;
+
+ @Override
+ public void setSupport(TavernaServerSupport support) {
+ this.support = support;
+ }
+
+ @Override
+ public ListenersREST connect(TavernaRun run) {
+ this.run = run;
+ return this;
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Response addListener(ListenerDefinition typeAndConfiguration,
+ UriInfo ui) throws NoUpdateException, NoListenerException {
+ String name = support.makeListener(run, typeAndConfiguration.type,
+ typeAndConfiguration.configuration).getName();
+ return created(secure(ui).path("{listenerName}").build(name)).build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public TavernaServerListenerREST getListener(String name)
+ throws NoListenerException {
+ Listener l = support.getListener(run, name);
+ if (l == null)
+ throw new NoListenerException();
+ return makeListenerInterface().connect(l, run);
+ }
+
+ @Nonnull
+ protected abstract SingleListenerREST makeListenerInterface();
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Listeners getDescription(UriInfo ui) {
+ List<ListenerDescription> result = new ArrayList<>();
+ UriBuilder ub = secure(ui).path("{name}");
+ for (Listener l : run.getListeners())
+ result.add(new ListenerDescription(l,
+ fromUri(ub.build(l.getName()))));
+ return new Listeners(result, ub);
+ }
+
+ @Override
+ @CallCounted
+ public Response listenersOptions() {
+ return opt();
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/ManagementState.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/ManagementState.java b/server-webapp/src/main/java/org/taverna/server/master/ManagementState.java
new file mode 100644
index 0000000..9d4a651
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/ManagementState.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master;
+
+import javax.annotation.PostConstruct;
+import javax.jdo.Query;
+import javax.jdo.annotations.PersistenceAware;
+import javax.jdo.annotations.PersistenceCapable;
+import javax.jdo.annotations.Persistent;
+import javax.jdo.annotations.PrimaryKey;
+
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.api.ManagementModel;
+import org.taverna.server.master.utils.JDOSupport;
+
+/** The persistent, manageable state of the Taverna Server web application. */
+@PersistenceAware
+class ManagementState extends JDOSupport<WebappState> implements
+ ManagementModel {
+ public ManagementState() {
+ super(WebappState.class);
+ }
+
+ /** Whether we should log all workflows sent to us. */
+ private boolean logIncomingWorkflows = false;
+
+ /** Whether we allow the creation of new workflow runs. */
+ private boolean allowNewWorkflowRuns = true;
+
+ /**
+ * Whether outgoing exceptions should be logged before being converted to
+ * responses.
+ */
+ private boolean logOutgoingExceptions = false;
+
+ /**
+ * The file that all usage records should be appended to, or <tt>null</tt>
+ * if they should be just dropped.
+ */
+ private String usageRecordLogFile = null;
+
+ @Override
+ public void setLogIncomingWorkflows(boolean logIncomingWorkflows) {
+ this.logIncomingWorkflows = logIncomingWorkflows;
+ if (loadedState)
+ self.store();
+ }
+
+ @Override
+ public boolean getLogIncomingWorkflows() {
+ self.load();
+ return logIncomingWorkflows;
+ }
+
+ @Override
+ public void setAllowNewWorkflowRuns(boolean allowNewWorkflowRuns) {
+ this.allowNewWorkflowRuns = allowNewWorkflowRuns;
+ if (loadedState)
+ self.store();
+ }
+
+ @Override
+ public boolean getAllowNewWorkflowRuns() {
+ self.load();
+ return allowNewWorkflowRuns;
+ }
+
+ @Override
+ public void setLogOutgoingExceptions(boolean logOutgoingExceptions) {
+ this.logOutgoingExceptions = logOutgoingExceptions;
+ if (loadedState)
+ self.store();
+ }
+
+ @Override
+ public boolean getLogOutgoingExceptions() {
+ self.load();
+ return logOutgoingExceptions || true;
+ }
+
+ @Override
+ public String getUsageRecordLogFile() {
+ self.load();
+ return usageRecordLogFile;
+ }
+
+ @Override
+ public void setUsageRecordLogFile(String usageRecordLogFile) {
+ this.usageRecordLogFile = usageRecordLogFile;
+ if (loadedState)
+ self.store();
+ }
+
+ private static final int KEY = 42; // whatever
+
+ private WebappState get() {
+ Query q = query("id == " + KEY);
+ q.setUnique(true);
+ return (WebappState) q.execute();
+ }
+
+ private boolean loadedState;
+ private ManagementState self;
+
+ @Required
+ public void setSelf(ManagementState self) {
+ this.self = self;
+ }
+
+ @PostConstruct
+ @WithinSingleTransaction
+ public void load() {
+ if (loadedState || !isPersistent())
+ return;
+ WebappState state = get();
+ if (state == null)
+ return;
+ allowNewWorkflowRuns = state.getAllowNewWorkflowRuns();
+ logIncomingWorkflows = state.getLogIncomingWorkflows();
+ logOutgoingExceptions = state.getLogOutgoingExceptions();
+ usageRecordLogFile = state.getUsageRecordLogFile();
+ loadedState = true;
+ }
+
+ @WithinSingleTransaction
+ public void store() {
+ if (!isPersistent())
+ return;
+ WebappState state = get();
+ if (state == null) {
+ state = new WebappState();
+ // save state
+ state.id = KEY; // whatever...
+ state = persist(state);
+ }
+ state.setAllowNewWorkflowRuns(allowNewWorkflowRuns);
+ state.setLogIncomingWorkflows(logIncomingWorkflows);
+ state.setLogOutgoingExceptions(logOutgoingExceptions);
+ state.setUsageRecordLogFile(usageRecordLogFile);
+ loadedState = true;
+ }
+}
+
+// WARNING! If you change the name of this class, update persistence.xml as
+// well!
+@PersistenceCapable(table = "MANAGEMENTSTATE__WEBAPPSTATE")
+class WebappState implements ManagementModel {
+ public WebappState() {
+ }
+
+ @PrimaryKey
+ protected int id;
+
+ /** Whether we should log all workflows sent to us. */
+ @Persistent
+ private boolean logIncomingWorkflows;
+
+ /** Whether we allow the creation of new workflow runs. */
+ @Persistent
+ private boolean allowNewWorkflowRuns;
+
+ /**
+ * Whether outgoing exceptions should be logged before being converted to
+ * responses.
+ */
+ @Persistent
+ private boolean logOutgoingExceptions;
+
+ /** Where to write usage records. */
+ @Persistent
+ private String usageRecordLogFile;
+
+ @Override
+ public void setLogIncomingWorkflows(boolean logIncomingWorkflows) {
+ this.logIncomingWorkflows = logIncomingWorkflows;
+ }
+
+ @Override
+ public boolean getLogIncomingWorkflows() {
+ return logIncomingWorkflows;
+ }
+
+ @Override
+ public void setAllowNewWorkflowRuns(boolean allowNewWorkflowRuns) {
+ this.allowNewWorkflowRuns = allowNewWorkflowRuns;
+ }
+
+ @Override
+ public boolean getAllowNewWorkflowRuns() {
+ return allowNewWorkflowRuns;
+ }
+
+ @Override
+ public void setLogOutgoingExceptions(boolean logOutgoingExceptions) {
+ this.logOutgoingExceptions = logOutgoingExceptions;
+ }
+
+ @Override
+ public boolean getLogOutgoingExceptions() {
+ return logOutgoingExceptions;
+ }
+
+ @Override
+ public String getUsageRecordLogFile() {
+ return usageRecordLogFile;
+ }
+
+ @Override
+ public void setUsageRecordLogFile(String usageRecordLogFile) {
+ this.usageRecordLogFile = usageRecordLogFile;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/RunREST.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/RunREST.java b/server-webapp/src/main/java/org/taverna/server/master/RunREST.java
new file mode 100644
index 0000000..563a822
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/RunREST.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_XML;
+import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.ok;
+import static javax.ws.rs.core.Response.status;
+import static org.apache.commons.logging.LogFactory.getLog;
+import static org.joda.time.format.ISODateTimeFormat.dateTime;
+import static org.joda.time.format.ISODateTimeFormat.dateTimeParser;
+import static org.taverna.server.master.common.Roles.SELF;
+import static org.taverna.server.master.common.Roles.USER;
+import static org.taverna.server.master.common.Status.Initialized;
+import static org.taverna.server.master.common.Status.Operating;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.util.Date;
+
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import javax.xml.bind.JAXBException;
+
+import org.apache.commons.logging.Log;
+import org.joda.time.DateTime;
+import org.ogf.usage.JobUsageRecord;
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.api.RunBean;
+import org.taverna.server.master.common.ProfileList;
+import org.taverna.server.master.common.Status;
+import org.taverna.server.master.common.Workflow;
+import org.taverna.server.master.exceptions.BadStateChangeException;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.exceptions.NoDirectoryEntryException;
+import org.taverna.server.master.exceptions.NoListenerException;
+import org.taverna.server.master.exceptions.NoUpdateException;
+import org.taverna.server.master.exceptions.NotOwnerException;
+import org.taverna.server.master.exceptions.OverloadedException;
+import org.taverna.server.master.exceptions.UnknownRunException;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.interfaces.TavernaSecurityContext;
+import org.taverna.server.master.rest.InteractionFeedREST;
+import org.taverna.server.master.rest.TavernaServerInputREST;
+import org.taverna.server.master.rest.TavernaServerListenersREST;
+import org.taverna.server.master.rest.TavernaServerRunREST;
+import org.taverna.server.master.rest.TavernaServerSecurityREST;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+import org.taverna.server.port_description.OutputDescription;
+
+/**
+ * RESTful interface to a single workflow run.
+ *
+ * @author Donal Fellows
+ */
+abstract class RunREST implements TavernaServerRunREST, RunBean {
+ private Log log = getLog("Taverna.Server.Webapp");
+ private String runName;
+ private TavernaRun run;
+ private TavernaServerSupport support;
+ private ContentsDescriptorBuilder cdBuilder;
+
+ @Override
+ @Required
+ public void setSupport(TavernaServerSupport support) {
+ this.support = support;
+ }
+
+ @Override
+ @Required
+ public void setCdBuilder(ContentsDescriptorBuilder cdBuilder) {
+ this.cdBuilder = cdBuilder;
+ }
+
+ @Override
+ public void setRunName(String runName) {
+ this.runName = runName;
+ }
+
+ @Override
+ public void setRun(TavernaRun run) {
+ this.run = run;
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public RunDescription getDescription(UriInfo ui) {
+ return new RunDescription(run, ui);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public Response destroy() throws NoUpdateException {
+ try {
+ support.unregisterRun(runName, run);
+ } catch (UnknownRunException e) {
+ log.fatal("can't happen", e);
+ }
+ return noContent().build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public TavernaServerListenersREST getListeners() {
+ return makeListenersInterface().connect(run);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public TavernaServerSecurityREST getSecurity() throws NotOwnerException {
+ TavernaSecurityContext secContext = run.getSecurityContext();
+ if (!support.getPrincipal().equals(secContext.getOwner()))
+ throw new NotOwnerException();
+
+ // context.getBean("run.security", run, secContext);
+ return makeSecurityInterface().connect(secContext, run);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public String getExpiryTime() {
+ return dateTime().print(new DateTime(run.getExpiry()));
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public String getCreateTime() {
+ return dateTime().print(new DateTime(run.getCreationTimestamp()));
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public String getFinishTime() {
+ Date f = run.getFinishTimestamp();
+ return f == null ? "" : dateTime().print(new DateTime(f));
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public String getStartTime() {
+ Date f = run.getStartTimestamp();
+ return f == null ? "" : dateTime().print(new DateTime(f));
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public String getStatus() {
+ return run.getStatus().toString();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public Workflow getWorkflow() {
+ return run.getWorkflow();
+ }
+
+ @Override
+ @CallCounted
+ public String getMainProfileName() {
+ String name = run.getWorkflow().getMainProfileName();
+ return (name == null ? "" : name);
+ }
+
+ @Override
+ @CallCounted
+ public ProfileList getProfiles() {
+ return support.getProfileDescriptor(run.getWorkflow());
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed({ USER, SELF })
+ public DirectoryREST getWorkingDirectory() {
+ return makeDirectoryInterface().connect(run);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public String setExpiryTime(String expiry) throws NoUpdateException,
+ IllegalArgumentException {
+ DateTime wanted = dateTimeParser().parseDateTime(expiry.trim());
+ Date achieved = support.updateExpiry(run, wanted.toDate());
+ return dateTime().print(new DateTime(achieved));
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public Response setStatus(String status) throws NoUpdateException {
+ Status newStatus = Status.valueOf(status.trim());
+ support.permitUpdate(run);
+ if (newStatus == Operating && run.getStatus() == Initialized) {
+ if (!support.getAllowStartWorkflowRuns())
+ throw new OverloadedException();
+ String issue = run.setStatus(newStatus);
+ if (issue == null)
+ issue = "starting run...";
+ return status(202).entity(issue).type("text/plain").build();
+ }
+ run.setStatus(newStatus); // Ignore the result
+ return ok(run.getStatus().toString()).type("text/plain").build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public TavernaServerInputREST getInputs(UriInfo ui) {
+ return makeInputInterface().connect(run, ui);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public String getOutputFile() {
+ String o = run.getOutputBaclavaFile();
+ return o == null ? "" : o;
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public String setOutputFile(String filename) throws NoUpdateException,
+ FilesystemAccessException, BadStateChangeException {
+ support.permitUpdate(run);
+ if (filename != null && filename.length() == 0)
+ filename = null;
+ run.setOutputBaclavaFile(filename);
+ String o = run.getOutputBaclavaFile();
+ return o == null ? "" : o;
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public OutputDescription getOutputDescription(UriInfo ui)
+ throws BadStateChangeException, FilesystemAccessException,
+ NoDirectoryEntryException {
+ if (run.getStatus() == Initialized)
+ throw new BadStateChangeException(
+ "may not get output description in initial state");
+ return cdBuilder.makeOutputDescriptor(run, ui);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed({ USER, SELF })
+ public InteractionFeedREST getInteractionFeed() {
+ return makeInteractionFeed().connect(run);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public String getName() {
+ return run.getName();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public String setName(String name) throws NoUpdateException {
+ support.permitUpdate(run);
+ run.setName(name);
+ return run.getName();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public String getStdout() throws NoListenerException {
+ return support.getProperty(run, "io", "stdout");
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public String getStderr() throws NoListenerException {
+ return support.getProperty(run, "io", "stderr");
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public Response getUsage() throws NoListenerException, JAXBException {
+ String ur = support.getProperty(run, "io", "usageRecord");
+ if (ur.isEmpty())
+ return noContent().build();
+ return ok(JobUsageRecord.unmarshal(ur), APPLICATION_XML).build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public Response getLogContents() {
+ FileConcatenation fc = support.getLogs(run);
+ if (fc.isEmpty())
+ return Response.noContent().build();
+ return Response.ok(fc, TEXT_PLAIN).build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public Response getRunBundle() {
+ FileConcatenation fc = support.getProv(run);
+ if (fc.isEmpty())
+ return Response.status(404).entity("no provenance currently available").build();
+ return Response.ok(fc, "application/vnd.wf4ever.robundle+zip").build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public boolean getGenerateProvenance() {
+ return run.getGenerateProvenance();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ @RolesAllowed(USER)
+ public boolean setGenerateProvenance(boolean newValue) throws NoUpdateException {
+ support.permitUpdate(run);
+ run.setGenerateProvenance(newValue);
+ return run.getGenerateProvenance();
+ }
+
+ /**
+ * Construct a RESTful interface to a run's filestore.
+ *
+ * @return The handle to the interface, as decorated by Spring.
+ */
+ protected abstract DirectoryREST makeDirectoryInterface();
+
+ /**
+ * Construct a RESTful interface to a run's input descriptors.
+ *
+ * @return The handle to the interface, as decorated by Spring.
+ */
+ protected abstract InputREST makeInputInterface();
+
+ /**
+ * Construct a RESTful interface to a run's listeners.
+ *
+ * @return The handle to the interface, as decorated by Spring.
+ */
+ protected abstract ListenersREST makeListenersInterface();
+
+ /**
+ * Construct a RESTful interface to a run's security.
+ *
+ * @return The handle to the interface, as decorated by Spring.
+ */
+ protected abstract RunSecurityREST makeSecurityInterface();
+
+ /**
+ * Construct a RESTful interface to a run's interaction feed.
+ *
+ * @return The handle to the interaface, as decorated by Spring.
+ */
+ protected abstract InteractionFeed makeInteractionFeed();
+
+ @Override
+ @CallCounted
+ public Response runOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response workflowOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response profileOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response expiryOptions() {
+ return opt("PUT");
+ }
+
+ @Override
+ @CallCounted
+ public Response createTimeOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response startTimeOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response finishTimeOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response statusOptions() {
+ return opt("PUT");
+ }
+
+ @Override
+ @CallCounted
+ public Response outputOptions() {
+ return opt("PUT");
+ }
+
+ @Override
+ @CallCounted
+ public Response nameOptions() {
+ return opt("PUT");
+ }
+
+ @Override
+ @CallCounted
+ public Response stdoutOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response stderrOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response usageOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response logOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response runBundleOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response generateProvenanceOptions() {
+ return opt("PUT");
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/RunSecurityREST.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/RunSecurityREST.java b/server-webapp/src/main/java/org/taverna/server/master/RunSecurityREST.java
new file mode 100644
index 0000000..5a366b2
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/RunSecurityREST.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2010-2012 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master;
+
+import static java.util.UUID.randomUUID;
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static org.taverna.server.master.common.Status.Initialized;
+import static org.taverna.server.master.common.Uri.secure;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.net.URI;
+import java.util.Map;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.taverna.server.master.api.SecurityBean;
+import org.taverna.server.master.common.Credential;
+import org.taverna.server.master.common.Permission;
+import org.taverna.server.master.common.Trust;
+import org.taverna.server.master.exceptions.BadStateChangeException;
+import org.taverna.server.master.exceptions.InvalidCredentialException;
+import org.taverna.server.master.exceptions.NoCredentialException;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.interfaces.TavernaSecurityContext;
+import org.taverna.server.master.rest.TavernaServerSecurityREST;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+
+/**
+ * RESTful interface to a single workflow run's security settings.
+ *
+ * @author Donal Fellows
+ */
+class RunSecurityREST implements TavernaServerSecurityREST, SecurityBean {
+ private TavernaServerSupport support;
+ private TavernaSecurityContext context;
+ private TavernaRun run;
+
+ @Override
+ public void setSupport(TavernaServerSupport support) {
+ this.support = support;
+ }
+
+ @Override
+ public RunSecurityREST connect(TavernaSecurityContext context,
+ TavernaRun run) {
+ this.context = context;
+ this.run = run;
+ return this;
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Descriptor describe(UriInfo ui) {
+ return new Descriptor(secure(ui).path("{element}"), context.getOwner()
+ .getName(), context.getCredentials(), context.getTrusted());
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public String getOwner() {
+ return context.getOwner().getName();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public CredentialList listCredentials() {
+ return new CredentialList(context.getCredentials());
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public CredentialHolder getParticularCredential(String id)
+ throws NoCredentialException {
+ for (Credential c : context.getCredentials())
+ if (c.id.equals(id))
+ return new CredentialHolder(c);
+ throw new NoCredentialException();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public CredentialHolder setParticularCredential(String id,
+ CredentialHolder cred, UriInfo ui)
+ throws InvalidCredentialException, BadStateChangeException {
+ if (run.getStatus() != Initialized)
+ throw new BadStateChangeException();
+ Credential c = cred.credential;
+ c.id = id;
+ c.href = ui.getAbsolutePath().toString();
+ context.validateCredential(c);
+ context.deleteCredential(c);
+ context.addCredential(c);
+ return new CredentialHolder(c);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Response addCredential(CredentialHolder cred, UriInfo ui)
+ throws InvalidCredentialException, BadStateChangeException {
+ if (run.getStatus() != Initialized)
+ throw new BadStateChangeException();
+ Credential c = cred.credential;
+ c.id = randomUUID().toString();
+ URI uri = secure(ui).path("{id}").build(c.id);
+ c.href = uri.toString();
+ context.validateCredential(c);
+ context.addCredential(c);
+ return created(uri).build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Response deleteAllCredentials(UriInfo ui)
+ throws BadStateChangeException {
+ if (run.getStatus() != Initialized)
+ throw new BadStateChangeException();
+ for (Credential c : context.getCredentials())
+ context.deleteCredential(c);
+ return noContent().build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Response deleteCredential(String id, UriInfo ui)
+ throws BadStateChangeException {
+ if (run.getStatus() != Initialized)
+ throw new BadStateChangeException();
+ context.deleteCredential(new Credential.Dummy(id));
+ return noContent().build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public TrustList listTrusted() {
+ return new TrustList(context.getTrusted());
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Trust getParticularTrust(String id) throws NoCredentialException {
+ for (Trust t : context.getTrusted())
+ if (t.id.equals(id))
+ return t;
+ throw new NoCredentialException();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Trust setParticularTrust(String id, Trust t, UriInfo ui)
+ throws InvalidCredentialException, BadStateChangeException {
+ if (run.getStatus() != Initialized)
+ throw new BadStateChangeException();
+ t.id = id;
+ t.href = ui.getAbsolutePath().toString();
+ context.validateTrusted(t);
+ context.deleteTrusted(t);
+ context.addTrusted(t);
+ return t;
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Response addTrust(Trust t, UriInfo ui)
+ throws InvalidCredentialException, BadStateChangeException {
+ if (run.getStatus() != Initialized)
+ throw new BadStateChangeException();
+ t.id = randomUUID().toString();
+ URI uri = secure(ui).path("{id}").build(t.id);
+ t.href = uri.toString();
+ context.validateTrusted(t);
+ context.addTrusted(t);
+ return created(uri).build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Response deleteAllTrusts(UriInfo ui) throws BadStateChangeException {
+ if (run.getStatus() != Initialized)
+ throw new BadStateChangeException();
+ for (Trust t : context.getTrusted())
+ context.deleteTrusted(t);
+ return noContent().build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Response deleteTrust(String id, UriInfo ui)
+ throws BadStateChangeException {
+ if (run.getStatus() != Initialized)
+ throw new BadStateChangeException();
+ Trust toDelete = new Trust();
+ toDelete.id = id;
+ context.deleteTrusted(toDelete);
+ return noContent().build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public PermissionsDescription describePermissions(UriInfo ui) {
+ Map<String, Permission> perm = support.getPermissionMap(context);
+ return new PermissionsDescription(secure(ui).path("{id}"), perm);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Permission describePermission(String id) {
+ return support.getPermission(context, id);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Permission setPermission(String id, Permission perm) {
+ support.setPermission(context, id, perm);
+ return support.getPermission(context, id);
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Response deletePermission(String id, UriInfo ui) {
+ support.setPermission(context, id, Permission.None);
+ return noContent().build();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public Response makePermission(PermissionDescription desc, UriInfo ui) {
+ support.setPermission(context, desc.userName, desc.permission);
+ return created(secure(ui).path("{user}").build(desc.userName)).build();
+ }
+
+ @Override
+ @CallCounted
+ public Response descriptionOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response ownerOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response credentialsOptions() {
+ return opt("POST", "DELETE");
+ }
+
+ @Override
+ @CallCounted
+ public Response credentialOptions(String id) {
+ return opt("PUT", "DELETE");
+ }
+
+ @Override
+ @CallCounted
+ public Response trustsOptions() {
+ return opt("POST", "DELETE");
+ }
+
+ @Override
+ @CallCounted
+ public Response trustOptions(String id) {
+ return opt("PUT", "DELETE");
+ }
+
+ @Override
+ @CallCounted
+ public Response permissionsOptions() {
+ return opt("POST");
+ }
+
+ @Override
+ @CallCounted
+ public Response permissionOptions(String id) {
+ return opt("PUT", "DELETE");
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/0b04b1ab/server-webapp/src/main/java/org/taverna/server/master/SingleListenerREST.java
----------------------------------------------------------------------
diff --git a/server-webapp/src/main/java/org/taverna/server/master/SingleListenerREST.java b/server-webapp/src/main/java/org/taverna/server/master/SingleListenerREST.java
new file mode 100644
index 0000000..6c9e8d8
--- /dev/null
+++ b/server-webapp/src/main/java/org/taverna/server/master/SingleListenerREST.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ *
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master;
+
+import static java.util.Arrays.asList;
+import static org.taverna.server.master.common.Uri.secure;
+import static org.taverna.server.master.utils.RestUtils.opt;
+
+import java.util.List;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.taverna.server.master.api.OneListenerBean;
+import org.taverna.server.master.exceptions.NoListenerException;
+import org.taverna.server.master.interfaces.Listener;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.rest.TavernaServerListenersREST;
+import org.taverna.server.master.rest.TavernaServerListenersREST.ListenerDescription;
+import org.taverna.server.master.rest.TavernaServerListenersREST.TavernaServerListenerREST;
+import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
+import org.taverna.server.master.utils.InvocationCounter.CallCounted;
+
+/**
+ * RESTful interface to a single listener attached to a workflow run.
+ *
+ * @author Donal Fellows
+ */
+abstract class SingleListenerREST implements TavernaServerListenerREST,
+ OneListenerBean {
+ private Listener listen;
+ private TavernaRun run;
+
+ @Override
+ public SingleListenerREST connect(Listener listen, TavernaRun run) {
+ this.listen = listen;
+ this.run = run;
+ return this;
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public String getConfiguration() {
+ return listen.getConfiguration();
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public ListenerDescription getDescription(UriInfo ui) {
+ return new ListenerDescription(listen, secure(ui));
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public TavernaServerListenersREST.Properties getProperties(UriInfo ui) {
+ return new TavernaServerListenersREST.Properties(secure(ui).path(
+ "{prop}"), listen.listProperties());
+ }
+
+ @Override
+ @CallCounted
+ @PerfLogged
+ public TavernaServerListenersREST.Property getProperty(
+ final String propertyName) throws NoListenerException {
+ List<String> p = asList(listen.listProperties());
+ if (p.contains(propertyName)) {
+ return makePropertyInterface().connect(listen, run, propertyName);
+ }
+ throw new NoListenerException("no such property");
+ }
+
+ protected abstract ListenerPropertyREST makePropertyInterface();
+
+ @Override
+ @CallCounted
+ public Response listenerOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response configurationOptions() {
+ return opt();
+ }
+
+ @Override
+ @CallCounted
+ public Response propertiesOptions() {
+ return opt();
+ }
+}