/** * Copyright 2015-2016 Red Hat, Inc, and individual contributors. * * Licensed 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.wildfly.swarm.tools; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.security.DigestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.jar.JarFile; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import org.jboss.shrinkwrap.api.Node; import org.jboss.shrinkwrap.api.asset.Asset; import org.wildfly.swarm.bootstrap.env.FractionManifest; import org.wildfly.swarm.bootstrap.env.WildFlySwarmManifest; /** * @author Bob McWhirter * @author Ken Finnigan * @author Heiko Braun */ public class DependencyManager implements ResolvedDependencies { public DependencyManager(ArtifactResolver resolver) { this.resolver = resolver; } public void addAdditionalModule(Path module) { try { analyzeModuleDependencies(new ModuleAnalyzer(module)); } catch (IOException e) { throw new RuntimeException(e); } } @Override public Set<ArtifactSpec> getDependencies() { return this.dependencies; } @Override public ArtifactSpec findWildFlySwarmBootstrapJar() { return findArtifact(WILDFLY_SWARM_GROUP_ID, WILDFLY_SWARM_BOOTSTRAP_ARTIFACT_ID, null, JAR, null, false); } @Override public ArtifactSpec findJBossModulesJar() { return findArtifact(JBOSS_MODULES_GROUP_ID, JBOSS_MODULES_ARTIFACT_ID, null, JAR, null, false); } @Override public ArtifactSpec findArtifact(String groupId, String artifactId, String version, String packaging, String classifier) { return findArtifact(groupId, artifactId, version, packaging, classifier, true); } @Override public ArtifactSpec findArtifact(String groupId, String artifactId, String version, String packaging, String classifier, boolean includeTestScope) { for (ArtifactSpec each : this.dependencies) { if (groupId != null && !groupId.equals(each.groupId())) { continue; } if (artifactId != null && !artifactId.equals(each.artifactId())) { continue; } if (version != null && !version.equals(each.version())) { continue; } if (packaging != null && !packaging.equals(each.type())) { continue; } if (classifier != null && !classifier.equals(each.classifier())) { continue; } if (!includeTestScope && each.scope.equals("test")) { continue; } return each; } return null; } public ResolvedDependencies analyzeDependencies(boolean autodetect, DeclaredDependencies declaredDependencies) throws Exception { // resolve to local files resolveDependencies(declaredDependencies, autodetect); // sort out removals, modules, etc analyzeRemovableDependencies(declaredDependencies); analyzeFractionManifests(declaredDependencies); this.dependencies.stream() .filter(e -> !this.removableDependencies.contains(e)) .forEach(e -> { this.applicationManifest.addDependency(e.mavenGav()); }); analyzeModuleDependencies(declaredDependencies); return this; } /** * Resolve declared dependencies to local files, aka turning them into @{@link ResolvedDependencies) * * @param declaredDependencies * @throws Exception */ private void resolveDependencies(DeclaredDependencies declaredDependencies, boolean autodetect) throws Exception { this.dependencies.clear(); // resolve the explicit deps to local files // expand to transitive if these are not pre-solved boolean resolveExplicitsTransitively = !declaredDependencies.isPresolved() || autodetect; Collection<ArtifactSpec> resolvedExplicitDependencies = resolveExplicitsTransitively ? resolver.resolveAllArtifactsTransitively(declaredDependencies.getExplicitDependencies(), false) : resolver.resolveAllArtifactsNonTransitively(declaredDependencies.getExplicitDependencies()); this.dependencies.addAll(resolvedExplicitDependencies); // resolve transitives if not pre-computed (i.e. from maven/gradle plugin) if (declaredDependencies.getTransientDependencies().isEmpty()) { Collection<ArtifactSpec> inputSet = declaredDependencies.getExplicitDependencies(); Collection<ArtifactSpec> filtered = inputSet .stream() .filter(dep -> dep.type().equals(JAR)) // filter out composite types, like ear, war, etc .collect(Collectors.toList()); Collection<ArtifactSpec> resolvedTransientDependencies = resolver.resolveAllArtifactsTransitively( filtered, false ); this.dependencies.addAll(resolvedTransientDependencies); // add the remaining transitive ones that have not been filtered Collection<ArtifactSpec> remainder = new ArrayList<>(); inputSet.forEach(remainder::add); remainder.removeAll(resolvedTransientDependencies); this.dependencies.addAll( resolver.resolveAllArtifactsNonTransitively(remainder) ); } else { // if transitive deps are pre-computed, resolve them to local files if needed Collection<ArtifactSpec> inputSet = declaredDependencies.getTransientDependencies(); Collection<ArtifactSpec> filtered = inputSet .stream() .filter(dep -> dep.type().equals(JAR)) .collect(Collectors.toList()); Collection<ArtifactSpec> resolvedTransientDependencies = Collections.emptySet(); if (filtered.size() > 0) { resolvedTransientDependencies = resolver.resolveAllArtifactsNonTransitively(filtered); this.dependencies.addAll(resolvedTransientDependencies); } // add the remaining transitive ones that have not been filtered Collection<ArtifactSpec> remainder = new ArrayList<>(); inputSet.forEach(remainder::add); remainder.removeAll(resolvedTransientDependencies); this.dependencies.addAll( resolver.resolveAllArtifactsNonTransitively(remainder) ); } } private void analyzeModuleDependencies(DeclaredDependencies declaredDependencies) { this.dependencies.stream() .filter(e -> e.type().equals(JAR)) .map(e -> e.file) .flatMap(ResolvedDependencies::findModuleXmls) .forEach(this::analyzeModuleDependencies); } private void analyzeModuleDependencies(ModuleAnalyzer analyzer) { this.moduleDependencies.addAll(analyzer.getDependencies()); } /** * Removable are basically all dependencies that are brought in by fractions. */ private void analyzeRemovableDependencies(DeclaredDependencies declaredDependencies) throws Exception { Collection<ArtifactSpec> bootstrapDeps = this.dependencies.stream() .filter(e -> isFractionJar(e.file)) .collect(Collectors.toSet()); List<ArtifactSpec> nonBootstrapDeps = new ArrayList<>(); nonBootstrapDeps.addAll(declaredDependencies.getExplicitDependencies()); nonBootstrapDeps.removeAll(bootstrapDeps); // re-resolve the application's dependencies minus any of our swarm dependencies // [hb] TODO this can be improved to use the previous results if the data-structure allows to reason about the parent of transitive deps Collection<ArtifactSpec> nonBootstrapTransitive = resolver.resolveAllArtifactsTransitively(nonBootstrapDeps, true); // do not remove .war or .rar or anything else weird-o like. Set<ArtifactSpec> justJars = this.dependencies .stream() .filter(e -> e.type().equals(JAR)) .collect(Collectors.toSet()); this.removableDependencies.addAll(justJars); this.removableDependencies.removeAll(nonBootstrapTransitive); } private void analyzeFractionManifests(DeclaredDependencies declaredDependencies) { this.dependencies.stream() .map(e -> fractionManifest(e.file)) .filter(e -> e != null) .forEach((manifest) -> { String module = manifest.getModule(); if (module != null) { this.applicationManifest.addBootstrapModule(module); } }); this.dependencies.stream() .filter(e -> isFractionJar(e.file) || isConfigApiModulesJar(e.file)) .forEach((artifact) -> { this.applicationManifest.addBootstrapArtifact(artifact.mavenGav()); }); } Set<ArtifactSpec> getRemovableDependencies() { return this.removableDependencies; } @Override public boolean isRemovable(Node node) { Asset asset = node.getAsset(); if (asset == null) { return false; } String path = node.getPath().get(); try (final InputStream inputStream = asset.openStream()) { byte[] checksum = checksum(inputStream); return this.removableDependencies.stream() .filter(e -> path.endsWith(e.artifactId() + "-" + e.version() + ".jar")) .map(e -> { try (final FileInputStream in = new FileInputStream(e.file)) { return checksum(in); } catch (IOException | NoSuchAlgorithmException | DigestException e1) { return null; } }) .filter(e -> e != null) .anyMatch(e -> { return Arrays.equals(e, checksum); }); } catch (NoSuchAlgorithmException | IOException | DigestException e) { e.printStackTrace(); } return false; } protected byte[] checksum(InputStream in) throws IOException, NoSuchAlgorithmException, DigestException { byte[] buf = new byte[1024]; int len = 0; MessageDigest md = MessageDigest.getInstance("SHA1"); while ((len = in.read(buf)) >= 0) { md.update(buf, 0, len); } return md.digest(); } protected boolean isConfigApiModulesJar(File file) { if (file == null) { return false; } try (JarFile jar = new JarFile(file)) { return jar.getEntry("wildfly-swarm-modules.conf") != null; } catch (IOException e) { // ignore } return false; } public static boolean isFractionJar(File file) { if (file == null) { return false; } try (JarFile jar = new JarFile(file)) { return jar.getEntry(FractionManifest.CLASSPATH_LOCATION) != null; } catch (IOException e) { // ignore } return false; } protected FractionManifest fractionManifest(File file) { try (JarFile jar = new JarFile(file)) { ZipEntry entry = jar.getEntry(FractionManifest.CLASSPATH_LOCATION); if (entry != null) { try (InputStream in = jar.getInputStream(entry)) { return new FractionManifest(in); } } } catch (IOException e) { // ignore } return null; } void setProjectAsset(ProjectAsset projectAsset) { if (!this.applicationManifest.isHollow()) { this.projectAsset = projectAsset; this.applicationManifest.setAsset(this.projectAsset.getName()); } } protected WildFlySwarmManifest getWildFlySwarmManifest() { return this.applicationManifest; } @Override public Set<ArtifactSpec> getModuleDependencies() { return moduleDependencies; } private static final String JAR = "jar"; private final WildFlySwarmManifest applicationManifest = new WildFlySwarmManifest(); private final Set<ArtifactSpec> dependencies = new HashSet<>(); private final Set<ArtifactSpec> removableDependencies = new HashSet<>(); private final Set<ArtifactSpec> moduleDependencies = new HashSet<>(); private ProjectAsset projectAsset; private ArtifactResolver resolver; }