org.apache.taverna.activities.dependencyactivity.AbstractAsynchronousDependencyActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.taverna.activities.dependencyactivity.AbstractAsynchronousDependencyActivity.java

Source

/*
* 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.
*/

package org.apache.taverna.activities.dependencyactivity;

import java.io.File;
import java.io.FilenameFilter;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashSet;
import java.util.WeakHashMap;

import org.apache.taverna.facade.WorkflowInstanceFacade;
import org.apache.taverna.workflowmodel.Dataflow;
import org.apache.taverna.workflowmodel.Processor;
import org.apache.taverna.workflowmodel.processor.activity.AbstractAsynchronousActivity;
import org.apache.taverna.workflowmodel.processor.activity.Activity;
import org.apache.taverna.workflowmodel.processor.activity.NestedDataflow;

import org.apache.log4j.Logger;

import org.apache.taverna.configuration.app.ApplicationConfiguration;

import com.fasterxml.jackson.databind.JsonNode;

/**
 * A parent abstract class for activities that require dependency management, such as
 * API Consumer and Beanshell. Defines dependencies on local JAR files
 * and Raven artifacts.
 *
 * @author Alex Nenadic
 * @author Tom Oinn
 * @author Stian Soiland-Reyes
 */
public abstract class AbstractAsynchronousDependencyActivity extends AbstractAsynchronousActivity<JsonNode> {

    private static final String LOCAL_JARS = "Local jars";

    private static Logger logger = Logger.getLogger(AbstractAsynchronousDependencyActivity.class);

    /**
     * For persisting class loaders across a whole workflow run (when classloader sharing
     * is set to 'workflow'). The key in the map is the workflow run ID and we are using
     * a WeakHashMap so we don't keep up references to classloaders of old workflow runs.
     */
    private static WeakHashMap<String, ClassLoader> workflowClassLoaders = new WeakHashMap<String, ClassLoader>();

    /**
     * System classloader, in case when classloader sharing is set to 'system'.
     */
    private static ClassLoader systemClassLoader = null;

    /**
     * Classloader to be used for 'executing' this activity, depending on the activity's
     * class loader sharing policy.
     */
    protected ClassLoader classLoader = null;

    /**
     * The location of the <code>lib</code> directory in TAVERNA_HOME,
     * where local JAR files the activity depends on should be located.
     */
    public File libDir;

    /**
     * Different ways to share a class loader among activities:
     *
     * <dl>
     * <dt>workflow</dt>
     * <dd>Same classloader for all activities using the <code>workflow</code> classloader sharing policy</dd>
     * <dt>system</dt>
     * <dd>System classloader</dd>
     * </dl>
     *
     */
    public static enum ClassLoaderSharing {
        workflow, system;
        public static final ClassLoaderSharing DEFAULT = workflow;

        public static ClassLoaderSharing fromString(String str) {
            if (str == null || str.isEmpty()) {
                return DEFAULT;
            }
            return valueOf(str.toLowerCase());
        }
    }

    public AbstractAsynchronousDependencyActivity(ApplicationConfiguration applicationConfiguration) {
        if (applicationConfiguration != null) {
            libDir = applicationConfiguration.getApplicationHomeDir().resolve("lib").toFile();
        }
    }

