com.ibm.streamsx.topology.internal.core.DependencyResolver.java Source code

Java tutorial

Introduction

Here is the source code for com.ibm.streamsx.topology.internal.core.DependencyResolver.java

Source

/*
# Licensed Materials - Property of IBM
# Copyright IBM Corp. 2015  
 */
package com.ibm.streamsx.topology.internal.core;

import static com.ibm.streamsx.topology.internal.context.remote.ToolkitRemoteContext.DEP_JAR_LOC;
import static com.ibm.streamsx.topology.internal.context.remote.ToolkitRemoteContext.DEP_OP_JAR_LOC;
import static com.ibm.streamsx.topology.internal.gson.GsonUtilities.array;
import static com.ibm.streamsx.topology.internal.gson.GsonUtilities.arrayCreate;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.ibm.streamsx.topology.Topology;
import com.ibm.streamsx.topology.builder.BOperator;
import com.ibm.streamsx.topology.builder.BOperatorInvocation;
import com.ibm.streamsx.topology.internal.logic.WrapperFunction;

/**
 * The DependencyResolver class exists to separate the logic of jar
 * resolution/copying from the topology.
 * 
 */
public class DependencyResolver {

    private final Map<BOperatorInvocation, Set<Path>> operatorToJarDependencies = new HashMap<>();
    /**
     * Map of jar paths that are dependencies.
     * Value is a boolean indicating if it includes a primitive
     * operator that needs to be part of the generated toolkit.
     */
    private final Map<Path, Boolean> globalDependencies = new HashMap<>();
    private final Set<Artifact> globalFileDependencies = new HashSet<>();

    private static class Artifact {
        final String dstDirName;
        final Path absPath;

        Artifact(String dirName, Path absPath) {
            if (dirName == null || absPath == null)
                throw new IllegalArgumentException("dstDirName=" + dirName + " absPath=" + absPath);
            this.dstDirName = dirName;
            this.absPath = absPath;
        }

        @Override
        public boolean equals(Object o) {
            if (o == this)
                return true;
            if (!(o instanceof Artifact))
                return false;
            Artifact o2 = (Artifact) o;
            return dstDirName.equals(o2.dstDirName) && absPath.equals(o2.absPath);
        }

        @Override
        public int hashCode() {
            // no need to get fancy here
            return absPath.hashCode();
        }
    }

    /**
     * Ensure we don't copy files multiple times and keep
     * a map between a code source and the jar we generate.
     */
    private final Map<Path, String> previouslyCopiedDependencies = new HashMap<>();

    private Topology topology;

    public DependencyResolver(Topology parentTopology) {
        this.topology = parentTopology;
    }

    public void addJarDependency(String location) throws IllegalArgumentException {
        File f = new File(location);
        if (!f.exists()) {
            throw new IllegalArgumentException("File not found. Invalid " + "third party dependency location:"
                    + f.toPath().toAbsolutePath().toString());
        }

        addGlobalDependency(f.toPath().toAbsolutePath(), false);
    }

    private void addGlobalDependency(Path path, boolean containsOps) {

        if (globalDependencies.containsKey(path)) {
            boolean jarContainsOps = globalDependencies.get(path);
            if ((jarContainsOps == containsOps) && !containsOps)
                return;
        }
        globalDependencies.put(path, containsOps);
    }

    public void addClassDependency(Class<?> clazz) {

        CodeSource source = clazz.getProtectionDomain().getCodeSource();
        if (source == null)
            return;
        Path absolutePath = null;
        try {
            absolutePath = Paths.get(source.getLocation().toURI()).toAbsolutePath();
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }

        boolean containsOperator = false;
        try {
            @SuppressWarnings("unchecked")
            Class<? extends Annotation> primOpClass = (Class<? extends Annotation>) Class
                    .forName("com.ibm.streams.operator.model.PrimitiveOperator");
            containsOperator = clazz.isAnnotationPresent(primOpClass);
        } catch (ClassNotFoundException e) {
            containsOperator = false;
        }

        addGlobalDependency(absolutePath, containsOperator);
    }

    public void addJarDependency(BOperatorInvocation op, Object logic) {

        while (logic instanceof WrapperFunction) {
            addJarDependency(op, logic.getClass());
            logic = ((WrapperFunction) logic).getWrappedFunction();

        }
        addJarDependency(op, logic.getClass());
    }

    public void addJarDependency(BOperatorInvocation op, Class<?> clazz) {

        CodeSource thisCodeSource = this.getClass().getProtectionDomain().getCodeSource();

        CodeSource source = clazz.getProtectionDomain().getCodeSource();
        if (null == source || thisCodeSource.equals(source)) {
            return;
        }

        Path absolutePath = null;
        try {
            absolutePath = Paths.get(source.getLocation().toURI()).toAbsolutePath();
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }

        if (operatorToJarDependencies.containsKey(op)) {
            operatorToJarDependencies.get(op).add(absolutePath);
        } else {
            operatorToJarDependencies.put(op, new HashSet<Path>());
            operatorToJarDependencies.get(op).add(absolutePath);
        }
    }

