package org.wildfly.swarm.bootstrap.env; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.jboss.modules.Module; import org.jboss.modules.ModuleIdentifier; import org.jboss.modules.ModuleLoadException; import org.jboss.modules.maven.ArtifactCoordinates; import org.wildfly.swarm.bootstrap.modules.MavenResolvers; import org.wildfly.swarm.bootstrap.performance.Performance; import org.wildfly.swarm.bootstrap.util.BootstrapProperties; import org.wildfly.swarm.bootstrap.util.MavenArtifactDescriptor; import org.yaml.snakeyaml.Yaml; /** * Entry-point to runtime environment. * * <p>This class uses the <code>fraction-manifest.yaml</code> from each fraction, * along with the container-wide <code>wildfly-swarm-manifest.yaml</code> if executing * in an uberjar mode in order to determine information such as:</p> * * <ul> * <li>uberjar vs non-uberjar execution mode</li> * <li>removable dependencies</li> * <li>bootstrap swarm artifacts</li> * <li>bootstrap swarm modules</li> * <li>all installed fractions</li> * </ul> * * @author Bob McWhirter */ public class ApplicationEnvironment { /** * Fetch the ApplicationEnvironment * * @return The environment. */ public static ApplicationEnvironment get() { return INSTANCE.updateAndGet((env) -> { if (env != null) { return env; } try (AutoCloseable handle = Performance.time("Load application environment")) { return new ApplicationEnvironment(); } catch (Exception e) { throw new RuntimeException(e); } }); } /** * Do not construct directly. */ private ApplicationEnvironment() { try { if (System.getProperty(BootstrapProperties.IS_UBERJAR) != null) { this.mode = Mode.UBERJAR; if (!loadWildFlySwarmApplicationManifestFromClasspath()) { loadWildFlySwarmApplicationManifestFromTCCL(); } } else { this.mode = Mode.CLASSPATH; loadDependencyTree(); loadFractionManifestsFromClasspath(); } } catch (IOException e) { throw new RuntimeException(e); } } private void loadDependencyTree() { final String cpInfoProp = System.getProperty("swarm.cp.info"); if (cpInfoProp == null) { return; } final DependencyTree<MavenArtifactDescriptor> dependencyTree = new DependencyTree<>(); final Yaml yaml = new Yaml(); try (final FileInputStream fileStream = new FileInputStream(cpInfoProp)) { @SuppressWarnings("unchecked") final Map<String, Collection<String>> data = yaml.loadAs(fileStream, Map.class); for (final Entry<String, Collection<String>> entry : data.entrySet()) { final MavenArtifactDescriptor parent = MavenArtifactDescriptor.fromMavenGav(entry.getKey()); final Collection<String> transientDeps = entry.getValue(); if (transientDeps != null && !transientDeps.isEmpty()) { for (final String transientDep : transientDeps) { dependencyTree.add( parent, MavenArtifactDescriptor.fromMavenGav(transientDep) ); } } else { dependencyTree.add(parent); } } this.dependencyTree = Optional.of(dependencyTree); } catch (final IOException e) { throw new RuntimeException("Failed to load cp info", e); } } /** * List of bootstrap modules to bootstrap the fractions. * * @return The list of simple module names */ public List<String> bootstrapModules() { return this.bootstrapModules; } /** * List of bootstrap artifacts to look for fractions. * * @return The list of Maven GAVs to bootstrap. */ public List<String> bootstrapArtifacts() { return this.bootstrapArtifacts; } public List<ArtifactCoordinates> bootstrapArtifactsAsCoordinates() { return this.bootstrapArtifacts.stream().map(artifact -> { String[] parts = artifact.split(":"); ArtifactCoordinates coords = null; if (parts.length == 4) { coords = new ArtifactCoordinates(parts[0], parts[1], parts[3]); } else if (parts.length == 5) { coords = new ArtifactCoordinates(parts[0], parts[1], parts[3], parts[4]); } return coords; }).collect(Collectors.toList()); } public Mode getMode() { return mode; } private boolean loadWildFlySwarmApplicationManifestFromClasspath() throws IOException { return loadWildFlySwarmApplicationManifest(ClassLoader.getSystemClassLoader()); } private boolean loadWildFlySwarmApplicationManifestFromTCCL() throws IOException { return loadWildFlySwarmApplicationManifest(Thread.currentThread().getContextClassLoader()); } private boolean loadWildFlySwarmApplicationManifest(ClassLoader cl) throws IOException { URL url = cl.getResource(WildFlySwarmManifest.CLASSPATH_LOCATION); if (url == null) { return false; } this.applicationManifest = new WildFlySwarmManifest(url); this.bootstrapModules.addAll(this.applicationManifest.bootstrapModules()); this.bootstrapArtifacts.addAll(this.applicationManifest.bootstrapArtifacts()); return true; } private void loadFractionManifestsFromClasspath() throws IOException { loadFractionManifests(ClassLoader.getSystemClassLoader()); } private void loadFractionManifestsFromUberjar() throws IOException, ModuleLoadException { if (this.manifests != null) { return; } this.manifests = new ArrayList<>(); Set<String> modulesManifests = new HashSet<>(); this.bootstrapModules .forEach(moduleName -> { try { Module module = Module.getBootModuleLoader().loadModule(ModuleIdentifier.create(moduleName)); ClassLoader cl = module.getClassLoader(); Enumeration<URL> results = cl.getResources(FractionManifest.CLASSPATH_LOCATION); while (results.hasMoreElements()) { URL each = results.nextElement(); FractionManifest manifest = new FractionManifest(each); this.manifests.add(manifest); modulesManifests.add(manifest.getGroupId() + manifest.getArtifactId()); } } catch (ModuleLoadException | IOException e) { throw new RuntimeException(e); } }); bootstrapArtifactsAsCoordinates().forEach((coords) -> { try { File artifactFile = MavenResolvers.get().resolveJarArtifact(coords); if (artifactFile == null) { throw new RuntimeException("Unable to resolve artifact from coordinates: " + coords); } try (ZipFile zip = new ZipFile(artifactFile)) { ZipEntry manifestEntry = zip.getEntry(FractionManifest.CLASSPATH_LOCATION); if (manifestEntry != null) { FractionManifest manifest = new FractionManifest(zip.getInputStream(manifestEntry)); if (!modulesManifests.contains(manifest.getGroupId() + manifest.getArtifactId())) { this.manifests.add(manifest); } } } } catch (IOException e) { throw new RuntimeException(e); } }); this.manifests.sort(new ManifestComparator()); } private void loadFractionManifests(ClassLoader cl) throws IOException { if (this.manifests != null) { return; } this.manifests = new ArrayList<>(); Enumeration<URL> results = cl.getResources(FractionManifest.CLASSPATH_LOCATION); Set<MavenArtifactDescriptor> fractionDependencies = new HashSet<>(); while (results.hasMoreElements()) { URL each = results.nextElement(); FractionManifest manifest = new FractionManifest(each); this.manifests.add(manifest); if (manifest.getModule() != null) { this.bootstrapModules.add(manifest.getModule()); } if (this.mode == Mode.CLASSPATH) { MavenArtifactDescriptor fraction = new MavenArtifactDescriptor( manifest.getGroupId(), manifest.getArtifactId(), manifest.getVersion() ); if (dependencyTree.isPresent()) { for (MavenArtifactDescriptor directDep : dependencyTree.get().getDirectDeps()) { if (fraction.equals(directDep)) { fractionDependencies.add(directDep); } } } this.removeableDependencies.add(fraction.mavenGav()); this.removeableDependencies.addAll(manifest.getDependencies()); } } if (dependencyTree.isPresent()) { Set<MavenArtifactDescriptor> applicationDependencies = new HashSet<>(); Set<MavenArtifactDescriptor> keep = new HashSet<>(dependencyTree.get().getDirectDeps()); keep.removeAll(fractionDependencies); for (MavenArtifactDescriptor dep : keep) { // the dep itself applicationDependencies.add(dep); // it's transient dependencies applicationDependencies.addAll(dependencyTree.get().getTransientDeps(dep)); } this.removeableDependencies.removeAll(applicationDependencies); } this.manifests.sort(new ManifestComparator()); } /** * List of <i>application-level</i> dependencies. * * <p>Only applicable for uberjar executions.</p> * * @return The list of Maven GAVs for application dependencies. */ public Set<String> getDependencies() { if (this.mode == Mode.UBERJAR) { return this.applicationManifest.getDependencies(); } return Collections.emptySet(); } /** * List of <i>removable</i> dependencies, such as the * bootstrap Swarm jars and anything transitive not directly * required by the application. * * @return The list of Maven GAVs that may be removed. */ public List<String> getRemovableDependencies() { return this.removeableDependencies; } /** * [hb] TODO: these javadocs are wrong and describe a previous implementation of this method * * Resolve the application's dependencies. * * <p>Using combinations of {@link #getDependencies()}} and {@link #getRemovableDependencies()}, * depending on execution mode, resolves application dependencies, taking * into account any exclusions.</p> * * @param exclusions Maven GAVs to exclude. * @return Set of paths to dependency artifacts. * @throws IOException */ public Set<String> resolveDependencies(List<String> exclusions) throws IOException { if (this.mode == Mode.UBERJAR) { return new MavenDependencyResolution().resolve(exclusions); } else { return new SystemDependencyResolution().resolve(exclusions); } } public String getAsset() { if (this.mode == Mode.UBERJAR) { return this.applicationManifest.getAsset(); } return null; } /** * Retrieve the user's main-class name, if specified, else the default. * * @return The user's main-class name, else the default Swarm main. */ public String getMainClassName() { if (this.mode == Mode.UBERJAR) { return this.applicationManifest.getMainClass(); } return DEFAULT_MAIN_CLASS_NAME; } /** * Determine if this application is defined to be hollow. * * @return <code>true</code> if hollow, otherwise <code>false</code>. */ public boolean isHollow() { if (this.mode == Mode.UBERJAR) { return this.applicationManifest.isHollow(); } return false; } private Path root() { URL location = ApplicationEnvironment.class.getProtectionDomain().getCodeSource().getLocation(); if (location.getProtocol().equals("file")) { try { return Paths.get(location.toURI()); } catch (URISyntaxException e) { return null; } } return null; } public ClassLoader getBootstrapClassLoader() throws ModuleLoadException { if (this.mode == Mode.UBERJAR) { try { return Module.getBootModuleLoader().loadModule(ModuleIdentifier.create("org.wildfly.swarm.bootstrap")).getClassLoader(); } catch (ModuleLoadException e) { // ignore } } return ApplicationEnvironment.class.getClassLoader(); } /** * Retrieve a sorted list of installed fraction manifests. * * @return The sorted list of installed fraction manifests. */ public List<FractionManifest> fractionManifests() { if (this.mode == Mode.UBERJAR && this.manifests == null) { try { loadFractionManifestsFromUberjar(); } catch (IOException | ModuleLoadException e) { throw new RuntimeException(e); } } return this.manifests; } private Optional<DependencyTree<MavenArtifactDescriptor>> dependencyTree = Optional.empty(); public enum Mode { UBERJAR, CLASSPATH } private Mode mode = Mode.CLASSPATH; private List<FractionManifest> manifests; private WildFlySwarmManifest applicationManifest; private List<String> bootstrapModules = new ArrayList<>(); private List<String> bootstrapArtifacts = new ArrayList<>(); private List<String> removeableDependencies = new ArrayList<>(); private static AtomicReference<ApplicationEnvironment> INSTANCE = new AtomicReference<>(); public static final String DEFAULT_MAIN_CLASS_NAME = "org.wildfly.swarm.Swarm"; }