    /**
     * Finds or constructs the classloader. The classloader depends on the
     * current classloader sharing policy as defined by {@link #ClassLoaderSharing}.
     * <p>
     * If the classloader sharing is {@link ClassLoaderSharing#workflow}, a
     * common classloader will be used for the whole workflow for all activities
     * with the same (i.e. {@link ClassLoaderSharing#workflow}) policy.
     * The dependencies will be constructed as union of local and artifact dependencies
     * of all 'workflow' classloader sharing activities at the point of the first
     * call to {@link #getClassLoader()}.
      * <p>
     * If the classloader sharing is {@link ClassLoaderSharing#system}, the
     * system classloader will be used. Note that both local and artifact dependencies
     * configured on the activity will be ignored. Local dependencies can be set by
     * using <code>-classpath</code> when starting the workbench.
     * This is useful in combination with JNI based libraries, which would also
     * require <code>-Djava.library.path</code> and possibly the operating
     * system's PATH / LD_LIBRARY_PATH / DYLD_LIBRARY_PATH environment variable.
     * @param classLoaderSharing
     *
     * @return A new or existing {@link ClassLoader} according to the
     *         classloader sharing policy
     */
    protected ClassLoader findClassLoader(JsonNode json, String workflowRunID) throws RuntimeException {
        ClassLoaderSharing classLoaderSharing;
        if (json.has("classLoaderSharing")) {
            classLoaderSharing = ClassLoaderSharing.fromString(json.get("classLoaderSharing").textValue());
        } else {
            classLoaderSharing = ClassLoaderSharing.workflow;
        }

        if (classLoaderSharing == ClassLoaderSharing.workflow) {
            synchronized (workflowClassLoaders) {
                ClassLoader cl = workflowClassLoaders.get(workflowRunID);
                if (cl == null) {
                    cl = makeClassLoader(json, workflowRunID);
                    workflowClassLoaders.put(workflowRunID, cl);
                }
                return cl;
            }
        }
        if (classLoaderSharing == ClassLoaderSharing.system) {
            //         if (systemClassLoader == null)
            //            systemClassLoader = PreLauncher.getInstance().getLaunchingClassLoader();

            //         if (systemClassLoader instanceof BootstrapClassLoader){
            //            // Add local and artifact dependencies to the classloader
            //            updateBootstrapClassLoader(
            //                  (BootstrapClassLoader) systemClassLoader,
            //                  configurationBean, workflowRunID);
            //            return systemClassLoader;
            //         }
            //         else{
            // Local dependencies will have to be set with the -classpath option
            // We cannot have artifact dependencies in this case
            String message = "System classloader is not Taverna's BootstrapClassLoader, so local dependencies "
                    + "have to defined with -classpath. Artifact dependencies are ignored completely.";
            logger.warn(message);
            return systemClassLoader;
            //         }
        }
        String message = "Unknown classloader sharing policy named '" + classLoaderSharing + "' for "
                + this.getClass();
        logger.error(message);
        throw new RuntimeException(message);
    }

    /**
     * Constructs a classloader capable of finding both local jar and artifact dependencies.
     * Called when classloader sharing policy is set to 'workflow'.
     *
     * @return A {@link ClassLoader} capable of accessing all the dependencies (both local jar and artifact)
     */
    private ClassLoader makeClassLoader(JsonNode json, String workflowID) {
        // Find all artifact dependencies
        //      HashSet<URL> urls = findDependencies(ARTIFACTS, configurationBean, workflowID);

        // Add all local jar dependencies
        HashSet<URL> urls = findDependencies(LOCAL_JARS, json, workflowID);

        // Create the classloader capable of loading both local jar and artifact dependencies
        ClassLoader parent = this.getClass().getClassLoader(); // this will be a LocalArtifactClassLoader

        return new URLClassLoader(urls.toArray(new URL[0]), parent) {

            // For finding native libraries that have to be stored in TAVERNA_HOME/lib
            @Override
            protected String findLibrary(String libname) {
                String filename = System.mapLibraryName(libname);
                File libraryFile = new File(libDir, filename);
                if (libraryFile.isFile()) {
                    logger.info("Found library " + libname + ": " + libraryFile.getAbsolutePath());
                    return libraryFile.getAbsolutePath();
                }
                return super.findLibrary(libname);
            }
        };
    }

    /**
     * Adds local or artifact dependencies identified by {@link #findDependencies()} to the
     * {@link BootstrapClassLoader} system classloader.
     * Called when classloader sharing policy is set to 'system'.
     *
     * @param loader The augmented BootstrapClassLoader system classloader
     */
    //   private void updateBootstrapClassLoader(BootstrapClassLoader loader,
    //         DependencyActivityConfigurationBean configurationBean,
    //         String workflowRunID) {
    //
    //      HashSet<URL> depsURLs = new HashSet<URL>();
    //      depsURLs.addAll(findDependencies(LOCAL_JARS, configurationBean, workflowRunID));
    //      depsURLs.addAll(findDependencies(ARTIFACTS, configurationBean, workflowRunID));
    //
    //      Set<URL> exists = new HashSet<URL>(Arrays.asList(loader.getURLs()));
    //      for (URL url : depsURLs) {
    //         if (exists.contains(url)) {
    //            continue;
    //         }
    //         logger.info("Registering with system classloader: " + url);
    //         loader.addURL(url);
    //         exists.add(url);
    //      }
    //   }

