/* # Licensed Materials - Property of IBM # Copyright IBM Corp. 2015 */ package com.ibm.streamsx.topology.internal.core; import java.io.File; import java.io.IOException; 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.ibm.json.java.JSONArray; import com.ibm.json.java.JSONObject; 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.functional.ops.Functional; 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<>(); private final Set<Path> globalDependencies = new HashSet<>(); 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()); } globalDependencies.add(f.toPath().toAbsolutePath()); } 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) { e.printStackTrace(); } globalDependencies.add(absolutePath); } 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) { e.printStackTrace(); } if (operatorToJarDependencies.containsKey(op)) { operatorToJarDependencies.get(op).add(absolutePath); } else { operatorToJarDependencies.put(op, new HashSet<Path>()); operatorToJarDependencies.get(op).add(absolutePath); } } /** * 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 = (JSONArray) graphConfig.get("includes"); if (includes == null) graphConfig.put("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, includes); jars.add("impl/lib/" + jarName); } String[] jarPaths = jars.toArray(new String[jars.size()]); op.setParameter("jar", jarPaths); } ArrayList<String> jars = new ArrayList<String>(); for(Path source : globalDependencies){ String jarName = resolveDependency(source, includes); jars.add("impl/lib/" + jarName); } List<BOperator> ops = topology.builder().getOps(); if(jars.size() != 0){ for (BOperator op : ops) { if (op instanceof BOperatorInvocation) { BOperatorInvocation bop = (BOperatorInvocation) op; if (Functional.class.isAssignableFrom(bop.op() .getOperatorClass())) { JSONObject params = (JSONObject) bop.json().get( "parameters"); JSONObject op_jars = (JSONObject) params.get("jar"); if (null == op_jars) { JSONObject val = new JSONObject(); val.put("value", new JSONArray()); params.put("jar", val); op_jars = val; } JSONArray value = (JSONArray) op_jars.get("value"); for (String jar : jars) { value.add(jar); } } } } } for(Artifact dep : globalFileDependencies) resolveFileDependency(dep, includes); } private String resolveDependency(Path source, 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.put("source", source.toAbsolutePath().toString()); include.put("target", "impl/lib"); } 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.put("classes", source.toAbsolutePath().toString()); include.put("name", jarName); include.put("target", "impl/lib"); } 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.put("source", a.absPath.toString()); include.put("target", a.dstDirName); includes.add(include); } }