diff options
Diffstat (limited to 'bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo')
4 files changed, 642 insertions, 0 deletions
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/Artifacts.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/Artifacts.java new file mode 100644 index 00000000000..fff88d413d0 --- /dev/null +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/Artifacts.java @@ -0,0 +1,69 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.plugin.mojo; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.project.MavenProject; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * @author Tony Vaagenes + * @author ollivir + */ +public class Artifacts { + public static class ArtifactSet { + private final List<Artifact> jarArtifactsToInclude; + private final List<Artifact> jarArtifactsProvided; + private final List<Artifact> nonJarArtifacts; + + private ArtifactSet(List<Artifact> jarArtifactsToInclude, List<Artifact> jarArtifactsProvided, List<Artifact> nonJarArtifacts) { + this.jarArtifactsToInclude = jarArtifactsToInclude; + this.jarArtifactsProvided = jarArtifactsProvided; + this.nonJarArtifacts = nonJarArtifacts; + } + + public List<Artifact> getJarArtifactsToInclude() { + return jarArtifactsToInclude; + } + + public List<Artifact> getJarArtifactsProvided() { + return jarArtifactsProvided; + } + + public List<Artifact> getNonJarArtifacts() { + return nonJarArtifacts; + } + } + + public static ArtifactSet getArtifacts(MavenProject project) { + + List<Artifact> jarArtifactsToInclude = new ArrayList<>(); + List<Artifact> jarArtifactsProvided = new ArrayList<>(); + List<Artifact> nonJarArtifactsToInclude = new ArrayList<>(); + List<Artifact> nonJarArtifactsProvided = new ArrayList<>(); + + for (Artifact artifact : project.getArtifacts()) { + if ("jar".equals(artifact.getType())) { + if (Artifact.SCOPE_COMPILE.equals(artifact.getScope())) { + jarArtifactsToInclude.add(artifact); + } else if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope())) { + jarArtifactsProvided.add(artifact); + } + } else { + if (Artifact.SCOPE_COMPILE.equals(artifact.getScope())) { + nonJarArtifactsToInclude.add(artifact); + } else if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope())) { + nonJarArtifactsProvided.add(artifact); + } + } + } + nonJarArtifactsToInclude.addAll(nonJarArtifactsProvided); + return new ArtifactSet(jarArtifactsToInclude, jarArtifactsProvided, nonJarArtifactsToInclude); + } + + public static Collection<Artifact> getArtifactsToInclude(MavenProject project) { + return getArtifacts(project).getJarArtifactsToInclude(); + } +} diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/AssembleContainerPluginMojo.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/AssembleContainerPluginMojo.java new file mode 100644 index 00000000000..b5fac517c9d --- /dev/null +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/AssembleContainerPluginMojo.java @@ -0,0 +1,145 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.plugin.mojo; + +import com.yahoo.container.plugin.util.Files; +import com.yahoo.container.plugin.util.JarFiles; +import org.apache.maven.archiver.MavenArchiveConfiguration; +import org.apache.maven.archiver.MavenArchiver; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Build; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.archiver.jar.JarArchiver; + +import java.io.File; +import java.nio.channels.Channels; +import java.util.EnumMap; +import java.util.Enumeration; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +/** + * @author Tony Vaagenes + * @author ollivir + */ +@Mojo(name = "assemble-container-plugin", requiresDependencyResolution = ResolutionScope.COMPILE, threadSafe = true) +public class AssembleContainerPluginMojo extends AbstractMojo { + private static enum Dependencies { + WITH, WITHOUT + } + + @Parameter(defaultValue = "${project}") + private MavenProject project = null; + + @Parameter(defaultValue = "${session}", readonly = true, required = true) + private MavenSession session = null; + + @Parameter + private MavenArchiveConfiguration archiveConfiguration = new MavenArchiveConfiguration(); + + @Parameter(alias = "UseCommonAssemblyIds", defaultValue = "false") + private boolean useCommonAssemblyIds = false; + + @Override + public void execute() throws MojoExecutionException { + Map<Dependencies, String> jarSuffixes = new EnumMap<Dependencies, String>(Dependencies.class); + + if (useCommonAssemblyIds) { + jarSuffixes.put(Dependencies.WITHOUT, ".jar"); + jarSuffixes.put(Dependencies.WITH, "-jar-with-dependencies.jar"); + } else { + jarSuffixes.put(Dependencies.WITHOUT, "-without-dependencies.jar"); + jarSuffixes.put(Dependencies.WITH, "-deploy.jar"); + } + + Map<Dependencies, File> jarFiles = new EnumMap<Dependencies, File>(Dependencies.class); + jarSuffixes.forEach((dep, suffix) -> { + jarFiles.put(dep, jarFileInBuildDirectory(build().getFinalName(), suffix)); + }); + + // force recreating the archive + archiveConfiguration.setForced(true); + archiveConfiguration.setManifestFile(new File(new File(build().getOutputDirectory()), JarFile.MANIFEST_NAME)); + + JarArchiver jarWithoutDependencies = new JarArchiver(); + addClassesDirectory(jarWithoutDependencies); + createArchive(jarFiles.get(Dependencies.WITHOUT), jarWithoutDependencies); + project.getArtifact().setFile(jarFiles.get(Dependencies.WITHOUT)); + + JarArchiver jarWithDependencies = new JarArchiver(); + addClassesDirectory(jarWithDependencies); + addDependencies(jarWithDependencies); + createArchive(jarFiles.get(Dependencies.WITH), jarWithDependencies); + } + + private File jarFileInBuildDirectory(String name, String suffix) { + return new File(build().getDirectory(), name + suffix); + } + + private void addClassesDirectory(JarArchiver jarArchiver) { + File classesDirectory = new File(build().getOutputDirectory()); + if (classesDirectory.isDirectory()) { + jarArchiver.addDirectory(classesDirectory); + } + } + + private void createArchive(File jarFile, JarArchiver jarArchiver) throws MojoExecutionException { + MavenArchiver mavenArchiver = new MavenArchiver(); + mavenArchiver.setArchiver(jarArchiver); + mavenArchiver.setOutputFile(jarFile); + try { + mavenArchiver.createArchive(session, project, archiveConfiguration); + } catch (Exception e) { + throw new MojoExecutionException("Error creating archive " + jarFile.getName(), e); + } + } + + private void addDependencies(JarArchiver jarArchiver) { + Artifacts.getArtifactsToInclude(project).forEach(artifact -> { + if ("jar".equals(artifact.getType())) { + jarArchiver.addFile(artifact.getFile(), "dependencies/" + artifact.getFile().getName()); + copyConfigDefinitions(artifact.getFile(), jarArchiver); + } else { + getLog().warn("Unkown artifact type " + artifact.getType()); + } + }); + } + + private void copyConfigDefinitions(File file, JarArchiver jarArchiver) { + JarFiles.withJarFile(file, jarFile -> { + for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.startsWith("configdefinitions/") && name.endsWith(".def")) { + copyConfigDefinition(jarFile, entry, jarArchiver); + } + } + return null; + }); + } + + private void copyConfigDefinition(JarFile jarFile, ZipEntry entry, JarArchiver jarArchiver) { + JarFiles.withInputStream(jarFile, entry, input -> { + String defPath = entry.getName().replace("/", File.separator); + File destinationFile = new File(build().getOutputDirectory(), defPath); + destinationFile.getParentFile().mkdirs(); + + Files.withFileOutputStream(destinationFile, output -> { + output.getChannel().transferFrom(Channels.newChannel(input), 0, Long.MAX_VALUE); + return null; + }); + jarArchiver.addFile(destinationFile, entry.getName()); + return null; + }); + } + + private Build build() { + return project.getBuild(); + } +} diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateBundleClassPathMappingsMojo.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateBundleClassPathMappingsMojo.java new file mode 100644 index 00000000000..b2abf13695f --- /dev/null +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateBundleClassPathMappingsMojo.java @@ -0,0 +1,115 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.plugin.mojo; + +import com.google.common.base.Preconditions; +import com.yahoo.container.plugin.bundle.AnalyzeBundle; +import com.yahoo.container.plugin.osgi.ProjectBundleClassPaths; +import com.yahoo.container.plugin.osgi.ProjectBundleClassPaths.BundleClasspathMapping; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Generates mapping from Bundle-SymbolicName to classpath elements, e.g myBundle -> [.m2/repository/com/mylib/Mylib.jar, + * myBundleProject/target/classes] The mapping in stored in a json file. + * + * @author Tony Vaagenes + * @author ollivir + */ +@Mojo(name = "generate-bundle-classpath-mappings", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, threadSafe = true) +public class GenerateBundleClassPathMappingsMojo extends AbstractMojo { + @Parameter(defaultValue = "${project}") + private MavenProject project = null; + + //TODO: Combine with com.yahoo.container.plugin.mojo.GenerateOsgiManifestMojo.bundleSymbolicName + @Parameter(alias = "Bundle-SymbolicName", defaultValue = "${project.artifactId}") + private String bundleSymbolicName = null; + + /* Sample output -- target/test-classes/bundle-plugin.bundle-classpath-mappings.json + { + "mainBundle": { + "bundleSymbolicName": "bundle-plugin-test", + "classPathElements": [ + "/Users/tonyv/Repos/vespa/bundle-plugin-test/target/classes", + "/Users/tonyv/.m2/repository/com/yahoo/vespa/jrt/6-SNAPSHOT/jrt-6-SNAPSHOT.jar", + "/Users/tonyv/.m2/repository/com/yahoo/vespa/annotations/6-SNAPSHOT/annotations-6-SNAPSHOT.jar" + ] + }, + "providedDependencies": [ + { + "bundleSymbolicName": "jrt", + "classPathElements": [ + "/Users/tonyv/.m2/repository/com/yahoo/vespa/jrt/6-SNAPSHOT/jrt-6-SNAPSHOT.jar" + ] + } + ] + } + */ + @Override + public void execute() throws MojoExecutionException { + Preconditions.checkNotNull(bundleSymbolicName); + + Artifacts.ArtifactSet artifacts = Artifacts.getArtifacts(project); + List<Artifact> embeddedArtifacts = artifacts.getJarArtifactsToInclude(); + List<Artifact> providedJarArtifacts = artifacts.getJarArtifactsProvided(); + + List<File> embeddedArtifactsFiles = embeddedArtifacts.stream().map(Artifact::getFile).collect(Collectors.toList()); + + List<String> classPathElements = Stream.concat(Stream.of(outputDirectory()), embeddedArtifactsFiles.stream()) + .map(File::getAbsolutePath).collect(Collectors.toList()); + + ProjectBundleClassPaths classPathMappings = new ProjectBundleClassPaths( + new BundleClasspathMapping(bundleSymbolicName, classPathElements), + providedJarArtifacts.stream().map(f -> createDependencyClasspathMapping(f)).filter(Optional::isPresent).map(Optional::get) + .collect(Collectors.toList())); + + try { + ProjectBundleClassPaths.save(testOutputPath().resolve(ProjectBundleClassPaths.CLASSPATH_MAPPINGS_FILENAME), classPathMappings); + } catch (IOException e) { + throw new MojoExecutionException("Error saving to file " + testOutputPath(), e); + } + } + + private File outputDirectory() { + return new File(project.getBuild().getOutputDirectory()); + } + + private Path testOutputPath() { + return Paths.get(project.getBuild().getTestOutputDirectory()); + } + + /* TODO: + * 1) add the dependencies of the artifact in the future(i.e. dependencies of dependencies) + * or + * 2) obtain bundles with embedded dependencies from the maven repository, + * and support loading classes from the nested jar files in those bundles. + */ + Optional<BundleClasspathMapping> createDependencyClasspathMapping(Artifact artifact) { + return bundleSymbolicNameForArtifact(artifact) + .map(name -> new BundleClasspathMapping(name, Arrays.asList(artifact.getFile().getAbsolutePath()))); + } + + private static Optional<String> bundleSymbolicNameForArtifact(Artifact artifact) { + if (artifact.getFile().getName().endsWith(".jar")) { + return AnalyzeBundle.bundleSymbolicName(artifact.getFile()); + } else { + // Not the best heuristic. The other alternatives are parsing the pom file or + // storing information in target/classes when building the provided bundles. + return Optional.of(artifact.getArtifactId()); + } + } +} diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.java new file mode 100644 index 00000000000..8d19f112765 --- /dev/null +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.java @@ -0,0 +1,313 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.plugin.mojo; + +import com.google.common.collect.Sets; +import com.yahoo.container.plugin.bundle.AnalyzeBundle; +import com.yahoo.container.plugin.classanalysis.Analyze; +import com.yahoo.container.plugin.classanalysis.ClassFileMetaData; +import com.yahoo.container.plugin.classanalysis.ExportPackageAnnotation; +import com.yahoo.container.plugin.classanalysis.PackageTally; +import com.yahoo.container.plugin.osgi.ExportPackageParser; +import com.yahoo.container.plugin.osgi.ExportPackages; +import com.yahoo.container.plugin.osgi.ExportPackages.Export; +import com.yahoo.container.plugin.osgi.ImportPackages; +import com.yahoo.container.plugin.osgi.ImportPackages.Import; +import com.yahoo.container.plugin.util.Strings; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.yahoo.container.plugin.util.Files.allDescendantFiles; +import static com.yahoo.container.plugin.util.IO.withFileOutputStream; +import static com.yahoo.container.plugin.util.JarFiles.withInputStream; +import static com.yahoo.container.plugin.util.JarFiles.withJarFile; + +/** + * @author Tony Vaagenes + * @author ollivir + */ +@Mojo(name = "generate-osgi-manifest", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true) +public class GenerateOsgiManifestMojo extends AbstractMojo { + + @Parameter(defaultValue = "${project}") + private MavenProject project = null; + + @Parameter + private String discApplicationClass = null; + + @Parameter + private String discPreInstallBundle = null; + + @Parameter(alias = "Bundle-Version", defaultValue = "${project.version}") + private String bundleVersion = null; + + @Parameter(alias = "Bundle-SymbolicName", defaultValue = "${project.artifactId}") + private String bundleSymbolicName = null; + + @Parameter(alias = "Bundle-Activator") + private String bundleActivator = null; + + @Parameter(alias = "X-JDisc-Privileged-Activator") + private String jdiscPrivilegedActivator = null; + + @Parameter(alias = "X-Config-Models") + private String configModels = null; + + @Parameter(alias = "Import-Package") + private String importPackage = null; + + @Parameter(alias = "WebInfUrl") + private String webInfUrl = null; + + @Parameter(alias = "Main-Class") + private String mainClass = null; + + @Parameter(alias = "X-Jersey-Binding") + private String jerseyBinding = null; + + public void execute() throws MojoExecutionException, MojoFailureException { + try { + Artifacts.ArtifactSet artifactSet = Artifacts.getArtifacts(project); + warnOnUnsupportedArtifacts(artifactSet.getNonJarArtifacts()); + + AnalyzeBundle.PublicPackages publicPackagesFromProvidedJars = AnalyzeBundle.publicPackagesAggregated( + artifactSet.getJarArtifactsProvided().stream().map(Artifact::getFile).collect(Collectors.toList())); + PackageTally includedJarPackageTally = definedPackages(artifactSet.getJarArtifactsToInclude()); + + PackageTally projectPackageTally = analyzeProjectClasses(); + PackageTally pluginPackageTally = projectPackageTally.combine(includedJarPackageTally); + + Set<String> definedPackages = new HashSet<>(projectPackageTally.definedPackages()); + definedPackages.addAll(includedJarPackageTally.definedPackages()); + + warnIfPackagesDefinedOverlapsGlobalPackages(definedPackages, publicPackagesFromProvidedJars.globals); + + if (getLog().isDebugEnabled()) { + getLog().debug("Referenced packages = " + pluginPackageTally.referencedPackages()); + getLog().debug("Defined packages = " + pluginPackageTally.definedPackages()); + getLog().debug("Exported packages of dependencies = " + publicPackagesFromProvidedJars.exports.stream() + .map(e -> "(" + e.getPackageNames().toString() + ", " + e.version().orElse("")).collect(Collectors.joining(", "))); + } + + Map<String, Import> calculatedImports = ImportPackages.calculateImports(pluginPackageTally.referencedPackages(), + pluginPackageTally.definedPackages(), ExportPackages.exportsByPackageName(publicPackagesFromProvidedJars.exports)); + + Map<String, Optional<String>> manualImports = emptyToNone(importPackage).map(GenerateOsgiManifestMojo::getManualImports) + .orElseGet(HashMap::new); + for (String packageName : manualImports.keySet()) { + calculatedImports.remove(packageName); + } + createManifestFile(new File(project.getBuild().getOutputDirectory()), manifestContent(project, + artifactSet.getJarArtifactsToInclude(), manualImports, calculatedImports.values(), pluginPackageTally)); + + } catch (Exception e) { + throw new MojoExecutionException("Failed generating osgi manifest.", e); + } + } + + private static void warnIfPackagesDefinedOverlapsGlobalPackages(Set<String> internalPackages, List<String> globalPackages) + throws MojoExecutionException { + Set<String> overlap = Sets.intersection(internalPackages, new HashSet<>(globalPackages)); + if (overlap.isEmpty() == false) { + throw new MojoExecutionException( + "The following packages are both global and included in the bundle:\n " + String.join("\n ", overlap)); + } + } + + private Collection<String> osgiExportPackages(Map<String, ExportPackageAnnotation> exportedPackages) { + return exportedPackages.entrySet().stream().map(entry -> entry.getKey() + ";version=" + entry.getValue().osgiVersion()) + .collect(Collectors.toList()); + } + + private static String trimWhitespace(Optional<String> lines) { + return Stream.of(lines.orElse("").split(",")).map(String::trim).collect(Collectors.joining(",")); + } + + private Map<String, String> manifestContent(MavenProject project, Collection<Artifact> jarArtifactsToInclude, + Map<String, Optional<String>> manualImports, Collection<Import> imports, PackageTally pluginPackageTally) { + Map<String, String> ret = new HashMap<>(); + String importPackage = Stream.concat(manualImports.entrySet().stream().map(e -> asOsgiImport(e.getKey(), e.getValue())), + imports.stream().map(Import::asOsgiImport)).sorted().collect(Collectors.joining(",")); + String exportPackage = osgiExportPackages(pluginPackageTally.exportedPackages()).stream().sorted().collect(Collectors.joining(",")); + + for (Pair<String, String> element : Arrays.asList(// + Pair.of("Created-By", "vespa container maven plugin"), // + Pair.of("Bundle-ManifestVersion", "2"), // + Pair.of("Bundle-Name", project.getName()), // + Pair.of("Bundle-SymbolicName", bundleSymbolicName), // + Pair.of("Bundle-Version", asBundleVersion(bundleVersion)), // + Pair.of("Bundle-Vendor", "Yahoo!"), // + Pair.of("Bundle-ClassPath", bundleClassPath(jarArtifactsToInclude)), // + Pair.of("Bundle-Activator", bundleActivator), // + Pair.of("X-JDisc-Privileged-Activator", jdiscPrivilegedActivator), // + Pair.of("Main-Class", mainClass), // + Pair.of("X-JDisc-Application", discApplicationClass), // + Pair.of("X-JDisc-Preinstall-Bundle", trimWhitespace(Optional.ofNullable(discPreInstallBundle))), // + Pair.of("X-Config-Models", configModels), // + Pair.of("X-Jersey-Binding", jerseyBinding), // + Pair.of("WebInfUrl", webInfUrl), // + Pair.of("Import-Package", importPackage), // + Pair.of("Export-Package", exportPackage))) { + if (element.getValue() != null && element.getValue().isEmpty() == false) { + ret.put(element.getKey(), element.getValue()); + } + } + return ret; + } + + private static String asOsgiImport(String packageName, Optional<String> version) { + return version.map(s -> packageName + ";version=" + quote(s)).orElse(packageName); + } + + private static String quote(String s) { + return "\"" + s + "\""; + } + + private static void createManifestFile(File outputDirectory, Map<String, String> manifestContent) { + Manifest manifest = toManifest(manifestContent); + + withFileOutputStream(new File(outputDirectory, JarFile.MANIFEST_NAME), outputStream -> { + manifest.write(outputStream); + return null; + }); + } + + private static Manifest toManifest(Map<String, String> manifestContent) { + Manifest manifest = new Manifest(); + Attributes mainAttributes = manifest.getMainAttributes(); + + mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + manifestContent.forEach(mainAttributes::putValue); + + return manifest; + } + + private static String bundleClassPath(Collection<Artifact> artifactsToInclude) { + return Stream.concat(Stream.of("."), artifactsToInclude.stream().map(GenerateOsgiManifestMojo::dependencyPath)) + .collect(Collectors.joining(",")); + } + + private static String dependencyPath(Artifact artifact) { + return "dependencies/" + artifact.getFile().getName(); + } + + private static String asBundleVersion(String projectVersion) { + if (projectVersion == null) { + throw new IllegalArgumentException("Missing project version."); + } + + String[] parts = projectVersion.split("-", 2); + List<String> numericPart = Stream.of(parts[0].split("\\.")).map(s -> Strings.replaceEmptyString(s, "0")).limit(3) + .collect(Collectors.toList()); + while (numericPart.size() < 3) { + numericPart.add("0"); + } + + return String.join(".", numericPart); + } + + private void warnOnUnsupportedArtifacts(Collection<Artifact> nonJarArtifacts) { + List<Artifact> unsupportedArtifacts = nonJarArtifacts.stream().filter(a -> "pom".equals(a.getType()) == false) + .collect(Collectors.toList()); + + unsupportedArtifacts.forEach(artifact -> getLog() + .warn(String.format("Unsupported artifact '%s': Type '%s' is not supported. Please file a feature request.", + artifact.getId(), artifact.getType()))); + } + + private PackageTally analyzeProjectClasses() { + File outputDirectory = new File(project.getBuild().getOutputDirectory()); + + List<ClassFileMetaData> analyzedClasses = allDescendantFiles(outputDirectory).filter(file -> file.getName().endsWith(".class")) + .map(Analyze::analyzeClass).collect(Collectors.toList()); + + return PackageTally.fromAnalyzedClassFiles(analyzedClasses); + } + + private static PackageTally definedPackages(Collection<Artifact> jarArtifacts) { + return PackageTally.combine(jarArtifacts.stream().map(ja -> withJarFile(ja.getFile(), GenerateOsgiManifestMojo::definedPackages)) + .collect(Collectors.toList())); + } + + private static PackageTally definedPackages(JarFile jarFile) throws MojoExecutionException { + List<ClassFileMetaData> analyzedClasses = new ArrayList<>(); + for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) { + JarEntry entry = entries.nextElement(); + if (entry.isDirectory() == false && entry.getName().endsWith(".class")) { + analyzedClasses.add(analyzeClass(jarFile, entry)); + } + } + return PackageTally.fromAnalyzedClassFiles(analyzedClasses); + } + + private static ClassFileMetaData analyzeClass(JarFile jarFile, JarEntry entry) throws MojoExecutionException { + try { + return withInputStream(jarFile, entry, Analyze::analyzeClass); + } catch (Exception e) { + throw new MojoExecutionException( + String.format("While analyzing the class '%s' in jar file '%s'", entry.getName(), jarFile.getName()), e); + } + } + + private static Map<String, Optional<String>> getManualImports(String importPackage) { + try { + Map<String, Optional<String>> ret = new HashMap<>(); + List<Export> imports = parseImportPackages(importPackage); + for (Export imp : imports) { + Optional<String> version = getVersionThrowOthers(imp.getParameters()); + imp.getPackageNames().forEach(pn -> ret.put(pn, version)); + } + + return ret; + } catch (Exception e) { + throw new RuntimeException("Error in Import-Package:" + importPackage, e); + } + } + + private static Optional<String> getVersionThrowOthers(List<ExportPackages.Parameter> parameters) { + if (parameters.size() == 1 && "version".equals(parameters.get(0).getName())) { + return Optional.of(parameters.get(0).getValue()); + } else if (parameters.size() == 0) { + return Optional.empty(); + } else { + List<String> paramNames = parameters.stream().map(ExportPackages.Parameter::getName).collect(Collectors.toList()); + throw new RuntimeException("A single, optional version parameter expected, but got " + paramNames); + } + } + + private static List<Export> parseImportPackages(String importPackages) { + return ExportPackageParser.parseExports(importPackages); + } + + private static Optional<String> emptyToNone(String str) { + return Optional.ofNullable(str).map(String::trim).filter(s -> s.isEmpty() == false); + } + + private static boolean isClassToAnalyze(String name) { + return name.endsWith(".class") && name.endsWith("module-info.class") == false; + } +} |