    /**
     * Finds either local jar or artifact dependencies' URLs for the given classloader
     * sharing policy (passed inside configuration bean) and a workflowRunID (used to
     * retrieve the workflow) that will be added to this activity classloader's list of URLs.
     */
    private HashSet<URL> findDependencies(String dependencyType, JsonNode json, String workflowRunID) {
        ClassLoaderSharing classLoaderSharing;
        if (json.has("classLoaderSharing")) {
            classLoaderSharing = ClassLoaderSharing.fromString(json.get("classLoaderSharing").textValue());
        } else {
            classLoaderSharing = ClassLoaderSharing.workflow;
        }
        // Get the WorkflowInstanceFacade which contains the current workflow
        WeakReference<WorkflowInstanceFacade> wfFacadeRef = WorkflowInstanceFacade.workflowRunFacades
                .get(workflowRunID);
        WorkflowInstanceFacade wfFacade = null;
        if (wfFacadeRef != null) {
            wfFacade = wfFacadeRef.get();
        }
        Dataflow wf = null;
        if (wfFacade != null) {
            wf = wfFacade.getDataflow();
        }

        // Files of dependencies for all activities in the workflow that share the classloading policy
        HashSet<File> dependencies = new HashSet<File>();
        // Urls of all dependencies
        HashSet<URL> dependenciesURLs = new HashSet<URL>();

        if (wf != null) {
            // Merge in dependencies from all activities that have the same classloader-sharing
            // as this activity
            for (Processor proc : wf.getProcessors()) {
                // Nested workflow case
                if (!proc.getActivityList().isEmpty() && proc.getActivityList().get(0) instanceof NestedDataflow) {
                    // Get the nested workflow
                    Dataflow nestedWorkflow = ((NestedDataflow) proc.getActivityList().get(0)).getNestedDataflow();
                    dependenciesURLs.addAll(findNestedDependencies(dependencyType, json, nestedWorkflow));
                } else { // Not nested - go through all of the processor's activities
                    Activity<?> activity = proc.getActivityList().get(0);
                    if (activity instanceof AbstractAsynchronousDependencyActivity) {
                        AbstractAsynchronousDependencyActivity dependencyActivity = (AbstractAsynchronousDependencyActivity) activity;
                        //                     if (dependencyType.equals(LOCAL_JARS)){
                        // Collect the files of all found local dependencies
                        if (dependencyActivity.getConfiguration().has("localDependency")) {
                            for (JsonNode jar : dependencyActivity.getConfiguration().get("localDependency")) {
                                try {
                                    dependencies.add(new File(libDir, jar.textValue()));
                                } catch (Exception ex) {
                                    logger.warn("Invalid URL for " + jar, ex);
                                    continue;
                                }
                            }
                        }
                        //                     } else if (dependencyType.equals(ARTIFACTS) && this.getClass().getClassLoader() instanceof LocalArtifactClassLoader){
                        //                        LocalArtifactClassLoader cl = (LocalArtifactClassLoader) this.getClass().getClassLoader(); // this class is always loaded with LocalArtifactClassLoader
                        //                        // Get the LocalReposotpry capable of finding artifact jar files
                        //                        LocalRepository rep  = (LocalRepository) cl.getRepository();
                        //                        for (BasicArtifact art : ((DependencyActivityConfigurationBean) activity
                        //                                    .getConfiguration())
                        //                                    .getArtifactDependencies()){
                        //                           dependencies.add(rep.jarFile(art));
                        //                        }
                        //                     }
                    }
                }
            }
        } else { // Just add dependencies for this activity since we can't get hold of the whole workflow
            //         if (dependencyType.equals(LOCAL_JARS)){
            if (json.has("localDependency")) {
                for (JsonNode jar : json.get("localDependency")) {
                    try {
                        dependencies.add(new File(libDir, jar.textValue()));
                    } catch (Exception ex) {
                        logger.warn("Invalid URL for " + jar, ex);
                        continue;
                    }
                }
            }
            //         }
            //         else if (dependencyType.equals(ARTIFACTS)){
            //            if (this.getClass().getClassLoader() instanceof LocalArtifactClassLoader){ // This should normally be the case
            //               LocalArtifactClassLoader cl = (LocalArtifactClassLoader)this.getClass().getClassLoader();
            //               LocalRepository rep  = (LocalRepository)cl.getRepository();
            //               if (rep != null){
            //                  for (BasicArtifact art : configurationBean.getArtifactDependencies()){
            //                     dependencies.add(rep.jarFile(art));
            //                  }
            //               }
            //            }
            //            else{
            //               // Tests will not be loaded using the LocalArtifactClassLoader as athey are loaded
            //               // outside Raven so there is nothing we can do about this - some tests
            //               // with dependencies will probably fail
            //            }
            //         }
        }

        // Collect the URLs of all found dependencies
        for (File file : dependencies) {
            try {
                dependenciesURLs.add(file.toURI().toURL());
            } catch (Exception ex) {
                logger.warn("Invalid URL for " + file.getAbsolutePath(), ex);
                continue;
            }
        }
        return dependenciesURLs;
    }

