http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/BaseProfileLocator.java ---------------------------------------------------------------------- diff --git a/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/BaseProfileLocator.java b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/BaseProfileLocator.java new file mode 100644 index 0000000..989f411 --- /dev/null +++ b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/BaseProfileLocator.java @@ -0,0 +1,154 @@ +package io.github.taverna_extras.component.profile; +/* + * 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.Locale.UK; +import static org.apache.commons.httpclient.HttpStatus.SC_OK; +import static org.apache.commons.io.FileUtils.writeStringToFile; +import static org.apache.log4j.Logger.getLogger; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.text.ParseException; +import java.text.SimpleDateFormat; + +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.params.HttpClientParams; +import org.apache.log4j.Logger; +import io.github.taverna_extras.component.api.ComponentException; +import org.apache.taverna.configuration.app.ApplicationConfiguration; + +public class BaseProfileLocator { + private static final String BASE_PROFILE_PATH = "BaseProfile.xml"; + private static final String BASE_PROFILE_URI = "http://build.mygrid.org.uk/taverna/BaseProfile.xml"; + private static final int TIMEOUT = 5000; + private static final String pattern = "EEE, dd MMM yyyy HH:mm:ss z"; + private static final SimpleDateFormat format = new SimpleDateFormat( + pattern, UK); + + private Logger logger = getLogger(BaseProfileLocator.class); + private ApplicationConfiguration appConfig; + private ComponentProfileImpl profile; + + private void locateBaseProfile() { + File baseProfileFile = getBaseProfileFile(); + @SuppressWarnings("unused") + boolean load = false; + Long remoteBaseProfileTime = null; + long localBaseProfileTime = -1; + + HttpClientParams params = new HttpClientParams(); + params.setConnectionManagerTimeout(TIMEOUT); + params.setSoTimeout(TIMEOUT); + HttpClient client = new HttpClient(params); + + try { + remoteBaseProfileTime = getRemoteBaseProfileTimestamp(client); + logger.info("NoticeTime is " + remoteBaseProfileTime); + } catch (URISyntaxException e) { + logger.error("URI problem", e); + } catch (IOException e) { + logger.info("Could not read base profile", e); + } catch (ParseException e) { + logger.error("Could not parse last-modified time", e); + } + if (baseProfileFile.exists()) + localBaseProfileTime = baseProfileFile.lastModified(); + + try { + if ((remoteBaseProfileTime != null) + && (remoteBaseProfileTime > localBaseProfileTime)) { + profile = new ComponentProfileImpl(null, new URL(BASE_PROFILE_URI), + null); + writeStringToFile(baseProfileFile, profile.getXML()); + } + } catch (MalformedURLException e) { + logger.error("URI problem", e); + profile = null; + } catch (ComponentException e) { + logger.error("Component Registry problem", e); + profile = null; + } catch (IOException e) { + logger.error("Unable to write profile", e); + profile = null; + } + + try { + if ((profile == null) && baseProfileFile.exists()) + profile = new ComponentProfileImpl(null, baseProfileFile.toURI() + .toURL(), null); + } catch (Exception e) { + logger.error("URI problem", e); + profile = null; + } + } + + private long parseTime(String timestamp) throws ParseException { + timestamp = timestamp.trim(); + if (timestamp.endsWith(" GMT")) + timestamp = timestamp.substring(0, timestamp.length() - 3) + + " +0000"; + else if (timestamp.endsWith(" BST")) + timestamp = timestamp.substring(0, timestamp.length() - 3) + + " +0100"; + return format.parse(timestamp).getTime(); + } + + private long getRemoteBaseProfileTimestamp(HttpClient client) + throws URISyntaxException, IOException, HttpException, + ParseException { + URI baseProfileURI = new URI(BASE_PROFILE_URI); + HttpMethod method = new GetMethod(baseProfileURI.toString()); + int statusCode = client.executeMethod(method); + if (statusCode != SC_OK) { + logger.warn("HTTP status " + statusCode + " while getting " + + baseProfileURI); + return -1; + } + Header h = method.getResponseHeader("Last-Modified"); + if (h == null) + return -1; + return parseTime(h.getValue()); + } + + private File getBaseProfileFile() { + File config = new File(appConfig.getApplicationHomeDir().toFile(), "conf"); + if (!config.exists()) + config.mkdir(); + return new File(config, BASE_PROFILE_PATH); + } + + public synchronized ComponentProfileImpl getProfile() { + if (profile == null) + locateBaseProfile(); + return profile; + } + + public void setAppConfig(ApplicationConfiguration appConfig) { + this.appConfig = appConfig; + } +}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/ComponentProfileImpl.java ---------------------------------------------------------------------- diff --git a/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/ComponentProfileImpl.java b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/ComponentProfileImpl.java new file mode 100644 index 0000000..6c7a5b0 --- /dev/null +++ b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/ComponentProfileImpl.java @@ -0,0 +1,683 @@ +package io.github.taverna_extras.component.profile; +/* + * 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.jena.rdf.model.ModelFactory.createOntologyModel; +import static java.lang.System.identityHashCode; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static org.apache.log4j.Logger.getLogger; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.transform.stream.StreamSource; + +import org.apache.commons.io.IOUtils; +import org.apache.log4j.Logger; +import io.github.taverna_extras.component.api.ComponentException; +import io.github.taverna_extras.component.api.Registry; +import io.github.taverna_extras.component.api.profile.ActivityProfile; +import io.github.taverna_extras.component.api.profile.ExceptionHandling; +import io.github.taverna_extras.component.api.profile.PortProfile; +import io.github.taverna_extras.component.api.profile.SemanticAnnotationProfile; + +import io.github.taverna_extras.component.api.profile.doc.Activity; +import io.github.taverna_extras.component.api.profile.doc.Ontology; +import io.github.taverna_extras.component.api.profile.doc.Port; +import io.github.taverna_extras.component.api.profile.doc.Profile; +import io.github.taverna_extras.component.api.profile.doc.SemanticAnnotation; + +import org.apache.jena.ontology.OntClass; +import org.apache.jena.ontology.OntModel; +import org.apache.jena.ontology.OntProperty; +import static org.apache.taverna.workflowmodel.health.HealthCheck.NO_PROBLEM; +import org.apache.taverna.workflowmodel.health.RemoteHealthChecker; + +/** + * A ComponentProfile specifies the inputs, outputs and semantic annotations + * that a Component must contain. + * + * @author David Withers + */ +public class ComponentProfileImpl implements + io.github.taverna_extras.component.api.profile.Profile { + private static final Logger logger = getLogger(ComponentProfileImpl.class); + private static final Map<String, OntModel> ontologyModels = new HashMap<>(); + private static final JAXBContext jaxbContext; + private BaseProfileLocator base; + static { + try { + jaxbContext = JAXBContext.newInstance(Profile.class); + } catch (JAXBException e) { + // Should never happen! Represents a critical error + throw new Error( + "Failed to initialize profile deserialization engine", e); + } + } + private io.github.taverna_extras.component.api.profile.Profile parent; + private Profile profileDoc; + private ExceptionHandling exceptionHandling; + private Registry parentRegistry = null; + private final Object lock = new Object(); + private Exception loaderException = null; + protected volatile boolean loaded = false; + + public ComponentProfileImpl(URL profileURL, BaseProfileLocator base) + throws ComponentException { + this(null, profileURL, base); + } + + public ComponentProfileImpl(String profileString, BaseProfileLocator base) + throws ComponentException { + this(null, profileString, base); + } + + public ComponentProfileImpl(Registry registry, URI profileURI, + BaseProfileLocator base) throws ComponentException, + MalformedURLException { + this(registry, profileURI.toURL(), base); + } + + public ComponentProfileImpl(Registry registry, URL profileURL, + BaseProfileLocator base) throws ComponentException { + logger.info("Loading profile in " + identityHashCode(this) + " from " + + profileURL); + this.base = base; + try { + URL url = profileURL; + if (url.getProtocol().startsWith("http")) + url = new URI(url.getProtocol(), url.getAuthority(), + url.getPath(), url.getQuery(), url.getRef()).toURL(); + loadProfile(this, url, base); + } catch (MalformedURLException e) { + logger.warn("Malformed URL? " + profileURL); + } catch (URISyntaxException e) { + logger.warn("Malformed URL? " + profileURL); + } + parentRegistry = registry; + } + + public ComponentProfileImpl(Registry registry, String profileString, + BaseProfileLocator base) throws ComponentException { + logger.info("Loading profile in " + identityHashCode(this) + + " from string"); + this.base = base; + loadProfile(this, profileString, base); + this.parentRegistry = registry; + } + + private static void loadProfile(final ComponentProfileImpl profile, + final Object source, BaseProfileLocator base) { + Runnable r = new Runnable() { + @Override + public void run() { + Date start = new Date(); + if (source instanceof URL) + loadProfileFromURL(profile, (URL) source); + else if (source instanceof String) + loadProfileFromString(profile, (String) source); + else + throw new IllegalArgumentException( + "Bad type of profile source: " + source.getClass()); + Date end = new Date(); + logger.info("Loaded profile in " + identityHashCode(profile) + + " (in " + (end.getTime() - start.getTime()) + + " msec)"); + } + }; + if (base.getProfile() == null) + // Must load the base profile synchronously, to avoid deadlock + r.run(); + else + new Thread(r).start(); + } + + private static void loadProfileFromURL(ComponentProfileImpl profile, URL source) { + try { + URLConnection conn = source.openConnection(); + try { + conn.addRequestProperty("Accept", "application/xml,*/*;q=0.1"); + } catch (Exception e) { + } + try (InputStream is = conn.getInputStream()) { + profile.profileDoc = jaxbContext.createUnmarshaller() + .unmarshal(new StreamSource(is), Profile.class) + .getValue(); + } + } catch (FileNotFoundException e) { + profile.loaderException = e; + logger.warn("URL not readable: " + source); + } catch (Exception e) { + profile.loaderException = e; + logger.warn("Failed to load profile.", e); + } + synchronized (profile.lock) { + profile.loaded = true; + profile.lock.notifyAll(); + } + } + + private static void loadProfileFromString(ComponentProfileImpl profile, + String source) { + try { + profile.profileDoc = jaxbContext + .createUnmarshaller() + .unmarshal(new StreamSource(new StringReader(source)), + Profile.class).getValue(); + } catch (Exception e) { + profile.loaderException = e; + logger.warn("Failed to load profile.", e); + } + synchronized (profile.lock) { + profile.loaded = true; + profile.lock.notifyAll(); + } + } + + @Override + public Registry getComponentRegistry() { + return parentRegistry; + } + + @Override + public String getXML() throws ComponentException { + try { + StringWriter stringWriter = new StringWriter(); + jaxbContext.createMarshaller().marshal(getProfileDocument(), + stringWriter); + return stringWriter.toString(); + } catch (JAXBException e) { + throw new ComponentException("Unable to serialize profile.", e); + } + } + + @Override + public Profile getProfileDocument() throws ComponentException { + try { + synchronized (lock) { + while (!loaded) + lock.wait(); + if (loaderException != null) { + if (loaderException instanceof FileNotFoundException) + throw new ComponentException( + "Profile not found/readable: " + + loaderException.getMessage(), + loaderException); + throw new ComponentException( + "Problem loading profile definition: " + + loaderException.getMessage(), + loaderException); + } + return profileDoc; + } + } catch (InterruptedException e) { + logger.info("Interrupted during wait for lock.", e); + return null; + } + } + + @Override + public String getId() { + try { + return getProfileDocument().getId(); + } catch (ComponentException e) { + return null; + } + } + + @Override + public String getName() { + try { + return getProfileDocument().getName(); + } catch (ComponentException e) { + return null; + } + } + + @Override + public String getDescription() { + try { + return getProfileDocument().getDescription(); + } catch (ComponentException e) { + return null; + } + } + + /** + * @return Is this the base profile? + */ + private boolean isBase() { + if (base == null) + return true; + Object o = base.getProfile(); + return o == null || o == this; + } + + private synchronized io.github.taverna_extras.component.api.profile.Profile parent() + throws ComponentException { + if (parent == null) { + try { + if (!isBase() && getProfileDocument().getExtends() != null + && parentRegistry != null) { + parent = parentRegistry + .getComponentProfile(getProfileDocument() + .getExtends().getProfileId()); + if (parent != null) + return parent; + } + } catch (ComponentException e) { + } + parent = new EmptyProfile(); + } + return parent; + } + + @Override + public String getOntologyLocation(String ontologyId) { + String ontologyURI = null; + try { + for (Ontology ontology : getProfileDocument().getOntology()) + if (ontology.getId().equals(ontologyId)) + ontologyURI = ontology.getValue(); + } catch (ComponentException e) { + } + if ((ontologyURI == null) && !isBase()) + ontologyURI = base.getProfile().getOntologyLocation(ontologyId); + return ontologyURI; + } + + private Map<String, String> internalGetPrefixMap() + throws ComponentException { + Map<String, String> result = new TreeMap<>(); + try { + for (Ontology ontology : getProfileDocument().getOntology()) + result.put(ontology.getId(), ontology.getValue()); + } catch (ComponentException e) { + } + result.putAll(parent().getPrefixMap()); + return result; + } + + @Override + public Map<String, String> getPrefixMap() throws ComponentException { + Map<String, String> result = internalGetPrefixMap(); + if (!isBase()) + result.putAll(base.getProfile().getPrefixMap()); + return result; + } + + private OntModel readOntologyFromURI(String ontologyId, String ontologyURI) { + logger.info("Reading ontology for " + ontologyId + " from " + + ontologyURI); + OntModel model = createOntologyModel(); + try { + URL url = new URL(ontologyURI); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + // CRITICAL: must be retrieved as correct content type + conn.addRequestProperty("Accept", + "application/rdf+xml,application/xml;q=0.9"); + try (InputStream in = conn.getInputStream()) { + // TODO Consider whether the encoding is handled right + // ontologyModel.read(in, url.toString()); + model.read(new StringReader(IOUtils.toString(in, "UTF-8")), + url.toString()); + } + } catch (MalformedURLException e) { + logger.error("Problem reading ontology " + ontologyId, e); + return null; + } catch (IOException e) { + logger.error("Problem reading ontology " + ontologyId, e); + return null; + } catch (NullPointerException e) { + // TODO Why is this different? + logger.error("Problem reading ontology " + ontologyId, e); + model = createOntologyModel(); + } + return model; + } + + private boolean isAccessible(String ontologyURI) { + return RemoteHealthChecker.contactEndpoint(null, ontologyURI).getResultId() == NO_PROBLEM; + } + + @Override + public OntModel getOntology(String ontologyId) { + String ontologyURI = getOntologyLocation(ontologyId); + synchronized (ontologyModels) { + if (ontologyModels.containsKey(ontologyURI)) + return ontologyModels.get(ontologyURI); + } + + // Drop out of critical section while we do I/O + if (!isAccessible(ontologyURI)) { + logger.warn("Catastrophic problem contacting ontology source."); + // Catastrophic problem?! + synchronized (ontologyModels) { + ontologyModels.put(ontologyURI, null); + } + return null; + } + OntModel model = readOntologyFromURI(ontologyId, ontologyURI); + + synchronized (ontologyModels) { + if (model != null && !ontologyModels.containsKey(ontologyURI)) { + ontologyModels.put(ontologyURI, model); + } + return ontologyModels.get(ontologyURI); + } + } + + @Override + public List<PortProfile> getInputPortProfiles() { + List<PortProfile> portProfiles = new ArrayList<>(); + try { + for (Port port : getProfileDocument().getComponent().getInputPort()) + portProfiles.add(new PortProfileImpl(this, port)); + } catch (ComponentException e) { + } + if (!isBase()) + portProfiles.addAll(base.getProfile().getInputPortProfiles()); + return portProfiles; + } + + @Override + public List<SemanticAnnotationProfile> getInputSemanticAnnotationProfiles() + throws ComponentException { + List<SemanticAnnotationProfile> saProfiles = new ArrayList<>(); + List<PortProfile> portProfiles = getInputPortProfiles(); + portProfiles.addAll(parent().getInputPortProfiles()); + for (PortProfile portProfile : portProfiles) + saProfiles.addAll(portProfile.getSemanticAnnotations()); + if (!isBase()) + saProfiles.addAll(base.getProfile() + .getInputSemanticAnnotationProfiles()); + return getUniqueSemanticAnnotationProfiles(saProfiles); + } + + @Override + public List<PortProfile> getOutputPortProfiles() { + List<PortProfile> portProfiles = new ArrayList<>(); + try { + for (Port port : getProfileDocument().getComponent() + .getOutputPort()) + portProfiles.add(new PortProfileImpl(this, port)); + } catch (ComponentException e) { + } + if (!isBase()) + portProfiles.addAll(base.getProfile().getOutputPortProfiles()); + return portProfiles; + } + + @Override + public List<SemanticAnnotationProfile> getOutputSemanticAnnotationProfiles() + throws ComponentException { + List<SemanticAnnotationProfile> saProfiles = new ArrayList<>(); + List<PortProfile> portProfiles = getOutputPortProfiles(); + portProfiles.addAll(parent().getOutputPortProfiles()); + for (PortProfile portProfile : portProfiles) + saProfiles.addAll(portProfile.getSemanticAnnotations()); + if (!isBase()) + saProfiles.addAll(base.getProfile() + .getOutputSemanticAnnotationProfiles()); + return getUniqueSemanticAnnotationProfiles(saProfiles); + } + + @Override + public List<io.github.taverna_extras.component.api.profile.ActivityProfile> getActivityProfiles() { + List<io.github.taverna_extras.component.api.profile.ActivityProfile> activityProfiles = new ArrayList<>(); + try { + for (Activity activity : getProfileDocument().getComponent() + .getActivity()) + activityProfiles.add(new ActivityProfileImpl(this, activity)); + } catch (ComponentException e) { + } + return activityProfiles; + } + + @Override + public List<SemanticAnnotationProfile> getActivitySemanticAnnotationProfiles() + throws ComponentException { + List<SemanticAnnotationProfile> saProfiles = new ArrayList<>(); + List<ActivityProfile> activityProfiles = getActivityProfiles(); + activityProfiles.addAll(parent().getActivityProfiles()); + for (ActivityProfile activityProfile : activityProfiles) + saProfiles.addAll(activityProfile.getSemanticAnnotations()); + if (!isBase()) + saProfiles.addAll(base.getProfile() + .getActivitySemanticAnnotationProfiles()); + return getUniqueSemanticAnnotationProfiles(saProfiles); + } + + @Override + public List<SemanticAnnotationProfile> getSemanticAnnotations() + throws ComponentException { + List<SemanticAnnotationProfile> saProfiles = getComponentProfiles(); + saProfiles.addAll(parent().getSemanticAnnotations()); + if (!isBase()) + saProfiles.addAll(base.getProfile().getSemanticAnnotations()); + return saProfiles; + } + + private List<SemanticAnnotationProfile> getComponentProfiles() { + List<SemanticAnnotationProfile> saProfiles = new ArrayList<>(); + try { + for (SemanticAnnotation semanticAnnotation : getProfileDocument() + .getComponent().getSemanticAnnotation()) + saProfiles.add(new SemanticAnnotationProfileImpl(this, + semanticAnnotation)); + } catch (ComponentException e) { + } + return saProfiles; + } + + private List<SemanticAnnotationProfile> getUniqueSemanticAnnotationProfiles( + List<SemanticAnnotationProfile> semanticAnnotationProfiles) { + List<SemanticAnnotationProfile> uniqueSemanticAnnotations = new ArrayList<>(); + Set<OntProperty> predicates = new HashSet<>(); + for (SemanticAnnotationProfile semanticAnnotationProfile : semanticAnnotationProfiles) { + OntProperty prop = semanticAnnotationProfile.getPredicate(); + if (prop != null && !predicates.contains(prop)) { + predicates.add(prop); + uniqueSemanticAnnotations.add(semanticAnnotationProfile); + } + } + return uniqueSemanticAnnotations; + } + + @Override + public ExceptionHandling getExceptionHandling() { + try { + if (exceptionHandling == null + && getProfileDocument().getComponent() + .getExceptionHandling() != null) + exceptionHandling = new ExceptionHandling(getProfileDocument() + .getComponent().getExceptionHandling()); + } catch (ComponentException e) { + } + return exceptionHandling; + } + + @Override + public String toString() { + return "ComponentProfile" + "\n Name : " + getName() + + "\n Description : " + getDescription() + + "\n InputPortProfiles : " + getInputPortProfiles() + + "\n OutputPortProfiles : " + getOutputPortProfiles(); + } + + @Override + public int hashCode() { + return 31 + ((getId() == null) ? 0 : getId().hashCode()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ComponentProfileImpl other = (ComponentProfileImpl) obj; + if (!loaded || !other.loaded) + return false; + if (getId() == null) + return other.getId() == null; + return getId().equals(other.getId()); + } + + public OntClass getClass(String className) { + try { + for (Ontology ontology : getProfileDocument().getOntology()) { + OntModel ontModel = getOntology(ontology.getId()); + if (ontModel != null) { + OntClass result = ontModel.getOntClass(className); + if (result != null) + return result; + } + } + } catch (ComponentException e) { + } + return null; + } + + @Override + public void delete() throws ComponentException { + throw new ComponentException("Deletion not supported."); + } +} + +/** + * A simple do-nothing implementation of a profile. Used when there's no other + * option for what a <i>real</i> profile extends. + * + * @author Donal Fellows + */ +final class EmptyProfile implements + io.github.taverna_extras.component.api.profile.Profile { + @Override + public String getName() { + return ""; + } + + @Override + public String getDescription() { + return ""; + } + + @Override + public Registry getComponentRegistry() { + return null; + } + + @Override + public String getXML() throws ComponentException { + throw new ComponentException("No document."); + } + + @Override + public Profile getProfileDocument() { + return new Profile(); + } + + @Override + public String getId() { + return ""; + } + + @Override + public String getOntologyLocation(String ontologyId) { + return ""; + } + + @Override + public Map<String, String> getPrefixMap() { + return emptyMap(); + } + + @Override + public OntModel getOntology(String ontologyId) { + return null; + } + + @Override + public List<PortProfile> getInputPortProfiles() { + return emptyList(); + } + + @Override + public List<SemanticAnnotationProfile> getInputSemanticAnnotationProfiles() { + return emptyList(); + } + + @Override + public List<PortProfile> getOutputPortProfiles() { + return emptyList(); + } + + @Override + public List<SemanticAnnotationProfile> getOutputSemanticAnnotationProfiles() { + return emptyList(); + } + + @Override + public List<io.github.taverna_extras.component.api.profile.ActivityProfile> getActivityProfiles() { + return emptyList(); + } + + @Override + public List<SemanticAnnotationProfile> getActivitySemanticAnnotationProfiles() { + return emptyList(); + } + + @Override + public List<SemanticAnnotationProfile> getSemanticAnnotations() { + return emptyList(); + } + + @Override + public ExceptionHandling getExceptionHandling() { + return null; + } + + @Override + public void delete() throws ComponentException { + throw new ComponentException("Deletion forbidden."); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/PortProfileImpl.java ---------------------------------------------------------------------- diff --git a/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/PortProfileImpl.java b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/PortProfileImpl.java new file mode 100644 index 0000000..53a1190 --- /dev/null +++ b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/PortProfileImpl.java @@ -0,0 +1,58 @@ +package io.github.taverna_extras.component.profile; +/* + * 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.util.ArrayList; +import java.util.List; + +import io.github.taverna_extras.component.api.profile.PortProfile; +import io.github.taverna_extras.component.api.profile.SemanticAnnotationProfile; + +import io.github.taverna_extras.component.api.profile.doc.Port; +import io.github.taverna_extras.component.api.profile.doc.SemanticAnnotation; + +/** + * Specifies the semantic annotations that a port must have. + * + * @author David Withers + */ +public class PortProfileImpl implements PortProfile { + private final ComponentProfileImpl componentProfile; + private final Port port; + + public PortProfileImpl(ComponentProfileImpl componentProfile, Port port) { + this.componentProfile = componentProfile; + this.port = port; + } + + @Override + public List<SemanticAnnotationProfile> getSemanticAnnotations() { + List<SemanticAnnotationProfile> saProfiles = new ArrayList<>(); + for (SemanticAnnotation annotation : port.getSemanticAnnotation()) + saProfiles.add(new SemanticAnnotationProfileImpl(componentProfile, + annotation)); + return saProfiles; + } + + @Override + public String toString() { + return "PortProfile \n SemanticAnnotations : " + + getSemanticAnnotations(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/SemanticAnnotationProfileImpl.java ---------------------------------------------------------------------- diff --git a/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/SemanticAnnotationProfileImpl.java b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/SemanticAnnotationProfileImpl.java new file mode 100644 index 0000000..a3b5afa --- /dev/null +++ b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/profile/SemanticAnnotationProfileImpl.java @@ -0,0 +1,175 @@ +package io.github.taverna_extras.component.profile; +/* + * 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.io.File.createTempFile; +import static org.apache.commons.io.FileUtils.writeStringToFile; +import static org.apache.log4j.Logger.getLogger; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; +import io.github.taverna_extras.component.api.profile.SemanticAnnotationProfile; + +import io.github.taverna_extras.component.api.profile.doc.SemanticAnnotation; + +import org.apache.jena.ontology.Individual; +import org.apache.jena.ontology.OntClass; +import org.apache.jena.ontology.OntModel; +import org.apache.jena.ontology.OntProperty; +import org.apache.jena.ontology.OntResource; + +/** + * Definition of a semantic annotation for a component element. + * + * @author David Withers + */ +public class SemanticAnnotationProfileImpl implements SemanticAnnotationProfile { + private static final Logger log = getLogger(SemanticAnnotationProfileImpl.class); + private final ComponentProfileImpl componentProfile; + private final SemanticAnnotation semanticAnnotation; + + public SemanticAnnotationProfileImpl(ComponentProfileImpl componentProfile, + SemanticAnnotation semanticAnnotation) { + this.componentProfile = componentProfile; + this.semanticAnnotation = semanticAnnotation; + } + + /** + * Returns the ontology that defines semantic annotation. + * + * @return the ontology that defines semantic annotation + */ + @Override + public OntModel getOntology() { + String ontology = semanticAnnotation.getOntology(); + if (ontology == null) + return null; + return componentProfile.getOntology(ontology); + } + + /** + * Returns the predicate for the semantic annotation. + * + * @return the predicate for the semantic annotation + */ + @Override + public OntProperty getPredicate() { + OntModel ontology = getOntology(); + if (ontology == null) + return null; + String predicate = semanticAnnotation.getPredicate(); + if (predicate == null) + return null; + if (predicate.contains("foaf")) { + StringWriter sw = new StringWriter(); + ontology.writeAll(sw, null, "RDF/XML"); + try { + writeStringToFile(createTempFile("foaf", null), sw.toString()); + } catch (IOException e) { + log.info("failed to write foaf ontology to temporary file", e); + } + } + + return ontology.getOntProperty(predicate); + } + + @Override + public String getPredicateString() { + return semanticAnnotation.getPredicate(); + } + + @Override + public String getClassString() { + return semanticAnnotation.getClazz(); + } + + /** + * Returns the individual that the semantic annotation must use. + * + * May be null if no explicit individual is required. + * + * @return the individual that the semantic annotation must use + */ + @Override + public Individual getIndividual() { + String individual = semanticAnnotation.getValue(); + if (individual == null || individual.isEmpty()) + return null; + return getOntology().getIndividual(individual); + } + + /** + * Returns the individuals in the range of the predicate defined in the + * ontology. + * + * @return the individuals in the range of the predicate defined in the + * ontology + */ + @Override + public List<Individual> getIndividuals() { + OntModel ontology = getOntology(); + OntProperty prop = getPredicate(); + if (ontology == null || prop == null) + return new ArrayList<>(); + OntResource range = prop.getRange(); + if (range == null) + return new ArrayList<>(); + return ontology.listIndividuals(range).toList(); + } + + @Override + public Integer getMinOccurs() { + return semanticAnnotation.getMinOccurs().intValue(); + } + + @Override + public Integer getMaxOccurs() { + try { + return Integer.valueOf(semanticAnnotation.getMaxOccurs()); + } catch (NumberFormatException e) { + return null; + } + } + + @Override + public String toString() { + return "SemanticAnnotation " + "\n Predicate : " + getPredicate() + + "\n Individual : " + getIndividual() + "\n Individuals : " + + getIndividuals(); + } + + @Override + public OntClass getRangeClass() { + String clazz = this.getClassString(); + if (clazz != null) + return componentProfile.getClass(clazz); + + OntProperty prop = getPredicate(); + if (prop == null) + return null; + OntResource range = prop.getRange(); + if (range != null && range.isClass()) + return range.asClass(); + return null; + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ClientVersion.java ---------------------------------------------------------------------- diff --git a/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ClientVersion.java b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ClientVersion.java new file mode 100644 index 0000000..c9fda59 --- /dev/null +++ b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ClientVersion.java @@ -0,0 +1,51 @@ +package io.github.taverna_extras.component.registry; +/* + * 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.IOException; +import java.io.InputStream; +import java.util.Properties; + +public class ClientVersion { + private static final String DEFAULT_VERSION = "1.1.0"; + public static final String VERSION; + + private ClientVersion() { + } + + static { + InputStream is = ClientVersion.class + .getResourceAsStream("version.properties"); + String version = DEFAULT_VERSION; + if (is != null) + try { + Properties p = new Properties(); + p.load(is); + version = p.getProperty("project.version", DEFAULT_VERSION); + } catch (IOException e) { + } finally { + try { + is.close(); + } catch (IOException e) { + } + } + VERSION = version; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/Component.java ---------------------------------------------------------------------- diff --git a/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/Component.java b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/Component.java new file mode 100644 index 0000000..afdf2b8 --- /dev/null +++ b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/Component.java @@ -0,0 +1,161 @@ +package io.github.taverna_extras.component.registry; +/* + * 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.Collections.synchronizedSortedMap; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.SortedMap; +import java.util.TreeMap; + +import io.github.taverna_extras.component.api.ComponentException; +import io.github.taverna_extras.component.api.Version; + +import org.apache.taverna.scufl2.api.container.WorkflowBundle; + +/** + * A Component is a building block for creating Taverna workflows. Components + * and must comply with the ComponentProfile of their ComponentFamily. + * + * @author David Withers + */ +public abstract class Component implements + io.github.taverna_extras.component.api.Component { + private String name; + private String description; + private URL url; + /** + * Mapping from version numbers to version implementations. + */ + protected SortedMap<Integer, Version> versionMap = new TreeMap<>(); + + protected Component(URL url) { + this.url = url; + } + + protected Component(String url) { + try { + this.url = new URL(url); + } catch (MalformedURLException e) { + // nothing + } + } + + protected Component(File fileDir) { + try { + this.url = fileDir.toURI().toURL(); + } catch (MalformedURLException e) { + // nothing + } + } + + @Override + public final synchronized String getName() { + if (name == null) + name = internalGetName(); + return name; + } + + /** + * The real implementation of the name fetching. Caching already handled. + * + * @return The name of the component. + */ + protected abstract String internalGetName(); + + @Override + public final synchronized String getDescription() { + if (description == null) + description = internalGetDescription(); + return description; + } + + /** + * The real implementation of the description fetching. Caching already + * handled. + * + * @return The description of the component. + */ + protected abstract String internalGetDescription(); + + @Override + public final SortedMap<Integer, Version> getComponentVersionMap() { + synchronized (versionMap) { + checkComponentVersionMap(); + return synchronizedSortedMap(versionMap); + } + } + + private void checkComponentVersionMap() { + if (versionMap.isEmpty()) + populateComponentVersionMap(); + } + + /** + * Create the contents of the {@link #versionMap} field. + */ + protected abstract void populateComponentVersionMap(); + + @Override + public final Version getComponentVersion(Integer version) + throws ComponentException { + synchronized (versionMap) { + checkComponentVersionMap(); + return versionMap.get(version); + } + } + + @Override + public final Version addVersionBasedOn(WorkflowBundle bundle, + String revisionComment) throws ComponentException { + Version result = internalAddVersionBasedOn(bundle, revisionComment); + synchronized (versionMap) { + checkComponentVersionMap(); + versionMap.put(result.getVersionNumber(), result); + } + return result; + } + + /** + * Manufacture a new version of a component. Does not add to the overall + * version map. + * + * @param bundle + * The definition of the component. + * @param revisionComment + * The description of the version. + * @return The new version of the component. + * @throws RegistryException + */ + protected abstract Version internalAddVersionBasedOn(WorkflowBundle bundle, + String revisionComment) throws ComponentException; + + @Override + public final URL getComponentURL() { + return url; + } + + @Override + public void delete() throws ComponentException { + getFamily().removeComponent(this); + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentFamily.java ---------------------------------------------------------------------- diff --git a/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentFamily.java b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentFamily.java new file mode 100644 index 0000000..ce8a782 --- /dev/null +++ b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentFamily.java @@ -0,0 +1,161 @@ +package io.github.taverna_extras.component.registry; +/* + * 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.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.github.taverna_extras.component.api.Component; +import io.github.taverna_extras.component.api.ComponentException; +import io.github.taverna_extras.component.api.Registry; +import io.github.taverna_extras.component.api.Version; +import io.github.taverna_extras.component.api.profile.Profile; + +import org.apache.taverna.scufl2.api.container.WorkflowBundle; + +/** + * A ComponentFamily is a collection of Components that share the same + * ComponentProfile. + * + * @author David Withers + */ +public abstract class ComponentFamily implements + io.github.taverna_extras.component.api.Family { + private Registry parentRegistry; + private String name; + private String description; + private Profile componentProfile; + private ComponentUtil util; + + protected Map<String, Component> componentCache = new HashMap<>(); + + public ComponentFamily(Registry componentRegistry, ComponentUtil util) { + this.parentRegistry = componentRegistry; + this.util = util; + } + + @Override + public Registry getComponentRegistry() { + return parentRegistry; + } + + @Override + public final synchronized String getName() { + if (name == null) { + name = internalGetName(); + } + return name; + } + + protected abstract String internalGetName(); + + @Override + public final synchronized String getDescription() { + if (description == null) { + description = internalGetDescription(); + } + return description; + } + + protected abstract String internalGetDescription(); + + @Override + public final synchronized Profile getComponentProfile() + throws ComponentException { + if (componentProfile == null) + componentProfile = internalGetComponentProfile(); + if (componentProfile == null) { + Profile baseProfile = util.getBaseProfile(); + if (baseProfile != null) { + return baseProfile; + } + } + return componentProfile; + } + + protected abstract Profile internalGetComponentProfile() + throws ComponentException; + + @Override + public final List<Component> getComponents() throws ComponentException { + checkComponentCache(); + return new ArrayList<>(componentCache.values()); + } + + private void checkComponentCache() throws ComponentException { + synchronized (componentCache) { + if (componentCache.isEmpty()) + populateComponentCache(); + } + } + + protected abstract void populateComponentCache() throws ComponentException; + + @Override + public final Component getComponent(String componentName) + throws ComponentException { + checkComponentCache(); + return componentCache.get(componentName); + } + + @Override + public final Version createComponentBasedOn(String componentName, + String description, WorkflowBundle bundle) throws ComponentException { + if (componentName == null) + throw new ComponentException("Component name must not be null"); + if (bundle == null) + throw new ComponentException("workflow must not be null"); + checkComponentCache(); + if (componentCache.containsKey(componentName)) + throw new ComponentException("Component name already used"); + Version version = internalCreateComponentBasedOn(componentName, + description, bundle); + synchronized (componentCache) { + Component c = version.getComponent(); + componentCache.put(componentName, c); + } + return version; + } + + protected abstract Version internalCreateComponentBasedOn( + String componentName, String description, WorkflowBundle bundle) + throws ComponentException; + + @Override + public final void removeComponent(Component component) + throws ComponentException { + if (component != null) { + checkComponentCache(); + synchronized (componentCache) { + componentCache.remove(component.getName()); + } + internalRemoveComponent(component); + } + } + + protected abstract void internalRemoveComponent(Component component) + throws ComponentException; + + @Override + public void delete() throws ComponentException { + getComponentRegistry().removeComponentFamily(this); + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentImplementationCache.java ---------------------------------------------------------------------- diff --git a/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentImplementationCache.java b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentImplementationCache.java new file mode 100644 index 0000000..91846a3 --- /dev/null +++ b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentImplementationCache.java @@ -0,0 +1,75 @@ +package io.github.taverna_extras.component.registry; +/* + * 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.currentTimeMillis; +import static org.apache.log4j.Logger.getLogger; + +import java.util.Map; +import java.util.WeakHashMap; + +import org.apache.log4j.Logger; +import io.github.taverna_extras.component.api.ComponentException; +import io.github.taverna_extras.component.api.Version; + +import org.apache.taverna.scufl2.api.container.WorkflowBundle; + +public class ComponentImplementationCache { + private class Entry { + WorkflowBundle implementation; + long timestamp; + } + private final long VALIDITY = 15 * 60 * 1000; + private final Logger logger = getLogger(ComponentImplementationCache.class); + private final Map<Version.ID, Entry> cache = new WeakHashMap<>(); + private ComponentUtil utils; + + public void setComponentUtil(ComponentUtil utils) { + this.utils = utils; + } + + public WorkflowBundle getImplementation(Version.ID id) throws ComponentException { + long now = currentTimeMillis(); + synchronized (id) { + Entry entry = cache.get(id); + if (entry != null && entry.timestamp >= now) + return entry.implementation; + logger.info("before calculate component version for " + id); + Version componentVersion; + try { + componentVersion = utils.getVersion(id); + } catch (RuntimeException e) { + if (entry != null) + return entry.implementation; + throw new ComponentException(e.getMessage(), e); + } + logger.info("calculated component version for " + id + " as " + + componentVersion.getVersionNumber() + "; retrieving dataflow"); + WorkflowBundle implementation = componentVersion.getImplementation(); + //DataflowValidationReport report = implementation.checkValidity(); + //logger.info("component version " + id + " incomplete:" + // + report.isWorkflowIncomplete() + " valid:" + // + report.isValid()); + entry = new Entry(); + entry.implementation = implementation; + entry.timestamp = now + VALIDITY; + return cache.put(id, entry).implementation; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentRegistry.java ---------------------------------------------------------------------- diff --git a/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentRegistry.java b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentRegistry.java new file mode 100644 index 0000000..325da27 --- /dev/null +++ b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentRegistry.java @@ -0,0 +1,242 @@ +package io.github.taverna_extras.component.registry; +/* + * 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.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.github.taverna_extras.component.api.ComponentException; +import io.github.taverna_extras.component.api.Family; +import io.github.taverna_extras.component.api.License; +import io.github.taverna_extras.component.api.SharingPolicy; +import io.github.taverna_extras.component.api.Version; +import io.github.taverna_extras.component.api.profile.Profile; + +/** + * A ComponentRegistry contains ComponentFamilies and ComponentProfiles. + * + * @author David Withers + */ +public abstract class ComponentRegistry implements + io.github.taverna_extras.component.api.Registry { + protected Map<String, Family> familyCache = new HashMap<>(); + protected List<Profile> profileCache = new ArrayList<>(); + protected List<SharingPolicy> permissionCache = new ArrayList<>(); + protected List<License> licenseCache = new ArrayList<>(); + + private URL registryBase; + + protected ComponentRegistry(URL registryBase) throws ComponentException { + this.registryBase = registryBase; + } + + protected ComponentRegistry(File fileDir) throws ComponentException { + try { + this.registryBase = fileDir.toURI().toURL(); + } catch (MalformedURLException e) { + throw new ComponentException(e); + } + } + + @Override + public final List<Family> getComponentFamilies() throws ComponentException { + checkFamilyCache(); + return new ArrayList<Family>(familyCache.values()); + } + + private void checkFamilyCache() throws ComponentException { + synchronized (familyCache) { + if (familyCache.isEmpty()) + populateFamilyCache(); + } + } + + protected abstract void populateFamilyCache() throws ComponentException; + + @Override + public final Family getComponentFamily(String familyName) + throws ComponentException { + checkFamilyCache(); + return familyCache.get(familyName); + } + + @Override + public final Family createComponentFamily(String familyName, + Profile componentProfile, String description, License license, + SharingPolicy sharingPolicy) throws ComponentException { + if (familyName == null) + throw new ComponentException( + "Component family name must not be null"); + if (componentProfile == null) + throw new ComponentException("Component profile must not be null"); + if (getComponentFamily(familyName) != null) + throw new ComponentException("Component family already exists"); + + Family result = internalCreateComponentFamily(familyName, + componentProfile, description, license, sharingPolicy); + checkFamilyCache(); + synchronized (familyCache) { + familyCache.put(familyName, result); + } + return result; + } + + protected abstract Family internalCreateComponentFamily(String familyName, + Profile componentProfile, String description, License license, + SharingPolicy sharingPolicy) throws ComponentException; + + @Override + public final void removeComponentFamily(Family componentFamily) + throws ComponentException { + if (componentFamily != null) { + checkFamilyCache(); + synchronized (familyCache) { + familyCache.remove(componentFamily.getName()); + } + internalRemoveComponentFamily(componentFamily); + } + } + + protected abstract void internalRemoveComponentFamily(Family componentFamily) + throws ComponentException; + + @Override + public final URL getRegistryBase() { + return registryBase; + } + + @Override + public final String getRegistryBaseString() { + String urlString = getRegistryBase().toString(); + if (urlString.endsWith("/")) + urlString = urlString.substring(0, urlString.length() - 1); + return urlString; + } + + private void checkProfileCache() throws ComponentException { + synchronized (profileCache) { + if (profileCache.isEmpty()) + populateProfileCache(); + } + } + + protected abstract void populateProfileCache() throws ComponentException; + + @Override + public final List<Profile> getComponentProfiles() throws ComponentException { + checkProfileCache(); + return profileCache; + } + + @Override + public final Profile getComponentProfile(String id) + throws ComponentException { + // TODO use a map instead of a *linear search*... + for (Profile p : getComponentProfiles()) + if (p.getId().equals(id)) + return p; + return null; + } + + @Override + public final Profile addComponentProfile(Profile componentProfile, + License license, SharingPolicy sharingPolicy) + throws ComponentException { + if (componentProfile == null) { + throw new ComponentException("componentProfile is null"); + } + Profile result = null; + checkProfileCache(); + for (Profile p : getComponentProfiles()) + if (p.getId().equals(componentProfile.getId())) { + result = p; + break; + } + + if (result == null) { + result = internalAddComponentProfile(componentProfile, license, + sharingPolicy); + synchronized (profileCache) { + profileCache.add(result); + } + } + return result; + } + + protected abstract Profile internalAddComponentProfile( + Profile componentProfile, License license, + SharingPolicy sharingPolicy) throws ComponentException; + + private void checkPermissionCache() { + synchronized (permissionCache) { + if (permissionCache.isEmpty()) + populatePermissionCache(); + } + } + + protected abstract void populatePermissionCache(); + + @Override + public final List<SharingPolicy> getPermissions() throws ComponentException { + checkPermissionCache(); + return permissionCache; + } + + private void checkLicenseCache() { + synchronized (licenseCache) { + if (licenseCache.isEmpty()) + populateLicenseCache(); + } + } + + protected abstract void populateLicenseCache(); + + @Override + public final List<License> getLicenses() throws ComponentException { + checkLicenseCache(); + return licenseCache; + } + + protected License getLicenseByAbbreviation(String licenseString) + throws ComponentException { + checkLicenseCache(); + for (License l : getLicenses()) + if (l.getAbbreviation().equals(licenseString)) + return l; + return null; + } + + @Override + public abstract License getPreferredLicense() throws ComponentException; + + @Override + public abstract Set<Version.ID> searchForComponents(String prefixString, + String text) throws ComponentException; + + @Override + public String toString() { + String[] names = getClass().getName().split("\\."); + return names[names.length-1] + ": " + registryBase; + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentUtil.java ---------------------------------------------------------------------- diff --git a/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentUtil.java b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentUtil.java new file mode 100644 index 0000000..cc31f05 --- /dev/null +++ b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentUtil.java @@ -0,0 +1,130 @@ +package io.github.taverna_extras.component.registry; +/* + * 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.util.HashMap; +import java.util.Map; + +import io.github.taverna_extras.component.api.Component; +import io.github.taverna_extras.component.api.ComponentException; +import io.github.taverna_extras.component.api.ComponentFactory; +import io.github.taverna_extras.component.api.Family; +import io.github.taverna_extras.component.api.Registry; +import io.github.taverna_extras.component.api.Version; +import io.github.taverna_extras.component.api.profile.Profile; +import io.github.taverna_extras.component.profile.BaseProfileLocator; +import io.github.taverna_extras.component.profile.ComponentProfileImpl; +import io.github.taverna_extras.component.registry.local.LocalComponentRegistryFactory; +import io.github.taverna_extras.component.registry.standard.NewComponentRegistryFactory; +import org.springframework.beans.factory.annotation.Required; + +/** + * @author alanrw + * @author dkf + */ +public class ComponentUtil implements ComponentFactory { + private NewComponentRegistryFactory netLocator; + private BaseProfileLocator base; + private LocalComponentRegistryFactory fileLocator; + + private final Map<String, Registry> cache = new HashMap<>(); + + @Required + public void setNetworkLocator(NewComponentRegistryFactory locator) { + this.netLocator = locator; + } + + @Required + public void setFileLocator(LocalComponentRegistryFactory fileLocator) { + this.fileLocator = fileLocator; + } + + @Required + public void setBaseLocator(BaseProfileLocator base) { + this.base = base; + } + + @Override + public Registry getRegistry(URL registryBase) throws ComponentException { + Registry registry = cache.get(registryBase.toString()); + if (registry != null) + return registry; + + if (registryBase.getProtocol().startsWith("http")) { + if (!netLocator.verifyBase(registryBase)) + throw new ComponentException( + "Unable to establish credentials for " + registryBase); + registry = netLocator.getComponentRegistry(registryBase); + } else + registry = fileLocator.getComponentRegistry(registryBase); + cache.put(registryBase.toString(), registry); + return registry; + } + + @Override + public Family getFamily(URL registryBase, String familyName) + throws ComponentException { + return getRegistry(registryBase).getComponentFamily(familyName); + } + + @Override + public Component getComponent(URL registryBase, String familyName, + String componentName) throws ComponentException { + return getRegistry(registryBase).getComponentFamily(familyName) + .getComponent(componentName); + } + + @Override + public Version getVersion(URL registryBase, String familyName, + String componentName, Integer componentVersion) + throws ComponentException { + return getRegistry(registryBase).getComponentFamily(familyName) + .getComponent(componentName) + .getComponentVersion(componentVersion); + } + + @Override + public Version getVersion(Version.ID ident) throws ComponentException { + return getVersion(ident.getRegistryBase(), ident.getFamilyName(), + ident.getComponentName(), ident.getComponentVersion()); + } + + @Override + public Component getComponent(Version.ID ident) throws ComponentException { + return getComponent(ident.getRegistryBase(), ident.getFamilyName(), + ident.getComponentName()); + } + + @Override + public Profile getProfile(URL url) throws ComponentException { + Profile p = new ComponentProfileImpl(url, base); + p.getProfileDocument(); // force immediate loading + return p; + } + + @Override + public Profile getBaseProfile() throws ComponentException { + return base.getProfile(); + } + + public BaseProfileLocator getBaseProfileLocator() { + return base; + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentVersion.java ---------------------------------------------------------------------- diff --git a/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentVersion.java b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentVersion.java new file mode 100644 index 0000000..ab95218 --- /dev/null +++ b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentVersion.java @@ -0,0 +1,77 @@ +package io.github.taverna_extras.component.registry; +/* + * 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 io.github.taverna_extras.component.api.Component; +import io.github.taverna_extras.component.api.ComponentException; + +import org.apache.taverna.scufl2.api.container.WorkflowBundle; + +public abstract class ComponentVersion implements + io.github.taverna_extras.component.api.Version { + private Integer versionNumber; + private String description; + private Component component; + + protected ComponentVersion(Component component) { + this.component = component; + } + + @Override + public final synchronized Integer getVersionNumber() { + if (versionNumber == null) + versionNumber = internalGetVersionNumber(); + return versionNumber; + } + + protected abstract Integer internalGetVersionNumber(); + + @Override + public final synchronized String getDescription() { + if (description == null) + description = internalGetDescription(); + + return description; + } + + protected abstract String internalGetDescription(); + + @Override + public final synchronized WorkflowBundle getImplementation() + throws ComponentException { + // Cached in dataflow cache + return internalGetImplementation(); + } + + protected abstract WorkflowBundle internalGetImplementation() + throws ComponentException; + + @Override + public final Component getComponent() { + return component; + } + + @Override + public ID getID() { + Component c = getComponent(); + return new ComponentVersionIdentification(c.getRegistry() + .getRegistryBase(), c.getFamily().getName(), c.getName(), + getVersionNumber()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentVersionIdentification.java ---------------------------------------------------------------------- diff --git a/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentVersionIdentification.java b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentVersionIdentification.java new file mode 100644 index 0000000..4cecab5 --- /dev/null +++ b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/ComponentVersionIdentification.java @@ -0,0 +1,212 @@ + +package io.github.taverna_extras.component.registry; +/* + * 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 io.github.taverna_extras.component.api.Family; +import io.github.taverna_extras.component.api.Registry; +import io.github.taverna_extras.component.api.Version; +import io.github.taverna_extras.component.api.Version.ID; + +/** + * @author alanrw + * + */ +public class ComponentVersionIdentification implements + io.github.taverna_extras.component.api.Version.ID { + private static final long serialVersionUID = 1768548650702925916L; + private URL registryBase; + private String familyName; + private String componentName; + private Integer componentVersion; + + public ComponentVersionIdentification(URL registryBase, String familyName, + String componentName, Integer componentVersion) { + super(); + this.registryBase = registryBase; + this.familyName = familyName; + this.componentName = componentName; + this.componentVersion = componentVersion; + } + + public ComponentVersionIdentification(Registry registry, Family family, + io.github.taverna_extras.component.api.Component component, Integer version) { + this(registry.getRegistryBase(), family.getName(), component.getName(), version); + } + + public ComponentVersionIdentification(Version.ID toBeCopied) { + this.registryBase = toBeCopied.getRegistryBase(); + this.familyName = toBeCopied.getFamilyName(); + this.componentName = toBeCopied.getComponentName(); + this.componentVersion = toBeCopied.getComponentVersion(); + } + + /** + * @return the registryBase + */ + @Override + public URL getRegistryBase() { + return registryBase; + } + + /** + * @return the familyName + */ + @Override + public String getFamilyName() { + return familyName; + } + + /** + * @return the componentName + */ + @Override + public String getComponentName() { + return componentName; + } + + /** + * @return the componentVersion + */ + @Override + public Integer getComponentVersion() { + return componentVersion; + } + + /** + * @param componentVersion + * the componentVersion to set + */ + public void setComponentVersion(Integer componentVersion) { + this.componentVersion = componentVersion; + } + + /** + * @param registryBase + * the registryBase to set + */ + public void setRegistryBase(URL registryBase) { + this.registryBase = registryBase; + } + + /** + * @param familyName + * the familyName to set + */ + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + /** + * @param componentName + * the componentName to set + */ + public void setComponentName(String componentName) { + this.componentName = componentName; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((componentName == null) ? 0 : componentName.hashCode()); + result = prime + * result + + ((componentVersion == null) ? 0 : componentVersion.hashCode()); + result = prime * result + + ((familyName == null) ? 0 : familyName.hashCode()); + result = prime * result + + ((registryBase == null) ? 0 : registryBase.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ComponentVersionIdentification other = (ComponentVersionIdentification) obj; + if (componentName == null) { + if (other.componentName != null) + return false; + } else if (!componentName.equals(other.componentName)) + return false; + if (componentVersion == null) { + if (other.componentVersion != null) + return false; + } else if (!componentVersion.equals(other.componentVersion)) + return false; + if (familyName == null) { + if (other.familyName != null) + return false; + } else if (!familyName.equals(other.familyName)) + return false; + if (registryBase == null) { + if (other.registryBase != null) + return false; + } else if (!registryBase.toString().equals(other.registryBase.toString())) + return false; + return true; + } + + @Override + public String toString() { + return getComponentName() + " V. " + getComponentVersion() + + " in family " + getFamilyName() + " on " + + getRegistryBase().toExternalForm(); + } + + @Override + public boolean mostlyEqualTo(ID id) { + if (this == id) + return true; + if (id == null) + return false; + if (getClass() != id.getClass()) + return false; + ComponentVersionIdentification other = (ComponentVersionIdentification) id; + if (componentName == null) { + if (other.componentName != null) + return false; + } else if (!componentName.equals(other.componentName)) + return false; + if (familyName == null) { + if (other.familyName != null) + return false; + } else if (!familyName.equals(other.familyName)) + return false; + if (registryBase == null) { + if (other.registryBase != null) + return false; + } else if (!registryBase.toString().equals(other.registryBase.toString())) + return false; + return true; + } + + @Override + public boolean mostlyEqualTo(io.github.taverna_extras.component.api.Component c) { + return mostlyEqualTo(new ComponentVersionIdentification(c.getRegistry(), c.getFamily(), c, 0)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/local/LocalComponent.java ---------------------------------------------------------------------- diff --git a/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/local/LocalComponent.java b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/local/LocalComponent.java new file mode 100644 index 0000000..697f57f --- /dev/null +++ b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/local/LocalComponent.java @@ -0,0 +1,149 @@ +package io.github.taverna_extras.component.registry.local; +/* + * 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.io.FileUtils.readFileToString; +import static org.apache.commons.io.FileUtils.writeStringToFile; +import static org.apache.log4j.Logger.getLogger; +import static io.github.taverna_extras.component.registry.local.LocalComponentRegistry.ENC; + +import java.io.File; +import java.io.IOException; +import java.util.NoSuchElementException; + +import org.apache.log4j.Logger; +import io.github.taverna_extras.component.api.ComponentException; +import io.github.taverna_extras.component.api.Family; +import io.github.taverna_extras.component.api.Registry; +import io.github.taverna_extras.component.api.Version; +import io.github.taverna_extras.component.registry.Component; +import io.github.taverna_extras.component.utils.SystemUtils; + +import org.apache.taverna.scufl2.api.container.WorkflowBundle; + +/** + * @author alanrw + * + */ +class LocalComponent extends Component { + static final String COMPONENT_FILENAME = "dataflow.t2flow"; + private final File componentDir; + private final LocalComponentRegistry registry; + private final LocalComponentFamily family; + private static Logger logger = getLogger(LocalComponent.class); + private SystemUtils system; + + public LocalComponent(File componentDir, LocalComponentRegistry registry, + LocalComponentFamily family, SystemUtils system) { + super(componentDir); + this.system = system; + this.componentDir = componentDir; + this.registry = registry; + this.family = family; + } + + @Override + protected final Version internalAddVersionBasedOn(WorkflowBundle bundle, + String revisionComment) throws ComponentException { + Integer nextVersionNumber = 1; + try { + nextVersionNumber = getComponentVersionMap().lastKey() + 1; + } catch (NoSuchElementException e) { + // This is OK + } + File newVersionDir = new File(componentDir, + nextVersionNumber.toString()); + newVersionDir.mkdirs(); + LocalComponentVersion newComponentVersion = new LocalComponentVersion( + this, newVersionDir, system); + try { + system.saveBundle(bundle, new File(newVersionDir, + COMPONENT_FILENAME)); + } catch (Exception e) { + throw new ComponentException("Unable to save component version", e); + } + File revisionCommentFile = new File(newVersionDir, "description"); + try { + writeStringToFile(revisionCommentFile, revisionComment, ENC); + } catch (IOException e) { + throw new ComponentException("Could not write out description", e); + } + + return newComponentVersion; + } + + @Override + protected final String internalGetName() { + return componentDir.getName(); + } + + @Override + protected final void populateComponentVersionMap() { + for (File subFile : componentDir.listFiles()) + try { + if (subFile.isDirectory()) + versionMap.put(Integer.valueOf(subFile.getName()), + new LocalComponentVersion(this, subFile, system)); + } catch (NumberFormatException e) { + // Ignore + } + } + + @Override + public int hashCode() { + return 31 + ((componentDir == null) ? 0 : componentDir.hashCode()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + LocalComponent other = (LocalComponent) obj; + if (componentDir == null) + return (other.componentDir == null); + return componentDir.equals(other.componentDir); + } + + @Override + protected final String internalGetDescription() { + File descriptionFile = new File(componentDir, "description"); + try { + if (descriptionFile.isFile()) + return readFileToString(descriptionFile); + } catch (IOException e) { + logger.error("failed to get description from " + descriptionFile, e); + } + return ""; + } + + @Override + public Registry getRegistry() { + return registry; + } + + @Override + public Family getFamily() { + return family; + } +} http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/local/LocalComponentFamily.java ---------------------------------------------------------------------- diff --git a/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/local/LocalComponentFamily.java b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/local/LocalComponentFamily.java new file mode 100644 index 0000000..b9a692c --- /dev/null +++ b/taverna-component-activity/src/main/java/io/github/taverna_extras/component/registry/local/LocalComponentFamily.java @@ -0,0 +1,155 @@ +package io.github.taverna_extras.component.registry.local; +/* + * 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.io.FileUtils.deleteDirectory; +import static org.apache.commons.io.FileUtils.readFileToString; +import static org.apache.commons.io.FileUtils.writeStringToFile; +import static org.apache.log4j.Logger.getLogger; +import static io.github.taverna_extras.component.registry.local.LocalComponentRegistry.ENC; + +import java.io.File; +import java.io.IOException; + +import org.apache.log4j.Logger; +import io.github.taverna_extras.component.api.Component; +import io.github.taverna_extras.component.api.ComponentException; +import io.github.taverna_extras.component.api.Version; +import io.github.taverna_extras.component.api.profile.Profile; +import io.github.taverna_extras.component.registry.ComponentFamily; +import io.github.taverna_extras.component.registry.ComponentUtil; +import io.github.taverna_extras.component.utils.SystemUtils; + +import org.apache.taverna.scufl2.api.container.WorkflowBundle; + +/** + * @author alanrw + * + */ +class LocalComponentFamily extends ComponentFamily { + private static Logger logger = getLogger(LocalComponentFamily.class); + private static final String PROFILE = "profile"; + + private final File componentFamilyDir; + private SystemUtils system; + + public LocalComponentFamily(LocalComponentRegistry parentRegistry, + File componentFamilyDir, ComponentUtil util, SystemUtils system) { + super(parentRegistry, util); + this.componentFamilyDir = componentFamilyDir; + this.system = system; + } + + @Override + protected final Profile internalGetComponentProfile() + throws ComponentException { + LocalComponentRegistry parentRegistry = (LocalComponentRegistry) getComponentRegistry(); + File profileFile = new File(componentFamilyDir, PROFILE); + String profileName; + try { + profileName = readFileToString(profileFile, ENC); + } catch (IOException e) { + throw new ComponentException("Unable to read profile name", e); + } + for (Profile p : parentRegistry.getComponentProfiles()) + if (p.getName().equals(profileName)) + return p; + return null; + } + + @Override + protected void populateComponentCache() throws ComponentException { + for (File subFile : componentFamilyDir.listFiles()) { + if (!subFile.isDirectory()) + continue; + LocalComponent newComponent = new LocalComponent(subFile, + (LocalComponentRegistry) getComponentRegistry(), this, + system); + componentCache.put(newComponent.getName(), newComponent); + } + } + + @Override + protected final String internalGetName() { + return componentFamilyDir.getName(); + } + + @Override + protected final Version internalCreateComponentBasedOn( + String componentName, String description, WorkflowBundle bundle) + throws ComponentException { + File newSubFile = new File(componentFamilyDir, componentName); + if (newSubFile.exists()) + throw new ComponentException("Component already exists"); + newSubFile.mkdirs(); + File descriptionFile = new File(newSubFile, "description"); + try { + writeStringToFile(descriptionFile, description, ENC); + } catch (IOException e) { + throw new ComponentException("Could not write out description", e); + } + LocalComponent newComponent = new LocalComponent(newSubFile, + (LocalComponentRegistry) getComponentRegistry(), this, system); + + return newComponent.addVersionBasedOn(bundle, "Initial version"); + } + + @Override + public int hashCode() { + return 31 + ((componentFamilyDir == null) ? 0 : componentFamilyDir + .hashCode()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + LocalComponentFamily other = (LocalComponentFamily) obj; + if (componentFamilyDir == null) + return (other.componentFamilyDir == null); + return componentFamilyDir.equals(other.componentFamilyDir); + } + + @Override + protected final String internalGetDescription() { + File descriptionFile = new File(componentFamilyDir, "description"); + try { + if (descriptionFile.isFile()) + return readFileToString(descriptionFile); + } catch (IOException e) { + logger.error("failed to get description from " + descriptionFile, e); + } + return ""; + } + + @Override + protected final void internalRemoveComponent(Component component) + throws ComponentException { + File componentDir = new File(componentFamilyDir, component.getName()); + try { + deleteDirectory(componentDir); + } catch (IOException e) { + throw new ComponentException("Unable to delete component", e); + } + } +}
