http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/misc/xsd/persistence_1_0.xsd ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/misc/xsd/persistence_1_0.xsd b/taverna-server-webapp/src/misc/xsd/persistence_1_0.xsd new file mode 100644 index 0000000..a485e30 --- /dev/null +++ b/taverna-server-webapp/src/misc/xsd/persistence_1_0.xsd @@ -0,0 +1,305 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- persistence.xml schema --> +<xsd:schema targetNamespace="http://java.sun.com/xml/ns/persistence" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns:persistence="http://java.sun.com/xml/ns/persistence" + elementFormDefault="qualified" + attributeFormDefault="unqualified" + version="1.0"> + + <xsd:annotation> + <xsd:documentation> + @(#)persistence_1_0.xsd 1.0 Feb 9 2006 + </xsd:documentation> + </xsd:annotation> + + <xsd:annotation> + <xsd:documentation> + + DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + + Copyright 2005-2007 Sun Microsystems, Inc. All rights reserved. + + The contents of this file are subject to the terms of either the + GNU General Public License Version 2 only ("GPL") or the Common + Development and Distribution License("CDDL") (collectively, the + "License"). You may not use this file except in compliance with + the License. You can obtain a copy of the License at + https://glassfish.dev.java.net/public/CDDL+GPL.html or + glassfish/bootstrap/legal/LICENSE.txt. See the License for the + specific language governing permissions and limitations under the + License. + + When distributing the software, include this License Header + Notice in each file and include the License file at + glassfish/bootstrap/legal/LICENSE.txt. Sun designates this + particular file as subject to the "Classpath" exception as + provided by Sun in the GPL Version 2 section of the License file + that accompanied this code. If applicable, add the following + below the License Header, with the fields enclosed by brackets [] + replaced by your own identifying information: + "Portions Copyrighted [year] [name of copyright owner]" + + Contributor(s): + + If you wish your version of this file to be governed by only the + CDDL or only the GPL Version 2, indicate your decision by adding + "[Contributor] elects to include this software in this + distribution under the [CDDL or GPL Version 2] license." If you + don't indicate a single choice of license, a recipient has the + option to distribute your version of this file under either the + CDDL, the GPL Version 2 or to extend the choice of license to its + licensees as provided above. However, if you add GPL Version 2 + code and therefore, elected the GPL Version 2 license, then the + option applies only if the new code is made subject to such + option by the copyright holder. + + </xsd:documentation> + </xsd:annotation> + + <xsd:annotation> + <xsd:documentation><![CDATA[ + + This is the XML Schema for the persistence configuration file. + The file must be named "META-INF/persistence.xml" in the + persistence archive. + Persistence configuration files must indicate + the persistence schema by using the persistence namespace: + + http://java.sun.com/xml/ns/persistence + + and indicate the version of the schema by + using the version element as shown below: + + <persistence xmlns="http://java.sun.com/xml/ns/persistence" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/persistence + http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" + version="1.0"> + ... + </persistence> + + ]]></xsd:documentation> + </xsd:annotation> + + <xsd:simpleType name="versionType"> + <xsd:restriction base="xsd:token"> + <xsd:pattern value="[0-9]+(\.[0-9]+)*"/> + </xsd:restriction> + </xsd:simpleType> + + <!-- **************************************************** --> + + <xsd:element name="persistence"> + <xsd:complexType> + <xsd:sequence> + + <!-- **************************************************** --> + + <xsd:element name="persistence-unit" + minOccurs="0" maxOccurs="unbounded"> + <xsd:complexType> + <xsd:annotation> + <xsd:documentation> + + Configuration of a persistence unit. + + </xsd:documentation> + </xsd:annotation> + <xsd:sequence> + + <!-- **************************************************** --> + + <xsd:element name="description" type="xsd:string" + minOccurs="0"> + <xsd:annotation> + <xsd:documentation> + + Textual description of this persistence unit. + + </xsd:documentation> + </xsd:annotation> + </xsd:element> + + <!-- **************************************************** --> + + <xsd:element name="provider" type="xsd:string" + minOccurs="0"> + <xsd:annotation> + <xsd:documentation> + + Provider class that supplies EntityManagers for this + persistence unit. + + </xsd:documentation> + </xsd:annotation> + </xsd:element> + + <!-- **************************************************** --> + + <xsd:element name="jta-data-source" type="xsd:string" + minOccurs="0"> + <xsd:annotation> + <xsd:documentation> + + The container-specific name of the JTA datasource to use. + + </xsd:documentation> + </xsd:annotation> + </xsd:element> + + <!-- **************************************************** --> + + <xsd:element name="non-jta-data-source" type="xsd:string" + minOccurs="0"> + <xsd:annotation> + <xsd:documentation> + + The container-specific name of a non-JTA datasource to use. + + </xsd:documentation> + </xsd:annotation> + </xsd:element> + + <!-- **************************************************** --> + + <xsd:element name="mapping-file" type="xsd:string" + minOccurs="0" maxOccurs="unbounded"> + <xsd:annotation> + <xsd:documentation> + + File containing mapping information. Loaded as a resource + by the persistence provider. + + </xsd:documentation> + </xsd:annotation> + </xsd:element> + + <!-- **************************************************** --> + + <xsd:element name="jar-file" type="xsd:string" + minOccurs="0" maxOccurs="unbounded"> + <xsd:annotation> + <xsd:documentation> + + Jar file that should be scanned for entities. + Not applicable to Java SE persistence units. + + </xsd:documentation> + </xsd:annotation> + </xsd:element> + + <!-- **************************************************** --> + + <xsd:element name="class" type="xsd:string" + minOccurs="0" maxOccurs="unbounded"> + <xsd:annotation> + <xsd:documentation> + + Class to scan for annotations. It should be annotated + with either @Entity, @Embeddable or @MappedSuperclass. + + </xsd:documentation> + </xsd:annotation> + </xsd:element> + + <!-- **************************************************** --> + + <xsd:element name="exclude-unlisted-classes" type="xsd:boolean" + default="false" minOccurs="0"> + <xsd:annotation> + <xsd:documentation> + + When set to true then only listed classes and jars will + be scanned for persistent classes, otherwise the enclosing + jar or directory will also be scanned. Not applicable to + Java SE persistence units. + + </xsd:documentation> + </xsd:annotation> + </xsd:element> + + <!-- **************************************************** --> + + <xsd:element name="properties" minOccurs="0"> + <xsd:annotation> + <xsd:documentation> + + A list of vendor-specific properties. + + </xsd:documentation> + </xsd:annotation> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="property" + minOccurs="0" maxOccurs="unbounded"> + <xsd:annotation> + <xsd:documentation> + A name-value pair. + </xsd:documentation> + </xsd:annotation> + <xsd:complexType> + <xsd:attribute name="name" type="xsd:string" + use="required"/> + <xsd:attribute name="value" type="xsd:string" + use="required"/> + </xsd:complexType> + </xsd:element> + </xsd:sequence> + </xsd:complexType> + </xsd:element> + + </xsd:sequence> + + <!-- **************************************************** --> + + <xsd:attribute name="name" type="xsd:string" use="required"> + <xsd:annotation> + <xsd:documentation> + + Name used in code to reference this persistence unit. + + </xsd:documentation> + </xsd:annotation> + </xsd:attribute> + + <!-- **************************************************** --> + + <xsd:attribute name="transaction-type" + type="persistence:persistence-unit-transaction-type"> + <xsd:annotation> + <xsd:documentation> + + Type of transactions used by EntityManagers from this + persistence unit. + + </xsd:documentation> + </xsd:annotation> + </xsd:attribute> + + </xsd:complexType> + </xsd:element> + </xsd:sequence> + <xsd:attribute name="version" type="persistence:versionType" + fixed="1.0" use="required"/> + </xsd:complexType> + </xsd:element> + + <!-- **************************************************** --> + + <xsd:simpleType name="persistence-unit-transaction-type"> + <xsd:annotation> + <xsd:documentation> + + public enum TransactionType { JTA, RESOURCE_LOCAL }; + + </xsd:documentation> + </xsd:annotation> + <xsd:restriction base="xsd:token"> + <xsd:enumeration value="JTA"/> + <xsd:enumeration value="RESOURCE_LOCAL"/> + </xsd:restriction> + </xsd:simpleType> + +</xsd:schema> +
http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/JaxbSanityTest.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/JaxbSanityTest.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/JaxbSanityTest.java new file mode 100644 index 0000000..79c026b --- /dev/null +++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/JaxbSanityTest.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2010-2012 The University of Manchester + * + * See the file "LICENSE" for license terms. + */ +package org.taverna.server.master; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.Arrays; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.SchemaOutputResolver; +import javax.xml.transform.Result; +import javax.xml.transform.stream.StreamResult; + +import org.junit.Before; +import org.junit.Test; +import org.taverna.server.master.admin.Admin; +import org.taverna.server.master.common.Credential.KeyPair; +import org.taverna.server.master.common.Credential.Password; +import org.taverna.server.master.common.Capability; +import org.taverna.server.master.common.DirEntryReference; +import org.taverna.server.master.common.InputDescription; +import org.taverna.server.master.common.Permission; +import org.taverna.server.master.common.ProfileList; +import org.taverna.server.master.common.RunReference; +import org.taverna.server.master.common.Status; +import org.taverna.server.master.common.Trust; +import org.taverna.server.master.common.Uri; +import org.taverna.server.master.common.Workflow; +import org.taverna.server.master.rest.DirectoryContents; +import org.taverna.server.master.rest.ListenerDefinition; +import org.taverna.server.master.rest.MakeOrUpdateDirEntry; +import org.taverna.server.master.rest.TavernaServerInputREST.InDesc; +import org.taverna.server.master.rest.TavernaServerInputREST.InputsDescriptor; +import org.taverna.server.master.rest.TavernaServerListenersREST.ListenerDescription; +import org.taverna.server.master.rest.TavernaServerListenersREST.Listeners; +import org.taverna.server.master.rest.TavernaServerListenersREST.Properties; +import org.taverna.server.master.rest.TavernaServerListenersREST.PropertyDescription; +import org.taverna.server.master.rest.TavernaServerREST.EnabledNotificationFabrics; +import org.taverna.server.master.rest.TavernaServerREST.PermittedListeners; +import org.taverna.server.master.rest.TavernaServerREST.PermittedWorkflows; +import org.taverna.server.master.rest.TavernaServerREST.PolicyView.CapabilityList; +import org.taverna.server.master.rest.TavernaServerREST.PolicyView.PolicyDescription; +import org.taverna.server.master.rest.TavernaServerREST.RunList; +import org.taverna.server.master.rest.TavernaServerREST.ServerDescription; +import org.taverna.server.master.rest.TavernaServerRunREST.RunDescription; +import org.taverna.server.master.rest.TavernaServerSecurityREST; +import org.taverna.server.master.rest.TavernaServerSecurityREST.CredentialHolder; +import org.taverna.server.master.soap.DirEntry; +import org.taverna.server.master.soap.FileContents; +import org.taverna.server.master.soap.PermissionList; + +/** + * This test file ensures that the JAXB bindings will work once deployed instead + * of mysteriously failing in service. + * + * @author Donal Fellows + */ +public class JaxbSanityTest { + SchemaOutputResolver sink; + StringWriter schema; + + String schema() { + return schema.toString(); + } + + @Before + public void init() { + schema = new StringWriter(); + sink = new SchemaOutputResolver() { + @Override + public Result createOutput(String namespaceUri, + String suggestedFileName) throws IOException { + StreamResult sr = new StreamResult(schema); + sr.setSystemId("/dev/null"); + return sr; + } + }; + assertEquals("", schema()); + } + + private boolean printSchema = false; + + private void testJAXB(Class<?>... classes) throws Exception { + JAXBContext.newInstance(classes).generateSchema(sink); + if (printSchema) + System.out.println(schema()); + assertTrue(schema().length() > 0); + } + + @Test + public void testJAXBForDirEntryReference() throws Exception { + JAXBContext.newInstance(DirEntryReference.class).generateSchema(sink); + assertTrue(schema().length() > 0); + } + + @Test + public void testJAXBForInputDescription() throws Exception { + testJAXB(InputDescription.class); + } + + @Test + public void testJAXBForRunReference() throws Exception { + testJAXB(RunReference.class); + } + + @Test + public void testJAXBForWorkflow() throws Exception { + testJAXB(Workflow.class); + } + + @Test + public void testJAXBForStatus() throws Exception { + testJAXB(Status.class); + } + + @Test + public void testJAXBForUri() throws Exception { + testJAXB(Uri.class); + } + + @Test + public void testJAXBForDirectoryContents() throws Exception { + testJAXB(DirectoryContents.class); + } + + @Test + public void testJAXBForListenerDefinition() throws Exception { + testJAXB(ListenerDefinition.class); + } + + @Test + public void testJAXBForMakeOrUpdateDirEntry() throws Exception { + testJAXB(MakeOrUpdateDirEntry.class); + } + + @Test + public void testJAXBForInDesc() throws Exception { + testJAXB(InDesc.class); + } + + @Test + public void testJAXBForInputsDescriptor() throws Exception { + testJAXB(InputsDescriptor.class); + } + + @Test + public void testJAXBForListenerDescription() throws Exception { + testJAXB(ListenerDescription.class); + } + + @Test + public void testJAXBForListeners() throws Exception { + testJAXB(Listeners.class); + } + + @Test + public void testJAXBForProperties() throws Exception { + testJAXB(Properties.class); + } + + @Test + public void testJAXBForPropertyDescription() throws Exception { + testJAXB(PropertyDescription.class); + } + + @Test + public void testJAXBForPermittedListeners() throws Exception { + testJAXB(PermittedListeners.class); + } + + @Test + public void testJAXBForPermittedWorkflows() throws Exception { + testJAXB(PermittedWorkflows.class); + } + + @Test + public void testJAXBForEnabledNotifiers() throws Exception { + testJAXB(EnabledNotificationFabrics.class); + } + + @Test + public void testJAXBForServerDescription() throws Exception { + testJAXB(ServerDescription.class); + } + + @Test + public void testJAXBForRunDescription() throws Exception { + testJAXB(RunDescription.class); + } + + @Test + public void testJAXBForRunList() throws Exception { + testJAXB(RunList.class); + } + + @Test + public void testJAXBForPolicyDescription() throws Exception { + testJAXB(PolicyDescription.class); + } + + @Test + public void testJAXBForSecurityCredential() throws Exception { + testJAXB(CredentialHolder.class); + } + + @Test + public void testJAXBForSecurityCredentialList() throws Exception { + testJAXB(TavernaServerSecurityREST.CredentialList.class); + } + + @Test + public void testJAXBForSecurityTrust() throws Exception { + testJAXB(Trust.class); + } + + @Test + public void testJAXBForSecurityTrustList() throws Exception { + testJAXB(TavernaServerSecurityREST.TrustList.class); + } + + @Test + public void testJAXBForPermission() throws Exception { + testJAXB(Permission.class); + } + + @Test + public void testJAXBForSecurityPermissionDescription() throws Exception { + testJAXB(TavernaServerSecurityREST.PermissionDescription.class); + } + + @Test + public void testJAXBForSecurityPermissionsDescription() throws Exception { + testJAXB(TavernaServerSecurityREST.PermissionsDescription.class); + } + + @Test + public void testJAXBForSecurityDescriptor() throws Exception { + testJAXB(TavernaServerSecurityREST.Descriptor.class); + } + + @Test + public void testJAXBForProfileList() throws Exception { + testJAXB(ProfileList.class); + } + + @Test + public void testJAXBForDirEntry() throws Exception { + testJAXB(DirEntry.class); + } + + @Test + public void testJAXBForCapability() throws Exception { + testJAXB(Capability.class); + } + + @Test + public void testJAXBForCapabilityList() throws Exception { + testJAXB(CapabilityList.class); + } + + @Test + public void testJAXBForEverythingREST() throws Exception { + testJAXB(DirEntryReference.class, InputDescription.class, + RunReference.class, Workflow.class, Status.class, + DirectoryContents.class, InDesc.class, + ListenerDefinition.class, MakeOrUpdateDirEntry.class, + InputsDescriptor.class, ListenerDescription.class, + Listeners.class, Properties.class, PropertyDescription.class, + PermittedListeners.class, PermittedWorkflows.class, + EnabledNotificationFabrics.class, ServerDescription.class, + RunDescription.class, Uri.class, RunList.class, + PolicyDescription.class, CredentialHolder.class, Trust.class, + TavernaServerSecurityREST.CredentialList.class, + TavernaServerSecurityREST.TrustList.class, Permission.class, + TavernaServerSecurityREST.Descriptor.class, + TavernaServerSecurityREST.PermissionDescription.class, + TavernaServerSecurityREST.PermissionsDescription.class, + ProfileList.class, Capability.class, CapabilityList.class); + } + + @Test + public void testJAXBForEverythingSOAP() throws Exception { + testJAXB(DirEntry.class, FileContents.class, InputDescription.class, + Permission.class, PermissionList.class, + PermissionList.SinglePermissionMapping.class, + RunReference.class, Status.class, Trust.class, Uri.class, + ProfileList.class, Workflow.class, Capability.class); + } + + @Test + public void testUserPassSerializeDeserialize() throws Exception { + JAXBContext c = JAXBContext.newInstance(CredentialHolder.class); + + Password password = new Password(); + password.username = "foo"; + password.password = "bar"; + + // Serialize + StringWriter sw = new StringWriter(); + CredentialHolder credIn = new CredentialHolder(password); + c.createMarshaller().marshal(credIn, sw); + + // Deserialize + StringReader sr = new StringReader(sw.toString()); + Object credOutObj = c.createUnmarshaller().unmarshal(sr); + + // Test value-equivalence + assertEquals(credIn.getClass(), credOutObj.getClass()); + CredentialHolder credOut = (CredentialHolder) credOutObj; + assertEquals(credIn.credential.getClass(), + credOut.credential.getClass()); + assertEquals(credIn.getUserpass().username, + credOut.getUserpass().username); + assertEquals(credIn.getUserpass().password, + credOut.getUserpass().password); + } + + @Test + public void testKeypairSerializeDeserialize() throws Exception { + JAXBContext c = JAXBContext.newInstance(CredentialHolder.class); + + KeyPair keypair = new KeyPair(); + keypair.credentialName = "foo"; + keypair.credentialBytes = new byte[] { 1, 2, 3 }; + + // Serialize + StringWriter sw = new StringWriter(); + CredentialHolder credIn = new CredentialHolder(keypair); + c.createMarshaller().marshal(credIn, sw); + + // Deserialize + StringReader sr = new StringReader(sw.toString()); + Object credOutObj = c.createUnmarshaller().unmarshal(sr); + + // Test value-equivalence + assertEquals(credIn.getClass(), credOutObj.getClass()); + CredentialHolder credOut = (CredentialHolder) credOutObj; + assertEquals(credIn.credential.getClass(), + credOut.credential.getClass()); + assertEquals(credIn.getKeypair().credentialName, + credOut.getKeypair().credentialName); + assertTrue(Arrays.equals(credIn.getKeypair().credentialBytes, + credOut.getKeypair().credentialBytes)); + } + + @Test + public void testJAXBforAdmininstration() throws Exception { + testJAXB(Admin.AdminDescription.class); + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/TavernaServerImplTest.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/TavernaServerImplTest.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/TavernaServerImplTest.java new file mode 100644 index 0000000..9665cf0 --- /dev/null +++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/TavernaServerImplTest.java @@ -0,0 +1,246 @@ +package org.taverna.server.master; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Before; +import org.junit.Test; +import org.taverna.server.master.api.ManagementModel; +import org.taverna.server.master.common.RunReference; +import org.taverna.server.master.exceptions.BadPropertyValueException; +import org.taverna.server.master.exceptions.NoListenerException; +import org.taverna.server.master.exceptions.NoUpdateException; +import org.taverna.server.master.exceptions.UnknownRunException; +import org.taverna.server.master.interfaces.Listener; +import org.taverna.server.master.interfaces.TavernaRun; +import org.taverna.server.master.mocks.ExampleRun; +import org.taverna.server.master.mocks.MockPolicy; +import org.taverna.server.master.mocks.SimpleListenerFactory; +import org.taverna.server.master.mocks.SimpleNonpersistentRunStore; + +public class TavernaServerImplTest { + private TavernaServer server; + private MockPolicy policy; + private SimpleNonpersistentRunStore store; + @java.lang.SuppressWarnings("unused") + private ExampleRun.Builder runFactory; + private SimpleListenerFactory lFactory; + private TavernaServerSupport support; + + private String lrunname; + private String lrunconf; + + Listener makeListener(TavernaRun run, final String config) { + lrunname = run.toString(); + lrunconf = config; + return new Listener() { + @Override + public String getConfiguration() { + return config; + } + + @Override + public String getName() { + return "bar"; + } + + @Override + public String getProperty(String propName) + throws NoListenerException { + throw new NoListenerException(); + } + + @Override + public String getType() { + return "foo"; + } + + @Override + public String[] listProperties() { + return new String[0]; + } + + @Override + public void setProperty(String propName, String value) + throws NoListenerException, BadPropertyValueException { + throw new NoListenerException(); + } + }; + } + + @Before + public void wireup() throws Exception { + // Wire everything up; ought to be done with Spring, but this works... + server = new TavernaServer() { + @Override + protected RunREST makeRunInterface() { + return new RunREST() { + @Override + protected ListenersREST makeListenersInterface() { + return new ListenersREST() { + @Override + protected SingleListenerREST makeListenerInterface() { + return new SingleListenerREST() { + @Override + protected ListenerPropertyREST makePropertyInterface() { + return new ListenerPropertyREST() { + }; + } + }; + } + }; + } + + @Override + protected RunSecurityREST makeSecurityInterface() { + return new RunSecurityREST() { + }; + } + + @Override + protected DirectoryREST makeDirectoryInterface() { + return new DirectoryREST() { + }; + } + + @Override + protected InputREST makeInputInterface() { + return new InputREST() { + }; + } + + @Override + protected InteractionFeed makeInteractionFeed() { + return null; // TODO... + } + }; + } + + @Override + public PolicyView getPolicyDescription() { + return new PolicyREST(); + } + }; + support = new TavernaServerSupport(); + server.setSupport(support); + support.setWebapp(server); + support.setLogGetPrincipalFailures(false); + support.setStateModel(new ManagementModel() { + @Override + public boolean getAllowNewWorkflowRuns() { + return true; + } + + @Override + public boolean getLogIncomingWorkflows() { + return false; + } + + @Override + public boolean getLogOutgoingExceptions() { + return false; + } + + @Override + public void setAllowNewWorkflowRuns(boolean allowNewWorkflowRuns) { + } + + @Override + public void setLogIncomingWorkflows(boolean logIncomingWorkflows) { + } + + @Override + public void setLogOutgoingExceptions(boolean logOutgoingExceptions) { + } + + @Override + public String getUsageRecordLogFile() { + return null; + } + + @Override + public void setUsageRecordLogFile(String usageRecordLogFile) { + } + }); + server.setPolicy(policy = new MockPolicy()); + support.setPolicy(policy); + server.setRunStore(store = new SimpleNonpersistentRunStore()); + support.setRunStore(store); + store.setPolicy(policy); + support.setRunFactory(runFactory = new ExampleRun.Builder(1)); + support.setListenerFactory(lFactory = new SimpleListenerFactory()); + lFactory.setBuilders(singletonMap( + "foo", + (SimpleListenerFactory.Builder) new SimpleListenerFactory.Builder() { + @Override + public Listener build(TavernaRun run, String configuration) + throws NoListenerException { + return makeListener(run, configuration); + } + })); + } + + @Test + public void defaults1() { + assertNotNull(server); + } + + @Test + public void defaults2() { + assertEquals(10, server.getServerMaxRuns()); + } + + @Test + public void defaults3() { + assertEquals(1, server.getServerListeners().length); + } + + @Test + public void defaults4() { + assertNotNull(support.getPrincipal()); + } + + @Test + public void serverAsksPolicyForMaxRuns() { + int oldmax = policy.maxruns; + try { + policy.maxruns = 1; + assertEquals(1, server.getServerMaxRuns()); + } finally { + policy.maxruns = oldmax; + } + } + + @Test + public void makeAndKillARun() throws NoUpdateException, UnknownRunException { + RunReference rr = server.submitWorkflow(null); + assertNotNull(rr); + assertNotNull(rr.name); + server.destroyRun(rr.name); + } + + @Test + public void makeListenKillRun() throws Exception { + RunReference run = server.submitWorkflow(null); + try { + lrunname = lrunconf = null; + assertEquals(asList("foo"), asList(server.getServerListeners())); + String l = server.addRunListener(run.name, "foo", "foobar"); + assertEquals("bar", l); + assertEquals("foobar", lrunconf); + assertEquals(lrunname, support.getRun(run.name).toString()); + assertEquals(asList("default", "bar"), + asList(server.getRunListeners(run.name))); + assertEquals(0, + server.getRunListenerProperties(run.name, "bar").length); + } finally { + try { + server.destroyRun(run.name); + } catch (Exception e) { + // Ignore + } + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/WorkflowSerializationTest.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/WorkflowSerializationTest.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/WorkflowSerializationTest.java new file mode 100644 index 0000000..0450317 --- /dev/null +++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/WorkflowSerializationTest.java @@ -0,0 +1,68 @@ +package org.taverna.server.master; + +import static org.taverna.server.master.rest.handler.T2FlowDocumentHandler.T2FLOW_NS; +import static org.taverna.server.master.rest.handler.T2FlowDocumentHandler.T2FLOW_ROOTNAME; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.Assert; +import org.junit.Test; +import org.taverna.server.master.common.Workflow; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class WorkflowSerializationTest { + @Test + public void testWorkflowSerialization() + throws ParserConfigurationException, IOException, + ClassNotFoundException { + DocumentBuilder db = DocumentBuilderFactory.newInstance() + .newDocumentBuilder(); + Document doc = db.getDOMImplementation().createDocument(null, null, + null); + Element workflow = doc.createElementNS(T2FLOW_NS, T2FLOW_ROOTNAME); + Element foo = doc.createElementNS("urn:foo:bar", "pqr:foo"); + foo.setTextContent("bar"); + foo.setAttribute("xyz", "abc"); + workflow.appendChild(foo); + Workflow w = new Workflow(workflow); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(w); + } + + Object o; + try (ObjectInputStream ois = new ObjectInputStream( + new ByteArrayInputStream(baos.toByteArray()))) { + o = ois.readObject(); + } + + Assert.assertNotNull(o); + Assert.assertEquals(w.getClass(), o.getClass()); + Workflow w2 = (Workflow) o; + Assert.assertNotNull(w2.getT2flowWorkflow()); + Element e = w2.getT2flowWorkflow(); + Assert.assertEquals(T2FLOW_ROOTNAME, e.getLocalName()); + Assert.assertEquals(T2FLOW_NS, e.getNamespaceURI()); + e = (Element) e.getFirstChild(); + Assert.assertEquals("foo", e.getLocalName()); + Assert.assertEquals("pqr", e.getPrefix()); + Assert.assertEquals("urn:foo:bar", e.getNamespaceURI()); + Assert.assertEquals("bar", e.getTextContent()); + Assert.assertEquals(1, e.getChildNodes().getLength()); + // WARNING: These are dependent on how namespaces are encoded! + Assert.assertEquals(2, e.getAttributes().getLength()); + Assert.assertEquals("xyz", ((Attr) e.getAttributes().item(1)).getLocalName()); + Assert.assertEquals("abc", e.getAttribute("xyz")); + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/ExampleRun.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/ExampleRun.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/ExampleRun.java new file mode 100644 index 0000000..a2a0791 --- /dev/null +++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/ExampleRun.java @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2010-2011 The University of Manchester + * + * See the file "LICENSE" for license terms. + */ +package org.taverna.server.master.mocks; + +import static java.util.Calendar.MINUTE; +import static java.util.Collections.unmodifiableList; +import static java.util.UUID.randomUUID; +import static org.taverna.server.master.common.Status.Initialized; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.ws.rs.core.HttpHeaders; +import javax.xml.ws.handler.MessageContext; + +import org.springframework.security.core.context.SecurityContext; +import org.taverna.server.master.common.Credential; +import org.taverna.server.master.common.Status; +import org.taverna.server.master.common.Trust; +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.InvalidCredentialException; +import org.taverna.server.master.exceptions.NoListenerException; +import org.taverna.server.master.exceptions.UnknownRunException; +import org.taverna.server.master.factories.RunFactory; +import org.taverna.server.master.interfaces.Directory; +import org.taverna.server.master.interfaces.Input; +import org.taverna.server.master.interfaces.Listener; +import org.taverna.server.master.interfaces.SecurityContextFactory; +import org.taverna.server.master.interfaces.TavernaRun; +import org.taverna.server.master.interfaces.TavernaSecurityContext; +import org.taverna.server.master.utils.UsernamePrincipal; + +@SuppressWarnings("serial") +public class ExampleRun implements TavernaRun, TavernaSecurityContext { + String id; + List<Listener> listeners; + Workflow workflow; + Status status; + Date expiry; + UsernamePrincipal owner; + String inputBaclava; + String outputBaclava; + java.io.File realRoot; + List<Input> inputs; + String name; + + public ExampleRun(UsernamePrincipal creator, Workflow workflow, Date expiry) { + this.id = randomUUID().toString(); + this.listeners = new ArrayList<>(); + this.status = Initialized; + this.owner = creator; + this.workflow = workflow; + this.expiry = expiry; + this.inputs = new ArrayList<>(); + listeners.add(new DefaultListener()); + } + + @Override + public void addListener(Listener l) { + listeners.add(l); + } + + @Override + public void destroy() { + // This does nothing... + } + + @Override + public Date getExpiry() { + return expiry; + } + + @Override + public List<Listener> getListeners() { + return listeners; + } + + @Override + public TavernaSecurityContext getSecurityContext() { + return this; + } + + @Override + public Status getStatus() { + return status; + } + + @Override + public Workflow getWorkflow() { + return workflow; + } + + @Override + public Directory getWorkingDirectory() { + // LATER: Implement this! + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public void setExpiry(Date d) { + if (d.after(new Date())) + this.expiry = d; + } + + @Override + public String setStatus(Status s) { + this.status = s; + return null; + } + + @Override + public UsernamePrincipal getOwner() { + return owner; + } + + public static class Builder implements RunFactory { + private int lifetime; + + public Builder(int initialLifetimeMinutes) { + this.lifetime = initialLifetimeMinutes; + } + + @Override + public TavernaRun create(UsernamePrincipal creator, Workflow workflow) { + Calendar c = GregorianCalendar.getInstance(); + c.add(MINUTE, lifetime); + return new ExampleRun(creator, workflow, c.getTime()); + } + + @Override + public boolean isAllowingRunsToStart() { + return true; + } + } + + static final String[] emptyArray = new String[0]; + + class DefaultListener implements Listener { + @Override + public String getConfiguration() { + return ""; + } + + @Override + public String getName() { + return "default"; + } + + @Override + public String getType() { + return "default"; + } + + @Override + public String[] listProperties() { + return emptyArray; + } + + @Override + public String getProperty(String propName) throws NoListenerException { + throw new NoListenerException("no such property"); + } + + @Override + public void setProperty(String propName, String value) + throws NoListenerException { + throw new NoListenerException("no such property"); + } + } + + @Override + public String getInputBaclavaFile() { + return inputBaclava; + } + + @Override + public List<Input> getInputs() { + return unmodifiableList(inputs); + } + + @Override + public String getOutputBaclavaFile() { + return outputBaclava; + } + + class ExampleInput implements Input { + public String name; + public String file; + public String value; + public String delim; + + public ExampleInput(String name) { + this.name = name; + } + + @Override + public String getFile() { + return file; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + + @Override + public void setFile(String file) throws FilesystemAccessException, + BadStateChangeException { + if (status != Status.Initialized) + throw new BadStateChangeException(); + checkBadFilename(file); + this.file = file; + this.value = null; + inputBaclava = null; + } + + @Override + public void setValue(String value) throws BadStateChangeException { + if (status != Status.Initialized) + throw new BadStateChangeException(); + this.value = value; + this.file = null; + inputBaclava = null; + } + + void reset() { + this.file = null; + this.value = null; + } + + @Override + public String getDelimiter() { + return delim; + } + + @Override + public void setDelimiter(String delimiter) + throws BadStateChangeException { + if (status != Status.Initialized) + throw new BadStateChangeException(); + if (delimiter == null) + delim = null; + else + delim = delimiter.substring(0, 1); + } + } + + @Override + public Input makeInput(String name) throws BadStateChangeException { + if (status != Status.Initialized) + throw new BadStateChangeException(); + Input i = new ExampleInput(name); + inputs.add(i); + return i; + } + + static void checkBadFilename(String filename) + throws FilesystemAccessException { + if (filename.startsWith("/")) + throw new FilesystemAccessException("filename may not be absolute"); + if (Arrays.asList(filename.split("/")).contains("..")) + throw new FilesystemAccessException( + "filename may not refer to parent"); + } + + @Override + public void setInputBaclavaFile(String filename) + throws FilesystemAccessException, BadStateChangeException { + if (status != Status.Initialized) + throw new BadStateChangeException(); + checkBadFilename(filename); + inputBaclava = filename; + for (Input i : inputs) + ((ExampleInput) i).reset(); + } + + @Override + public void setOutputBaclavaFile(String filename) + throws FilesystemAccessException, BadStateChangeException { + if (status != Status.Initialized) + throw new BadStateChangeException(); + if (filename != null) + checkBadFilename(filename); + outputBaclava = filename; + } + + private Date created = new Date(); + @Override + public Date getCreationTimestamp() { + return created; + } + + @Override + public Date getFinishTimestamp() { + return null; + } + + @Override + public Date getStartTimestamp() { + return null; + } + + @Override + public Credential[] getCredentials() { + return new Credential[0]; + } + + @Override + public void addCredential(Credential toAdd) { + } + + @Override + public void deleteCredential(Credential toDelete) { + } + + @Override + public Trust[] getTrusted() { + return new Trust[0]; + } + + @Override + public void addTrusted(Trust toAdd) { + } + + @Override + public void deleteTrusted(Trust toDelete) { + } + + @Override + public void validateCredential(Credential c) + throws InvalidCredentialException { + } + + @Override + public void validateTrusted(Trust t) throws InvalidCredentialException { + } + + @Override + public void initializeSecurityFromSOAPContext(MessageContext context) { + // Do nothing + } + + @Override + public void initializeSecurityFromRESTContext(HttpHeaders headers) { + // Do nothing + } + + @Override + public void conveySecurity() throws GeneralSecurityException, IOException { + // Do nothing + } + + @Override + public SecurityContextFactory getFactory() { + return null; + } + + private Set<String> destroyers = new HashSet<String>(); + private Set<String> updaters = new HashSet<String>(); + private Set<String> readers = new HashSet<String>(); + @Override + public Set<String> getPermittedDestroyers() { + return destroyers; + } + + @Override + public void setPermittedDestroyers(Set<String> destroyers) { + this.destroyers = destroyers; + updaters.addAll(destroyers); + readers.addAll(destroyers); + } + + @Override + public Set<String> getPermittedUpdaters() { + return updaters; + } + + @Override + public void setPermittedUpdaters(Set<String> updaters) { + this.updaters = updaters; + this.updaters.addAll(destroyers); + readers.addAll(updaters); + } + + @Override + public Set<String> getPermittedReaders() { + return readers; + } + + @Override + public void setPermittedReaders(Set<String> readers) { + this.readers = readers; + this.readers.addAll(destroyers); + this.readers.addAll(updaters); + } + + @Override + public String getId() { + return id; + } + + @Override + public void initializeSecurityFromContext(SecurityContext securityContext) + throws Exception { + // Do nothing + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = (name.length() > 5 ? name.substring(0, 5) : name); + } + + @Override + public void ping() throws UnknownRunException { + // Do nothing + } + + @Override + public boolean getGenerateProvenance() { + // TODO Auto-generated method stub + return false; + } + + @Override + public void setGenerateProvenance(boolean generateProvenance) { + // TODO Auto-generated method stub + + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/MockPolicy.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/MockPolicy.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/MockPolicy.java new file mode 100644 index 0000000..81dd08c --- /dev/null +++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/MockPolicy.java @@ -0,0 +1,59 @@ +package org.taverna.server.master.mocks; + +import java.util.HashSet; +import java.util.Set; + +import org.taverna.server.master.common.Workflow; +import org.taverna.server.master.exceptions.NoCreateException; +import org.taverna.server.master.exceptions.NoDestroyException; +import org.taverna.server.master.exceptions.NoUpdateException; +import org.taverna.server.master.interfaces.TavernaRun; +import org.taverna.server.master.utils.UsernamePrincipal; + +public class MockPolicy extends SimpleServerPolicy { + public MockPolicy() { + super(); + super.setCleanerInterval(30); + } + + public int maxruns = 10; + Integer usermaxruns; + Set<TavernaRun> denyaccess = new HashSet<>(); + boolean exnOnUpdate, exnOnCreate, exnOnDelete; + + @Override + public int getMaxRuns() { + return maxruns; + } + + @Override + public Integer getMaxRuns(UsernamePrincipal user) { + return usermaxruns; + } + + @Override + public boolean permitAccess(UsernamePrincipal user, TavernaRun run) { + return !denyaccess.contains(run); + } + + @Override + public void permitCreate(UsernamePrincipal user, Workflow workflow) + throws NoCreateException { + if (this.exnOnCreate) + throw new NoCreateException(); + } + + @Override + public void permitDestroy(UsernamePrincipal user, TavernaRun run) + throws NoDestroyException { + if (this.exnOnDelete) + throw new NoDestroyException(); + } + + @Override + public void permitUpdate(UsernamePrincipal user, TavernaRun run) + throws NoUpdateException { + if (this.exnOnUpdate) + throw new NoUpdateException(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleListenerFactory.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleListenerFactory.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleListenerFactory.java new file mode 100644 index 0000000..d864214 --- /dev/null +++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleListenerFactory.java @@ -0,0 +1,64 @@ +package org.taverna.server.master.mocks; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.taverna.server.master.exceptions.NoListenerException; +import org.taverna.server.master.factories.ListenerFactory; +import org.taverna.server.master.interfaces.Listener; +import org.taverna.server.master.interfaces.TavernaRun; + +/** + * A factory for event listener. The factory is configured using Spring. + * + * @author Donal Fellows + */ +public class SimpleListenerFactory implements ListenerFactory { + private Map<String, Builder> builders = new HashMap<>(); + + public void setBuilders(Map<String, Builder> builders) { + this.builders = builders; + } + + @Override + public List<String> getSupportedListenerTypes() { + return new ArrayList<>(builders.keySet()); + } + + @Override + public Listener makeListener(TavernaRun run, String listenerType, + String configuration) throws NoListenerException { + Builder b = builders.get(listenerType); + if (b == null) + throw new NoListenerException("no such listener type"); + Listener l = b.build(run, configuration); + run.addListener(l); + return l; + } + + /** + * How to actually construct a listener. + * + * @author Donal Fellows + */ + public interface Builder { + /** + * Make an event listener attached to a run. + * + * @param run + * The run to attach to. + * @param configuration + * A user-specified configuration document. The constructed + * listener <i>should</i> process this configuration document + * and be able to return it to the user when requested. + * @return The listener object. + * @throws NoListenerException + * If the listener construction failed or the + * <b>configuration</b> document was bad in some way. + */ + public Listener build(TavernaRun run, String configuration) + throws NoListenerException; + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleNonpersistentRunStore.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleNonpersistentRunStore.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleNonpersistentRunStore.java new file mode 100644 index 0000000..a3751e4 --- /dev/null +++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleNonpersistentRunStore.java @@ -0,0 +1,151 @@ +package org.taverna.server.master.mocks; + +import java.lang.ref.WeakReference; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + +import org.taverna.server.master.exceptions.NoDestroyException; +import org.taverna.server.master.exceptions.UnknownRunException; +import org.taverna.server.master.interfaces.Policy; +import org.taverna.server.master.interfaces.RunStore; +import org.taverna.server.master.interfaces.TavernaRun; +import org.taverna.server.master.utils.UsernamePrincipal; + +/** + * Example of a store for Taverna Workflow Runs. + * + * @author Donal Fellows + */ +public class SimpleNonpersistentRunStore implements RunStore { + private Map<String, TavernaRun> store = new HashMap<>(); + private Object lock = new Object(); + + Timer timer; + private CleanerTask cleaner; + + /** + * The connection to the main policy store. Suitable for wiring up with + * Spring. + * + * @param p + * The policy to connect to. + */ + public void setPolicy(SimpleServerPolicy p) { + p.store = this; + cleanerIntervalUpdated(p.getCleanerInterval()); + } + + public SimpleNonpersistentRunStore() { + timer = new Timer("SimpleNonpersistentRunStore.CleanerTimer", true); + cleanerIntervalUpdated(300); + } + + @Override + protected void finalize() { + timer.cancel(); + } + + /** + * Remove and destroy all runs that are expired at the moment that this + * method starts. + */ + void clean() { + Date now = new Date(); + synchronized (lock) { + // Use an iterator so we have access to its remove() method... + Iterator<TavernaRun> i = store.values().iterator(); + while (i.hasNext()) { + TavernaRun w = i.next(); + if (w.getExpiry().before(now)) { + i.remove(); + try { + w.destroy(); + } catch (NoDestroyException e) { + } + } + } + } + } + + /** + * Reconfigure the cleaner task's call interval. This is called internally + * and from the Policy when the interval is set there. + * + * @param intervalInSeconds + * How long between runs of the cleaner task, in seconds. + */ + void cleanerIntervalUpdated(int intervalInSeconds) { + if (cleaner != null) + cleaner.cancel(); + cleaner = new CleanerTask(this, intervalInSeconds); + } + + @Override + public TavernaRun getRun(UsernamePrincipal user, Policy p, String uuid) + throws UnknownRunException { + synchronized (lock) { + TavernaRun w = store.get(uuid); + if (w == null || !p.permitAccess(user, w)) + throw new UnknownRunException(); + return w; + } + } + + @Override + public TavernaRun getRun(String uuid) throws UnknownRunException { + synchronized (lock) { + TavernaRun w = store.get(uuid); + if (w == null) + throw new UnknownRunException(); + return w; + } + } + + @Override + public Map<String, TavernaRun> listRuns(UsernamePrincipal user, Policy p) { + Map<String, TavernaRun> filtered = new HashMap<>(); + synchronized (lock) { + for (Map.Entry<String, TavernaRun> entry : store.entrySet()) + if (p.permitAccess(user, entry.getValue())) + filtered.put(entry.getKey(), entry.getValue()); + } + return filtered; + } + + @Override + public String registerRun(TavernaRun run) { + synchronized (lock) { + store.put(run.getId(), run); + return run.getId(); + } + } + + @Override + public void unregisterRun(String uuid) { + synchronized (lock) { + store.remove(uuid); + } + } +} + +class CleanerTask extends TimerTask { + WeakReference<SimpleNonpersistentRunStore> store; + + CleanerTask(SimpleNonpersistentRunStore store, int interval) { + this.store = new WeakReference<>(store); + int tms = interval * 1000; + store.timer.scheduleAtFixedRate(this, tms, tms); + } + + @Override + public void run() { + SimpleNonpersistentRunStore s = store.get(); + if (s != null) { + s.clean(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleServerPolicy.java ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleServerPolicy.java b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleServerPolicy.java new file mode 100644 index 0000000..1a68d2c --- /dev/null +++ b/taverna-server-webapp/src/test/java/org/taverna/server/master/mocks/SimpleServerPolicy.java @@ -0,0 +1,110 @@ +package org.taverna.server.master.mocks; + +import java.net.URI; +import java.util.List; + +import org.taverna.server.master.common.Workflow; +import org.taverna.server.master.exceptions.NoCreateException; +import org.taverna.server.master.exceptions.NoDestroyException; +import org.taverna.server.master.exceptions.NoUpdateException; +import org.taverna.server.master.interfaces.Policy; +import org.taverna.server.master.interfaces.TavernaRun; +import org.taverna.server.master.utils.UsernamePrincipal; + +/** + * A very simple (and unsafe) security model. The number of runs is configurable + * through Spring (or 10 if unconfigured) with no per-user limits supported, all + * workflows are permitted, and all identified users may create a workflow run. + * Any user may read off information about any run, but only its owner may + * modify or destroy it. + * <p> + * Note that this is a <i>Policy Enforcement Point</i> for access control to + * individual workflows. + * + * @author Donal Fellows + */ +public class SimpleServerPolicy implements Policy { + private int maxRuns = 10; + private int cleanerInterval; + SimpleNonpersistentRunStore store; + + public void setMaxRuns(int maxRuns) { + this.maxRuns = maxRuns; + } + + @Override + public int getMaxRuns() { + return maxRuns; + } + + @Override + public Integer getMaxRuns(UsernamePrincipal p) { + return null; // No per-user limits + } + + public int getCleanerInterval() { + return cleanerInterval; + } + + /** + * Sets how often the store of workflow runs will try to clean out expired + * runs. + * + * @param intervalInSeconds + */ + public void setCleanerInterval(int intervalInSeconds) { + cleanerInterval = intervalInSeconds; + if (store != null) + store.cleanerIntervalUpdated(intervalInSeconds); + } + + @Override + public boolean permitAccess(UsernamePrincipal p, TavernaRun run) { + // No secrets here! + return true; + } + + @Override + public void permitCreate(UsernamePrincipal p, Workflow workflow) + throws NoCreateException { + // Only identified users may create + if (p == null) + throw new NoCreateException(); + // Global run count limit enforcement + if (store.listRuns(p, this).size() >= maxRuns) + throw new NoCreateException(); + // Per-user run count enforcement would come here + } + + @Override + public void permitDestroy(UsernamePrincipal p, TavernaRun run) + throws NoDestroyException { + // Only the creator may destroy + if (p == null || !p.equals(run.getSecurityContext().getOwner())) + throw new NoDestroyException(); + } + + @Override + public void permitUpdate(UsernamePrincipal p, TavernaRun run) + throws NoUpdateException { + // Only the creator may change + if (p == null || !p.equals(run.getSecurityContext().getOwner())) + throw new NoUpdateException(); + } + + @Override + public int getOperatingLimit() { + return 1; + } + + @Override + public List<URI> listPermittedWorkflowURIs(UsernamePrincipal user) { + return null; + } + + @Override + public void setPermittedWorkflowURIs(UsernamePrincipal user, + List<URI> permitted) { + // Ignore + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/resources/example.xml ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/test/resources/example.xml b/taverna-server-webapp/src/test/resources/example.xml new file mode 100644 index 0000000..14cc242 --- /dev/null +++ b/taverna-server-webapp/src/test/resources/example.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> + + <bean id="policy" class="org.taverna.server.master.mocks.SimpleServerPolicy" + lazy-init="false" scope="singleton"> + <property name="maxRuns" value="1"> + <description> + Limit on total number of simultaneous runs. + </description> + </property> + <property name="cleanerInterval" value="300"> + <description> + Time between trying to delete expired runs, in seconds. + </description> + </property> + </bean> + + <bean id="runFactory" class="org.taverna.server.master.mocks.ExampleRun$Builder"> + <constructor-arg type="int" value="10" /> <!-- "initialLifetimeMinutes" --> + </bean> + + <bean id="runCatalog" scope="singleton" + class="org.taverna.server.master.mocks.SimpleNonpersistentRunStore"> + <property name="policy" ref="policy" /> + </bean> + + <bean id="listenerFactory" class="org.taverna.server.master.mocks.SimpleListenerFactory"> + <property name="builders"> + <description> + This map describes how to build each type of supported + event listener that is not installed by default. Any site policy for + a listeners should be installed using its properties, as shown. The + "key" is the type, the "class" is the builder for actual instances + (which must be an instance of + org.taverna.server.master.factories.SimpleListenerFactory.Builder) + and any policies and installation-specific configurations are + characterised by properties such as "sitePolicy" below. + </description> + <map> + <!-- <entry key="exampleListener">--> + <!-- + <bean + class="org.taverna.server.master.example.ExampleListener$Builder"> + --> + <!-- <property name="sitePolicy">--> + <!-- <value>Just an example!</value>--> + <!-- </property>--> + <!-- </bean>--> + <!-- </entry>--> + </map> + </property> + </bean> +</beans> http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/test/resources/log4j.properties ---------------------------------------------------------------------- diff --git a/taverna-server-webapp/src/test/resources/log4j.properties b/taverna-server-webapp/src/test/resources/log4j.properties new file mode 100644 index 0000000..6707f55 --- /dev/null +++ b/taverna-server-webapp/src/test/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=info, R +log4j.appender.R=org.apache.log4j.ConsoleAppender +log4j.appender.R.layout=org.apache.log4j.PatternLayout +log4j.appender.R.layout.ConversionPattern=%d{yyyyMMdd'T'HHmmss.SSS} %-5p %c{1} %C{1} - %m%n \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/pom.xml ---------------------------------------------------------------------- diff --git a/taverna-server-worker/pom.xml b/taverna-server-worker/pom.xml new file mode 100644 index 0000000..8006c6d --- /dev/null +++ b/taverna-server-worker/pom.xml @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.taverna.server</groupId> + <artifactId>taverna-server</artifactId> + <version>3.1.0-incubating-SNAPSHOT</version> + </parent> + <artifactId>taverna-server-worker</artifactId> + <name>Apache Taverna Server Workflow Executor/File System Access Process Implementation</name> + <description>This is the implementation of the factory process that is started up by the web application to manage executing Taverna Workflow executeworkflow.sh calls. Also provides per-user access to filestore.</description> + + <properties> + <workerMainClass>org.taverna.server.localworker.impl.TavernaRunManager</workerMainClass> + </properties> + + <dependencies> + <dependency> + <groupId>${project.parent.groupId}</groupId> + <artifactId>taverna-server-runinterface</artifactId> + <version>${project.parent.version}</version> + </dependency> + <dependency> + <groupId>commons-collections</groupId> + <artifactId>commons-collections</artifactId> + </dependency> + <dependency> + <groupId>${project.parent.groupId}</groupId> + <artifactId>taverna-server-usagerecord</artifactId> + <version>${project.parent.version}</version> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + </dependency> + <dependency> + <groupId>org.apache.taverna.language</groupId> + <artifactId>taverna-scufl2-api</artifactId> + <version>${taverna.language.version}</version> + </dependency> + <dependency> + <groupId>org.apache.taverna.language</groupId> + <artifactId>taverna-scufl2-t2flow</artifactId> + <version>${taverna.language.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.taverna.language</groupId> + <artifactId>taverna-scufl2-wfbundle</artifactId> + <version>${taverna.language.version}</version> + <scope>runtime</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <descriptorRefs> + <descriptorRef>jar-with-dependencies</descriptorRef> + </descriptorRefs> + <archive> + <manifest> + <mainClass>${workerMainClass}</mainClass> + </manifest> + </archive> + </configuration> + <executions> + <execution> + <id>make-assembly</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.eclipse.m2e</groupId> + <artifactId>lifecycle-mapping</artifactId> + <version>1.0.0</version> + <configuration> + <lifecycleMappingMetadata> + <pluginExecutions> + <pluginExecution> + <pluginExecutionFilter> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-assembly-plugin</artifactId> + <versionRange>[2.0,)</versionRange> + <goals> + <goal>single</goal> + </goals> + </pluginExecutionFilter> + <action> + <execute /> + </action> + </pluginExecution> + </pluginExecutions> + </lifecycleMappingMetadata> + </configuration> + </plugin> + </plugins> + </pluginManagement> + </build> +</project> http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/src/main/java/META-INF/MANIFEST.MF ---------------------------------------------------------------------- diff --git a/taverna-server-worker/src/main/java/META-INF/MANIFEST.MF b/taverna-server-worker/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000..5e94951 --- /dev/null +++ b/taverna-server-worker/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Constants.java ---------------------------------------------------------------------- diff --git a/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Constants.java b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Constants.java new file mode 100644 index 0000000..600913a --- /dev/null +++ b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Constants.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2013 The University of Manchester + * + * See the file "LICENSE" for license terms. + */ +package org.taverna.server.localworker.api; + +import static java.nio.charset.Charset.defaultCharset; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * The defaults associated with this worker, together with various other + * constants. + * + * @author Donal Fellows + */ +public abstract class Constants { + /** + * Subdirectories of the working directory to create by default. + */ + public static final String[] SUBDIR_LIST = { "conf", "externaltool", "feed", + "interactions", "lib", "logs", "plugins", "repository", "var" }; + + /** The name of the default encoding for characters on this machine. */ + public static final String SYSTEM_ENCODING = defaultCharset().name(); + + /** + * Password to use to encrypt security information. This default is <7 chars + * to work even without Unlimited Strength JCE. + */ + public static final char[] KEYSTORE_PASSWORD = { 'c', 'h', 'a', 'n', 'g', 'e' }; + + /** + * The name of the directory (in the home directory) where security settings + * will be written. + */ + public static final String SECURITY_DIR_NAME = ".taverna-server-security"; + + /** The name of the file that will be the created keystore. */ + public static final String KEYSTORE_FILE = "t2keystore.ubr"; + + /** The name of the file that will be the created truststore. */ + public static final String TRUSTSTORE_FILE = "t2truststore.ubr"; + + /** + * The name of the file that contains the password to unlock the keystore + * and truststore. + */ + public static final String PASSWORD_FILE = "password.txt"; + + // --------- UNUSED --------- + // /** + // * The name of the file that contains the mapping from URIs to keystore + // * aliases. + // */ + // public static final String URI_ALIAS_MAP = "urlmap.txt"; + + /** + * Used to instruct the Taverna credential manager to use a non-default + * location for user credentials. + */ + public static final String CREDENTIAL_MANAGER_DIRECTORY = "-cmdir"; + + /** + * Used to instruct the Taverna credential manager to take its master + * password from standard input. + */ + public static final String CREDENTIAL_MANAGER_PASSWORD = "-cmpassword"; + + /** + * Name of environment variable used to pass HELIO security tokens to + * workflows. + */ + // This technique is known to be insecure; bite me. + public static final String HELIO_TOKEN_NAME = "HELIO_CIS_TOKEN"; + + /** + * The name of the standard listener, which is installed by default. + */ + public static final String DEFAULT_LISTENER_NAME = "io"; + + /** + * Time to wait for the subprocess to wait, in milliseconds. + */ + public static final int START_WAIT_TIME = 1500; + + /** + * Time to wait for success or failure of a death-causing activity (i.e., + * sending a signal). + */ + public static final int DEATH_TIME = 333; + + /** + * The name of the file (in this code's resources) that provides the default + * security policy that we use. + */ + public static final String SECURITY_POLICY_FILE = "security.policy"; + + /** + * The Java property holding security policy info. + */ + public static final String SEC_POLICY_PROP = "java.security.policy"; + /** + * The Java property to set to make this code not try to enforce security + * policy. + */ + public static final String UNSECURE_PROP = "taverna.suppressrestrictions.rmi"; + /** + * The Java property that holds the name of the host name to enforce. + */ + public static final String RMI_HOST_PROP = "java.rmi.server.hostname"; + /** + * The default hostname to require in secure mode. This is the + * <i>resolved</i> version of "localhost". + */ + public static final String LOCALHOST; + static { + String h = "127.0.0.1"; // fallback + try { + h = InetAddress.getByName("localhost").getHostAddress(); + } catch (UnknownHostException e) { + e.printStackTrace(); + } finally { + LOCALHOST = h; + } + } + + /** + * Time to wait during closing down this process. In milliseconds. + */ + public static final int DEATH_DELAY = 500; + /** + * The name of the property describing where shared directories should be + * located. + */ + public static final String SHARED_DIR_PROP = "taverna.sharedDirectory"; + + public static final String TIME = "/usr/bin/time"; +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/RunAccounting.java ---------------------------------------------------------------------- diff --git a/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/RunAccounting.java b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/RunAccounting.java new file mode 100644 index 0000000..3896c06 --- /dev/null +++ b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/RunAccounting.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2013 The University of Manchester + * + * See the file "LICENSE" for license terms. + */ +package org.taverna.server.localworker.api; + +/** + * + * @author Donal Fellows + */ +public interface RunAccounting { + /** + * Logs that a run has started executing. + */ + void runStarted(); + + /** + * Logs that a run has finished executing. + */ + void runCeased(); +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Worker.java ---------------------------------------------------------------------- diff --git a/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Worker.java b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Worker.java new file mode 100644 index 0000000..c513ed8 --- /dev/null +++ b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/Worker.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2010-2012 The University of Manchester + * + * See the file "LICENSE" for license terms. + */ +package org.taverna.server.localworker.api; + +import java.io.File; +import java.util.List; +import java.util.Map; + +import org.taverna.server.localworker.impl.LocalWorker; +import org.taverna.server.localworker.remote.ImplementationException; +import org.taverna.server.localworker.remote.RemoteListener; +import org.taverna.server.localworker.remote.RemoteStatus; +import org.taverna.server.localworker.server.UsageRecordReceiver; + +/** + * The interface between the connectivity layer and the thunk to the + * subprocesses. + * + * @author Donal Fellows + */ +public interface Worker { + /** + * Fire up the workflow. This causes a transition into the operating state. + * + * @param local + * The reference to the factory class for this worker. + * @param executeWorkflowCommand + * The command to run to execute the workflow. + * @param workflow + * The workflow document to execute. + * @param workingDir + * What directory to use as the working directory. + * @param inputBaclavaFile + * The baclava file to use for inputs, or <tt>null</tt> to use + * the other <b>input*</b> arguments' values. + * @param inputRealFiles + * A mapping of input names to files that supply them. Note that + * we assume that nothing mapped here will be mapped in + * <b>inputValues</b>. + * @param inputValues + * A mapping of input names to values to supply to them. Note + * that we assume that nothing mapped here will be mapped in + * <b>inputFiles</b>. + * @param inputDelimiters + * A mapping of input names to characters used to split them into + * lists. + * @param outputBaclavaFile + * What baclava file to write the output from the workflow into, + * or <tt>null</tt> to have it written into the <tt>out</tt> + * subdirectory. + * @param contextDirectory + * The directory containing the keystore and truststore. <i>Must + * not be <tt>null</tt>.</i> + * @param keystorePassword + * The password to the keystore and truststore. <i>Must not be + * <tt>null</tt>.</i> + * @param generateProvenance + * Whether to generate a run bundle containing provenance data. + * @param environment + * Any environment variables that need to be added to the + * invokation. + * @param masterToken + * The internal name of the workflow run. + * @param runtimeSettings + * List of configuration details for the forked runtime. + * @return Whether a successful start happened. + * @throws Exception + * If any of quite a large number of things goes wrong. + */ + boolean initWorker(LocalWorker local, String executeWorkflowCommand, + byte[] workflow, File workingDir, File inputBaclavaFile, + Map<String, File> inputRealFiles, Map<String, String> inputValues, + Map<String, String> inputDelimiters, File outputBaclavaFile, + File contextDirectory, char[] keystorePassword, + boolean generateProvenance, Map<String, String> environment, + String masterToken, List<String> runtimeSettings) throws Exception; + + /** + * Kills off the subprocess if it exists and is alive. + * + * @throws Exception + * if anything goes badly wrong when the worker is being killed + * off. + */ + void killWorker() throws Exception; + + /** + * Move the worker out of the stopped state and back to operating. + * + * @throws Exception + * if it fails (which it always does; operation currently + * unsupported). + */ + void startWorker() throws Exception; + + /** + * Move the worker into the stopped state from the operating state. + * + * @throws Exception + * if it fails (which it always does; operation currently + * unsupported). + */ + void stopWorker() throws Exception; + + /** + * @return The status of the workflow run. Note that this can be an + * expensive operation. + */ + RemoteStatus getWorkerStatus(); + + /** + * @return The listener that is registered by default, in addition to all + * those that are explicitly registered by the user. + */ + RemoteListener getDefaultListener(); + + /** + * @param receiver + * The destination where any final usage records are to be + * written in order to log them back to the server. + */ + void setURReceiver(UsageRecordReceiver receiver); + + /** + * Arrange for the deletion of any resources created during worker process + * construction. Guaranteed to be the last thing done before finalization. + * + * @throws ImplementationException + * If anything goes wrong. + */ + void deleteLocalResources() throws ImplementationException; +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/WorkerFactory.java ---------------------------------------------------------------------- diff --git a/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/WorkerFactory.java b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/WorkerFactory.java new file mode 100644 index 0000000..4ce13a7 --- /dev/null +++ b/taverna-server-worker/src/main/java/org/taverna/server/localworker/api/WorkerFactory.java @@ -0,0 +1,18 @@ +package org.taverna.server.localworker.api; + + +/** + * Class that manufactures instances of {@link Worker}. + * + * @author Donal Fellows + */ +public interface WorkerFactory { + /** + * Create an instance of the low-level worker class. + * + * @return The worker object. + * @throws Exception + * If anything goes wrong. + */ + Worker makeInstance() throws Exception; +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/DirectoryDelegate.java ---------------------------------------------------------------------- diff --git a/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/DirectoryDelegate.java b/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/DirectoryDelegate.java new file mode 100644 index 0000000..1692856 --- /dev/null +++ b/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/DirectoryDelegate.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2010-2011 The University of Manchester + * + * See the file "LICENSE" for license terms. + */ +package org.taverna.server.localworker.impl; + +import static org.apache.commons.io.FileUtils.forceDelete; +import static org.apache.commons.io.FileUtils.forceMkdir; +import static org.apache.commons.io.FileUtils.touch; +import static org.taverna.server.localworker.impl.utils.FilenameVerifier.getValidatedNewFile; + +import java.io.File; +import java.io.IOException; +import java.rmi.RemoteException; +import java.rmi.server.UnicastRemoteObject; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import javax.annotation.Nonnull; + +import org.apache.commons.collections.MapIterator; +import org.apache.commons.collections.map.ReferenceMap; +import org.taverna.server.localworker.remote.RemoteDirectory; +import org.taverna.server.localworker.remote.RemoteDirectoryEntry; +import org.taverna.server.localworker.remote.RemoteFile; + +/** + * This class acts as a remote-aware delegate for the workflow run's working + * directory and its subdirectories. + * + * @author Donal Fellows + * @see FileDelegate + */ +@SuppressWarnings("serial") +public class DirectoryDelegate extends UnicastRemoteObject implements + RemoteDirectory { + private File dir; + private DirectoryDelegate parent; + private ReferenceMap localCache; + + /** + * @param dir + * @param parent + * @throws RemoteException + * If registration of the directory fails. + */ + public DirectoryDelegate(@Nonnull File dir, + @Nonnull DirectoryDelegate parent) throws RemoteException { + super(); + this.localCache = new ReferenceMap(); + this.dir = dir; + this.parent = parent; + } + + @Override + public Collection<RemoteDirectoryEntry> getContents() + throws RemoteException { + List<RemoteDirectoryEntry> result = new ArrayList<>(); + for (String s : dir.list()) { + if (s.equals(".") || s.equals("..")) + continue; + File f = new File(dir, s); + RemoteDirectoryEntry entry; + synchronized (localCache) { + entry = (RemoteDirectoryEntry) localCache.get(s); + if (f.isDirectory()) { + if (entry == null || !(entry instanceof DirectoryDelegate)) { + entry = new DirectoryDelegate(f, this); + localCache.put(s, entry); + } + } else if (f.isFile()) { + if (entry == null || !(entry instanceof FileDelegate)) { + entry = new FileDelegate(f, this); + localCache.put(s, entry); + } + } else { + // not file or dir; skip... + continue; + } + } + result.add(entry); + } + return result; + } + + @Override + public RemoteFile makeEmptyFile(String name) throws IOException { + File f = getValidatedNewFile(dir, name); + touch(f); + FileDelegate delegate = new FileDelegate(f, this); + synchronized (localCache) { + localCache.put(name, delegate); + } + return delegate; + } + + @Override + public RemoteDirectory makeSubdirectory(String name) throws IOException { + File f = getValidatedNewFile(dir, name); + forceMkdir(f); + DirectoryDelegate delegate = new DirectoryDelegate(f, this); + synchronized (localCache) { + localCache.put(name, delegate); + } + return delegate; + } + + @SuppressWarnings("unchecked") + @Override + public void destroy() throws IOException { + if (parent == null) + throw new IOException("tried to destroy main job working directory"); + Collection<RemoteDirectoryEntry> values; + synchronized (localCache) { + values = new ArrayList<>(localCache.values()); + } + for (RemoteDirectoryEntry obj : values) { + if (obj == null) + continue; + try { + obj.destroy(); + } catch (IOException e) { + } + } + forceDelete(dir); + parent.forgetEntry(this); + } + + @Override + public RemoteDirectory getContainingDirectory() { + return parent; + } + + void forgetEntry(@Nonnull RemoteDirectoryEntry entry) { + synchronized (localCache) { + MapIterator i = localCache.mapIterator(); + while (i.hasNext()) { + Object key = i.next(); + if (entry == i.getValue()) { + localCache.remove(key); + break; + } + } + } + } + + @Override + public String getName() { + if (parent == null) + return ""; + return dir.getName(); + } + + @Override + public Date getModificationDate() throws RemoteException { + return new Date(dir.lastModified()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/FileDelegate.java ---------------------------------------------------------------------- diff --git a/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/FileDelegate.java b/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/FileDelegate.java new file mode 100644 index 0000000..7e47af9 --- /dev/null +++ b/taverna-server-worker/src/main/java/org/taverna/server/localworker/impl/FileDelegate.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2010-2011 The University of Manchester + * + * See the file "LICENSE" for license terms. + */ +package org.taverna.server.localworker.impl; + +import static java.lang.System.arraycopy; +import static java.net.InetAddress.getLocalHost; +import static org.apache.commons.io.FileUtils.copyFile; +import static org.apache.commons.io.FileUtils.forceDelete; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.UnknownHostException; +import java.rmi.RemoteException; +import java.rmi.server.UnicastRemoteObject; +import java.util.Date; + +import javax.annotation.Nonnull; + +import org.taverna.server.localworker.remote.RemoteDirectory; +import org.taverna.server.localworker.remote.RemoteFile; + +/** + * This class acts as a remote-aware delegate for the files in a workflow run's + * working directory and its subdirectories. + * + * @author Donal Fellows + * @see DirectoryDelegate + */ [email protected]("serial") +public class FileDelegate extends UnicastRemoteObject implements RemoteFile { + private File file; + private DirectoryDelegate parent; + + /** + * @param file + * @param parent + * @throws RemoteException + * If registration of the file fails. + */ + public FileDelegate(@Nonnull File file, @Nonnull DirectoryDelegate parent) + throws RemoteException { + super(); + this.file = file; + this.parent = parent; + } + + @Override + public byte[] getContents(int offset, int length) throws IOException { + if (length == -1) + length = (int) (file.length() - offset); + if (length < 0 || length > 1024 * 64) + length = 1024 * 64; + byte[] buffer = new byte[length]; + int read; + try (FileInputStream fis = new FileInputStream(file)) { + if (offset > 0 && fis.skip(offset) != offset) + throw new IOException("did not move to correct offset in file"); + read = fis.read(buffer); + } + if (read <= 0) + return new byte[0]; + if (read < buffer.length) { + byte[] shortened = new byte[read]; + arraycopy(buffer, 0, shortened, 0, read); + return shortened; + } + return buffer; + } + + @Override + public long getSize() { + return file.length(); + } + + @Override + public void setContents(byte[] data) throws IOException { + try (FileOutputStream fos = new FileOutputStream(file)) { + fos.write(data); + } + } + + @Override + public void appendContents(byte[] data) throws IOException { + try (FileOutputStream fos = new FileOutputStream(file, true)) { + fos.write(data); + } + } + + @Override + public void destroy() throws IOException { + forceDelete(file); + parent.forgetEntry(this); + parent = null; + } + + @Override + public RemoteDirectory getContainingDirectory() { + return parent; + } + + @Override + public String getName() { + return file.getName(); + } + + @Override + public void copy(RemoteFile sourceFile) throws RemoteException, IOException { + String sourceHost = sourceFile.getNativeHost(); + if (!getNativeHost().equals(sourceHost)) { + throw new IOException( + "cross-system copy not implemented; cannot copy from " + + sourceHost + " to " + getNativeHost()); + } + // Must copy; cannot count on other file to stay unmodified + copyFile(new File(sourceFile.getNativeName()), file); + } + + @Override + public String getNativeName() { + return file.getAbsolutePath(); + } + + @Override + public String getNativeHost() { + try { + return getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + throw new RuntimeException( + "unexpected failure to resolve local host address", e); + } + } + + @Override + public Date getModificationDate() throws RemoteException { + return new Date(file.lastModified()); + } +}