    /**
     * Finds dependencies for a nested workflow.
     */
    private HashSet<URL> findNestedDependencies(String dependencyType, JsonNode json, Dataflow nestedWorkflow) {
        ClassLoaderSharing classLoaderSharing;
        if (json.has("classLoaderSharing")) {
            classLoaderSharing = ClassLoaderSharing.fromString(json.get("classLoaderSharing").textValue());
        } else {
            classLoaderSharing = ClassLoaderSharing.workflow;
        }

        // Files of dependencies for all activities in the nested workflow that share the classloading policy
        HashSet<File> dependencies = new HashSet<File>();
        // Urls of all dependencies
        HashSet<URL> dependenciesURLs = new HashSet<URL>();

        for (Processor proc : nestedWorkflow.getProcessors()) {
            // Another nested workflow
            if (!proc.getActivityList().isEmpty() && proc.getActivityList().get(0) instanceof NestedDataflow) {
                // Get the nested workflow
                Dataflow nestedNestedWorkflow = ((NestedDataflow) proc.getActivityList().get(0))
                        .getNestedDataflow();
                dependenciesURLs.addAll(findNestedDependencies(dependencyType, json, nestedNestedWorkflow));
            } else { // Not nested - go through all of the processor's activities
                Activity<?> activity = proc.getActivityList().get(0);
                if (activity instanceof AbstractAsynchronousDependencyActivity) {
                    AbstractAsynchronousDependencyActivity dependencyActivity = (AbstractAsynchronousDependencyActivity) activity;
                    //                  if (dependencyType.equals(LOCAL_JARS)){
                    // Collect the files of all found local dependencies
                    if (dependencyActivity.getConfiguration().has("localDependency")) {
                        for (JsonNode jar : dependencyActivity.getConfiguration().get("localDependency")) {
                            try {
                                dependencies.add(new File(libDir, jar.textValue()));
                            } catch (Exception ex) {
                                logger.warn("Invalid URL for " + jar, ex);
                                continue;
                            }
                        }
                    }
                    //                  } else if (dependencyType.equals(ARTIFACTS) && this.getClass().getClassLoader() instanceof LocalArtifactClassLoader){
                    //                     LocalArtifactClassLoader cl = (LocalArtifactClassLoader) this.getClass().getClassLoader(); // this class is always loaded with LocalArtifactClassLoader
                    //                     LocalRepository rep  = (LocalRepository) cl.getRepository();
                    //                     for (BasicArtifact art : ((DependencyActivityConfigurationBean) activity
                    //                                 .getConfiguration())
                    //                                 .getArtifactDependencies()){
                    //                        dependencies.add(rep.jarFile(art));
                    //                     }
                    //                  }
                }
            }
        }

        // Collect the URLs of all found dependencies
        for (File file : dependencies) {
            try {
                dependenciesURLs.add(file.toURI().toURL());
            } catch (Exception ex) {
                logger.warn("Invalid URL for " + file.getAbsolutePath(), ex);
                continue;
            }
        }
        return dependenciesURLs;
    }

    /**
     * File filter.
     */
    public static class FileExtFilter implements FilenameFilter {

        String ext = null;

        public FileExtFilter(String ext) {
            this.ext = ext;
        }

        public boolean accept(File dir, String name) {
            return name.endsWith(ext);
        }
    }

    /**
     * @param classLoader the classLoader to set
     */
    public void setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    /**
     * @return the classLoader
     */
    public ClassLoader getClassLoader() {
        return classLoader;
    }
}