http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteRunFactory.java ---------------------------------------------------------------------- diff --git a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteRunFactory.java b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteRunFactory.java deleted file mode 100644 index bc91f0e..0000000 --- a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteRunFactory.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - */ -package org.taverna.server.localworker.remote; -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.rmi.Remote; -import java.rmi.RemoteException; -import java.util.UUID; - -import org.taverna.server.localworker.server.UsageRecordReceiver; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * The main RMI-enabled interface for creating runs. - * - * @author Donal Fellows - */ -public interface RemoteRunFactory extends Remote { - /** - * Makes a workflow run that will process a particular workflow document. - * - * @param workflow - * The (serialised) workflow to instantiate as a run. - * @param creator - * Who is this run created for? - * @param usageRecordReceiver - * Where to write any usage records. May be <tt>null</tt> to - * cause them to not be written. - * @param masterID - * The UUID of the run to use, or <tt>null</tt> if the execution - * engine is to manufacture a new one for itself. - * @return A remote handle for the run. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - @Nonnull - RemoteSingleRun make(@Nonnull byte[] workflow, @Nonnull String creator, - @Nullable UsageRecordReceiver usageRecordReceiver, - @Nullable UUID masterID) throws RemoteException; - - /** - * Asks this factory to unregister itself from the registry and cease - * operation. - * - * @throws RemoteException - * If anything goes wrong with the communication. - */ - void shutdown() throws RemoteException; - - /** - * Configures the details to use when setting up the workflow run's - * connnection to the interaction feed. - * - * @param host - * The host where the feed is located. - * @param port - * The port where the feed is located. - * @param webdavPath - * The path used for pushing web pages into the feed. - * @param feedPath - * The path used for reading and writing notifications on the - * feed. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - void setInteractionServiceDetails(@Nonnull String host, - @Nonnull String port, @Nonnull String webdavPath, - @Nonnull String feedPath) throws RemoteException; - - /** - * Gets a count of the number of {@linkplain RemoteSingleRun workflow runs} - * that this factor knows about that are in the - * {@link RemoteStatus#Operating Operating} state. - * - * @return A count of "running" workflow runs. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - int countOperatingRuns() throws RemoteException; -}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSecurityContext.java ---------------------------------------------------------------------- diff --git a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSecurityContext.java b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSecurityContext.java deleted file mode 100644 index fc4baff..0000000 --- a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSecurityContext.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - */ -package org.taverna.server.localworker.remote; -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.net.URI; -import java.rmi.Remote; -import java.rmi.RemoteException; -import java.util.Map; - -import javax.annotation.Nonnull; - -/** - * Outline of the security context for a workflow run. - * - * @author Donal Fellows - */ -public interface RemoteSecurityContext extends Remote { - void setKeystore(@Nonnull byte[] keystore) throws RemoteException, - ImplementationException; - - void setPassword(@Nonnull char[] password) throws RemoteException, - ImplementationException; - - void setTruststore(@Nonnull byte[] truststore) throws RemoteException, - ImplementationException; - - void setUriToAliasMap(@Nonnull Map<URI, String> uriToAliasMap) - throws RemoteException; - - void setHelioToken(@Nonnull String helioToken) throws RemoteException; -} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSingleRun.java ---------------------------------------------------------------------- diff --git a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSingleRun.java b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSingleRun.java deleted file mode 100644 index e2519f8..0000000 --- a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteSingleRun.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - */ -package org.taverna.server.localworker.remote; -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.net.URL; -import java.rmi.Remote; -import java.rmi.RemoteException; -import java.util.Date; -import java.util.List; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public interface RemoteSingleRun extends Remote { - /** - * @return The name of the Baclava file to use for all inputs, or - * <tt>null</tt> if no Baclava file is set. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - @Nullable - public String getInputBaclavaFile() throws RemoteException; - - /** - * Sets the Baclava file to use for all inputs. This overrides the use of - * individual inputs. - * - * @param filename - * The filename to use. Must not start with a <tt>/</tt> or - * contain any <tt>..</tt> segments. Will be interpreted relative - * to the run's working directory. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - public void setInputBaclavaFile(@Nonnull String filename) - throws RemoteException; - - /** - * @return The list of input assignments. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - @Nonnull - public List<RemoteInput> getInputs() throws RemoteException; - - /** - * Create an input assignment. - * - * @param name - * The name of the port that this will be an input for. - * @return The assignment reference. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - @Nonnull - public RemoteInput makeInput(@Nonnull String name) throws RemoteException; - - /** - * @return The file (relative to the working directory) to write the outputs - * of the run to as a Baclava document, or <tt>null</tt> if they are - * to be written to non-Baclava files in a directory called - * <tt>out</tt>. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - @Nullable - public String getOutputBaclavaFile() throws RemoteException; - - /** - * Sets where the output of the run is to be written to. This will cause the - * output to be generated as a Baclava document, rather than a collection of - * individual non-Baclava files in the subdirectory of the working directory - * called <tt>out</tt>. - * - * @param filename - * Where to write the Baclava file (or <tt>null</tt> to cause the - * output to be written to individual files); overwrites any - * previous setting of this value. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - public void setOutputBaclavaFile(@Nullable String filename) - throws RemoteException; - - /** - * @return The current status of the run. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - @Nonnull - public RemoteStatus getStatus() throws RemoteException; - - /** - * Set the status of the run, which should cause it to move into the given - * state. This may cause some significant changes. - * - * @param s - * The state to try to change to. - * @throws IllegalStateTransitionException - * If the requested state change is impossible. (Note that it is - * always legal to set the status to the current status.) - * @throws RemoteException - * If anything goes wrong with the communication. - * @throws ImplementationException - * If something goes horribly wrong on the back end. - * @throws StillWorkingOnItException - * If the startup time of the workflow implementation exceeds a - * built-in threshold. - */ - public void setStatus(@Nonnull RemoteStatus s) - throws IllegalStateTransitionException, RemoteException, - ImplementationException, StillWorkingOnItException; - - /** - * @return When this workflow run was found to have finished, or - * <tt>null</tt> if it has never finished (either still running or - * never started). - * @throws RemoteException - * If anything goes wrong with the communication. - */ - @Nullable - public Date getFinishTimestamp() throws RemoteException; - - /** - * @return When this workflow run was started, or <tt>null</tt> if it has - * never been started. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - @Nullable - public Date getStartTimestamp() throws RemoteException; - - /** - * @return Handle to the main working directory of the run. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - @Nonnull - public RemoteDirectory getWorkingDirectory() throws RemoteException; - - /** - * @return The list of listener instances attached to the run. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - @Nonnull - public List<RemoteListener> getListeners() throws RemoteException; - - /** - * Add a listener to the run. - * - * @param listener - * The listener to add. - * @throws RemoteException - * If anything goes wrong with the communication. - * @throws ImplementationException - * If something goes wrong when adding the listener. - */ - public void addListener(@Nonnull RemoteListener listener) - throws RemoteException, ImplementationException; - - /** - * @return The security context structure for this run. - * @throws RemoteException - * If anything goes wrong with the communication. - * @throws ImplementationException - * If something goes wrong when getting the context. - */ - @Nonnull - public RemoteSecurityContext getSecurityContext() throws RemoteException, - ImplementationException; - - /** - * Kill off this run, removing all resources which it consumes. - * - * @throws RemoteException - * If anything goes wrong with the communication. - * @throws ImplementationException - * If something goes horribly wrong when destroying the run. - */ - public void destroy() throws RemoteException, ImplementationException; - - /** - * Get the types of listener supported by this run. - * - * @return A list of listener type names. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - @Nonnull - public List<String> getListenerTypes() throws RemoteException; - - /** - * Create a listener that can be attached to this run. - * - * @param type - * The type name of the listener to create; it must be one of the - * names returned by the {@link #getListenerTypes()} operation. - * @param configuration - * The configuration document for this listener. The nature of - * the contents of this are determined by the type. - * @return A handle for the listener. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - @Nonnull - public RemoteListener makeListener(@Nonnull String type, - @Nonnull String configuration) throws RemoteException; - - /** - * Configures the details to use when setting up the workflow run's - * connnection to the interaction feed. - * - * @param interactionFeed - * The location of the interaction feed. If <tt>null</tt>, - * defaults from the factory will be used instead. - * @param webdavPath - * The location used for pushing web pages to support the feed. - * If <tt>null</tt>, a default from the factory will be used - * instead. - * @param publishUrlBase - * Where to <i>actually</i> publish to, if this needs to be - * different from the location presented in the published HTML - * and Feed entries. Necessary in complex network scenarios. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - void setInteractionServiceDetails(@Nonnull URL interactionFeed, - @Nonnull URL webdavPath, @Nullable URL publishUrlBase) throws RemoteException; - - /** - * A do-nothing method, used to check the general reachability of the - * workflow run. - * - * @throws RemoteException - * If anything goes wrong with the communication. - */ - void ping() throws RemoteException; - - /** - * Sets whether we should generate provenance information from a run. - * - * @param generateProvenance - * Boolean flag, true for do the generation. Must be set before - * starting the run for this to have an effect. - * @throws RemoteException - * If anything goes wrong with the communication. - */ - void setGenerateProvenance(boolean generateProvenance) - throws RemoteException; -} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteStatus.java ---------------------------------------------------------------------- diff --git a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteStatus.java b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteStatus.java deleted file mode 100644 index 89a7cff..0000000 --- a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/RemoteStatus.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - */ -package org.taverna.server.localworker.remote; -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * States of a workflow run. They are {@link RemoteStatus#Initialized - * Initialized}, {@link RemoteStatus#Operating Operating}, - * {@link RemoteStatus#Stopped Stopped}, and {@link RemoteStatus#Finished - * Finished}. Conceptually, there is also a <tt>Destroyed</tt> state, but the - * workflow run does not exist (and hence can't have its state queried or set) - * in that case. - * - * @author Donal Fellows - */ -public enum RemoteStatus { - /** - * The workflow run has been created, but is not yet running. The run will - * need to be manually moved to {@link #Operating} when ready. - */ - Initialized, - /** - * The workflow run is going, reading input, generating output, etc. Will - * eventually either move automatically to {@link #Finished} or can be moved - * manually to {@link #Stopped} (where supported). - */ - Operating, - /** - * The workflow run is paused, and will need to be moved back to - * {@link #Operating} manually. - */ - Stopped, - /** - * The workflow run has ceased; data files will continue to exist until the - * run is destroyed (which may be manual or automatic). - */ - Finished -} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/StillWorkingOnItException.java ---------------------------------------------------------------------- diff --git a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/StillWorkingOnItException.java b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/StillWorkingOnItException.java deleted file mode 100644 index a7af96b..0000000 --- a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/StillWorkingOnItException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - */ -package org.taverna.server.localworker.remote; -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Exception that indicates that the implementation is still working on - * processing the operation. Note that though this is an exception, it is <i>not - * a failure</i>. - * - * @author Donal Fellows - */ -@SuppressWarnings("serial") -public class StillWorkingOnItException extends Exception { - public StillWorkingOnItException(String string) { - super(string); - } -} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/package-info.java ---------------------------------------------------------------------- diff --git a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/package-info.java b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/package-info.java deleted file mode 100644 index 6466e2f..0000000 --- a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/remote/package-info.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - */ -/** - * Interfaces exported by worker classes to the server. - */ -package org.taverna.server.localworker.remote; -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/UsageRecordReceiver.java ---------------------------------------------------------------------- diff --git a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/UsageRecordReceiver.java b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/UsageRecordReceiver.java deleted file mode 100644 index cc0127e..0000000 --- a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/UsageRecordReceiver.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - */ -package org.taverna.server.localworker.server; -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.rmi.Remote; -import java.rmi.RemoteException; - -/** - * Interface exported by (part of) the webapp to allow processes it creates to - * push in usage records. - * - * @author Donal Fellows - */ -public interface UsageRecordReceiver extends Remote { - /** - * Called to push in a usage record. Note that it is assumed that the usage - * record already contains all the information required to locate and - * process the job; there is no separate handle. - * - * @param usageRecord - * The serialised XML of the usage record. - * @throws RemoteException - * if anything goes wrong. - */ - void acceptUsageRecord(String usageRecord) throws RemoteException; -} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/package-info.java ---------------------------------------------------------------------- diff --git a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/package-info.java b/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/package-info.java deleted file mode 100644 index 584f1ed..0000000 --- a/taverna-server-runinterface/src/main/java/org/taverna/server/localworker/server/package-info.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - */ -/** - * Interfaces exported by the server to worker classes. - */ -package org.taverna.server.localworker.server; -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-unix-forker/src/main/java/org/apache/taverna/server/unixforker/Forker.java ---------------------------------------------------------------------- diff --git a/taverna-server-unix-forker/src/main/java/org/apache/taverna/server/unixforker/Forker.java b/taverna-server-unix-forker/src/main/java/org/apache/taverna/server/unixforker/Forker.java new file mode 100644 index 0000000..ebe73f4 --- /dev/null +++ b/taverna-server-unix-forker/src/main/java/org/apache/taverna/server/unixforker/Forker.java @@ -0,0 +1,221 @@ +/* + */ +package org.taverna.server.unixforker; +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import static java.lang.System.err; +import static java.lang.System.getProperty; +import static java.lang.System.in; +import static java.lang.System.out; +import static java.util.Arrays.asList; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.List; + +import javax.annotation.Nonnull; + +/** + * A simple class that forks off processes when asked to over its standard + * input. The one complication is that it forks them off as other users, through + * the use of the <tt>sudo</tt> utility. It is Unix-specific. + * + * @author Donal Fellows + */ +public class Forker extends Thread { + private static String password; + private static BufferedReader br; + + /** + * Helper to make reading a password from a file clearer. The password must + * be the first line of the file. + * + * @param passwordFile + * The file to load from. + * @throws IOException + * If anything goes wrong. + */ + private static void loadPassword(@Nonnull File passwordFile) + throws IOException { + try { + err.println("attempting to load password from " + passwordFile); + try (FileReader fr = new FileReader(passwordFile)) { + password = new BufferedReader(fr).readLine(); + } + } catch (IOException e) { + err.println("failed to read password from file " + passwordFile + + "described in password.file property"); + throw e; + } + } + + /** + * Initialization code, which runs before the main loop starts processing. + * + * @param args + * The arguments to the program. + * @throws Exception + * If anything goes wrong. + */ + public static void init(String[] args) throws Exception { + if (args.length < 1) + throw new IllegalArgumentException( + "wrong # args: must be \"program ?argument ...?\""); + if (getProperty("password.file") != null) + loadPassword(new File(getProperty("password.file"))); + if (password == null) + err.println("no password.file property or empty file; " + + "assuming password-less sudo is configured"); + else + err.println("password is of length " + password.length()); + br = new BufferedReader(new InputStreamReader(in)); + } + + /** + * The body of the main loop of this program. + * + * @param args + * The arguments to use when running the other program. + * @return Whether to repeat the loop. + * @throws Exception + * If anything goes wrong. Note that the loop is repeated if an + * exception occurs in it. + */ + public static boolean mainLoopBody(String[] args) throws Exception { + String line = br.readLine(); + if (line == null) + return false; + List<String> vals = asList(line.split("[ \t]+")); + if (vals.size() != 2) { + out.println("wrong # values: must be \"username UUID\""); + return true; + } + ProcessBuilder pb = new ProcessBuilder(); + pb.command() + .addAll(asList("sudo", "-u", vals.get(0), "-S", "-H", "--")); + pb.command().addAll(asList(args)); + pb.command().add(vals.get(1)); + Forker f = new Forker(pb); + f.setDaemon(true); + f.start(); + return true; + } + + /** + * The main code for this class, which turns this into an executable + * program. Runs the initialisation and then the main loop, in both cases + * with appropriate error handling. + * + * @param args + * Arguments to this program. + */ + public static void main(String... args) { + try { + init(args); + while (true) { + try { + if (!mainLoopBody(args)) + break; + } catch (Exception e) { + e.printStackTrace(err); + out.println(e.getClass().getName() + ": " + e.getMessage()); + } + } + System.exit(0); + } catch (Exception e) { + e.printStackTrace(err); + System.exit(1); + } + } + + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + public Forker(ProcessBuilder pb) throws IOException { + out.println("Starting subprocess: " + pb.command()); + final Process p = pb.start(); + abstract class ProcessAttachedDaemon extends Thread { + public ProcessAttachedDaemon() { + setDaemon(true); + start(); + } + + abstract void act() throws Exception; + + @Override + public final void run() { + try { + act(); + p.waitFor(); + } catch (InterruptedException e) { + // Just drop + } catch (Exception e) { + p.destroy(); + e.printStackTrace(err); + } + } + } + new ProcessAttachedDaemon() { + @Override + void act() throws Exception { + copyFromSudo("Subprocess(out):", p.getInputStream()); + } + }; + new ProcessAttachedDaemon() { + @Override + void act() throws Exception { + copyFromSudo("Subprocess(err):", p.getErrorStream()); + } + }; + new ProcessAttachedDaemon() { + @Override + void act() throws Exception { + interactWithSudo(p.getOutputStream()); + } + }; + } + + protected void interactWithSudo(OutputStream os) throws Exception { + if (password != null) { + OutputStreamWriter osw = new OutputStreamWriter(os); + osw.write(password + "\n"); + osw.flush(); + } + os.close(); + } + + protected void copyFromSudo(String header, InputStream sudoStream) + throws Exception { + int b = '\n'; + while (true) { + if (b == '\n') + out.print(header); + b = sudoStream.read(); + if (b == -1) + break; + out.write(b); + out.flush(); + } + sudoStream.close(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-unix-forker/src/main/java/org/taverna/server/unixforker/Forker.java ---------------------------------------------------------------------- diff --git a/taverna-server-unix-forker/src/main/java/org/taverna/server/unixforker/Forker.java b/taverna-server-unix-forker/src/main/java/org/taverna/server/unixforker/Forker.java deleted file mode 100644 index ebe73f4..0000000 --- a/taverna-server-unix-forker/src/main/java/org/taverna/server/unixforker/Forker.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - */ -package org.taverna.server.unixforker; -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import static java.lang.System.err; -import static java.lang.System.getProperty; -import static java.lang.System.in; -import static java.lang.System.out; -import static java.util.Arrays.asList; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.util.List; - -import javax.annotation.Nonnull; - -/** - * A simple class that forks off processes when asked to over its standard - * input. The one complication is that it forks them off as other users, through - * the use of the <tt>sudo</tt> utility. It is Unix-specific. - * - * @author Donal Fellows - */ -public class Forker extends Thread { - private static String password; - private static BufferedReader br; - - /** - * Helper to make reading a password from a file clearer. The password must - * be the first line of the file. - * - * @param passwordFile - * The file to load from. - * @throws IOException - * If anything goes wrong. - */ - private static void loadPassword(@Nonnull File passwordFile) - throws IOException { - try { - err.println("attempting to load password from " + passwordFile); - try (FileReader fr = new FileReader(passwordFile)) { - password = new BufferedReader(fr).readLine(); - } - } catch (IOException e) { - err.println("failed to read password from file " + passwordFile - + "described in password.file property"); - throw e; - } - } - - /** - * Initialization code, which runs before the main loop starts processing. - * - * @param args - * The arguments to the program. - * @throws Exception - * If anything goes wrong. - */ - public static void init(String[] args) throws Exception { - if (args.length < 1) - throw new IllegalArgumentException( - "wrong # args: must be \"program ?argument ...?\""); - if (getProperty("password.file") != null) - loadPassword(new File(getProperty("password.file"))); - if (password == null) - err.println("no password.file property or empty file; " - + "assuming password-less sudo is configured"); - else - err.println("password is of length " + password.length()); - br = new BufferedReader(new InputStreamReader(in)); - } - - /** - * The body of the main loop of this program. - * - * @param args - * The arguments to use when running the other program. - * @return Whether to repeat the loop. - * @throws Exception - * If anything goes wrong. Note that the loop is repeated if an - * exception occurs in it. - */ - public static boolean mainLoopBody(String[] args) throws Exception { - String line = br.readLine(); - if (line == null) - return false; - List<String> vals = asList(line.split("[ \t]+")); - if (vals.size() != 2) { - out.println("wrong # values: must be \"username UUID\""); - return true; - } - ProcessBuilder pb = new ProcessBuilder(); - pb.command() - .addAll(asList("sudo", "-u", vals.get(0), "-S", "-H", "--")); - pb.command().addAll(asList(args)); - pb.command().add(vals.get(1)); - Forker f = new Forker(pb); - f.setDaemon(true); - f.start(); - return true; - } - - /** - * The main code for this class, which turns this into an executable - * program. Runs the initialisation and then the main loop, in both cases - * with appropriate error handling. - * - * @param args - * Arguments to this program. - */ - public static void main(String... args) { - try { - init(args); - while (true) { - try { - if (!mainLoopBody(args)) - break; - } catch (Exception e) { - e.printStackTrace(err); - out.println(e.getClass().getName() + ": " + e.getMessage()); - } - } - System.exit(0); - } catch (Exception e) { - e.printStackTrace(err); - System.exit(1); - } - } - - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - - public Forker(ProcessBuilder pb) throws IOException { - out.println("Starting subprocess: " + pb.command()); - final Process p = pb.start(); - abstract class ProcessAttachedDaemon extends Thread { - public ProcessAttachedDaemon() { - setDaemon(true); - start(); - } - - abstract void act() throws Exception; - - @Override - public final void run() { - try { - act(); - p.waitFor(); - } catch (InterruptedException e) { - // Just drop - } catch (Exception e) { - p.destroy(); - e.printStackTrace(err); - } - } - } - new ProcessAttachedDaemon() { - @Override - void act() throws Exception { - copyFromSudo("Subprocess(out):", p.getInputStream()); - } - }; - new ProcessAttachedDaemon() { - @Override - void act() throws Exception { - copyFromSudo("Subprocess(err):", p.getErrorStream()); - } - }; - new ProcessAttachedDaemon() { - @Override - void act() throws Exception { - interactWithSudo(p.getOutputStream()); - } - }; - } - - protected void interactWithSudo(OutputStream os) throws Exception { - if (password != null) { - OutputStreamWriter osw = new OutputStreamWriter(os); - osw.write(password + "\n"); - osw.flush(); - } - os.close(); - } - - protected void copyFromSudo(String header, InputStream sudoStream) - throws Exception { - int b = '\n'; - while (true) { - if (b == '\n') - out.print(header); - b = sudoStream.read(); - if (b == -1) - break; - out.write(b); - out.flush(); - } - sudoStream.close(); - } -} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ContentsDescriptorBuilder.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ContentsDescriptorBuilder.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ContentsDescriptorBuilder.java new file mode 100644 index 0000000..051a037 --- /dev/null +++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ContentsDescriptorBuilder.java @@ -0,0 +1,305 @@ +/* + */ +package org.taverna.server.master; +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import static eu.medsea.util.MimeUtil.getMimeType; +import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE; +import static javax.ws.rs.core.UriBuilder.fromUri; +import static org.apache.commons.logging.LogFactory.getLog; +import static org.taverna.server.master.common.Uri.secure; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; + +import org.apache.commons.logging.Log; +import org.springframework.beans.factory.annotation.Required; +import org.taverna.server.master.exceptions.FilesystemAccessException; +import org.taverna.server.master.exceptions.NoDirectoryEntryException; +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.interfaces.UriBuilderFactory; +import org.taverna.server.master.utils.FilenameUtils; +import org.taverna.server.port_description.AbsentValue; +import org.taverna.server.port_description.AbstractPortDescription; +import org.taverna.server.port_description.AbstractValue; +import org.taverna.server.port_description.ErrorValue; +import org.taverna.server.port_description.InputDescription; +import org.taverna.server.port_description.InputDescription.InputPort; +import org.taverna.server.port_description.LeafValue; +import org.taverna.server.port_description.ListValue; +import org.taverna.server.port_description.OutputDescription; +import org.taverna.server.port_description.OutputDescription.OutputPort; + +import org.apache.taverna.scufl2.api.container.WorkflowBundle; +import org.apache.taverna.scufl2.api.core.Workflow; +import org.apache.taverna.scufl2.api.port.InputWorkflowPort; +import org.apache.taverna.scufl2.api.port.OutputWorkflowPort; + +/** + * A class that is used to build descriptions of the contents of a workflow + * run's filesystem. + * + * @author Donal Fellows + */ +public class ContentsDescriptorBuilder { + private Log log = getLog("Taverna.Server.Webapp"); + private FilenameUtils fileUtils; + private UriBuilderFactory uriBuilderFactory; + + @Required + public void setUriBuilderFactory(UriBuilderFactory uriBuilderFactory) { + this.uriBuilderFactory = uriBuilderFactory; + } + + @Required + public void setFileUtils(FilenameUtils fileUtils) { + this.fileUtils = fileUtils; + } + + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + private Workflow fillInFromWorkflow(TavernaRun run, UriBuilder ub, + AbstractPortDescription portDesc) throws IOException { + WorkflowBundle bundle = run.getWorkflow().getScufl2Workflow(); + bundle.getMainWorkflow().getInputPorts(); + portDesc.fillInBaseData(bundle.getMainWorkflow() + .getIdentifier().toString(), run.getId(), ub.build()); + return bundle.getMainWorkflow(); + } + + /** + * Computes the depth of value in a descriptor. + * + * @param value + * The value description to characterise. + * @return Its depth (i.e., the depth of the port outputting the value) or + * <tt>null</tt> if that is impossible to determine. + */ + private Integer computeDepth(AbstractValue value) { + if (value instanceof ListValue) { + int mv = 1; + for (AbstractValue v : ((ListValue) value).contents) { + Integer d = computeDepth(v); + if (d != null && mv <= d) + mv = d + 1; + } + return mv; + } else if (value instanceof LeafValue || value instanceof ErrorValue) + return 0; + else + return null; + } + + /** + * Build a description of a leaf value. + * + * @param file + * The file representing the value. + * @return A value descriptor. + * @throws FilesystemAccessException + * If anything goes wrong. + */ + private LeafValue constructLeafValue(File file) + throws FilesystemAccessException { + LeafValue v = new LeafValue(); + v.fileName = file.getFullName(); + v.byteLength = file.getSize(); + try { + byte[] head = file.getContents(0, 1024); + v.contentType = getMimeType(new ByteArrayInputStream(head)); + } catch (Exception e) { + v.contentType = APPLICATION_OCTET_STREAM_TYPE.toString(); + } + return v; + } + + /** + * Build a description of an error value. + * + * @param file + * The file representing the error. + * @return A value descriptor. + * @throws FilesystemAccessException + * If anything goes wrong. + */ + private ErrorValue constructErrorValue(File file) + throws FilesystemAccessException { + ErrorValue v = new ErrorValue(); + v.fileName = file.getFullName(); + v.byteLength = file.getSize(); + return v; + } + + /** + * Build a description of a list value. + * + * @param dir + * The directory representing the list. + * @param ub + * The factory for URIs. + * @return A value descriptor. + * @throws FilesystemAccessException + * If anything goes wrong. + */ + private ListValue constructListValue(Directory dir, UriBuilder ub) + throws FilesystemAccessException { + ListValue v = new ListValue(); + v.length = 0; + Set<DirectoryEntry> contents = new HashSet<>(dir.getContents()); + Iterator<DirectoryEntry> it = contents.iterator(); + while (it.hasNext()) + if (!it.next().getName().matches("^[0-9]+([.].*)?$")) + it.remove(); + for (int i = 1; !contents.isEmpty(); i++) { + String exact = Integer.toString(i); + AbstractValue subval = constructValue(contents, ub, exact); + v.contents.add(subval); + if (!(subval instanceof AbsentValue)) { + v.length = i; + String pfx = i + "."; + for (DirectoryEntry de : contents) + if (de.getName().equals(exact) + || de.getName().startsWith(pfx)) { + contents.remove(de); + break; + } + } + } + return v; + } + + /** + * Build a value description. + * + * @param parentContents + * The contents of the parent directory. + * @param ub + * The factory for URIs. + * @param name + * The name of the value's file/directory representative. + * @return A value descriptor. + * @throws FilesystemAccessException + * If anything goes wrong. + */ + private AbstractValue constructValue( + Collection<DirectoryEntry> parentContents, UriBuilder ub, + String name) throws FilesystemAccessException { + String error = name + ".error"; + String prefix = name + "."; + for (DirectoryEntry entry : parentContents) { + AbstractValue av; + if (entry.getName().equals(error) && entry instanceof File) { + av = constructErrorValue((File) entry); + } else if (!entry.getName().equals(name) + && !entry.getName().startsWith(prefix)) + continue; + else if (entry instanceof File) + av = constructLeafValue((File) entry); + else + av = constructListValue((Directory) entry, ub); + String fullPath = entry.getFullName().replaceFirst("^/", ""); + av.href = ub.clone().path(fullPath).build(); + return av; + } + return new AbsentValue(); + } + + /** + * Construct a description of the outputs of a workflow run. + * + * @param run + * The workflow run whose outputs are to be described. + * @param ui + * The origin for URIs. + * @return The description, which can be serialized to XML. + * @throws FilesystemAccessException + * If something goes wrong reading the directories. + * @throws NoDirectoryEntryException + * If something goes wrong reading the directories. + */ + public OutputDescription makeOutputDescriptor(TavernaRun run, UriInfo ui) + throws FilesystemAccessException, NoDirectoryEntryException { + OutputDescription descriptor = new OutputDescription(); + try { + UriBuilder ub = getRunUriBuilder(run, ui); + Workflow dataflow = fillInFromWorkflow(run, ub, descriptor); + Collection<DirectoryEntry> outs = null; + ub = ub.path("wd/{path}"); + for (OutputWorkflowPort output : dataflow.getOutputPorts()) { + OutputPort p = descriptor.addPort(output.getName()); + if (run.getOutputBaclavaFile() == null) { + if (outs == null) + outs = fileUtils.getDirectory(run, "out").getContents(); + p.output = constructValue(outs, ub, p.name); + p.depth = computeDepth(p.output); + } + } + } catch (IOException e) { + log.info("failure in conversion to .scufl2", e); + } + return descriptor; + } + + private UriBuilder getRunUriBuilder(TavernaRun run, UriInfo ui) { + if (ui == null) + return secure(uriBuilderFactory.getRunUriBuilder(run)); + else + return secure(fromUri(ui.getAbsolutePath().toString() + .replaceAll("/(out|in)put/?$", ""))); + } + + /** + * Constructs input descriptions. + * + * @param run + * The run to build for. + * @param ui + * The mechanism for building URIs. + * @return The description of the <i>expected</i> inputs of the run. + */ + public InputDescription makeInputDescriptor(TavernaRun run, UriInfo ui) { + InputDescription desc = new InputDescription(); + try { + UriBuilder ub = getRunUriBuilder(run, ui); + Workflow workflow = fillInFromWorkflow(run, ub, desc); + ub = ub.path("input/{name}"); + for (InputWorkflowPort port : workflow.getInputPorts()) { + InputPort in = desc.addPort(port.getName()); + in.href = ub.build(in.name); + try { + in.depth = port.getDepth(); + } catch (NumberFormatException ex) { + in.depth = null; + } + } + } catch (IOException e) { + log.info("failure in conversion to .scufl2", e); + } + return desc; + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/DirectoryREST.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/DirectoryREST.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/DirectoryREST.java new file mode 100644 index 0000000..b800c1c --- /dev/null +++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/DirectoryREST.java @@ -0,0 +1,388 @@ +/* + */ +package org.taverna.server.master; +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/FileConcatenation.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/FileConcatenation.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/FileConcatenation.java new file mode 100644 index 0000000..4ccb033 --- /dev/null +++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/FileConcatenation.java @@ -0,0 +1,84 @@ +package org.taverna.server.master; +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InputREST.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InputREST.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InputREST.java new file mode 100644 index 0000000..b9c8f55 --- /dev/null +++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InputREST.java @@ -0,0 +1,265 @@ +/* + */ +package org.taverna.server.master; +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InteractionFeed.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InteractionFeed.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InteractionFeed.java new file mode 100644 index 0000000..6dfa3e2 --- /dev/null +++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/InteractionFeed.java @@ -0,0 +1,120 @@ +/* + */ +package org.taverna.server.master; +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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/00397eff/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ListenerPropertyREST.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ListenerPropertyREST.java b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ListenerPropertyREST.java new file mode 100644 index 0000000..26a8982 --- /dev/null +++ b/taverna-server-webapp/src/main/java/org/apache/taverna/server/master/ListenerPropertyREST.java @@ -0,0 +1,91 @@ +/* + */ +package org.taverna.server.master; +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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