    /**
     * Copy dependencies from one operator to another.
     */
    public void copyDependencies(BOperatorInvocation source, BOperatorInvocation op) {
        if (operatorToJarDependencies.containsKey(source)) {
            Set<Path> sourceDeps = operatorToJarDependencies.get(source);
            if (!operatorToJarDependencies.containsKey(op))
                operatorToJarDependencies.put(op, new HashSet<>());

            operatorToJarDependencies.get(op).addAll(sourceDeps);
        }
    }

    /**
     * Add a file dependency {@code location} to be 
     * added to directory {@code dstDirName} in the bundle.
     * @param location path to a file or directory
     * @param dstDirName name of directory in the bundle
     * @throws IllegalArgumentException if {@code dstDirName} is not {@code etc}
     *     or {@code opt}, or {@code location} is not a file or directory.
     */
    public void addFileDependency(String location, String dstDirName) throws IllegalArgumentException {

        if (dstDirName == null || !(dstDirName.equals("etc") || dstDirName.equals("opt")))
            throw new IllegalArgumentException("dstDirName=" + dstDirName);

        File f = new File(location);
        if (!f.exists() || (!f.isFile() && !f.isDirectory()))
            throw new IllegalArgumentException("Not a file or directory. Invalid " + "file dependency location:"
                    + f.toPath().toAbsolutePath().toString());

        globalFileDependencies.add(new Artifact(dstDirName, f.toPath().toAbsolutePath()));
    }

    /**
     * Resolve the dependencies.
     * Creates entries in the graph config that will
     * result in files being copied into the toolkit.
     */
    public void resolveDependencies() throws IOException, URISyntaxException {

        JsonObject graphConfig = topology.builder().getConfig();
        JsonArray includes = array(graphConfig, "includes");
        if (includes == null)
            graphConfig.add("includes", includes = new JsonArray());

        for (BOperatorInvocation op : operatorToJarDependencies.keySet()) {
            ArrayList<String> jars = new ArrayList<String>();

            for (Path source : operatorToJarDependencies.get(op)) {
                String jarName = resolveDependency(source, false, includes);
                jars.add(DEP_JAR_LOC + File.separator + jarName);
            }

            String[] jarPaths = jars.toArray(new String[jars.size()]);
            op.setParameter("jar", jarPaths);
        }

        ArrayList<String> jars = new ArrayList<String>();
        for (Path source : globalDependencies.keySet()) {
            boolean containsOperator = globalDependencies.get(source);
            String jarName = resolveDependency(source, containsOperator, includes);
            String location = depJarRoot(containsOperator);
            jars.add(location + File.separator + jarName);
        }

        List<BOperator> ops = topology.builder().getOps();
        if (jars.size() != 0) {
            for (BOperator op : ops) {
                if (JavaFunctionalOps.isFunctional(op)) {
                    JsonArray value = arrayCreate(op._json(), "parameters", "jar", "value");
                    for (String jar : jars) {
                        value.add(new JsonPrimitive(jar));
                    }
                }
            }
        }

        for (Artifact dep : globalFileDependencies)
            resolveFileDependency(dep, includes);
    }

    private static String depJarRoot(boolean containsOperator) {
        return containsOperator ? DEP_OP_JAR_LOC : DEP_JAR_LOC;
    }

    private String resolveDependency(Path source, boolean containsOperator, JsonArray includes) {

        String jarName;

        if (!previouslyCopiedDependencies.containsKey(source)) {

            File sourceFile = source.toFile();

            JsonObject include = new JsonObject();

            // If it's a file, we assume its a jar file.
            if (sourceFile.isFile()) {
                jarName = source.getFileName().toString();

                include.addProperty("source", source.toAbsolutePath().toString());
                include.addProperty("target", depJarRoot(containsOperator));
            }

            else if (sourceFile.isDirectory()) {
                // Create an entry that will convert the classes dir into a jar file
                jarName = "classes" + previouslyCopiedDependencies.size() + "_" + sourceFile.getName() + ".jar";
                include.addProperty("classes", source.toAbsolutePath().toString());
                include.addProperty("name", jarName);
                include.addProperty("target", DEP_JAR_LOC);
            }

            else {
                throw new IllegalArgumentException("Path not a file or directory:" + source);
            }
            includes.add(include);
            previouslyCopiedDependencies.put(source, jarName);
        }

        else {
            jarName = previouslyCopiedDependencies.get(source);
        }

        // Sanity check
        if (null == jarName) {
            throw new IllegalStateException("Error resolving dependency " + source);
        }
        return jarName;
    }

    /**
     * Copy the Artifact to the toolkit
     */
    private void resolveFileDependency(Artifact a, JsonArray includes) {
        JsonObject include = new JsonObject();
        include.addProperty("source", a.absPath.toString());
        include.addProperty("target", a.dstDirName);
        includes.add(include);
    }
}