http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/DirectoryREST.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/DirectoryREST.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/DirectoryREST.java new file mode 100644 index 0000000..48969fa --- /dev/null +++ b/taverna-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/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/FileConcatenation.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/FileConcatenation.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/FileConcatenation.java new file mode 100644 index 0000000..3893b3d --- /dev/null +++ b/taverna-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/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/InputREST.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/InputREST.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/InputREST.java new file mode 100644 index 0000000..0f48207 --- /dev/null +++ b/taverna-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/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/InteractionFeed.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/InteractionFeed.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/InteractionFeed.java new file mode 100644 index 0000000..b686491 --- /dev/null +++ b/taverna-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/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/ListenerPropertyREST.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/ListenerPropertyREST.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/ListenerPropertyREST.java new file mode 100644 index 0000000..3e983a9 --- /dev/null +++ b/taverna-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/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/ListenersREST.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/ListenersREST.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/ListenersREST.java new file mode 100644 index 0000000..4b7d7f3 --- /dev/null +++ b/taverna-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/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/ManagementState.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/ManagementState.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/ManagementState.java new file mode 100644 index 0000000..9d4a651 --- /dev/null +++ b/taverna-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/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/RunREST.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/RunREST.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/RunREST.java new file mode 100644 index 0000000..563a822 --- /dev/null +++ b/taverna-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/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/RunSecurityREST.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/RunSecurityREST.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/RunSecurityREST.java new file mode 100644 index 0000000..5a366b2 --- /dev/null +++ b/taverna-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/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/SingleListenerREST.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/SingleListenerREST.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/SingleListenerREST.java new file mode 100644 index 0000000..6c9e8d8 --- /dev/null +++ b/taverna-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(); + } +}
