org.apache.taverna.component.profile.ComponentProfileImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.taverna.component.profile.ComponentProfileImpl.java

Source

/*******************************************************************************
 * Copyright (C) 2012 The University of Manchester
 *
 *  Modifications to the initial code base are copyright of their
 *  respective authors, or their employers as appropriate.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2.1 of
 *  the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 ******************************************************************************/
package org.apache.taverna.component.profile;

import static com.hp.hpl.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 net.sf.taverna.t2.workflowmodel.health.HealthCheck.NO_PROBLEM;
import static net.sf.taverna.t2.workflowmodel.health.RemoteHealthChecker.contactEndpoint;
import static org.slf4j.LoggerFactory.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.taverna.component.api.ComponentException;
import org.apache.taverna.component.api.Registry;
import org.apache.taverna.component.api.profile.ActivityProfile;
import org.apache.taverna.component.api.profile.ExceptionHandling;
import org.apache.taverna.component.api.profile.PortProfile;
import org.apache.taverna.component.api.profile.SemanticAnnotationProfile;
import org.apache.taverna.component.api.profile.doc.Activity;
import org.apache.taverna.component.api.profile.doc.Ontology;
import org.apache.taverna.component.api.profile.doc.Port;
import org.apache.taverna.component.api.profile.doc.Profile;
import org.apache.taverna.component.api.profile.doc.SemanticAnnotation;
import org.slf4j.Logger;

import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntProperty;

/**
 * A ComponentProfile specifies the inputs, outputs and semantic annotations
 * that a Component must contain.
 * 
 * @author David Withers
 */
public class ComponentProfileImpl implements org.apache.taverna.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 org.apache.taverna.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 org.apache.taverna.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 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<org.apache.taverna.component.api.profile.ActivityProfile> getActivityProfiles() {
        List<org.apache.taverna.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 org.apache.taverna.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<org.apache.taverna.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.");
    }
}