aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bundle-plugin-test/pom.xml21
-rw-r--r--bundle-plugin/pom.xml25
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/bundle/AnalyzeBundle.java94
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/bundle/TransformExportPackages.java62
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Analyze.java87
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.java162
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeFieldVisitor.java49
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.java168
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.java119
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.java35
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.java62
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ImportCollector.java35
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/PackageTally.java79
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Packages.java43
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/Artifacts.java69
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/AssembleContainerPluginMojo.java145
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateBundleClassPathMappingsMojo.java115
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.java313
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ExportPackageParser.java283
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ExportPackages.java70
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ImportPackages.java97
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/util/Files.java30
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/util/IO.java41
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/util/JarFiles.java36
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/util/Maps.java31
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/util/Strings.java26
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/util/ThrowingFunction.java11
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/AnalyzeBundle.scala72
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/TransformExportPackages.scala54
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Analyze.scala28
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.scala102
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.scala88
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.scala68
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnnotationVisitorTrait.scala39
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AttributeVisitorTrait.scala15
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.scala10
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.scala24
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/PackageTally.scala46
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Packages.scala27
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/SubVisitorTrait.scala19
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/package.scala27
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/Artifacts.scala29
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/AssembleContainerPluginMojo.scala116
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateBundleClassPathMappingsMojo.scala96
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.scala286
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackageParser.scala89
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackages.scala27
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ImportPackages.scala51
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Extractors.scala17
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Files.scala23
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/IO.scala46
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Iteration.scala14
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/JarFiles.scala24
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Maps.scala19
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Strings.scala14
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.java85
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/AnalyzeClassTest.java167
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/AnalyzeMethodBodyTest.java73
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/TestUtilities.java40
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassReference.java1
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/MethodInvocation.java1
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Methods.java1
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/osgi/ExportPackageParserTest.java295
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/osgi/ImportPackageTest.java130
-rw-r--r--bundle-plugin/src/test/scala/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.scala41
-rw-r--r--bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassTest.scala118
-rw-r--r--bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodBodyTest.scala52
-rw-r--r--bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/TestUtilities.scala18
-rw-r--r--bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ExportPackageParserTest.scala63
-rw-r--r--bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ImportPackageTest.scala95
-rw-r--r--bundle-plugin/src/test/scala/com/yahoo/container/plugin/util/IOTest.scala45
-rw-r--r--jdisc_core/pom.xml7
-rw-r--r--jdisc_core/src/main/java/com/yahoo/jdisc/core/ExportPackages.java9
73 files changed, 3057 insertions, 1962 deletions
diff --git a/bundle-plugin-test/pom.xml b/bundle-plugin-test/pom.xml
index 53be71352c8..4b36435529e 100644
--- a/bundle-plugin-test/pom.xml
+++ b/bundle-plugin-test/pom.xml
@@ -44,36 +44,15 @@
<version>${project.version}</version>
</dependency>
<dependency>
- <groupId>org.scala-lang</groupId>
- <artifactId>scala-library</artifactId>
- <scope>provided</scope>
- </dependency>
-
- <dependency>
<!-- Added to verify that module-info.class can be handled by bundle-plugin without throwing an exception. -->
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
-
</dependencies>
<build>
<plugins>
<plugin>
- <groupId>net.alchim31.maven</groupId>
- <artifactId>scala-maven-plugin</artifactId>
- <executions>
- <execution>
- <goals>
- <goal>add-source</goal>
- <goal>compile</goal>
- <goal>testCompile</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
-
- <plugin>
<groupId>com.yahoo.vespa</groupId>
<artifactId>bundle-plugin</artifactId>
<version>${project.version}</version>
diff --git a/bundle-plugin/pom.xml b/bundle-plugin/pom.xml
index b68ffa811b2..a5cb3c9fbcc 100644
--- a/bundle-plugin/pom.xml
+++ b/bundle-plugin/pom.xml
@@ -39,16 +39,8 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.scala-lang</groupId>
- <artifactId>scala-library</artifactId>
- </dependency>
- <dependency>
- <groupId>org.scala-lang.modules</groupId>
- <artifactId>scala-parser-combinators_${scala.major-version}</artifactId>
- </dependency>
- <dependency>
- <groupId>org.scalatest</groupId>
- <artifactId>scalatest_${scala.major-version}</artifactId>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
<scope>test</scope>
</dependency>
<dependency>
@@ -95,19 +87,6 @@
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
- <groupId>net.alchim31.maven</groupId>
- <artifactId>scala-maven-plugin</artifactId>
- <executions>
- <execution>
- <goals>
- <goal>add-source</goal>
- <goal>compile</goal>
- <goal>testCompile</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
</plugin>
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/bundle/AnalyzeBundle.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/bundle/AnalyzeBundle.java
new file mode 100644
index 00000000000..798fea2644e
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/bundle/AnalyzeBundle.java
@@ -0,0 +1,94 @@
+// 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.bundle;
+
+import com.yahoo.container.plugin.osgi.ExportPackageParser;
+import com.yahoo.container.plugin.osgi.ExportPackages.Export;
+import com.yahoo.container.plugin.util.JarFiles;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.jar.Manifest;
+import java.util.stream.Collectors;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class AnalyzeBundle {
+ public static class PublicPackages {
+ public final List<Export> exports;
+ public final List<String> globals;
+
+ public PublicPackages(List<Export> exports, List<String> globals) {
+ this.exports = exports;
+ this.globals = globals;
+ }
+ }
+
+ public static PublicPackages publicPackagesAggregated(Collection<File> jarFiles) {
+ List<Export> exports = new ArrayList<>();
+ List<String> globals = new ArrayList<>();
+
+ for (File jarFile : jarFiles) {
+ PublicPackages pp = publicPackages(jarFile);
+ exports.addAll(pp.exports);
+ globals.addAll(pp.globals);
+ }
+ return new PublicPackages(exports, globals);
+ }
+
+ public static PublicPackages publicPackages(File jarFile) {
+ try {
+ Optional<Manifest> jarManifest = JarFiles.getManifest(jarFile);
+ if (jarManifest.isPresent()) {
+ Manifest manifest = jarManifest.get();
+ if (isOsgiManifest(manifest)) {
+ return new PublicPackages(parseExports(manifest), parseGlobals(manifest));
+ }
+ }
+ return new PublicPackages(Collections.emptyList(), Collections.emptyList());
+ } catch (Exception e) {
+ throw new RuntimeException(String.format("Invalid manifest in bundle '%s'", jarFile.getPath()), e);
+ }
+ }
+
+ public static Optional<String> bundleSymbolicName(File jarFile) {
+ return JarFiles.getManifest(jarFile).flatMap(AnalyzeBundle::getBundleSymbolicName);
+ }
+
+ private static List<Export> parseExportsFromAttribute(Manifest manifest, String attributeName) {
+ return getMainAttributeValue(manifest, attributeName).map(ExportPackageParser::parseExports).orElseGet(() -> new ArrayList<>());
+ }
+
+ private static List<Export> parseExports(Manifest jarManifest) {
+ return parseExportsFromAttribute(jarManifest, "Export-Package");
+ }
+
+ private static List<String> parseGlobals(Manifest manifest) {
+ List<Export> globals = parseExportsFromAttribute(manifest, "Global-Package");
+
+ for (Export export : globals) {
+ if (export.getParameters().isEmpty() == false) {
+ throw new RuntimeException("Parameters not valid for Global-Package.");
+ }
+ }
+
+ return globals.stream().flatMap(g -> g.getPackageNames().stream()).collect(Collectors.toList());
+ }
+
+ private static Optional<String> getMainAttributeValue(Manifest manifest, String attributeName) {
+ return Optional.ofNullable(manifest.getMainAttributes().getValue(attributeName));
+ }
+
+ private static boolean isOsgiManifest(Manifest mf) {
+ return getBundleSymbolicName(mf).isPresent();
+ }
+
+ private static Optional<String> getBundleSymbolicName(Manifest mf) {
+ return getMainAttributeValue(mf, "Bundle-SymbolicName");
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/bundle/TransformExportPackages.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/bundle/TransformExportPackages.java
new file mode 100644
index 00000000000..8686fef0a55
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/bundle/TransformExportPackages.java
@@ -0,0 +1,62 @@
+// 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.bundle;
+
+import com.yahoo.container.plugin.osgi.ExportPackages.Export;
+import com.yahoo.container.plugin.osgi.ExportPackages.Parameter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class TransformExportPackages {
+ public static List<Export> replaceVersions(List<Export> exports, String newVersion) {
+ List<Export> ret = new ArrayList<>();
+
+ for (Export export : exports) {
+ List<Parameter> newParams = new ArrayList<>();
+ for (Parameter param : export.getParameters()) {
+ if ("version".equals(param.getName())) {
+ newParams.add(new Parameter("version", newVersion));
+ } else {
+ newParams.add(param);
+ }
+ }
+ ret.add(new Export(export.getPackageNames(), newParams));
+ }
+ return ret;
+ }
+
+ public static List<Export> removeUses(List<Export> exports) {
+ List<Export> ret = new ArrayList<>();
+
+ for (Export export : exports) {
+ List<Parameter> newParams = new ArrayList<>();
+ for (Parameter param : export.getParameters()) {
+ if ("uses".equals(param.getName()) == false) {
+ newParams.add(param);
+ }
+ }
+ ret.add(new Export(export.getPackageNames(), newParams));
+ }
+ return ret;
+ }
+
+ public static String toExportPackageProperty(List<Export> exports) {
+ return exports.stream().map(exp -> {
+ String oneExport = String.join(";", exp.getPackageNames());
+ if (exp.getParameters().size() > 0) {
+ String paramString = exp.getParameters().stream().map(param -> param.getName() + "=" + quote(param.getValue())).collect(Collectors.joining(";"));
+ oneExport += ";" + paramString;
+ }
+ return oneExport;
+ }).collect(Collectors.joining(","));
+ }
+
+ public static String quote(String s) {
+ return "\"" + s + "\"";
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Analyze.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Analyze.java
new file mode 100644
index 00000000000..c59f8559405
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Analyze.java
@@ -0,0 +1,87 @@
+// 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.classanalysis;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Optional;
+
+import static com.yahoo.container.plugin.util.IO.withFileInputStream;
+
+/**
+ * Main entry point for class analysis
+ *
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class Analyze {
+ public static ClassFileMetaData analyzeClass(File classFile) {
+ try {
+ return withFileInputStream(classFile, Analyze::analyzeClass);
+ } catch (RuntimeException e) {
+ throw new RuntimeException("An error occurred when analyzing " + classFile.getPath(), e);
+ }
+ }
+
+ public static ClassFileMetaData analyzeClass(InputStream inputStream) {
+ try {
+ AnalyzeClassVisitor visitor = new AnalyzeClassVisitor();
+ new ClassReader(inputStream).accept(visitor, ClassReader.SKIP_DEBUG);
+ return visitor.result();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static Optional<String> internalNameToClassName(String internalClassName) {
+ if(internalClassName == null) {
+ return Optional.empty();
+ } else {
+ return getClassName(Type.getObjectType(internalClassName));
+ }
+ }
+
+ static Optional<String> getClassName(Type aType) {
+ switch (aType.getSort()) {
+ case Type.ARRAY:
+ return getClassName(aType.getElementType());
+ case Type.OBJECT:
+ return Optional.of(aType.getClassName());
+ default:
+ return Optional.empty();
+ }
+ }
+
+ static AnnotationVisitor visitAnnotationDefault(ImportCollector collector) {
+ return new AnnotationVisitor(Opcodes.ASM6) {
+ @Override
+ public void visit(String name, Object value) {
+ }
+
+ @Override
+ public void visitEnum(String name, String desc, String value) {
+ collector.addImportWithTypeDesc(desc);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return this;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ collector.addImportWithTypeDesc(desc);
+ return this;
+ }
+
+ @Override
+ public void visitEnd() {
+ }
+ };
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.java
new file mode 100644
index 00000000000..d9519fd7986
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.java
@@ -0,0 +1,162 @@
+// 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.classanalysis;
+
+import com.yahoo.osgi.annotation.ExportPackage;
+import com.yahoo.osgi.annotation.Version;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Picks up classes used in class files.
+ *
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+class AnalyzeClassVisitor extends ClassVisitor implements ImportCollector {
+ private String name = null;
+ private Set<String> imports = new HashSet<>();
+ private Optional<ExportPackageAnnotation> exportPackageAnnotation = Optional.empty();
+
+ AnalyzeClassVisitor() {
+ super(Opcodes.ASM6);
+ }
+
+ @Override
+ public Set<String> imports() {
+ return imports;
+ }
+
+ @Override
+ public void visitAttribute(Attribute attribute) {
+ addImport(Type.getObjectType(attribute.type));
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ Analyze.getClassName(Type.getReturnType(desc)).ifPresent(imports::add);
+ Arrays.asList(Type.getArgumentTypes(desc)).forEach(argType -> Analyze.getClassName(argType).ifPresent(imports::add));
+ if (exceptions != null) {
+ Arrays.asList(exceptions).forEach(ex -> Analyze.internalNameToClassName(ex).ifPresent(imports::add));
+ }
+
+ AnalyzeSignatureVisitor.analyzeMethod(signature, this);
+ return new AnalyzeMethodVisitor(this);
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
+ Analyze.getClassName(Type.getType(desc)).ifPresent(imports::add);
+
+ AnalyzeSignatureVisitor.analyzeField(signature, this);
+ return new AnalyzeFieldVisitor(this);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ this.name = Analyze.internalNameToClassName(name)
+ .orElseThrow(() -> new RuntimeException("Unable to resolve class name for " + name));
+
+ addImportWithInternalName(superName);
+ Arrays.asList(interfaces).forEach(this::addImportWithInternalName);
+
+ AnalyzeSignatureVisitor.analyzeClass(signature, this);
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ }
+
+ @Override
+ public void visitOuterClass(String owner, String name, String desc) {
+ }
+
+ @Override
+ public void visitSource(String source, String debug) {
+ }
+
+ @Override
+ public void visitEnd() {
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> T defaultVersionValue(String name) {
+ try {
+ return (T) Version.class.getMethod(name).getDefaultValue();
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("Could not locate method " + name);
+ }
+ }
+
+ private AnnotationVisitor visitExportPackage() {
+ return new AnnotationVisitor(Opcodes.ASM6) {
+ private int major = defaultVersionValue("major");
+ private int minor = defaultVersionValue("minor");
+ private int micro = defaultVersionValue("micro");
+ private String qualifier = defaultVersionValue("qualifier");
+
+ @Override
+ public void visit(String name, Object value) {
+ if (name != null) {
+ switch (name) {
+ case "major":
+ major = (int) value;
+ break;
+ case "minor":
+ minor = (int) value;
+ break;
+ case "micro":
+ micro = (int) value;
+ break;
+ case "qualifier":
+ qualifier = (String) value;
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void visitEnd() {
+ exportPackageAnnotation = Optional.of(new ExportPackageAnnotation(major, minor, micro, qualifier));
+ }
+
+ @Override
+ public void visitEnum(String name, String desc, String value) {
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return this;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ return this;
+ }
+ };
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (ExportPackage.class.getName().equals(Type.getType(desc).getClassName())) {
+ return visitExportPackage();
+ } else {
+ addImportWithTypeDesc(desc);
+ return Analyze.visitAnnotationDefault(this);
+ }
+ }
+
+ ClassFileMetaData result() {
+ assert (!imports.contains("int"));
+ return new ClassFileMetaData(name, imports, exportPackageAnnotation);
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeFieldVisitor.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeFieldVisitor.java
new file mode 100644
index 00000000000..ea10b6ef0aa
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeFieldVisitor.java
@@ -0,0 +1,49 @@
+// 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.classanalysis;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author ollivir
+ */
+public class AnalyzeFieldVisitor extends FieldVisitor implements ImportCollector {
+ private final AnalyzeClassVisitor analyzeClassVisitor;
+ private final Set<String> imports = new HashSet<>();
+
+ public AnalyzeFieldVisitor(AnalyzeClassVisitor analyzeClassVisitor) {
+ super(Opcodes.ASM6);
+ this.analyzeClassVisitor = analyzeClassVisitor;
+ }
+
+ @Override
+ public Set<String> imports() {
+ return imports;
+ }
+
+ @Override
+ public void visitAttribute(Attribute attribute) {
+ addImport(Type.getObjectType(attribute.type));
+ }
+
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ addImportWithTypeDesc(desc);
+
+ return Analyze.visitAnnotationDefault(this);
+ }
+
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
+ return visitAnnotation(desc, visible);
+ }
+
+ @Override
+ public void visitEnd() {
+ analyzeClassVisitor.addImports(imports);
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.java
new file mode 100644
index 00000000000..b1a92f9c10b
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.java
@@ -0,0 +1,168 @@
+// 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.classanalysis;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Picks up classes used in method bodies.
+ *
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+class AnalyzeMethodVisitor extends MethodVisitor implements ImportCollector {
+ private final Set<String> imports = new HashSet<>();
+ private final AnalyzeClassVisitor analyzeClassVisitor;
+
+ AnalyzeMethodVisitor(AnalyzeClassVisitor analyzeClassVisitor) {
+ super(Opcodes.ASM6);
+ this.analyzeClassVisitor = analyzeClassVisitor;
+ }
+
+ public Set<String> imports() {
+ return imports;
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
+ return visitAnnotation(desc, visible);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return Analyze.visitAnnotationDefault(this);
+ }
+
+ @Override
+ public void visitAttribute(Attribute attribute) {
+ addImport(Type.getObjectType(attribute.type));
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ addImportWithTypeDesc(desc);
+
+ return Analyze.visitAnnotationDefault(this);
+ }
+
+ @Override
+ public void visitEnd() {
+ super.visitEnd();
+ analyzeClassVisitor.addImports(imports);
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ addImportWithTypeDesc(desc);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ addImportWithInternalName(owner);
+ Arrays.asList(Type.getArgumentTypes(desc)).forEach(this::addImport);
+ addImport(Type.getReturnType(desc));
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ addImportWithInternalName(owner);
+ addImportWithTypeDesc(desc);
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ addImportWithInternalName(type);
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ if (type != null) { //null means finally block
+ addImportWithInternalName(type);
+ }
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
+ addImportWithTypeDesc(desc);
+ }
+
+ @Override
+ public void visitLdcInsn(Object constant) {
+ if (constant instanceof Type) {
+ addImport((Type) constant);
+ }
+ }
+
+ @Override
+ public void visitInvokeDynamicInsn(String name, String desc, Handle bootstrapMethod, Object... bootstrapMethodArgs) {
+ for (Object arg : bootstrapMethodArgs) {
+ if (arg instanceof Type) {
+ addImport((Type) arg);
+ } else if (arg instanceof Handle) {
+ addImportWithInternalName(((Handle) arg).getOwner());
+ Arrays.asList(Type.getArgumentTypes(desc)).forEach(this::addImport);
+ } else if ((arg instanceof Number) == false && (arg instanceof String) == false) {
+ throw new AssertionError("Unexpected type " + arg.getClass() + " with value '" + arg + "'");
+ }
+ }
+ }
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ }
+
+ //only for debugging
+ @Override
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ }
+
+ @Override
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
+ super.visitTableSwitchInsn(min, max, dflt, labels);
+ }
+
+ @Override
+ public void visitIincInsn(int variable, int increment) {
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int variable) {
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ }
+
+ @Override
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+ }
+
+ @Override
+ public void visitCode() {
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.java
new file mode 100644
index 00000000000..0f5fcf89f6a
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.java
@@ -0,0 +1,119 @@
+// 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.classanalysis;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.signature.SignatureReader;
+import org.objectweb.asm.signature.SignatureVisitor;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+
+class AnalyzeSignatureVisitor extends SignatureVisitor implements ImportCollector {
+ private final AnalyzeClassVisitor analyzeClassVisitor;
+ private Set<String> imports = new HashSet<>();
+
+ AnalyzeSignatureVisitor(AnalyzeClassVisitor analyzeClassVisitor) {
+ super(Opcodes.ASM6);
+ this.analyzeClassVisitor = analyzeClassVisitor;
+ }
+
+ public Set<String> imports() {
+ return imports;
+ }
+
+ @Override
+ public void visitEnd() {
+ super.visitEnd();
+ analyzeClassVisitor.addImports(imports);
+ }
+
+ @Override
+ public void visitClassType(String className) {
+ addImportWithInternalName(className);
+ }
+
+ @Override
+ public void visitFormalTypeParameter(String name) {
+ }
+
+ @Override
+ public SignatureVisitor visitClassBound() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitInterfaceBound() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitSuperclass() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitInterface() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitParameterType() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitReturnType() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitExceptionType() {
+ return this;
+ }
+
+ @Override
+ public void visitBaseType(char descriptor) {
+ }
+
+ @Override
+ public void visitTypeVariable(String name) {
+ }
+
+ @Override
+ public SignatureVisitor visitArrayType() {
+ return this;
+ }
+
+ @Override
+ public void visitInnerClassType(String name) {
+ }
+
+ @Override
+ public void visitTypeArgument() {
+ }
+
+ @Override
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ return this;
+ }
+
+ static void analyzeClass(String signature, AnalyzeClassVisitor analyzeClassVisitor) {
+ if (signature != null) {
+ new SignatureReader(signature).accept(new AnalyzeSignatureVisitor(analyzeClassVisitor));
+ }
+ }
+
+ static void analyzeMethod(String signature, AnalyzeClassVisitor analyzeClassVisitor) {
+ analyzeClass(signature, analyzeClassVisitor);
+ }
+
+ static void analyzeField(String signature, AnalyzeClassVisitor analyzeClassVisitor) {
+ if (signature != null)
+ new SignatureReader(signature).acceptType(new AnalyzeSignatureVisitor(analyzeClassVisitor));
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.java
new file mode 100644
index 00000000000..198618cabc4
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.java
@@ -0,0 +1,35 @@
+// 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.classanalysis;
+
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * The result of analyzing a .class file.
+ *
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class ClassFileMetaData {
+ private final String name;
+ private final Set<String> referencedClasses;
+ private final Optional<ExportPackageAnnotation> exportPackage;
+
+ public ClassFileMetaData(String name, Set<String> referencedClasses, Optional<ExportPackageAnnotation> exportPackage) {
+ this.name = name;
+ this.referencedClasses = referencedClasses;
+ this.exportPackage = exportPackage;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Set<String> getReferencedClasses() {
+ return referencedClasses;
+ }
+
+ public Optional<ExportPackageAnnotation> getExportPackage() {
+ return exportPackage;
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.java
new file mode 100644
index 00000000000..d2da9b5a226
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.java
@@ -0,0 +1,62 @@
+// 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.classanalysis;
+
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class ExportPackageAnnotation {
+ private final int major;
+ private final int minor;
+ private final int micro;
+ private final String qualifier;
+
+ private static final Pattern QUALIFIER_PATTERN = Pattern.compile("[\\p{Alpha}\\p{Digit}_-]*");
+
+ public ExportPackageAnnotation(int major, int minor, int micro, String qualifier) {
+ this.major = major;
+ this.minor = minor;
+ this.micro = micro;
+ this.qualifier = qualifier;
+
+ requireNonNegative(major, "major");
+ requireNonNegative(minor, "minor");
+ requireNonNegative(micro, "micro");
+ if (QUALIFIER_PATTERN.matcher(qualifier).matches() == false) {
+ throw new IllegalArgumentException(
+ exportPackageError(String.format("qualifier must follow the format (alpha|digit|'_'|'-')* but was '%s'.", qualifier)));
+ }
+ }
+
+ public String osgiVersion() {
+ return String.format("%d.%d.%d", major, minor, micro) + (qualifier.isEmpty() ? "" : "." + qualifier);
+ }
+
+ private static String exportPackageError(String msg) {
+ return "ExportPackage anntotation: " + msg;
+ }
+
+ private static void requireNonNegative(int i, String fieldName) {
+ if (i < 0) {
+ throw new IllegalArgumentException(exportPackageError(String.format("%s must be non-negative but was %d.", fieldName, i)));
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ ExportPackageAnnotation that = (ExportPackageAnnotation) o;
+ return major == that.major && minor == that.minor && micro == that.micro && Objects.equals(qualifier, that.qualifier);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(major, minor, micro, qualifier);
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ImportCollector.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ImportCollector.java
new file mode 100644
index 00000000000..3946fe297f9
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ImportCollector.java
@@ -0,0 +1,35 @@
+// 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.classanalysis;
+
+import org.objectweb.asm.Type;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * @author ollivir
+ */
+public interface ImportCollector {
+ Set<String> imports();
+
+ default void addImportWithTypeDesc(String typeDescriptor) {
+ addImport(Type.getType(typeDescriptor));
+ }
+
+ default void addImport(Type type) {
+ addImport(Analyze.getClassName(type));
+ }
+
+ default void addImportWithInternalName(String name) {
+ addImport(Analyze.internalNameToClassName(name));
+ }
+
+ default void addImports(Collection<String> imports) {
+ imports().addAll(imports);
+ }
+
+ default void addImport(Optional<String> anImport) {
+ anImport.ifPresent(pkg -> imports().add(pkg));
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/PackageTally.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/PackageTally.java
new file mode 100644
index 00000000000..13bbc63192c
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/PackageTally.java
@@ -0,0 +1,79 @@
+// 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.classanalysis;
+
+import com.google.common.collect.Sets;
+import com.yahoo.container.plugin.util.Maps;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class PackageTally {
+ private final Map<String, Optional<ExportPackageAnnotation>> definedPackagesMap;
+ private final Set<String> referencedPackagesUnfiltered;
+
+ public PackageTally(Map<String, Optional<ExportPackageAnnotation>> definedPackagesMap, Set<String> referencedPackagesUnfiltered) {
+ this.definedPackagesMap = definedPackagesMap;
+ this.referencedPackagesUnfiltered = referencedPackagesUnfiltered;
+ }
+
+ public Set<String> definedPackages() {
+ return definedPackagesMap.keySet();
+ }
+
+ public Set<String> referencedPackages() {
+ return Sets.difference(referencedPackagesUnfiltered, definedPackages());
+ }
+
+ public Map<String, ExportPackageAnnotation> exportedPackages() {
+ Map<String, ExportPackageAnnotation> ret = new HashMap<>();
+ definedPackagesMap.forEach((k, v) -> {
+ v.ifPresent(annotation -> ret.put(k, annotation));
+ });
+ return ret;
+ }
+
+ /**
+ * Represents the classes for two package tallies that are deployed as a single unit.
+ * <p>
+ * ExportPackageAnnotations from this has precedence over the other.
+ */
+ public PackageTally combine(PackageTally other) {
+ Map<String, Optional<ExportPackageAnnotation>> map = Maps.combine(this.definedPackagesMap, other.definedPackagesMap,
+ (l, r) -> l.isPresent() ? l : r);
+ Set<String> referencedPkgs = new HashSet<>(this.referencedPackagesUnfiltered);
+ referencedPkgs.addAll(other.referencedPackagesUnfiltered);
+
+ return new PackageTally(map, referencedPkgs);
+ }
+
+ public static PackageTally combine(Collection<PackageTally> packageTallies) {
+ Map<String, Optional<ExportPackageAnnotation>> map = new HashMap<>();
+ Set<String> referencedPkgs = new HashSet<>();
+
+ for (PackageTally pt : packageTallies) {
+ pt.definedPackagesMap.forEach((k, v) -> map.merge(k, v, (l, r) -> l.isPresent() ? l : r));
+ referencedPkgs.addAll(pt.referencedPackagesUnfiltered);
+ }
+ return new PackageTally(map, referencedPkgs);
+ }
+
+ public static PackageTally fromAnalyzedClassFiles(Collection<ClassFileMetaData> analyzedClassFiles) {
+ Map<String, Optional<ExportPackageAnnotation>> map = new HashMap<>();
+ Set<String> referencedPkgs = new HashSet<>();
+
+ for (ClassFileMetaData metaData : analyzedClassFiles) {
+ String packageName = Packages.packageName(metaData.getName());
+ map.merge(packageName, metaData.getExportPackage(), (l, r) -> l.isPresent() ? l : r);
+ metaData.getReferencedClasses().forEach(className -> referencedPkgs.add(Packages.packageName(className)));
+ }
+ return new PackageTally(map, referencedPkgs);
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Packages.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Packages.java
new file mode 100644
index 00000000000..f9c6503c475
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Packages.java
@@ -0,0 +1,43 @@
+// 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.classanalysis;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Utility methods related to packages.
+ *
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class Packages {
+ public static class PackageMetaData {
+ public final Set<String> definedPackages;
+ public final Set<String> referencedExternalPackages;
+
+ public PackageMetaData(Set<String> definedPackages, Set<String> referencedExternalPackages) {
+ this.definedPackages = definedPackages;
+ this.referencedExternalPackages = referencedExternalPackages;
+ }
+ }
+
+ public static String packageName(String fullClassName) {
+ int index = fullClassName.lastIndexOf('.');
+ if (index == -1) {
+ return "";
+ } else {
+ return fullClassName.substring(0, index);
+ }
+ }
+
+ public static PackageMetaData analyzePackages(Set<ClassFileMetaData> allClasses) {
+ Set<String> definedPackages = new HashSet<>();
+ Set<String> referencedPackages = new HashSet<>();
+ for (ClassFileMetaData metaData : allClasses) {
+ definedPackages.add(packageName(metaData.getName()));
+ metaData.getReferencedClasses().forEach(className -> referencedPackages.add(packageName(className)));
+ }
+ referencedPackages.removeAll(definedPackages);
+ return new PackageMetaData(definedPackages, referencedPackages);
+ }
+}
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 -&gt; [.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;
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ExportPackageParser.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ExportPackageParser.java
new file mode 100644
index 00000000000..16858808a58
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ExportPackageParser.java
@@ -0,0 +1,283 @@
+// 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.osgi;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class ExportPackageParser {
+ public static List<ExportPackages.Export> parseExports(String exportAttribute) {
+ ParsingContext p = new ParsingContext(exportAttribute.trim());
+
+ List<ExportPackages.Export> exports = parseExportPackage(p);
+ if (exports.isEmpty()) {
+ p.fail("Expected a list of exports");
+ } else if (p.atEnd() == false) {
+ p.fail("Exports not fully processed");
+ }
+ return exports;
+ }
+
+ private static class ParsingContext {
+ private enum State {
+ Invalid, WantMore, End
+ }
+
+ private CharSequence input;
+ private int pos;
+ private State state;
+ private int length;
+ private char ch;
+
+ private ParsingContext(CharSequence input) {
+ this.input = input;
+ this.pos = 0;
+ }
+
+ private Optional<String> read(Consumer<ParsingContext> rule) {
+ StringBuilder ret = new StringBuilder();
+
+ parse: while (true) {
+ if (input.length() < pos + 1) {
+ break;
+ }
+ ch = input.charAt(pos);
+ state = State.WantMore;
+ length = ret.length();
+ rule.accept(this);
+
+ switch (state) {
+ case Invalid:
+ if (ret.length() == 0) {
+ break parse;
+ } else {
+ String printable = Character.isISOControl(ch) ? "#" + Integer.toString((int) ch)
+ : "[" + Character.toString(ch) + "]";
+ pos++;
+ fail("Character " + printable + " was not acceptable");
+ }
+ break;
+ case WantMore:
+ ret.append(ch);
+ pos++;
+ break;
+ case End:
+ break parse;
+ }
+ }
+
+ if (ret.length() == 0) {
+ return Optional.empty();
+ } else {
+ return Optional.of(ret.toString());
+ }
+ }
+
+ private Optional<String> regexp(Pattern pattern) {
+ Matcher matcher = pattern.matcher(input);
+ matcher.region(pos, input.length());
+ if (matcher.lookingAt()) {
+ String value = matcher.group();
+ pos += value.length();
+ return Optional.of(value);
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ private Optional<String> exactly(String string) {
+ if (input.length() - pos < string.length()) {
+ return Optional.empty();
+ }
+ if (input.subSequence(pos, pos + string.length()).equals(string)) {
+ pos += string.length();
+ return Optional.of(string);
+ }
+ return Optional.empty();
+ }
+
+ private boolean atEnd() {
+ return pos == input.length();
+ }
+
+ private void invalid() {
+ this.state = State.Invalid;
+ }
+
+ private void end() {
+ this.state = State.End;
+ }
+
+ private void fail(String message) {
+ throw new RuntimeException("Failed parsing Export-Package: " + message + " at position " + pos);
+ }
+ }
+
+ /* ident = ? a valid Java identifier ? */
+ private static Optional<String> parseIdent(ParsingContext p) {
+ Optional<String> ident = p.read(ctx -> {
+ if (ctx.length == 0) {
+ if (Character.isJavaIdentifierStart(ctx.ch) == false) {
+ ctx.invalid();
+ }
+ } else {
+ if (Character.isJavaIdentifierPart(ctx.ch) == false) {
+ ctx.end();
+ }
+ }
+ });
+ return ident;
+ }
+
+ /* stringLiteral = ? sequence of any character except double quotes, control characters or backslash,
+ a backslash followed by another backslash, a single or double quote, or one of the letters b,f,n,r or t
+ a backslash followed by u followed by four hexadecimal digits ? */
+ private static Pattern STRING_LITERAL_PATTERN = Pattern
+ .compile("\"" + "(?:[^\"\\p{Cntrl}\\\\]|\\\\[\\\\'\"bfnrt]|\\\\u[0-9a-fA-F]{4})+" + "\"");
+
+ private static Optional<String> parseStringLiteral(ParsingContext p) {
+ return p.regexp(STRING_LITERAL_PATTERN).map(quoted -> quoted.substring(1, quoted.length() - 1));
+ }
+
+ /* extended = { \p{Alnum} | '_' | '-' | '.' }+ */
+ private static Pattern EXTENDED_PATTERN = Pattern.compile("[\\p{Alnum}_.-]+");
+
+ private static Optional<String> parseExtended(ParsingContext p) {
+ return p.regexp(EXTENDED_PATTERN);
+ }
+
+ /* argument = extended | stringLiteral | ? failure ? */
+ private static String parseArgument(ParsingContext p) {
+ Optional<String> argument = parseExtended(p);
+ if (argument.isPresent() == false) {
+ argument = parseStringLiteral(p);
+ }
+ if (argument.isPresent() == false) {
+ p.fail("Expected an extended token or a string literal");
+ }
+ return argument.get();
+ }
+
+ /*
+ * parameter = ( directive | attribute )
+ * directive = extended, ':=', argument
+ * attribute = extended, '=', argument
+ */
+ private static Pattern DIRECTIVE_OR_ATTRIBUTE_SEPARATOR_PATTERN = Pattern.compile("\\s*:?=\\s*");
+
+ private static Optional<ExportPackages.Parameter> parseParameter(ParsingContext p) {
+ int backtrack = p.pos;
+ Optional<String> ext = parseExtended(p);
+ if (ext.isPresent()) {
+ Optional<String> sep = p.regexp(DIRECTIVE_OR_ATTRIBUTE_SEPARATOR_PATTERN);
+ if (sep.isPresent() == false) {
+ p.pos = backtrack;
+ return Optional.empty();
+ }
+ String argument = parseArgument(p);
+ return Optional.of(new ExportPackages.Parameter(ext.get(), argument));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ /* parameters = parameter, { ';' parameter } */
+ private static Pattern PARAMETER_SEPARATOR_PATTERN = Pattern.compile("\\s*;\\s*");
+
+ private static List<ExportPackages.Parameter> parseParameters(ParsingContext p) {
+ List<ExportPackages.Parameter> params = new ArrayList<>();
+ boolean wantMore = true;
+ do {
+ Optional<ExportPackages.Parameter> param = parseParameter(p);
+ if (param.isPresent()) {
+ params.add(param.get());
+ wantMore = p.regexp(PARAMETER_SEPARATOR_PATTERN).isPresent();
+ } else {
+ wantMore = false;
+ }
+ } while (wantMore);
+
+ return params;
+ }
+
+ /* packageName = ident, { '.', ident } */
+ private static Optional<String> parsePackageName(ParsingContext p) {
+ StringBuilder ret = new StringBuilder();
+
+ boolean wantMore = true;
+ do {
+ Optional<String> ident = parseIdent(p);
+ if (ident.isPresent()) {
+ ret.append(ident.get());
+ Optional<String> separator = p.exactly(".");
+ if (separator.isPresent()) {
+ ret.append(separator.get());
+ wantMore = true;
+ } else {
+ wantMore = false;
+ }
+ } else {
+ wantMore = false;
+ }
+ } while (wantMore);
+
+ if (ret.length() > 0) {
+ return Optional.of(ret.toString());
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ /* export = packageName, [ ';', ( parameters | export ) ] */
+ private static ExportPackages.Export parseExport(ParsingContext p) {
+ List<String> exports = new ArrayList<>();
+
+ boolean wantMore = true;
+ do {
+ if (exports.isEmpty() == false) { // second+ iteration
+ List<ExportPackages.Parameter> params = parseParameters(p);
+ if (params.isEmpty() == false) {
+ return new ExportPackages.Export(exports, params);
+ }
+ }
+
+ Optional<String> packageName = parsePackageName(p);
+ if (packageName.isPresent()) {
+ exports.add(packageName.get());
+ } else {
+ p.fail(exports.isEmpty() ? "Expected a package name" : "Expected either a package name or a parameter list");
+ }
+
+ wantMore = p.regexp(PARAMETER_SEPARATOR_PATTERN).isPresent();
+ } while (wantMore);
+
+ return new ExportPackages.Export(exports, new ArrayList<>());
+ }
+
+ /* exportPackage = export, { ',', export } */
+ private static Pattern EXPORT_SEPARATOR_PATTERN = Pattern.compile("\\s*,\\s*");
+
+ private static List<ExportPackages.Export> parseExportPackage(ParsingContext p) {
+ List<ExportPackages.Export> exports = new ArrayList<>();
+
+ boolean wantMore = true;
+ do {
+ ExportPackages.Export export = parseExport(p);
+ if (export.getPackageNames().isEmpty()) {
+ wantMore = false;
+ } else {
+ exports.add(export);
+ wantMore = p.regexp(EXPORT_SEPARATOR_PATTERN).isPresent();
+ }
+ } while (wantMore);
+
+ return exports;
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ExportPackages.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ExportPackages.java
new file mode 100644
index 00000000000..253e0727050
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ExportPackages.java
@@ -0,0 +1,70 @@
+// 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.osgi;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class ExportPackages {
+ public static class Export {
+ private final List<String> packageNames;
+ private final List<Parameter> parameters;
+
+ public Export(List<String> packageNames, List<Parameter> parameters) {
+ this.packageNames = packageNames;
+ this.parameters = parameters;
+ }
+
+ public Optional<String> version() {
+ for (Parameter par : parameters) {
+ if ("version".equals(par.getName())) {
+ return Optional.of(par.getValue());
+ }
+ }
+ return Optional.empty();
+ }
+
+ public List<String> getPackageNames() {
+ return packageNames;
+ }
+
+ public List<Parameter> getParameters() {
+ return parameters;
+ }
+ }
+
+ public static class Parameter {
+ private final String name;
+ private final String value;
+
+ public Parameter(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+ }
+
+ public static Map<String, Export> exportsByPackageName(Collection<Export> exports) {
+ Map<String, Export> ret = new HashMap<>();
+ for (Export export : exports) {
+ for (String packageName : export.getPackageNames()) {
+ //ensure that earlier exports of a package overrides later exports.
+ ret.computeIfAbsent(packageName, ign -> export);
+ }
+ }
+ return ret;
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ImportPackages.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ImportPackages.java
new file mode 100644
index 00000000000..b58248ec4a6
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/osgi/ImportPackages.java
@@ -0,0 +1,97 @@
+// 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.osgi;
+
+import com.google.common.collect.Sets;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class ImportPackages {
+ public static final int INFINITE_VERSION = 99999;
+ private static final String GUAVA_BASE_PACKAGE = "com.google.common";
+
+ public static class Import {
+ private final String packageName;
+ private final List<Integer> versionNumber;
+
+ public Import(String packageName, Optional<String> version) {
+ this.packageName = packageName;
+ this.versionNumber = new ArrayList<>();
+
+ if (version.isPresent()) {
+ try {
+ Arrays.stream(version.get().split("\\.")).map(Integer::parseInt).limit(3).forEach(this.versionNumber::add);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ String.format("Invalid version number '%s' for package '%s'.", version.get(), packageName), e);
+ }
+ }
+ }
+
+ public Optional<Integer> majorVersion() {
+ if (versionNumber.size() >= 1) {
+ return Optional.of(versionNumber.get(0));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ public String packageName() {
+ return packageName;
+ }
+
+ public String version() {
+ return versionNumber.stream().map(Object::toString).collect(Collectors.joining("."));
+ }
+
+ // TODO: Detecting guava packages should be based on Bundle-SymbolicName, not package name.
+ public Optional<String> importVersionRange() {
+ if (versionNumber.isEmpty()) {
+ return Optional.empty();
+ } else {
+ int upperLimit = isGuavaPackage() ? INFINITE_VERSION // guava increases major version for each release
+ : versionNumber.get(0) + 1;
+ return Optional.of(String.format("[%s,%d)", version(), upperLimit));
+ }
+ }
+
+ public boolean isGuavaPackage() {
+ return packageName.equals(GUAVA_BASE_PACKAGE) || packageName.startsWith(GUAVA_BASE_PACKAGE + ".");
+ }
+
+ public String asOsgiImport() {
+ return packageName + importVersionRange().map(version -> ";version=\"" + version + '"').orElse("");
+ }
+ }
+
+ public static Map<String, Import> calculateImports(Set<String> referencedPackages, Set<String> implementedPackages,
+ Map<String, ExportPackages.Export> exportedPackages) {
+ Map<String, Import> ret = new HashMap<>();
+ for (String undefinedPackage : Sets.difference(referencedPackages, implementedPackages)) {
+ ExportPackages.Export export = exportedPackages.get(undefinedPackage);
+ if (export != null) {
+ ret.put(undefinedPackage, new Import(undefinedPackage, version(export)));
+ }
+ }
+ return ret;
+ }
+
+ private static Optional<String> version(ExportPackages.Export export) {
+ for (ExportPackages.Parameter param : export.getParameters()) {
+ if ("version".equals(param.getName())) {
+ return Optional.of(param.getValue());
+ }
+ }
+ return Optional.empty();
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/Files.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/Files.java
new file mode 100644
index 00000000000..bcd5d3768f3
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/Files.java
@@ -0,0 +1,30 @@
+// 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.util;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.stream.Stream;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class Files {
+ public static Stream<File> allDescendantFiles(File file) {
+ if (file.isFile()) {
+ return Stream.of(file);
+ } else if (file.isDirectory()) {
+ return Stream.of(file.listFiles()).flatMap(Files::allDescendantFiles);
+ } else {
+ return Stream.empty();
+ }
+ }
+
+ public static <T> T withFileOutputStream(File file, ThrowingFunction<FileOutputStream, T> f) {
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ return f.apply(fos);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/IO.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/IO.java
new file mode 100644
index 00000000000..a1e313b920b
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/IO.java
@@ -0,0 +1,41 @@
+// 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.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+
+/**
+ * Utility methods relating to IO
+ *
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class IO {
+ public static <T> T withFileInputStream(File file, ThrowingFunction<FileInputStream, T> f) {
+ try (FileInputStream fis = new FileInputStream(file)) {
+ return f.apply(fis);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Creates a new file and all its parent directories, and provides a file output stream to the file.
+ */
+ public static <T> T withFileOutputStream(File file, ThrowingFunction<OutputStream, T> f) {
+ makeDirectoriesRecursive(file.getParentFile());
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ return f.apply(fos);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void makeDirectoriesRecursive(File file) {
+ if (!file.mkdirs() && !file.isDirectory()) {
+ throw new RuntimeException("Could not create directory " + file.getPath());
+ }
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/JarFiles.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/JarFiles.java
new file mode 100644
index 00000000000..398b2f5a72a
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/JarFiles.java
@@ -0,0 +1,36 @@
+// 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.util;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Optional;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class JarFiles {
+ public static <T> T withJarFile(File file, ThrowingFunction<JarFile, T> action) {
+ try (JarFile jar = new JarFile(file)) {
+ return action.apply(jar);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static <T> T withInputStream(ZipFile zipFile, ZipEntry zipEntry, ThrowingFunction<InputStream, T> action) {
+ try (InputStream is = zipFile.getInputStream(zipEntry)) {
+ return action.apply(is);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Optional<Manifest> getManifest(File jarFile) {
+ return withJarFile(jarFile, jar -> Optional.ofNullable(jar.getManifest()));
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/Maps.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/Maps.java
new file mode 100644
index 00000000000..5aa14402d4e
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/Maps.java
@@ -0,0 +1,31 @@
+// 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.util;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiFunction;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class Maps {
+ public static <K, V> Map<K, V> combine(Map<K, V> left, Map<K, V> right, BiFunction<V, V, V> combiner) {
+ Map<K, V> ret = new HashMap<>();
+ Set<K> keysRight = new HashSet<>(right.keySet());
+
+ left.forEach((k, v) -> {
+ if (keysRight.contains(k)) {
+ ret.put(k, combiner.apply(v, right.get(k)));
+ keysRight.remove(k);
+ } else {
+ ret.put(k, v);
+ }
+ });
+ keysRight.forEach(k -> ret.put(k, right.get(k)));
+
+ return ret;
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/Strings.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/Strings.java
new file mode 100644
index 00000000000..15bdfb153ad
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/Strings.java
@@ -0,0 +1,26 @@
+// 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.util;
+
+import java.util.Optional;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class Strings {
+ public static String replaceEmptyString(String s, String replacement) {
+ if (s == null || s.isEmpty()) {
+ return replacement;
+ } else {
+ return s;
+ }
+ }
+
+ public static Optional<String> noneIfEmpty(String s) {
+ if (s == null || s.isEmpty()) {
+ return Optional.empty();
+ } else {
+ return Optional.of(s);
+ }
+ }
+} \ No newline at end of file
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/ThrowingFunction.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/ThrowingFunction.java
new file mode 100644
index 00000000000..9ca64aabd73
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/util/ThrowingFunction.java
@@ -0,0 +1,11 @@
+// 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.util;
+
+/* Equivalent to java.util.function.Function, but allows throwing of Exceptions */
+
+/**
+ * @author ollivir
+ */
+public interface ThrowingFunction<T, U> {
+ U apply(T input) throws Exception;
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/AnalyzeBundle.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/AnalyzeBundle.scala
deleted file mode 100644
index 1b3979476bd..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/AnalyzeBundle.scala
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.bundle
-
-import java.io.File
-import java.util.jar.{Manifest => JarManifest}
-
-import com.yahoo.container.plugin.osgi.ExportPackageParser
-import com.yahoo.container.plugin.osgi.ExportPackages.Export
-import com.yahoo.container.plugin.util.JarFiles
-
-
-/**
- * @author tonytv
- */
-object AnalyzeBundle {
- case class PublicPackages(exports : List[Export], globals : List[String])
-
- def publicPackagesAggregated(jarFiles : Iterable[File]) = aggregate(jarFiles map {publicPackages(_)})
-
- def aggregate(publicPackagesList : Iterable[PublicPackages]) =
- (PublicPackages(List(), List()) /: publicPackagesList) { (a,b) =>
- PublicPackages(a.exports ++ b.exports, a.globals ++ b.globals)
- }
-
- def publicPackages(jarFile: File): PublicPackages = {
- try {
-
- (for {
- manifest <- JarFiles.getManifest(jarFile)
- if isOsgiManifest(manifest)
- } yield PublicPackages(parseExports(manifest), parseGlobals(manifest))).
- getOrElse(PublicPackages(List(), List()))
-
- } catch {
- case e : Exception => throw new RuntimeException("Invalid manifest in bundle '%s'".format(jarFile.getPath), e)
- }
- }
-
- def bundleSymbolicName(jarFile: File): Option[String] = {
- JarFiles.getManifest(jarFile).flatMap(getBundleSymbolicName)
- }
-
- private def parseExportsFromAttribute(manifest : JarManifest, attributeName : String) = {
- (for (export <- getMainAttributeValue(manifest, attributeName)) yield
- ExportPackageParser.parseAll(export) match {
- case noSuccess: ExportPackageParser.NoSuccess => throw new RuntimeException(
- "Failed parsing %s: %s".format(attributeName, noSuccess))
- case success => success.get
- }).
- getOrElse(List())
- }
-
- private def parseExports = parseExportsFromAttribute(_ : JarManifest, "Export-Package")
-
- private def parseGlobals(manifest : JarManifest) = {
- //TODO: Use separate parser for global packages.
- val globals = parseExportsFromAttribute(manifest, "Global-Package")
-
- if (globals map {_.parameters} exists {!_.isEmpty}) {
- throw new RuntimeException("Parameters not valid for Global-Package.")
- }
-
- globals flatMap {_.packageNames}
- }
-
- private def getMainAttributeValue(manifest: JarManifest, name: String): Option[String] =
- Option(manifest.getMainAttributes.getValue(name))
-
- private def isOsgiManifest = getBundleSymbolicName(_: JarManifest).isDefined
-
- private def getBundleSymbolicName = getMainAttributeValue(_: JarManifest, "Bundle-SymbolicName")
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/TransformExportPackages.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/TransformExportPackages.scala
deleted file mode 100644
index f924b12c539..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/TransformExportPackages.scala
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.bundle
-
-import java.io.File
-import com.yahoo.container.plugin.osgi.ExportPackages.{Export, Parameter}
-import com.yahoo.container.plugin.osgi.ExportPackages.Export
-
-/**
- * @author tonytv
- */
-object TransformExportPackages extends App {
- def replaceVersions(exports: List[Export], newVersion: String): List[Export] = {
- mapParameters(exports) { parameters =>
- parameters map replaceVersion(newVersion)
- }
- }
-
- def removeUses(exports: List[Export]): List[Export] = {
- mapParameters(exports) { parameters =>
- parameters filter {_.name != "uses"}
- }
- }
-
- def mapParameters(exports: List[Export])(f: List[Parameter] => List[Parameter]): List[Export] = {
- exports map { case Export(packageNames: List[String], parameters: List[Parameter]) =>
- Export(packageNames, f(parameters))
- }
- }
-
- private def replaceVersion(newVersion: String)(parameter: Parameter) = {
- parameter match {
- case Parameter("version", _) => Parameter("version", newVersion)
- case other => other
- }
- }
-
- def toExportPackageProperty(exports: List[Export]): String = {
- val exportPackages =
- exports map { case Export(packageNames: List[String], parameters: List[Parameter]) =>
- val parameterString = nameEqualsValue(parameters)
- (packageNames ++ parameterString) mkString ";"
- }
-
- exportPackages mkString ","
- }
-
- private def nameEqualsValue(parameters: List[Parameter]) = {
- parameters map { case Parameter(name, value) =>
- s"$name=${quote(value)}"
- }
- }
-
- def quote(s: String) = '"' + s + '"'
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Analyze.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Analyze.scala
deleted file mode 100644
index 2f2d034679d..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Analyze.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.classanalysis
-
-import org.objectweb.asm._
-import java.io.{InputStream, File}
-import com.yahoo.container.plugin.util.IO.withFileInputStream
-
-/**
- * Main entry point for class analysis
- * @author tonytv
- */
-object Analyze {
- def analyzeClass(classFile : File) : ClassFileMetaData = {
- try {
- withFileInputStream(classFile) { fileInputStream =>
- analyzeClass(fileInputStream)
- }
- } catch {
- case e : RuntimeException => throw new RuntimeException("An error occurred when analyzing " + classFile.getPath, e)
- }
- }
-
- def analyzeClass(inputStream : InputStream) : ClassFileMetaData = {
- val visitor = new AnalyzeClassVisitor()
- new ClassReader(inputStream).accept(visitor, ClassReader.SKIP_DEBUG)
- visitor.result
- }
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.scala
deleted file mode 100644
index 539684f2024..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.scala
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.classanalysis
-
-import org.objectweb.asm._
-import com.yahoo.osgi.annotation.{ExportPackage, Version}
-import collection.mutable
-
-/**
- * Picks up classes used in class files.
- * @author tonytv
- */
-private class AnalyzeClassVisitor extends ClassVisitor(Opcodes.ASM6) with AnnotationVisitorTrait with AttributeVisitorTrait {
- private var name : String = null
- protected val imports : ImportsSet = mutable.Set()
- protected var exportPackageAnnotation: Option[ExportPackageAnnotation] = None
-
-
- override def visitAttribute(attribute: Attribute): Unit = super.visitAttribute(attribute)
-
- override def visitMethod(access: Int, name: String, desc: String, signature: String,
- exceptions: Array[String]): MethodVisitor = {
-
- imports ++= (Type.getReturnType(desc) +: Type.getArgumentTypes(desc)).flatMap(getClassName)
-
- imports ++= Option(exceptions) getOrElse(Array()) flatMap internalNameToClassName
-
- AnalyzeSignatureVisitor.analyzeMethod(signature, this)
- new AnalyzeMethodVisitor(this)
- }
-
- override def visitField(access: Int, name: String, desc: String, signature: String, value: AnyRef): FieldVisitor = {
- imports ++= getClassName(Type.getType(desc)).toList
-
- AnalyzeSignatureVisitor.analyzeField(signature, this)
- new FieldVisitor(Opcodes.ASM6) with SubVisitorTrait with AttributeVisitorTrait with AnnotationVisitorTrait {
- val analyzeClassVisitor = AnalyzeClassVisitor.this
-
- override def visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor = super.visitAnnotation(desc, visible)
- override def visitAttribute(attribute: Attribute): Unit = super.visitAttribute(attribute)
- override def visitEnd(): Unit = super.visitEnd()
- }
- }
-
- override def visit(version: Int, access: Int, name: String, signature: String, superName: String, interfaces: Array[String]) {
- this.name = internalNameToClassName(name).get
-
- imports ++= (superName +: interfaces) flatMap internalNameToClassName
- AnalyzeSignatureVisitor.analyzeClass(signature, this)
- }
-
- override def visitInnerClass(name: String, outerName: String, innerName: String, access: Int) {}
- override def visitOuterClass(owner: String, name: String, desc: String) {}
- override def visitSource(source: String, debug: String) {}
- override def visitEnd() {}
-
- def addImports(imports: TraversableOnce[String]) {
- this.imports ++= imports
- }
-
- override def visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor = {
- if (Type.getType(desc).getClassName == classOf[ExportPackage].getName) {
- visitExportPackage()
- } else {
- super.visitAnnotation(desc, visible)
- }
- }
-
- def visitExportPackage(): AnnotationVisitor = {
- def defaultVersionValue[T](name: String) = classOf[Version].getMethod(name).getDefaultValue().asInstanceOf[T]
-
- new AnnotationVisitor(Opcodes.ASM6) {
- var major: Int = defaultVersionValue("major")
- var minor: Int = defaultVersionValue("minor")
- var micro: Int = defaultVersionValue("micro")
- var qualifier: String = defaultVersionValue("qualifier")
-
- override def visit(name: String, value: AnyRef) {
- def valueAsInt = value.asInstanceOf[Int]
-
- name match {
- case "major" => major = valueAsInt
- case "minor" => minor = valueAsInt
- case "micro" => micro = valueAsInt
- case "qualifier" => qualifier = value.asInstanceOf[String]
- }
- }
-
- override def visitEnd() {
- exportPackageAnnotation = Some(ExportPackageAnnotation(major, minor, micro, qualifier))
- }
-
- override def visitEnum(name: String, desc: String, value: String) {}
- override def visitArray(name: String): AnnotationVisitor = this
- override def visitAnnotation(name: String, desc: String): AnnotationVisitor = this
- }
- }
-
- def result = {
- assert(!imports.contains("int"))
- new ClassFileMetaData(name, imports.toSet, exportPackageAnnotation)
- }
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.scala
deleted file mode 100644
index a8032b6a912..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.scala
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.classanalysis
-
-import org.objectweb.asm._
-
-/**
- * Picks up classes used in method bodies.
- * @author tonytv
- */
-private class AnalyzeMethodVisitor(val analyzeClassVisitor : AnalyzeClassVisitor)
- extends MethodVisitor(Opcodes.ASM6) with AnnotationVisitorTrait with AttributeVisitorTrait with SubVisitorTrait {
-
-
- override def visitParameterAnnotation(parameter: Int, desc: String, visible: Boolean): AnnotationVisitor = super.visitParameterAnnotation(parameter, desc, visible)
- override def visitAnnotationDefault(): AnnotationVisitor = super.visitAnnotationDefault()
- override def visitAttribute(attribute: Attribute): Unit = super.visitAttribute(attribute)
- override def visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor = super.visitAnnotation(desc, visible)
- override def visitEnd(): Unit = super.visitEnd()
-
- override def visitMultiANewArrayInsn(desc: String, dims: Int) {
- imports ++= getClassName(Type.getType(desc)).toList
- }
-
-
- override def visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
- imports ++= internalNameToClassName(owner)
- imports ++= Type.getArgumentTypes(desc).flatMap(getClassName)
- imports ++= getClassName(Type.getReturnType(desc))
- }
-
- override def visitFieldInsn(opcode: Int, owner: String, name: String, desc: String) {
- imports ++= internalNameToClassName(owner) ++ getClassName(Type.getType(desc)).toList
-
- }
-
- override def visitTypeInsn(opcode: Int, `type` : String) {
- imports ++= internalNameToClassName(`type`)
- }
-
- override def visitTryCatchBlock(start: Label, end: Label, handler: Label, `type` : String) {
- if (`type` != null) //null means finally block
- imports ++= internalNameToClassName(`type`)
- }
-
- override def visitLocalVariable(name: String, desc: String, signature: String, start: Label, end: Label, index: Int) {
- imports += Type.getType(desc).getClassName
- }
-
- override def visitLdcInsn(constant: AnyRef) {
- constant match {
- case typeConstant: Type => imports ++= getClassName(typeConstant)
- case _ =>
- }
- }
-
- override def visitInvokeDynamicInsn(name: String, desc: String, bootstrapMethod: Handle, bootstrapMethodArgs: AnyRef*) {
- bootstrapMethodArgs.foreach {
- case typeConstant: Type =>
- imports ++= getClassName(typeConstant)
- case handle: Handle =>
- imports ++= internalNameToClassName(handle.getOwner)
- imports ++= Type.getArgumentTypes(desc).flatMap(getClassName)
- case _ : Number =>
- case _ : String =>
- case other => throw new AssertionError(s"Unexpected type ${other.getClass} with value '$other'")
- }
- }
-
- override def visitMaxs(maxStack: Int, maxLocals: Int) {}
- override def visitLineNumber(line: Int, start: Label) {}
- //only for debugging
- override def visitLookupSwitchInsn(dflt: Label, keys: Array[Int], labels: Array[Label]) {}
-
-
- override def visitTableSwitchInsn(min: Int, max: Int, dflt: Label, labels: Label*): Unit = super.visitTableSwitchInsn(min, max, dflt, labels: _*)
- override def visitIincInsn(`var` : Int, increment: Int) {}
- override def visitLabel(label: Label) {}
- override def visitJumpInsn(opcode: Int, label: Label) {}
- override def visitVarInsn(opcode: Int, `var` : Int) {}
- override def visitIntInsn(opcode: Int, operand: Int) {}
- override def visitInsn(opcode: Int) {}
- override def visitFrame(`type` : Int, nLocal: Int, local: Array[AnyRef], nStack: Int, stack: Array[AnyRef]) {}
- override def visitCode() {}
-}
-
-
-
-
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.scala
deleted file mode 100644
index 5bb8304cf1e..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.scala
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.classanalysis
-
-import org.objectweb.asm.Opcodes
-import org.objectweb.asm.signature.{SignatureReader, SignatureVisitor}
-
-
-/**
- * @author tonytv
- */
-
-private class AnalyzeSignatureVisitor(val analyzeClassVisitor: AnalyzeClassVisitor)
- extends SignatureVisitor(Opcodes.ASM6)
- with SubVisitorTrait {
-
-
- override def visitEnd(): Unit = super.visitEnd()
-
- override def visitClassType(className: String) {
- imports ++= internalNameToClassName(className)
- }
-
- override def visitFormalTypeParameter(name: String) {}
-
- override def visitClassBound() = this
-
- override def visitInterfaceBound() = this
-
- override def visitSuperclass() = this
-
- override def visitInterface() = this
-
- override def visitParameterType() = this
-
- override def visitReturnType() = this
-
- override def visitExceptionType() = this
-
- override def visitBaseType(descriptor: Char) {}
-
- override def visitTypeVariable(name: String) {}
-
- override def visitArrayType() = this
-
- override def visitInnerClassType(name: String) {}
-
- override def visitTypeArgument() {}
-
- override def visitTypeArgument(wildcard: Char) = this
-}
-
-
-object AnalyzeSignatureVisitor {
- def analyzeClass(signature: String, analyzeClassVisitor: AnalyzeClassVisitor) {
- if (signature != null) {
- new SignatureReader(signature).accept(new AnalyzeSignatureVisitor(analyzeClassVisitor))
- }
- }
-
- def analyzeMethod(signature: String, analyzeClassVisitor: AnalyzeClassVisitor) {
- analyzeClass(signature, analyzeClassVisitor)
- }
-
- def analyzeField(signature: String, analyzeClassVisitor: AnalyzeClassVisitor) {
- if (signature != null)
- new SignatureReader(signature).acceptType(new AnalyzeSignatureVisitor(analyzeClassVisitor))
- }
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnnotationVisitorTrait.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnnotationVisitorTrait.scala
deleted file mode 100644
index 0bf6ee4a6b4..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnnotationVisitorTrait.scala
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.classanalysis
-
-import org.objectweb.asm.{Opcodes, AnnotationVisitor, Type}
-
-/**
- * Picks up classes used in annotations.
- * @author tonytv
- */
-private trait AnnotationVisitorTrait {
- protected val imports: ImportsSet
-
- def visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor = {
- imports ++= getClassName(Type.getType(desc)).toList
-
- visitAnnotationDefault()
- }
-
- def visitAnnotationDefault(): AnnotationVisitor =
- new AnnotationVisitor(Opcodes.ASM6) {
- override def visit(name: String, value: AnyRef) {}
-
- override def visitEnum(name: String, desc: String, value: String) {
- imports ++= getClassName(Type.getType(desc)).toList
- }
-
- override def visitArray(name: String): AnnotationVisitor = this
-
- override def visitAnnotation(name: String, desc: String): AnnotationVisitor = {
- imports ++= getClassName(Type.getType(desc)).toList
- this
- }
-
- override def visitEnd() {}
- }
-
- def visitParameterAnnotation(parameter: Int, desc: String, visible: Boolean): AnnotationVisitor =
- visitAnnotation(desc, visible)
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AttributeVisitorTrait.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AttributeVisitorTrait.scala
deleted file mode 100644
index 454bea99f29..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AttributeVisitorTrait.scala
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.classanalysis
-
-import org.objectweb.asm.{Type, Attribute}
-
-/**
- * @author tonytv
- */
-private trait AttributeVisitorTrait {
- protected val imports: ImportsSet
-
- def visitAttribute(attribute: Attribute) {
- imports ++= getClassName(Type.getObjectType(attribute.`type`)).toList
- }
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.scala
deleted file mode 100644
index fb395749895..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.scala
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.classanalysis
-
-/**
- * The result of analyzing a .class file.
- * @author tonytv
- */
-sealed case class ClassFileMetaData(name:String,
- referencedClasses : Set[String],
- exportPackage : Option[ExportPackageAnnotation])
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.scala
deleted file mode 100644
index f9dc459fcc7..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.scala
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.classanalysis
-
-import com.yahoo.container.plugin.util.Strings
-
-/**
- * @author tonytv
- */
-case class ExportPackageAnnotation(major: Int, minor: Int, micro: Int, qualifier: String) {
- requireNonNegative(major, "major")
- requireNonNegative(minor, "minor")
- requireNonNegative(micro, "micro")
- require(qualifier.matches("""(\p{Alpha}|\p{Digit}|_|-)*"""),
- exportPackageError("qualifier must follow the format (alpha|digit|'_'|'-')* but was '%s'.".format(qualifier)))
-
-
- private def requireNonNegative(i: Int, fieldName: String) {
- require(i >= 0, exportPackageError("%s must be non-negative but was %d.".format(fieldName, i)))
- }
-
- private def exportPackageError(s: String) = "ExportPackage anntotation: " + s
-
- def osgiVersion : String = (List(major, minor, micro) ++ Strings.noneIfEmpty(qualifier)).mkString(".")
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/PackageTally.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/PackageTally.scala
deleted file mode 100644
index 2d2460cc9fd..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/PackageTally.scala
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.classanalysis
-
-import com.yahoo.container.plugin.util.Maps
-
-/**
- *
- * @author tonytv
- */
-final class PackageTally (private val definedPackagesMap : Map[String, Option[ExportPackageAnnotation]],
- referencedPackagesUnfiltered : Set[String]) {
-
- val referencedPackages = referencedPackagesUnfiltered diff definedPackages
-
- def definedPackages = definedPackagesMap.keySet
-
- def exportedPackages = definedPackagesMap collect { case (name, Some(export)) => (name, export) }
-
- /**
- * Represents the classes for two package tallies that are deployed as a single unit.
- *
- * ExportPackageAnnotations from this has precedence over the other.
- */
- def combine(other: PackageTally): PackageTally = {
- new PackageTally(
- Maps.combine(definedPackagesMap, other.definedPackagesMap)(_ orElse _),
- referencedPackages ++ other.referencedPackages)
- }
-}
-
-
-object PackageTally {
- def fromAnalyzedClassFiles(analyzedClassFiles : Seq[ClassFileMetaData]) : PackageTally = {
- combine(
- for (metaData <- analyzedClassFiles)
- yield {
- new PackageTally(
- Map(Packages.packageName(metaData.name) -> metaData.exportPackage),
- metaData.referencedClasses.map(Packages.packageName))
- })
- }
-
- def combine(packageTallies : Iterable[PackageTally]) : PackageTally = (empty /: packageTallies)(_.combine(_))
-
- val empty : PackageTally = new PackageTally(Map(), Set())
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Packages.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Packages.scala
deleted file mode 100644
index 517ef2626f9..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Packages.scala
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.classanalysis
-
-/**
- * Utility methods related to packages.
- * @author tonytv
- */
-object Packages {
- case class PackageMetaData(definedPackages: Set[String], referencedExternalPackages: Set[String])
-
- def packageName(fullClassName: String) = {
- def nullIfNotFound(index : Int) = if (index == -1) 0 else index
-
- fullClassName.substring(0, nullIfNotFound(fullClassName.lastIndexOf(".")))
- }
-
-
-
- def analyzePackages(allClasses: Seq[ClassFileMetaData]): PackageMetaData = {
- val (definedPackages, referencedClasses) =
- (for (classMetaData <- allClasses)
- yield (packageName(classMetaData.name), classMetaData.referencedClasses.map(packageName))).
- unzip
-
- PackageMetaData(definedPackages.toSet, referencedClasses.flatten.toSet diff definedPackages.toSet)
- }
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/SubVisitorTrait.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/SubVisitorTrait.scala
deleted file mode 100644
index 06ccfa7b88c..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/SubVisitorTrait.scala
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.classanalysis
-
-import collection.mutable
-
-/**
- * A visitor that's run for sub construct of a class
- * and forwards all its imports the the owning ClassVisitor at the end.
- * @author tonytv
- */
-private trait SubVisitorTrait {
- val analyzeClassVisitor : AnalyzeClassVisitor
-
- val imports : ImportsSet = mutable.Set()
-
- def visitEnd() {
- analyzeClassVisitor.addImports(imports)
- }
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/package.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/package.scala
deleted file mode 100644
index 631884c58e3..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/package.scala
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin
-
-import org.objectweb.asm.Type
-import collection.mutable
-
-package object classanalysis {
- type ImportsSet = mutable.Set[String]
-
- def internalNameToClassName(internalClassName: String) : Option[String] = {
- internalClassName match {
- case null => None
- case _ => getClassName(Type.getObjectType(internalClassName))
- }
- }
-
- def getClassName(aType: Type): Option[String] = {
- import Type._
-
- aType.getSort match {
- case ARRAY => getClassName(aType.getElementType)
- case OBJECT => Some(aType.getClassName)
- case _ => None
- }
- }
-}
-
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/Artifacts.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/Artifacts.scala
deleted file mode 100644
index 8370f8c615e..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/Artifacts.scala
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2017 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 scala.collection.JavaConverters._
-
-/**
- * @author tonytv
- */
-object Artifacts {
- def getArtifacts(project : MavenProject) = {
- type artifactSet = java.util.Set[Artifact]
- val artifacts = project.getArtifacts.asInstanceOf[artifactSet].asScala.groupBy(_.getScope)
-
- def isTypeJar(artifact : Artifact) = artifact.getType == "jar"
- def getByScope(scope: String) =
- artifacts.getOrElse(scope, Iterable.empty).partition(isTypeJar)
-
-
- val (jarArtifactsToInclude, nonJarArtifactsToInclude) = getByScope(Artifact.SCOPE_COMPILE)
- val (jarArtifactsProvided, nonJarArtifactsProvided) = getByScope(Artifact.SCOPE_PROVIDED)
-
- (jarArtifactsToInclude, jarArtifactsProvided, nonJarArtifactsToInclude ++ nonJarArtifactsProvided)
- }
-
- def getArtifactsToInclude(project: MavenProject) = getArtifacts(project)._1
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/AssembleContainerPluginMojo.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/AssembleContainerPluginMojo.scala
deleted file mode 100644
index 4104e349208..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/AssembleContainerPluginMojo.scala
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2017 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 java.io.File
-import java.nio.channels.Channels
-import java.util.jar.JarFile
-import java.util.zip.ZipEntry
-
-import com.yahoo.container.plugin.util.{Files, JarFiles}
-import org.apache.maven.archiver.{MavenArchiveConfiguration, MavenArchiver}
-import org.apache.maven.execution.MavenSession
-import org.apache.maven.plugin.AbstractMojo
-import org.apache.maven.plugins.annotations.{Mojo, Parameter, ResolutionScope}
-import org.apache.maven.project.MavenProject
-import org.codehaus.plexus.archiver.jar.JarArchiver
-
-import scala.collection.JavaConverters._
-
-/**
- * @author tonytv
- */
-@Mojo(name = "assemble-container-plugin", requiresDependencyResolution = ResolutionScope.COMPILE, threadSafe = true)
-class AssembleContainerPluginMojo extends AbstractMojo {
- object withDependencies
- object withoutDependencies
-
- @Parameter(defaultValue = "${project}")
- var project: MavenProject = null
-
- @Parameter(defaultValue = "${session}", readonly = true, required = true)
- var session: MavenSession = null
-
- @Parameter
- var archiveConfiguration: MavenArchiveConfiguration = new MavenArchiveConfiguration
-
- @Parameter(alias = "UseCommonAssemblyIds", defaultValue = "false")
- var useCommonAssemblyIds: Boolean = false
-
-
- def execute() {
- val jarSuffixes =
- if (useCommonAssemblyIds) Map(withoutDependencies -> ".jar", withDependencies -> "-jar-with-dependencies.jar")
- else Map(withoutDependencies -> "-without-dependencies.jar", withDependencies -> "-deploy.jar")
-
- val jarFiles = jarSuffixes mapValues jarFileInBuildDirectory(build.getFinalName)
-
- //force recreating the archive
- archiveConfiguration.setForced(true)
- archiveConfiguration.setManifestFile(new File(new File(project.getBuild.getOutputDirectory), JarFile.MANIFEST_NAME))
-
- val jarWithoutDependencies = new JarArchiver()
- addClassesDirectory(jarWithoutDependencies)
- createArchive(jarFiles(withoutDependencies), jarWithoutDependencies)
- project.getArtifact.setFile(jarFiles(withoutDependencies))
-
- val jarWithDependencies = new JarArchiver()
- addClassesDirectory(jarWithDependencies)
- addDependencies(jarWithDependencies)
- createArchive(jarFiles(withDependencies), jarWithDependencies)
- }
-
- private def jarFileInBuildDirectory(name: String)(jarSuffix: String) = {
- new File(build.getDirectory, name + jarSuffix)
- }
-
- private def addClassesDirectory(jarArchiver: JarArchiver) {
- val classesDirectory = new File(build.getOutputDirectory)
- if (classesDirectory.isDirectory) {
- jarArchiver.addDirectory(classesDirectory)
- }
- }
-
- private def createArchive(jarFile: File, jarArchiver: JarArchiver) {
- val mavenArchiver = new MavenArchiver
- mavenArchiver.setArchiver(jarArchiver)
- mavenArchiver.setOutputFile(jarFile)
- mavenArchiver.createArchive(session, project, archiveConfiguration)
- }
-
- private def addDependencies(jarArchiver: JarArchiver) {
- Artifacts.getArtifactsToInclude(project).foreach { artifact =>
- if (artifact.getType == "jar") {
- jarArchiver.addFile(artifact.getFile, "dependencies/" + artifact.getFile.getName)
- copyConfigDefinitions(artifact.getFile, jarArchiver)
- }
- else
- getLog.warn("Unkown artifact type " + artifact.getType)
- }
- }
-
- private def copyConfigDefinitions(file: File, jarArchiver: JarArchiver) {
- JarFiles.withJarFile(file) { jarFile =>
- for {
- entry <- jarFile.entries().asScala
- name = entry.getName
- if name.startsWith("configdefinitions/") && name.endsWith(".def")
-
- } copyConfigDefinition(jarFile, entry, jarArchiver)
- }
- }
-
- private def copyConfigDefinition(jarFile: JarFile, entry: ZipEntry, jarArchiver: JarArchiver) {
- JarFiles.withInputStream(jarFile, entry) { input =>
- val defPath = entry.getName.replace("/", File.separator)
- val destinationFile = new File(project.getBuild.getOutputDirectory, defPath)
- destinationFile.getParentFile.mkdirs()
-
- Files.withFileOutputStream(destinationFile) { output =>
- output.getChannel.transferFrom(Channels.newChannel(input), 0, Long.MaxValue)
- }
- jarArchiver.addFile(destinationFile, entry.getName)
- }
- }
-
- private def build = project.getBuild
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateBundleClassPathMappingsMojo.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateBundleClassPathMappingsMojo.scala
deleted file mode 100644
index 2bd146a3920..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateBundleClassPathMappingsMojo.scala
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2017 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 java.io.File
-import java.nio.file.Paths
-
-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.plugins.annotations.{Mojo, Parameter, ResolutionScope}
-import org.apache.maven.project.MavenProject
-
-import scala.collection.JavaConverters._
-
-
-
-/**
- * Generates mapping from Bundle-SymbolicName to classpath elements, e.g
- * myBundle -> List(.m2/repository/com/mylib/Mylib.jar, myBundleProject/target/classes)
- * The mapping in stored in a json file.
- * @author tonytv
- */
-@Mojo(name = "generate-bundle-classpath-mappings", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, threadSafe = true)
-class GenerateBundleClassPathMappingsMojo extends AbstractMojo {
- @Parameter(defaultValue = "${project}")
- private var project: MavenProject = null
-
- //TODO: Combine with com.yahoo.container.plugin.mojo.GenerateOsgiManifestMojo.bundleSymbolicName
- @Parameter(alias = "Bundle-SymbolicName", defaultValue = "${project.artifactId}")
- private var bundleSymbolicName: String = 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 def execute(): Unit = {
- Preconditions.checkNotNull(bundleSymbolicName)
-
- val (embeddedArtifacts, providedJarArtifacts, _) = asLists(Artifacts.getArtifacts(project))
-
- val embeddedArtifactsFiles = embeddedArtifacts.map(_.getFile)
-
- val classPathElements = (outputDirectory +: embeddedArtifactsFiles).map(_.getAbsolutePath)
-
- val classPathMappings = new ProjectBundleClassPaths(
- new BundleClasspathMapping(bundleSymbolicName, classPathElements.asJava),
- providedJarArtifacts.flatMap(createDependencyClasspathMapping).asJava)
-
- ProjectBundleClassPaths.save(
- testOutputPath.resolve(ProjectBundleClassPaths.CLASSPATH_MAPPINGS_FILENAME),
- classPathMappings)
- }
-
- private def outputDirectory = new File(project.getBuild.getOutputDirectory)
- private def testOutputPath = 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.
- */
- def createDependencyClasspathMapping(artifact: Artifact): Option[BundleClasspathMapping] = {
- for (bundleSymbolicName <- bundleSymbolicNameForArtifact(artifact))
- yield new BundleClasspathMapping(bundleSymbolicName, List(artifact.getFile.getAbsolutePath).asJava)
- }
-
- def bundleSymbolicNameForArtifact(artifact: Artifact): Option[String] = {
- if (artifact.getFile.getName.endsWith(".jar")) AnalyzeBundle.bundleSymbolicName(artifact.getFile)
- else Some(artifact.getArtifactId) //Not the best heuristic. The other alternatives are parsing the pom file or
- //storing information in target/classes when building the provided bundles.
- }
-
- def asLists[A](tuple: (Iterable[A], Iterable[A], Iterable[A])) =
- (tuple._1.toList, tuple._2.toList, tuple._3.toList)
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.scala
deleted file mode 100644
index 67ce45ed7c6..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.scala
+++ /dev/null
@@ -1,286 +0,0 @@
-// Copyright 2017 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 java.io.File
-import java.util.jar.{Attributes, JarEntry, JarFile}
-import java.util.regex.Pattern
-
-import com.yahoo.container.plugin.bundle.AnalyzeBundle
-import com.yahoo.container.plugin.classanalysis.{Analyze, ExportPackageAnnotation, PackageTally}
-import com.yahoo.container.plugin.mojo.GenerateOsgiManifestMojo._
-import com.yahoo.container.plugin.osgi.ExportPackages
-import com.yahoo.container.plugin.osgi.ExportPackages.Export
-import com.yahoo.container.plugin.osgi.ImportPackages.Import
-import com.yahoo.container.plugin.osgi.{ExportPackageParser, ExportPackages, ImportPackages}
-import com.yahoo.container.plugin.util.Files.allDescendantFiles
-import com.yahoo.container.plugin.util.IO.withFileOutputStream
-import com.yahoo.container.plugin.util.Iteration.toStream
-import com.yahoo.container.plugin.util.JarFiles.{withInputStream, withJarFile}
-import com.yahoo.container.plugin.util.Strings
-import org.apache.maven.artifact.Artifact
-import org.apache.maven.plugin.{AbstractMojo, MojoExecutionException, MojoFailureException}
-import org.apache.maven.plugins.annotations.{Mojo, Parameter, ResolutionScope}
-import org.apache.maven.project.MavenProject
-
-import scala.collection.immutable.Map
-
-
-/**
- * @author tonytv
- */
-@Mojo(name = "generate-osgi-manifest", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true)
-class GenerateOsgiManifestMojo extends AbstractMojo {
-
- @Parameter(defaultValue = "${project}")
- var project: MavenProject = null
-
- @Parameter
- var discApplicationClass: String = null
-
- @Parameter
- var discPreInstallBundle: String = null
-
- @Parameter(alias = "Bundle-Version", defaultValue = "${project.version}")
- var bundleVersion: String = null
-
- @Parameter(alias = "Bundle-SymbolicName", defaultValue = "${project.artifactId}")
- var bundleSymbolicName: String = null
-
- @Parameter(alias = "Bundle-Activator")
- var bundleActivator: String = null
-
- @Parameter(alias = "X-JDisc-Privileged-Activator")
- var jdiscPrivilegedActivator: String = null
-
- @Parameter(alias = "X-Config-Models")
- var configModels: String = null
-
- @Parameter(alias = "Import-Package")
- var importPackage: String = null
-
- @Parameter(alias = "WebInfUrl")
- var webInfUrl: String = null
-
- @Parameter(alias = "Main-Class")
- var mainClass: String = null
-
- @Parameter(alias = "X-Jersey-Binding")
- var jerseyBinding: String = null
-
- case class PackageInfo(name : String, exportAnnotation : Option[ExportPackageAnnotation])
-
- def execute() {
- try {
- val (jarArtifactsToInclude, jarArtifactsProvided, nonJarArtifacts) = Artifacts.getArtifacts(project)
- warnOnUnsupportedArtifacts(nonJarArtifacts)
-
- val publicPackagesFromProvidedJars = AnalyzeBundle.publicPackagesAggregated(jarArtifactsProvided.map(_.getFile))
- val includedJarPackageTally = definedPackages(jarArtifactsToInclude)
-
- val projectPackageTally = analyzeProjectClasses()
-
- val pluginPackageTally = projectPackageTally.combine(includedJarPackageTally)
-
- warnIfPackagesDefinedOverlapsGlobalPackages(projectPackageTally.definedPackages ++ includedJarPackageTally.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.map(e => (e.packageNames, e.version getOrElse "")).toSet )
- }
-
- val calculatedImports = ImportPackages.calculateImports(
- pluginPackageTally.referencedPackages,
- pluginPackageTally.definedPackages,
- ExportPackages.exportsByPackageName(publicPackagesFromProvidedJars.exports))
-
- val manualImports = emptyToNone(importPackage) map getManualImports getOrElse Map()
-
- createManifestFile(new File(project.getBuild.getOutputDirectory),
- manifestContent(
- project,
- jarArtifactsToInclude,
- manualImports,
- (calculatedImports -- manualImports.keys).values.toSet,
- pluginPackageTally))
-
- } catch {
- case e: MojoFailureException => throw e
- case e: MojoExecutionException => throw e
- case e: Exception => throw new MojoExecutionException("Failed generating osgi manifest.", e)
- }
- }
-
- //TODO: Tell which dependency overlaps
- private def warnIfPackagesDefinedOverlapsGlobalPackages(internalPackages: Set[String], globalPackages: List[String]) {
- val overlap = internalPackages intersect globalPackages.toSet
- if (overlap.nonEmpty)
- throw new MojoExecutionException(
- "The following packages are both global and included in the bundle:\n%s".format(overlap map (" " + _) mkString ("\n")))
- }
-
-
- def osgiExportPackages(exportedPackages: Map[String, ExportPackageAnnotation]): Iterable[String] = {
- for ((name, annotation) <- exportedPackages)
- yield name + ";version=" + annotation.osgiVersion
- }
-
- def trimWhitespace(lines: Option[String]): String = {
- lines.getOrElse("").split(",").map(_.trim).mkString(",")
- }
-
- def manifestContent(project: MavenProject, jarArtifactsToInclude: Traversable[Artifact],
- manualImports: Map[String, Option[String]], imports : Set[Import],
- pluginPackageTally : PackageTally) = {
- Map[String, String](
- "Created-By" -> "vespa container maven plugin",
- "Bundle-ManifestVersion" -> "2",
- "Bundle-Name" -> project.getName,
- "Bundle-SymbolicName" -> bundleSymbolicName,
- "Bundle-Version" -> asBundleVersion(bundleVersion),
- "Bundle-Vendor" -> "Yahoo!",
- "Bundle-ClassPath" -> bundleClassPath(jarArtifactsToInclude),
- "Bundle-Activator" -> bundleActivator,
- "X-JDisc-Privileged-Activator" -> jdiscPrivilegedActivator,
- "Main-Class" -> mainClass,
- "X-JDisc-Application" -> discApplicationClass,
- "X-JDisc-Preinstall-Bundle" -> trimWhitespace(Option(discPreInstallBundle)),
- "X-Config-Models" -> configModels,
- "X-Jersey-Binding" -> jerseyBinding,
- "WebInfUrl" -> webInfUrl,
- "Import-Package" -> ((manualImports map asOsgiImport) ++ (imports map {_.asOsgiImport})).toList.sorted.mkString(","),
- "Export-Package" -> osgiExportPackages(pluginPackageTally.exportedPackages).toList.sorted.mkString(","))
- .filterNot { case (key, value) => value == null || value.isEmpty }
-
- }
-
- def asOsgiImport(importSpec: (String, Option[String])) = importSpec match {
- case (packageName, Some(version)) => packageName + ";version=" + quote(version)
- case (packageName, None) => packageName
- }
-
- def quote(s: String) = '"' + s + '"'
-
- def createManifestFile(outputDirectory: File, manifestContent: Map[String, String]) {
- val manifest = toManifest(manifestContent)
-
- withFileOutputStream(new File(outputDirectory, JarFile.MANIFEST_NAME)) {
- outputStream =>
- manifest.write(outputStream)
- }
- }
-
- def toManifest(manifestContent: Map[String, String]) = {
- val manifest = new java.util.jar.Manifest
- val mainAttributes = manifest.getMainAttributes
-
- mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0")
- for ((key, value) <- manifestContent)
- mainAttributes.putValue(key, value)
-
- manifest
- }
-
- private def bundleClassPath(artifactsToInclude : Traversable[Artifact]) =
- ("." +: artifactsToInclude.map(dependencyPath).toList).mkString(",")
-
- private def dependencyPath(artifact : Artifact) =
- "dependencies/" + artifact.getFile.getName
-
- private def asBundleVersion(projectVersion: String) = {
- require(projectVersion != null, "Missing project version.")
-
- val parts = projectVersion.split(Pattern.quote("-"), 2)
- val numericPart = parts.head.split('.').map(Strings.emptyStringTo("0")).padTo(3, "0").toList
-
- val majorMinorMicro = numericPart take 3
- majorMinorMicro.mkString(".")
- }
-
- private def warnOnUnsupportedArtifacts(nonJarArtifacts: Traversable[Artifact]) {
- val unsupportedArtifacts = nonJarArtifacts.toSet.filter(_.getType != "pom")
-
- for (artifact <- unsupportedArtifacts) {
- getLog.warn(s"Unsupported artifact '${artifact.getId}': Type '${artifact.getType}' is not supported. Please file a feature request.")
- }
- }
-
- private def analyzeProjectClasses() : PackageTally = {
- val outputDirectory = new File(project.getBuild.getOutputDirectory)
-
- val analyzedClasses = allDescendantFiles(outputDirectory).filter(file => isClassToAnalyze(file.getName)).
- map(Analyze.analyzeClass)
-
- PackageTally.fromAnalyzedClassFiles(analyzedClasses)
- }
-
- def definedPackages(jarArtifacts: Iterable[Artifact]) : PackageTally = {
- PackageTally.combine(
- for (jarArtifact <- jarArtifacts) yield {
- withJarFile(jarArtifact.getFile) { jarFile =>
- definedPackages(jarFile)
- }
- })
- }
-
- def definedPackages(jarFile: JarFile) = {
- val analyzedClasses =
- for {
- entry <- toStream(jarFile.entries())
- if !entry.isDirectory
- if isClassToAnalyze(entry.getName)
- metaData = analyzeClass(jarFile, entry)
- } yield metaData
-
- PackageTally.fromAnalyzedClassFiles(analyzedClasses)
- }
-
- def analyzeClass(jarFile : JarFile, entry : JarEntry) = {
- try {
- withInputStream(jarFile, entry)(Analyze.analyzeClass)
- } catch {
- case e : Exception =>
- throw new MojoExecutionException(
- "While analyzing the class '%s' in jar file '%s'".format(entry.getName, jarFile.getName),
- e)
- }
- }
-}
-
-object GenerateOsgiManifestMojo {
- def getManualImports(importPackage: String): Map[String, Option[String]] = {
- try {
- (for {
- importDirective <- parseImportPackages(importPackage)
- packageName <- importDirective.packageNames
- } yield packageName -> getVersionThrowOthers(importDirective.parameters)).
- toMap
-
- } catch {
- case e: Exception => throw new RuntimeException("Error in Import-Package:" + importPackage, e)
- }
- }
-
- def getVersionThrowOthers(parameters: List[ExportPackages.Parameter]): Option[String] = {
- parameters match {
- case List() => None
- case List(ExportPackages.Parameter("version", v)) => Some(v)
- case default => throw new RuntimeException("A single, optional version parameter expected, but got " + default)
- }
- }
-
- def parseImportPackages(importPackages: String): List[Export] = {
- ExportPackageParser.parseAll(importPackages) match {
- case ExportPackageParser.NoSuccess(msg, _) => throw new RuntimeException(msg)
- case ExportPackageParser.Success(packages, _) => packages
- }
- }
-
- def isClassToAnalyze(name: String): Boolean =
- name.endsWith(".class") && ! name.endsWith("module-info.class")
-
- def emptyToNone(str: String) =
- Option(str) map {_.trim} filterNot {_.isEmpty}
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackageParser.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackageParser.scala
deleted file mode 100644
index 5cd93e84e87..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackageParser.scala
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.osgi
-
-import scala.util.parsing.combinator.JavaTokenParsers
-import ExportPackages.{Parameter, Export}
-import com.yahoo.container.plugin.util.Extractors.ListOf
-import scala.util.parsing.input.CharSequenceReader
-import scala.annotation.tailrec
-
-/**
- * @author tonytv
- */
-object ExportPackageParser extends JavaTokenParsers {
- val ListOfParameter = new ListOf(classOf[Parameter])
-
-
- def exportPackage = rep1sep(export, ",")
-
- //TODO: remove when fix is in current scala library
- //Fix for https://github.com/scala/scala-parser-combinators/pull/4
- def stringLiteral_fixed: Parser[String] = ("\""+"""([^"\p{Cntrl}\\]|\\[\\'"bfnrt]|\\u[a-fA-F0-9]{4})*+"""+"\"").r
-
- @SuppressWarnings(Array("unchecked"))
- def export : Parser[Export] = packageName ~ opt(";" ~> (parameters | export)) ^^ {
- case (packageName : String) ~ optional => {
- optional match {
- case None => Export(List(packageName.asInstanceOf[String]), List())
- case Some(e: Export) => e.copy(packageNames = packageName +: e.packageNames)
- case Some(ListOfParameter(parameters)) => Export(List(packageName), parameters)
- }
- }
- }
-
- def parameters = rep1sep(parameter, ";")
-
- def parameter = (directive | attribute) ^^ {
- case k ~ v => Parameter(k.toString, v.toString)
- }
-
- def directive = (extended_ <~ ":=") ~ argument
- def attribute = (extended_ <~ "=") ~ argument
-
- def packageName = rep1sep(ident_, ".") ^^ {
- x => x.mkString(".")
- }
-
- def extended = rep1("""\p{Alnum}""".r | "_" | "-" | ".") ^^ {
- _.mkString
- }
-
- def argument = (extended_ | stringLiteral_ | failure("argument expected")) ^^ {
- val quote = '"'.toString
- _.toString.stripPrefix(quote).stripSuffix(quote)
- }
-
- def parseAll(in: CharSequence): ParseResult[List[Export]] = {
- try {
- parseAll(exportPackage, in)
- } catch {
- case e: StackOverflowError =>
- throw new RuntimeException("Failed parsing Export-Package: '''\n" + in + "\n'''", e)
- }
- }
-
- //*** For debugging StackOverflow error **/
- def ident_ = printStackOverflow(ident)("ident")
- def stringLiteral_ = printStackOverflow(stringLiteral_fixed)("stringLiteral_fixed")
- def extended_ = printStackOverflow(extended)("extended")
-
- def printStackOverflow[T](p: => Parser[T])(name: String): Parser[T] = Parser{ in =>
- try {
- p(in)
- } catch {
- case e: StackOverflowError =>
- val input = in match {
- case reader: CharSequenceReader => readerToString(reader)
- case other => other.toString
- }
- println(s"***StackOverflow for $name with input '''$input'''")
- throw e
- }
- }
-
- @tailrec
- def readerToString(reader: CharSequenceReader, current: String = ""): String = {
- if (reader.atEnd) current
- else readerToString(reader.rest, current + reader.first)
- }
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackages.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackages.scala
deleted file mode 100644
index 4a973c0b9b1..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackages.scala
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.osgi
-
-/**
- * @author tonytv
- */
-object ExportPackages {
-
- case class Export(packageNames: List[String], parameters: List[Parameter]) {
- def version: Option[String] = {
- (for (
- param <- parameters if param.name == "version"
- ) yield param.value).
- headOption
- }
- }
-
- case class Parameter(name: String, value: String)
-
- def exportsByPackageName(exports: Seq[Export]): Map[String, Export] = {
- (for {
- export <- exports.reverse //ensure that earlier exports of a package overrides later exports.
- packageName <- export.packageNames
- } yield packageName -> export).
- toMap
- }
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ImportPackages.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ImportPackages.scala
deleted file mode 100644
index b6b47b954c0..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ImportPackages.scala
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.osgi
-
-import ExportPackages.Export
-import util.control.Exception
-
-/**
- * @author tonytv
- */
-object ImportPackages {
- case class Import(packageName : String, version : Option[String]) {
- val majorMinorMicroVersion = Exception.handling(classOf[NumberFormatException]).
- by( e => throw new IllegalArgumentException(
- "Invalid version number '%s' for package '%s'.".format(version.get, packageName), e)) {
-
- version map { _.split('.') take 3 map {_.toInt} }
- }
-
- def majorVersion = majorMinorMicroVersion map { _.head }
-
- // TODO: Detecting guava packages should be based on Bundle-SymbolicName, not package name.
- def importVersionRange = {
- def upperLimit =
- if (isGuavaPackage) InfiniteVersion // guava increases major version for each release
- else majorVersion.get + 1
-
- version map (v => "[%s,%s)".format(majorMinorMicroVersion.get.mkString("."), upperLimit))
- }
-
- def isGuavaPackage = packageName.equals(GuavaBasePackage) || packageName.startsWith(GuavaBasePackage + ".")
-
- def asOsgiImport = packageName + (importVersionRange map {";version=\"" + _ + '"'} getOrElse(""))
- }
-
-
- val GuavaBasePackage = "com.google.common"
- val InfiniteVersion = 99999
-
- def calculateImports(referencedPackages : Set[String],
- implementedPackages : Set[String],
- exportedPackages : Map[String, Export]) : Map[String, Import] = {
- (for {
- undefinedPackage <- referencedPackages diff implementedPackages
- export <- exportedPackages.get(undefinedPackage)
- } yield undefinedPackage -> Import(undefinedPackage, version(export)))(
- collection.breakOut)
- }
-
- def version(export: Export): Option[String] =
- export.parameters.find(_.name == "version").map(_.value)
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Extractors.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Extractors.scala
deleted file mode 100644
index 83f23f905fb..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Extractors.scala
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.util
-
-/**
-* @author tonytv
-*/
-object Extractors {
- class ListOf[C](val c : Class[C]) {
- def unapply[X](xs : X) : Option[List[C]] = {
- xs match {
- case x :: xr if c.isInstance(x) => unapply(xr) map ( c.cast(x) :: _)
- case Nil => Some(Nil)
- case _ => None
- }
- }
- }
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Files.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Files.scala
deleted file mode 100644
index d32e57784da..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Files.scala
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.util
-
-import java.io.{FileOutputStream, File}
-import com.yahoo.container.plugin.util.IO._
-
-/**
- * @author tonytv
- */
-object Files {
- def allDescendantFiles(file: File): Stream[File] = {
- if (file.isFile)
- Stream(file)
- else if (file.isDirectory)
- file.listFiles().toStream.map(allDescendantFiles).flatten
- else
- Stream.empty
- }
-
- def withFileOutputStream[T](file: File)(f: FileOutputStream => T): T = {
- using(new FileOutputStream(file), readOnly = false)(f)
- }
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/IO.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/IO.scala
deleted file mode 100644
index 90f3eb3e7fd..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/IO.scala
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.util
-
-import java.io.{Closeable, FileOutputStream, OutputStream, FileInputStream, File}
-import util.control.Exception
-import scala.Either
-
-/** Utility methods relating to IO
- * @author tonytv
- */
-object IO {
- def withFileInputStream[T](file : File)(f : FileInputStream => T) = {
- using(new FileInputStream(file), readOnly = true)(f)
- }
-
- /**
- * Creates a new file and all it's parent directories,
- * and provides a file output stream to the file.
- *
- * Exceptions from closing have priority over exceptions from f.
- */
- def withFileOutputStream[T](file: File)(f: OutputStream => T) {
- makeDirectoriesRecursive(file.getParentFile)
- using(new FileOutputStream(file), readOnly = false )(f)
- }
-
- def makeDirectoriesRecursive(file: File) {
- if (!file.mkdirs() && !file.isDirectory) {
- throw new RuntimeException("Could not create directory " + file.getPath)
- }
- }
-
- def using[RESOURCE <: Closeable, T](resource : RESOURCE, readOnly : Boolean)(f : RESOURCE => T) : T = {
- def catchPromiscuously = Exception.catchingPromiscuously(classOf[Throwable])
-
- val resultOrException = catchPromiscuously either f(resource)
- val closeException = Exception.allCatch either resource.close()
-
- prioritizeFirstException(
- resultOrException,
- if (readOnly) Right(()) else closeException) fold (throw _, identity)
- }
-
- private def prioritizeFirstException[T](first: Either[Throwable, T], second: Either[Throwable, Unit]) =
- first fold ( Left(_), value => second fold ( Left(_), _ => Right(value) ) )
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Iteration.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Iteration.scala
deleted file mode 100644
index fa50f5cdd17..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Iteration.scala
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.util
-
- /**
- * @author tonytv
- */
-object Iteration {
- def toStream[T](enumeration: java.util.Enumeration[T]): Stream[T] = {
- if (enumeration.hasMoreElements)
- Stream.cons(enumeration.nextElement(), toStream(enumeration))
- else
- Stream.Empty
- }
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/JarFiles.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/JarFiles.scala
deleted file mode 100644
index d7578f2b338..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/JarFiles.scala
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.util
-
-import java.util.jar.JarFile
-import java.util.zip.{ZipFile, ZipEntry}
-import IO.using
-import java.io.{Closeable, InputStream, File}
-
-/**
- * @author tonytv
- */
-object JarFiles {
- def withJarFile[T](file : File)(f : JarFile => T ) : T =
- using(new JarFile(file) with Closeable, readOnly = true)(f)
-
- def withInputStream[T](zipFile: ZipFile, zipEntry: ZipEntry)(f: InputStream => T): T =
- using(zipFile.getInputStream(zipEntry), readOnly = true)(f)
-
- def getManifest(jarFile : File) : Option[java.util.jar.Manifest] = {
- withJarFile(jarFile) { jar =>
- Option(jar.getManifest)
- }
- }
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Maps.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Maps.scala
deleted file mode 100644
index cafc8431b0e..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Maps.scala
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.util
-
-import collection.mutable.MultiMap
-
-/**
- * @author tonytv
- */
-object Maps {
- def combine[K, V](map1 : Map[K, V], map2 : Map[K, V])(f : (V, V) => V) : Map[K, V] = {
- def logicError : V = throw new RuntimeException("Logic error.")
- def combineValues(key : K) = key -> f(map1.getOrElse(key, logicError), map2.getOrElse(key, logicError))
-
- val keysInBoth = map1.keySet intersect map2.keySet
- def notInBoth = !keysInBoth.contains(_ : K)
-
- map1.filterKeys(notInBoth) ++ map2.filterKeys(notInBoth) ++ keysInBoth.map(combineValues)
- }
-}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Strings.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Strings.scala
deleted file mode 100644
index 5f854fda944..00000000000
--- a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Strings.scala
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.util
-
- /**
- * @author tonytv
- */
-object Strings {
- def emptyStringTo(replacement: String)(s: String) = {
- if (s.isEmpty) replacement
- else s
- }
-
- def noneIfEmpty(s: String) = Option(s).filterNot(_.isEmpty)
-}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.java
new file mode 100644
index 00000000000..8cccf0598ab
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.java
@@ -0,0 +1,85 @@
+// 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.bundle;
+
+import com.yahoo.container.plugin.bundle.AnalyzeBundle.PublicPackages;
+import com.yahoo.container.plugin.osgi.ExportPackages;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static com.yahoo.container.plugin.classanalysis.TestUtilities.throwableMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class AnalyzeBundleTest {
+ private final List<ExportPackages.Export> exports;
+ private final Map<String, ExportPackages.Export> exportsByPackageName;
+
+ File jarDir = new File("src/test/resources/jar");
+
+ public AnalyzeBundleTest() {
+ File notOsgi = new File(jarDir, "notAOsgiBundle.jar");
+ File simple = new File(jarDir, "simple1.jar");
+ PublicPackages pp = AnalyzeBundle.publicPackagesAggregated(Arrays.asList(notOsgi, simple));
+ this.exports = pp.exports;
+ this.exportsByPackageName = ExportPackages.exportsByPackageName(exports);
+ }
+
+ private File jarFile(String name) {
+ return new File(jarDir, name);
+ }
+
+ @Test
+ public void require_that_non_osgi_bundles_are_ignored() {
+ assertThat(exportsByPackageName.keySet(), not(hasItem("com.yahoo.sample.exported.package.ignored")));
+ }
+
+ @Test
+ public void require_that_exports_are_retrieved_from_manifest_in_jars() {
+ assertThat(exportsByPackageName.keySet().size(), is(1));
+ assertThat(exportsByPackageName.keySet(), hasItem("com.yahoo.sample.exported.package"));
+ }
+
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ @Test
+ public void require_that_invalid_exports_throws_exception() {
+ exception.expect(Exception.class);
+
+ exception.expectMessage(containsString("Invalid manifest in bundle"));
+ exception.expectMessage(matchesPattern("Invalid manifest in bundle '.*errorExport.jar'"));
+ exception.expectCause(throwableMessage(startsWith("Failed parsing Export-Package")));
+
+ AnalyzeBundle.publicPackages(jarFile("errorExport.jar"));
+ }
+
+ private TypeSafeMatcher<String> matchesPattern(String pattern) {
+ return new TypeSafeMatcher<String>() {
+ @Override
+ protected boolean matchesSafely(String s) {
+ return s.matches(pattern);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("expects String that matches the pattern ").appendValue(pattern);
+ }
+ };
+ }
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/AnalyzeClassTest.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/AnalyzeClassTest.java
new file mode 100644
index 00000000000..aba6e8f14e8
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/AnalyzeClassTest.java
@@ -0,0 +1,167 @@
+// 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.classanalysis;
+
+import com.yahoo.container.plugin.classanalysis.sampleclasses.Base;
+import com.yahoo.container.plugin.classanalysis.sampleclasses.ClassAnnotation;
+import com.yahoo.container.plugin.classanalysis.sampleclasses.Derived;
+import com.yahoo.container.plugin.classanalysis.sampleclasses.DummyAnnotation;
+import com.yahoo.container.plugin.classanalysis.sampleclasses.Fields;
+import com.yahoo.container.plugin.classanalysis.sampleclasses.Interface1;
+import com.yahoo.container.plugin.classanalysis.sampleclasses.Interface2;
+import com.yahoo.container.plugin.classanalysis.sampleclasses.MethodAnnotation;
+import com.yahoo.container.plugin.classanalysis.sampleclasses.MethodInvocation;
+import com.yahoo.osgi.annotation.ExportPackage;
+import com.yahoo.osgi.annotation.Version;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import javax.security.auth.login.LoginException;
+import java.awt.Image;
+import java.awt.image.ImagingOpException;
+import java.awt.image.Kernel;
+import java.util.Optional;
+
+import static com.yahoo.container.plugin.classanalysis.TestUtilities.analyzeClass;
+import static com.yahoo.container.plugin.classanalysis.TestUtilities.classFile;
+import static com.yahoo.container.plugin.classanalysis.TestUtilities.name;
+import static com.yahoo.container.plugin.classanalysis.TestUtilities.throwableMessage;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Tests that analysis of class files works.
+ *
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class AnalyzeClassTest {
+ @Test
+ public void require_that_full_class_name_is_returned() {
+ assertThat(analyzeClass(Base.class).getName(), is(name(Base.class)));
+ }
+
+ @Test
+ public void require_that_base_class_is_included() {
+ assertThat(analyzeClass(Derived.class).getReferencedClasses(), hasItem(name(Base.class)));
+ }
+
+ @Test
+ public void require_that_implemented_interfaces_are_included() {
+ assertThat(analyzeClass(Base.class).getReferencedClasses(),
+ allOf(hasItem(name(Interface1.class)), hasItem(name(Interface2.class))));
+ }
+
+ @Test
+ public void require_that_interface_can_be_analyzed() {
+ ClassFileMetaData classMetaData = analyzeClass(Interface1.class);
+
+ assertThat(classMetaData.getName(), is(name(Interface1.class)));
+ assertThat(classMetaData.getReferencedClasses(), hasItem(name(Interface2.class)));
+ }
+
+ @Test
+ public void require_that_return_type_is_included() {
+ assertThat(analyzeClass(Interface1.class).getReferencedClasses(), hasItem(name(Image.class)));
+ }
+
+ @Test
+ public void require_that_parameters_are_included() {
+ assertThat(analyzeClass(Interface1.class).getReferencedClasses(), hasItem(name(Kernel.class)));
+ }
+
+ @Test
+ public void require_that_exceptions_are_included() {
+ assertThat(analyzeClass(Interface1.class).getReferencedClasses(), hasItem(name(ImagingOpException.class)));
+ }
+
+ @Test
+ public void require_that_basic_types_ignored() {
+ assertThat(analyzeClass(Interface1.class).getReferencedClasses(), not(anyOf(hasItem("int"), hasItem("float"))));
+ }
+
+ @Test
+ public void require_that_arrays_of_basic_types_ignored() {
+ assertThat(analyzeClass(Interface1.class).getReferencedClasses(), not(anyOf(hasItem("int[]"), hasItem("int[][]"))));
+ }
+
+ @Test
+ public void require_that_instance_field_types_are_included() {
+ assertThat(analyzeClass(Fields.class).getReferencedClasses(), hasItem(name(String.class)));
+ }
+
+ @Test
+ public void require_that_static_field_types_are_included() {
+ assertThat(analyzeClass(Fields.class).getReferencedClasses(), hasItem(name(java.util.List.class)));
+ }
+
+ @Test
+ public void require_that_field_annotation_is_included() {
+ assertThat(analyzeClass(Fields.class).getReferencedClasses(), hasItem(name(DummyAnnotation.class)));
+ }
+
+ @Test
+ public void require_that_class_annotation_is_included() {
+ assertThat(analyzeClass(ClassAnnotation.class).getReferencedClasses(), hasItem(name(DummyAnnotation.class)));
+ }
+
+ @Test
+ public void require_that_method_annotation_is_included() {
+ assertThat(analyzeClass(MethodAnnotation.class).getReferencedClasses(), hasItem(name(DummyAnnotation.class)));
+ }
+
+ @Test
+ public void require_that_export_package_annotations_are_ignored() {
+ assertThat(Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.package-info"))
+ .getReferencedClasses(), not(anyOf(hasItem(name(ExportPackage.class)), hasItem(name(Version.class)))));
+ }
+
+ @Test
+ public void require_that_export_annotations_are_processed() {
+ assertThat(
+ Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.package-info")).getExportPackage(),
+ is(Optional.of(new ExportPackageAnnotation(3, 1, 4, "TEST_QUALIFIER-2"))));
+ }
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void require_that_export_annotations_are_validated() {
+ expectedException.expect(RuntimeException.class);
+ expectedException.expectMessage(containsString("invalid/package-info"));
+ expectedException.expectCause(throwableMessage(containsString("qualifier must follow the format")));
+ expectedException.expectCause(throwableMessage(containsString("'EXAMPLE INVALID QUALIFIER'")));
+
+ Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.invalid.package-info"));
+ }
+
+ @Test
+ public void require_that_catch_clauses_are_included() {
+ assertThat(Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.CatchException"))
+ .getReferencedClasses(), hasItem(name(LoginException.class)));
+ }
+
+ @Test
+ public void require_that_class_references_are_included() {
+ assertThat(Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.ClassReference"))
+ .getReferencedClasses(), hasItem(name(Interface1.class)));
+ }
+
+ @Test
+ public void require_that_return_type_of_method_invocations_are_included() {
+ assertThat(analyzeClass(MethodInvocation.class).getReferencedClasses(), hasItem(name(Image.class)));
+ }
+
+ @Test
+ public void require_that_attributes_are_included() {
+ //Uses com/coremedia/iso/Utf8.class from com.googlecode.mp4parser:isoparser:1.0-RC-1
+ assertThat(Analyze.analyzeClass(classFile("class/Utf8")).getReferencedClasses(),
+ hasItem("org.aspectj.weaver.MethodDeclarationLineNumber"));
+ }
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/AnalyzeMethodBodyTest.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/AnalyzeMethodBodyTest.java
new file mode 100644
index 00000000000..d722f1eba2d
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/AnalyzeMethodBodyTest.java
@@ -0,0 +1,73 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis;
+
+import com.yahoo.container.plugin.classanalysis.sampleclasses.Base;
+import com.yahoo.container.plugin.classanalysis.sampleclasses.ClassWithMethod;
+import com.yahoo.container.plugin.classanalysis.sampleclasses.Derived;
+import com.yahoo.container.plugin.classanalysis.sampleclasses.Dummy;
+import com.yahoo.container.plugin.classanalysis.sampleclasses.Fields;
+import com.yahoo.container.plugin.classanalysis.sampleclasses.Interface1;
+import com.yahoo.container.plugin.classanalysis.sampleclasses.Methods;
+import org.junit.Test;
+
+import java.io.PrintStream;
+
+import static com.yahoo.container.plugin.classanalysis.TestUtilities.analyzeClass;
+import static com.yahoo.container.plugin.classanalysis.TestUtilities.name;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Tests that classes used in method bodies are included in the imports list.
+ *
+ * @author tonytv
+ */
+public class AnalyzeMethodBodyTest {
+ @Test
+ public void require_that_class_of_locals_are_included() {
+ assertThat(analyzeClass(Methods.class).getReferencedClasses(), hasItem(name(Base.class)));
+ }
+
+ @Test
+ public void require_that_class_of_locals_in_static_method_are_included() {
+ assertThat(analyzeClass(Methods.class).getReferencedClasses(), hasItem(name(Derived.class)));
+ }
+
+ @Test
+ public void require_that_field_references_are_included() {
+ assertThat(analyzeClass(Methods.class).getReferencedClasses(),
+ allOf(hasItem(name(java.util.List.class)), hasItem(name(Fields.class))));
+ }
+
+ @Test
+ public void require_that_class_owning_field_is_included() {
+ assertThat(analyzeClass(Methods.class).getReferencedClasses(), hasItem(name(System.class)));
+ }
+
+ @Test
+ public void require_that_class_containing_method_is_included() {
+ assertThat(analyzeClass(Methods.class).getReferencedClasses(), hasItem(name(PrintStream.class)));
+ }
+
+ @Test
+ public void require_that_element_of_new_multidimensional_array_is_included() {
+ assertThat(analyzeClass(Methods.class).getReferencedClasses(), hasItem(name(Interface1.class)));
+ }
+
+ @Test
+ public void require_that_basic_arrays_are_not_included() {
+ assertThat(analyzeClass(Methods.class).getReferencedClasses(), not(hasItem("int[]")));
+ }
+
+ @Test
+ public void require_that_container_generic_parameters_are_included() {
+ assertThat(analyzeClass(Methods.class).getReferencedClasses(), hasItem(name(Dummy.class)));
+ }
+
+ @Test
+ public void require_that_class_owning_method_handler_is_included() {
+ assertThat(analyzeClass(Methods.class).getReferencedClasses(), hasItem(name(ClassWithMethod.class)));
+ }
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/TestUtilities.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/TestUtilities.java
new file mode 100644
index 00000000000..6ff6bafd816
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/TestUtilities.java
@@ -0,0 +1,40 @@
+// 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.classanalysis;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+import java.io.File;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class TestUtilities {
+ public static ClassFileMetaData analyzeClass(Class<?> clazz) {
+ return Analyze.analyzeClass(classFile(name(clazz)));
+ }
+
+ public static File classFile(String className) {
+ return new File("target/test-classes/" + className.replace('.', '/') + ".class");
+ }
+
+ public static String name(Class<?> clazz) {
+ return clazz.getName();
+ }
+
+ public static TypeSafeMatcher<Throwable> throwableMessage(final Matcher<String> matcher) {
+ return new TypeSafeMatcher<Throwable>() {
+ @Override
+ protected boolean matchesSafely(Throwable throwable) {
+ return matcher.matches(throwable.getMessage());
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("expects Throwable and a message matching ").appendDescriptionOf(matcher);
+ }
+ };
+ }
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassReference.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassReference.java
index b4a4f90398f..f650f0f0c7f 100644
--- a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassReference.java
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassReference.java
@@ -6,6 +6,7 @@ package com.yahoo.container.plugin.classanalysis.sampleclasses;
*/
public class ClassReference {
void classReference() {
+ @SuppressWarnings("unused")
Class<?> clazz = Interface1.class;
}
}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/MethodInvocation.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/MethodInvocation.java
index 992799e3032..a576c764eec 100644
--- a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/MethodInvocation.java
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/MethodInvocation.java
@@ -7,6 +7,7 @@ package com.yahoo.container.plugin.classanalysis.sampleclasses;
public class MethodInvocation {
void invokeMethod() {
Interface1 interface1 = null;
+ @SuppressWarnings({ "unused", "null" })
Object o = interface1.methodSignatureTest(null, null);
}
}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Methods.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Methods.java
index 568a5070961..abe325768fc 100644
--- a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Methods.java
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Methods.java
@@ -8,6 +8,7 @@ import java.util.Map;
* Input for class analysis tests.
* @author tonytv
*/
+@SuppressWarnings("unused")
public class Methods {
public void method1() {
Base b = new Base();
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/osgi/ExportPackageParserTest.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/osgi/ExportPackageParserTest.java
new file mode 100644
index 00000000000..d869b8ec4d9
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/osgi/ExportPackageParserTest.java
@@ -0,0 +1,295 @@
+// 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.osgi;
+
+import com.yahoo.container.plugin.osgi.ExportPackages.Export;
+import com.yahoo.container.plugin.osgi.ExportPackages.Parameter;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.empty;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class ExportPackageParserTest {
+ private final Parameter versionParameter = new Parameter("version", "1.2.3.sample");
+
+ @Test
+ public void require_that_package_is_parsed_correctly() {
+ List<Export> exports = ExportPackageParser.parseExports("sample.exported.package");
+
+ assertThat(exports.size(), is(1));
+ assertThat(exports.get(0).getParameters(), empty());
+ assertThat(exports.get(0).getPackageNames(), contains("sample.exported.package"));
+ }
+
+ @Test
+ public void require_that_version_is_parsed_correctly() {
+ List<Export> exports = ExportPackageParser.parseExports("com.yahoo.sample.exported.package;version=\"1.2.3.sample\"");
+
+ assertThat(exports.size(), is(1));
+ Export export = exports.get(0);
+ assertThat(export.getPackageNames(), contains("com.yahoo.sample.exported.package"));
+ assertThat(export.getParameters(), contains(parameterMatching(versionParameter)));
+ }
+
+ @Test
+ public void require_that_multiple_packages_with_same_parameters_is_parsed_correctly() {
+ List<Export> exports = ExportPackageParser.parseExports("exported.package1;exported.package2;version=\"1.2.3.sample\"");
+
+ assertThat(exports.size(), is(1));
+ Export export = exports.get(0);
+ assertThat(export.getPackageNames(), contains("exported.package1", "exported.package2"));
+ assertThat(export.getParameters(), contains(parameterMatching(versionParameter)));
+ }
+
+ @Test
+ public void require_that_spaces_between_separators_are_allowed() {
+ List<Export> exports = ExportPackageParser.parseExports("exported.package1 , exported.package2 ; version = \"1.2.3.sample\" ");
+
+ assertThat(exports.size(), is(2));
+ Export export = exports.get(0);
+ assertThat(export.getPackageNames(), contains("exported.package1"));
+ export = exports.get(1);
+ assertThat(export.getPackageNames(), contains("exported.package2"));
+ assertThat(export.getParameters(), contains(parameterMatching(versionParameter)));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void require_that_multiple_parameters_for_a_package_is_parsed_correctly() {
+ List<Export> exports = ExportPackageParser.parseExports("exported.package;version=\"1.2.3.sample\";param2=true");
+
+ assertThat(exports.size(), is(1));
+ Export export = exports.get(0);
+ assertThat(export.getParameters(), contains(parameterMatching(versionParameter), parameterMatching("param2", "true")));
+ }
+
+ @Test
+ public void require_that_multiple_exports_are_parsed_correctly() {
+ List<Export> exports = ExportPackageParser.parseExports("exported.package1,exported.package2");
+ assertThat(exports.size(), is(2));
+ Export export = exports.get(0);
+ assertThat(export.getPackageNames(), contains("exported.package1"));
+ assertThat(export.getParameters(), empty());
+ export = exports.get(1);
+ assertThat(export.getPackageNames(), contains("exported.package2"));
+ assertThat(export.getParameters(), empty());
+
+ exports = ExportPackageParser.parseExports("exported.package1;version=\"1.2.3.sample\",exported.package2");
+ assertThat(exports.size(), is(2));
+ export = exports.get(0);
+ assertThat(export.getPackageNames(), contains("exported.package1"));
+ assertThat(export.getParameters(), contains(parameterMatching(versionParameter)));
+ export = exports.get(1);
+ assertThat(export.getPackageNames(), contains("exported.package2"));
+ assertThat(export.getParameters(), empty());
+
+ exports = ExportPackageParser.parseExports("exported.package1,exported.package2;version=\"1.2.3.sample\"");
+ assertThat(exports.size(), is(2));
+ export = exports.get(0);
+ assertThat(export.getPackageNames(), contains("exported.package1"));
+ assertThat(export.getParameters(), empty());
+ export = exports.get(1);
+ assertThat(export.getPackageNames(), contains("exported.package2"));
+ assertThat(export.getParameters(), contains(parameterMatching(versionParameter)));
+ }
+
+ @Test
+ public void require_that_long_string_literals_do_not_cause_stack_overflow_error() {
+ //From jersey-server-1.13.jar
+ String exportHeader = "com.sun.jersey.server.impl.wadl;uses:=\"com.sun.jersey.api.model,com.sun.resea"
+ + "rch.ws.wadl,com.sun.jersey.api.wadl.config,com.sun.jersey.server.wadl,com.sun."
+ + "jersey.api.core,javax.xml.bind,javax.ws.rs.core,com.sun.jersey.server.impl.uri"
+ + ",com.sun.jersey.core.spi.factory,com.sun.jersey.server.impl.model.method,com.s"
+ + "un.jersey.api.uri,com.sun.jersey.core.header,com.sun.jersey.spi.dispatch,javax"
+ + ".ws.rs,com.sun.jersey.spi.resource\";version=\"1.13.0\",com.sun.jersey.server."
+ + "impl.model.parameter.multivalued;uses:=\"com.sun.jersey.spi,javax.ws.rs.core,c"
+ + "om.sun.jersey.api.container,com.sun.jersey.impl,javax.xml.parsers,org.xml.sax,"
+ + "javax.xml.transform,javax.xml.bind.annotation,javax.xml.transform.sax,com.sun."
+ + "jersey.spi.inject,javax.xml.bind,javax.ws.rs.ext,com.sun.jersey.api.model,com."
+ + "sun.jersey.core.reflection,javax.ws.rs,com.sun.jersey.core.spi.component,com.s"
+ + "un.jersey.core.header\";version=\"1.13.0\",com.sun.jersey.server.impl.model.pa"
+ + "rameter;uses:=\"com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com"
+ + ".sun.jersey.server.impl.model.parameter.multivalued,com.sun.jersey.spi.inject,"
+ + "com.sun.jersey.api,com.sun.jersey.api.core,com.sun.jersey.server.impl.inject,j"
+ + "avax.ws.rs.core,javax.ws.rs,com.sun.jersey.core.header,com.sun.jersey.api.repr"
+ + "esentation\";version=\"1.13.0\",com.sun.jersey.server.impl.application;uses:="
+ + "\"com.sun.jersey.core.spi.component,com.sun.jersey.api.core,com.sun.jersey.spi"
+ + ",com.sun.jersey.spi.inject,javax.ws.rs.core,com.sun.jersey.api.container,javax"
+ + ".ws.rs.ext,com.sun.jersey.spi.container,com.sun.jersey.core.reflection,com.sun"
+ + ".jersey.api.model,com.sun.jersey.impl,com.sun.jersey.spi.dispatch,com.sun.jers"
+ + "ey.server.impl.model,com.sun.jersey.server.impl.wadl,com.sun.jersey.server.imp"
+ + "l.uri,com.sun.jersey.core.spi.factory,com.sun.jersey.api.uri,com.sun.jersey.se"
+ + "rver.impl.uri.rules,com.sun.jersey.spi.uri.rules,com.sun.jersey.server.spi.com"
+ + "ponent,com.sun.jersey.core.util,com.sun.jersey.core.header,com.sun.jersey.core"
+ + ".spi.component.ioc,javax.ws.rs,com.sun.jersey.server.impl,com.sun.jersey.serve"
+ + "r.wadl,com.sun.jersey.server.impl.inject,com.sun.jersey.server.impl.component,"
+ + "com.sun.jersey.spi.monitoring,com.sun.jersey.server.impl.monitoring,com.sun.je"
+ + "rsey.api.container.filter,com.sun.jersey.server.impl.model.parameter.multivalu"
+ + "ed,com.sun.jersey.server.impl.model.parameter,com.sun.jersey.server.impl.templ"
+ + "ate,com.sun.jersey.spi.template,com.sun.jersey.server.impl.resource,com.sun.je"
+ + "rsey.server.impl.modelapi.annotation,com.sun.jersey.server.impl.container.filt"
+ + "er,com.sun.jersey.server.impl.modelapi.validation,com.sun.jersey.api,com.sun.j"
+ + "ersey.spi.service\";version=\"1.13.0\",com.sun.jersey.server.impl.component;us"
+ + "es:=\"com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com.sun.jerse"
+ + "y.server.spi.component,com.sun.jersey.api.core,com.sun.jersey.core.spi.compone"
+ + "nt.ioc,com.sun.jersey.server.impl.inject,com.sun.jersey.server.impl.resource,c"
+ + "om.sun.jersey.api.container,com.sun.jersey.core.reflection,com.sun.jersey.spi."
+ + "inject\";version=\"1.13.0\",com.sun.jersey.server.impl.provider;uses:=\"com.su"
+ + "n.jersey.core.spi.factory,com.sun.jersey.api.container,com.sun.jersey.api.core"
+ + ",javax.ws.rs.core\";version=\"1.13.0\",com.sun.jersey.server.impl.template;use"
+ + "s:=\"com.sun.jersey.core.spi.component,com.sun.jersey.api.view,com.sun.jersey."
+ + "spi.template,javax.ws.rs.core,com.sun.jersey.core.header,com.sun.jersey.server"
+ + ".impl.model.method,com.sun.jersey.spi.dispatch,com.sun.jersey.api.uri,javax.ws"
+ + ".rs,com.sun.jersey.spi.inject,javax.ws.rs.ext,com.sun.jersey.server.impl.uri.r"
+ + "ules,com.sun.jersey.server.probes,com.sun.jersey.core.reflection,com.sun.jerse"
+ + "y.spi.uri.rules,com.sun.jersey.spi.container,com.sun.jersey.api.core\";version"
+ + "=\"1.13.0\",com.sun.jersey.server.osgi;uses:=\"com.sun.jersey.server.impl.prov"
+ + "ider,org.osgi.framework,javax.ws.rs.ext\";version=\"1.13.0\",com.sun.jersey.se"
+ + "rver.wadl.generators.resourcedoc.model;uses:=\"javax.xml.bind.annotation,javax"
+ + ".xml.namespace\";version=\"1.13.0\",com.sun.jersey.server.impl.resource;uses:="
+ + "\"com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com.sun.jersey.se"
+ + "rver.spi.component,com.sun.jersey.api.core,com.sun.jersey.api.container,javax."
+ + "ws.rs,com.sun.jersey.server.impl.inject,com.sun.jersey.core.spi.component.ioc,"
+ + "javax.ws.rs.core\";version=\"1.13.0\",com.sun.jersey.server.impl.monitoring;us"
+ + "es:=\"com.sun.jersey.spi.monitoring,com.sun.jersey.spi.service,com.sun.jersey."
+ + "api.model,com.sun.jersey.spi.container,javax.ws.rs.ext,com.sun.jersey.core.spi"
+ + ".component\";version=\"1.13.0\",com.sun.jersey.server.impl.modelapi.annotation"
+ + ";uses:=\"com.sun.jersey.api.model,javax.ws.rs.core,javax.ws.rs,com.sun.jersey."
+ + "core.reflection,com.sun.jersey.core.header,com.sun.jersey.impl\";version=\"1.1"
+ + "3.0\",com.sun.jersey.server.impl.container;uses:=\"com.sun.jersey.server.impl."
+ + "application,com.sun.jersey.spi.container,com.sun.jersey.api.container\";versio"
+ + "n=\"1.13.0\",com.sun.jersey.server.wadl;uses:=\"javax.ws.rs.core,com.sun.resea"
+ + "rch.ws.wadl,javax.xml.namespace,com.sun.jersey.api.model,javax.xml.bind,javax."
+ + "ws.rs,com.sun.jersey.server.wadl.generators,com.sun.jersey.server.impl.modelap"
+ + "i.annotation,com.sun.jersey.server.impl\";version=\"1.13.0\",com.sun.jersey.se"
+ + "rver.impl.model.method.dispatch;uses:=\"com.sun.jersey.api.model,com.sun.jerse"
+ + "y.api.core,com.sun.jersey.spi.container,com.sun.jersey.server.impl.inject,com."
+ + "sun.jersey.api,javax.ws.rs.core,com.sun.jersey.core.spi.factory,com.sun.jersey"
+ + ".spi.inject,com.sun.jersey.spi.dispatch,com.sun.jersey.core.spi.component,java"
+ + "x.ws.rs,com.sun.jersey.server.impl.model.parameter.multivalued,com.sun.jersey."
+ + "api.representation,com.sun.jersey.api.container\";version=\"1.13.0\",com.sun.j"
+ + "ersey.server.impl;uses:=\"javax.naming,com.sun.jersey.api.core,com.sun.jersey."
+ + "core.header,javax.ws.rs.core,com.sun.jersey.server.impl.model,com.sun.jersey.s"
+ + "pi.container\";version=\"1.13.0\",com.sun.jersey.server.wadl.generators.resour"
+ + "cedoc;uses:=\"com.sun.jersey.api.model,com.sun.jersey.server.wadl.generators.r"
+ + "esourcedoc.model,com.sun.jersey.server.wadl.generators.resourcedoc.xhtml,com.s"
+ + "un.research.ws.wadl,javax.xml.namespace,com.sun.jersey.server.wadl,javax.xml.b"
+ + "ind,javax.ws.rs.core\";version=\"1.13.0\",com.sun.jersey.server.impl.container"
+ + ".httpserver;uses:=\"com.sun.net.httpserver,com.sun.jersey.spi.container,javax."
+ + "ws.rs.core,com.sun.jersey.core.header,com.sun.jersey.api.container,com.sun.jer"
+ + "sey.core.util,com.sun.jersey.api.core\";version=\"1.13.0\",com.sun.jersey.serv"
+ + "er.impl.container.filter;uses:=\"com.sun.jersey.api.model,com.sun.jersey.spi.c"
+ + "ontainer,com.sun.jersey.core.spi.component,com.sun.jersey.api.core,javax.ws.rs"
+ + ",com.sun.jersey.server.impl.uri,javax.ws.rs.core\";version=\"1.13.0\",com.sun."
+ + "jersey.server.wadl.generators.resourcedoc.xhtml;uses:=\"javax.xml.bind,javax.x"
+ + "ml.namespace,javax.xml.bind.annotation\";version=\"1.13.0\",com.sun.jersey.ser"
+ + "ver.impl.uri.rules;uses:=\"com.sun.jersey.spi.uri.rules,com.sun.jersey.api.uri"
+ + ",com.sun.jersey.api.core,com.sun.jersey.server.impl.model.method,com.sun.jerse"
+ + "y.spi.dispatch,com.sun.jersey.core.header,javax.ws.rs.core,com.sun.jersey.api."
+ + "model,com.sun.jersey.server.probes,com.sun.jersey.core.reflection,com.sun.jers"
+ + "ey.server.impl.template,com.sun.jersey.spi.monitoring,com.sun.jersey.api,com.s"
+ + "un.jersey.spi.container,com.sun.jersey.server.impl.uri,javax.ws.rs,com.sun.jer"
+ + "sey.api.container,com.sun.jersey.server.impl.inject,com.sun.jersey.spi.inject,"
+ + "com.sun.jersey.server.impl.uri.rules.automata\";version=\"1.13.0\",com.sun.jer"
+ + "sey.server.spi.component;uses:=\"com.sun.jersey.spi.inject,com.sun.jersey.api."
+ + "model,com.sun.jersey.core.spi.component,com.sun.jersey.api.core,com.sun.jersey"
+ + ".server.impl.inject,com.sun.jersey.api.container,com.sun.jersey.core.spi.compo"
+ + "nent.ioc\";version=\"1.13.0\",com.sun.jersey.server.probes;version=\"1.13.0\","
+ + "com.sun.jersey.server.wadl.generators;uses:=\"com.sun.research.ws.wadl,javax.x"
+ + "ml.bind.annotation,com.sun.jersey.api.model,com.sun.jersey.server.wadl,javax.x"
+ + "ml.bind,javax.ws.rs.core,com.sun.jersey.api,javax.xml.namespace,javax.xml.tran"
+ + "sform,javax.xml.transform.stream\";version=\"1.13.0\",com.sun.jersey.server.im"
+ + "pl.modelapi.validation;uses:=\"com.sun.jersey.api.model,javax.ws.rs,com.sun.je"
+ + "rsey.impl,com.sun.jersey.api.core,com.sun.jersey.core.reflection,javax.ws.rs.c"
+ + "ore\";version=\"1.13.0\",com.sun.jersey.server.impl.model.method;uses:=\"com.s"
+ + "un.jersey.api.container,com.sun.jersey.spi.dispatch,com.sun.jersey.api.uri,com"
+ + ".sun.jersey.api.model,com.sun.jersey.server.impl.container.filter,com.sun.jers"
+ + "ey.impl,com.sun.jersey.spi.container,com.sun.jersey.spi.inject,com.sun.jersey."
+ + "api.core,javax.ws.rs.core,com.sun.jersey.core.header\";version=\"1.13.0\",com."
+ + "sun.jersey.server.impl.model;uses:=\"javax.ws.rs,com.sun.jersey.impl,com.sun.j"
+ + "ersey.api.container,com.sun.jersey.core.header,com.sun.jersey.core.header.read"
+ + "er,com.sun.jersey.api.core,javax.ws.rs.core,com.sun.jersey.server.impl.model.m"
+ + "ethod,com.sun.jersey.server.impl.container.filter,com.sun.jersey.api.model,com"
+ + ".sun.jersey.server.impl.wadl,com.sun.jersey.spi.monitoring,com.sun.jersey.serv"
+ + "er.impl.uri,com.sun.jersey.spi.container,com.sun.jersey.server.impl.inject,com"
+ + ".sun.jersey.spi.inject,com.sun.jersey.api.uri,com.sun.jersey.core.spi.componen"
+ + "t,com.sun.jersey.server.impl.uri.rules,com.sun.jersey.server.impl.template,com"
+ + ".sun.jersey.api.view,com.sun.jersey.spi.uri.rules\";version=\"1.13.0\",com.sun"
+ + ".jersey.server.impl.uri.rules.automata;uses:=\"com.sun.jersey.server.impl.uri,"
+ + "com.sun.jersey.spi.uri.rules,com.sun.jersey.server.impl.uri.rules,com.sun.jers"
+ + "ey.api.uri\";version=\"1.13.0\",com.sun.jersey.server.impl.uri;uses:=\"com.sun"
+ + ".jersey.api.uri,javax.ws.rs.core\";version=\"1.13.0\",com.sun.jersey.server.im"
+ + "pl.inject;uses:=\"com.sun.jersey.api.core,com.sun.jersey.spi.inject,javax.ws.r"
+ + "s,com.sun.jersey.api.container,com.sun.jersey.api.model,com.sun.jersey.core.sp"
+ + "i.component,com.sun.jersey.core.spi.factory\";version=\"1.13.0\",com.sun.jerse"
+ + "y.spi.scanning;uses:=\"org.objectweb.asm,com.sun.jersey.core.reflection,com.su"
+ + "n.jersey.core.spi.scanning,javax.ws.rs,javax.ws.rs.ext\";version=\"1.13.0\",co"
+ + "m.sun.jersey.spi.resource;uses:=\"com.sun.jersey.server.impl.resource,com.sun."
+ + "jersey.server.spi.component\";version=\"1.13.0\",com.sun.jersey.spi.template;u"
+ + "ses:=\"com.sun.jersey.api.view,javax.ws.rs.core,com.sun.jersey.api.container\""
+ + ";version=\"1.13.0\",com.sun.jersey.spi.dispatch;uses:=\"com.sun.jersey.api.cor"
+ + "e\";version=\"1.13.0\",com.sun.jersey.spi.uri.rules;uses:=\"com.sun.jersey.api"
+ + ".core,com.sun.jersey.api.model,com.sun.jersey.spi.container,com.sun.jersey.api"
+ + ".uri\";version=\"1.13.0\",com.sun.jersey.spi.container;uses:=\"javax.ws.rs,com"
+ + ".sun.jersey.api.representation,com.sun.jersey.core.header,com.sun.jersey.spi,j"
+ + "avax.ws.rs.core,com.sun.jersey.api.container,com.sun.jersey.api.core,com.sun.j"
+ + "ersey.core.util,com.sun.jersey.core.header.reader,com.sun.jersey.server.impl,c"
+ + "om.sun.jersey.core.reflection,javax.ws.rs.ext,com.sun.jersey.server.impl.model"
+ + ",com.sun.jersey.api,com.sun.jersey.api.uri,com.sun.jersey.core.spi.factory,com"
+ + ".sun.jersey.spi.monitoring,com.sun.jersey.api.model,com.sun.jersey.core.spi.co"
+ + "mponent,com.sun.jersey.server.impl.application,com.sun.jersey.impl,com.sun.jer"
+ + "sey.spi.inject,com.sun.jersey.spi.dispatch,com.sun.jersey.server.impl.inject,c"
+ + "om.sun.jersey.core.spi.component.ioc,com.sun.jersey.spi.service\";version=\"1."
+ + "13.0\",com.sun.jersey.spi.monitoring;uses:=\"com.sun.jersey.api.model,com.sun."
+ + "jersey.spi.container,javax.ws.rs.ext\";version=\"1.13.0\",com.sun.jersey.api;u"
+ + "ses:=\"javax.ws.rs,javax.ws.rs.core,com.sun.jersey.core.header,com.sun.jersey."
+ + "core.spi.factory\";version=\"1.13.0\",com.sun.jersey.api.core;uses:=\"javax.ws"
+ + ".rs.core,com.sun.jersey.core.spi.scanning,com.sun.jersey.api.model,com.sun.jer"
+ + "sey.api.uri,javax.ws.rs,com.sun.jersey.core.header,com.sun.jersey.api.represen"
+ + "tation,com.sun.jersey.core.util,javax.ws.rs.ext,com.sun.jersey.api.container,c"
+ + "om.sun.jersey.spi.scanning,com.sun.jersey.spi.container,com.sun.jersey.server."
+ + "impl.application\";version=\"1.13.0\",com.sun.jersey.api.wadl.config;uses:=\"c"
+ + "om.sun.jersey.server.wadl,com.sun.jersey.api.core,com.sun.jersey.core.reflecti"
+ + "on,com.sun.jersey.server.wadl.generators\";version=\"1.13.0\",com.sun.jersey.a"
+ + "pi.model;uses:=\"javax.ws.rs.core,com.sun.jersey.spi.container\";version=\"1.1"
+ + "3.0\",com.sun.jersey.api.view;version=\"1.13.0\",com.sun.jersey.api.container."
+ + "filter;uses:=\"javax.ws.rs,com.sun.jersey.spi.container,javax.ws.rs.core,com.s"
+ + "un.jersey.api.container,com.sun.jersey.api.core,com.sun.jersey.core.util,com.s"
+ + "un.jersey.core.header,com.sun.jersey.api.representation,com.sun.jersey.api.mod"
+ + "el,javax.annotation.security\";version=\"1.13.0\",com.sun.jersey.api.container"
+ + ";uses:=\"com.sun.jersey.api.core,com.sun.jersey.spi.container,com.sun.jersey.s"
+ + "pi.service,com.sun.jersey.core.spi.component.ioc\";version=\"1.13.0\",com.sun."
+ + "jersey.api.container.httpserver;uses:=\"com.sun.net.httpserver,com.sun.jersey."
+ + "api.core,com.sun.jersey.api.container,com.sun.jersey.core.spi.component.ioc\";"
+ + "version=\"1.13.0\",com.sun.research.ws.wadl;uses:=\"javax.xml.bind.annotation,"
+ + "javax.xml.bind.annotation.adapters,javax.xml.namespace\";version=\"1.13.0\"";
+ ExportPackageParser.parseExports(exportHeader);
+ }
+
+ private static TypeSafeMatcher<Parameter> parameterMatching(final String name, final String value) {
+ return new TypeSafeMatcher<Parameter>() {
+ @Override
+ protected boolean matchesSafely(Parameter parameter) {
+ return parameter.getName().equals(name) && parameter.getValue().equals(value);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Parameter with name ").appendValue(name).appendText(" with value ").appendValue(value);
+ }
+ };
+ }
+
+ private static TypeSafeMatcher<Parameter> parameterMatching(final Parameter param) {
+ return parameterMatching(param.getName(), param.getValue());
+ }
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/osgi/ImportPackageTest.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/osgi/ImportPackageTest.java
new file mode 100644
index 00000000000..23960323a31
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/osgi/ImportPackageTest.java
@@ -0,0 +1,130 @@
+// 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.osgi;
+
+import com.yahoo.container.plugin.osgi.ExportPackages.Export;
+import com.yahoo.container.plugin.osgi.ExportPackages.Parameter;
+import com.yahoo.container.plugin.osgi.ImportPackages.Import;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singletonList;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class ImportPackageTest {
+ private static final String PACKAGE_NAME = "com.yahoo.exported";
+ private Set<String> referencedPackages = Collections.singleton(PACKAGE_NAME);
+ private Map<String, Export> exports = exportByPackageName(new Export(singletonList(PACKAGE_NAME), Collections.emptyList()));
+ private Map<String, Export> exportsWithVersion = exportByPackageName(
+ new Export(singletonList(PACKAGE_NAME), singletonList(new Parameter("version", "1.3"))));
+
+ private static Map<String, Export> exportByPackageName(Export export) {
+ return ExportPackages.exportsByPackageName(singletonList(export));
+ }
+
+ @Test
+ public void require_that_non_implemented_import_with_matching_export_is_included() {
+ Set<Import> imports = calculateImports(referencedPackages, emptySet(), exports);
+ assertThat(imports, contains(importMatching(PACKAGE_NAME, "")));
+ }
+
+ @Test
+ public void require_that_non_implemented_import_without_matching_export_is_excluded() {
+ Set<Import> imports = calculateImports(referencedPackages, emptySet(), emptyMap());
+ assertThat(imports, empty());
+ }
+
+ @Test
+ public void require_that_implemented_import_with_matching_export_is_excluded() {
+ Set<Import> imports = calculateImports(referencedPackages, referencedPackages, exports);
+
+ assertThat(imports, empty());
+ }
+
+ @Test
+ public void require_that_version_is_included() {
+ Set<Import> imports = calculateImports(referencedPackages, emptySet(), exportsWithVersion);
+
+ assertThat(imports, contains(importMatching(PACKAGE_NAME, "1.3")));
+ }
+
+ @Test
+ public void require_that_all_versions_up_to_the_next_major_version_is_in_range() {
+ assertThat(new Import("foo", Optional.of("1.2")).importVersionRange().get(), is("[1.2,2)"));
+ }
+
+ // TODO: Detecting guava packages should be based on bundle-symbolicName, not package name.
+ @Test
+ public void require_that_for_guava_all_future_major_versions_are_in_range() {
+ Optional<String> rangeWithInfiniteUpperLimit = Optional.of("[18.1," + ImportPackages.INFINITE_VERSION + ")");
+ assertThat(new Import("com.google.common", Optional.of("18.1")).importVersionRange(), is(rangeWithInfiniteUpperLimit));
+ assertThat(new Import("com.google.common.foo", Optional.of("18.1")).importVersionRange(), is(rangeWithInfiniteUpperLimit));
+ assertThat(new Import("com.google.commonality", Optional.of("18.1")).importVersionRange(), is(Optional.of("[18.1,19)")));
+ }
+
+ @Test
+ public void require_that_none_version_gives_non_version_range() {
+ assertThat(new Import("foo", Optional.empty()).importVersionRange(), is(Optional.empty()));
+ }
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void require_that_exception_is_thrown_when_major_component_is_non_numeric() {
+ expectedException.expect(IllegalArgumentException.class);
+ new Import("foo", Optional.of("1notValid.2"));
+ }
+
+ @Test
+ public void require_that_osgi_import_supports_missing_version() {
+ assertThat(new Import("com.yahoo.exported", Optional.empty()).asOsgiImport(), is("com.yahoo.exported"));
+ }
+
+ @Test
+ public void require_that_osgi_import_version_range_includes_all_versions_from_the_current_up_to_the_next_major_version() {
+ assertThat(new Import("com.yahoo.exported", Optional.of("1.2")).asOsgiImport(), is("com.yahoo.exported;version=\"[1.2,2)\""));
+ }
+
+ @Test
+ public void require_that_osgi_import_version_range_ignores_qualifier() {
+ assertThat(new Import("com.yahoo.exported", Optional.of("1.2.3.qualifier")).asOsgiImport(),
+ is("com.yahoo.exported;version=\"[1.2.3,2)\""));
+ }
+
+ private static Set<Import> calculateImports(Set<String> referencedPackages, Set<String> implementedPackages,
+ Map<String, Export> exportedPackages) {
+ return new HashSet<>(ImportPackages.calculateImports(referencedPackages, implementedPackages, exportedPackages).values());
+ }
+
+ private static TypeSafeMatcher<Import> importMatching(String packageName, String version) {
+ return new TypeSafeMatcher<Import>() {
+ @Override
+ protected boolean matchesSafely(Import anImport) {
+ return anImport.packageName().equals(packageName) && anImport.version().equals(version);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("an Import of package ").appendValue(packageName).appendText(" with version ").appendValue(version);
+ }
+ };
+ }
+}
diff --git a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.scala b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.scala
deleted file mode 100644
index 3ef1b1fea87..00000000000
--- a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.scala
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.bundle
-
-import org.scalatest.junit.{AssertionsForJUnit, JUnitSuite}
-import org.junit.Test
-import com.yahoo.container.plugin.bundle.AnalyzeBundle.PublicPackages
-import com.yahoo.container.plugin.osgi.ExportPackages
-import java.io.File
-
-import org.scalatest.Matchers
-
-/**
- * @author tonytv
- */
-class AnalyzeBundleTest extends JUnitSuite with AssertionsForJUnit with Matchers {
- val jarDir = new File("src/test/resources/jar")
-
- val PublicPackages(exports, globals) = AnalyzeBundle.publicPackagesAggregated(
- List("notAOsgiBundle.jar", "simple1.jar").map(jarFile))
-
- val exportsByPackageName = ExportPackages.exportsByPackageName(exports)
-
- def jarFile(name : String) : File = new File(jarDir, name)
-
- @Test
- def require_that_non_osgi_bundles_are_ignored() {
- exportsByPackageName should not contain key("com.yahoo.sample.exported.package.ignored")
- }
-
- @Test
- def require_that_exports_are_retrieved_from_manifest_in_jars() {
- exportsByPackageName.keySet should be (Set("com.yahoo.sample.exported.package"))
- }
-
- @Test
- def require_that_invalid_exports_throws_exception() {
- val exception = intercept[Exception](AnalyzeBundle.publicPackages(jarFile("errorExport.jar")))
- exception.getMessage should startWith regex ("Invalid manifest in bundle '.*errorExport.jar'")
- exception.getCause.getMessage should startWith ("Failed parsing Export-Package")
- }
-}
diff --git a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassTest.scala b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassTest.scala
deleted file mode 100644
index e2d0e3c6f10..00000000000
--- a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassTest.scala
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.classanalysis
-
-import org.junit.Test
-import org.scalatest.junit.{AssertionsForJUnit, JUnitSuite}
-import java.awt.Image
-import java.awt.image.{ImagingOpException, Kernel}
-
-import sampleclasses._
-import TestUtilities._
-import com.yahoo.osgi.annotation.{ExportPackage, Version}
-import javax.security.auth.login.LoginException
-
-import org.scalatest.Matchers
-
-/**
- * Tests that analysis of class files works.
- * @author tonytv
- */
-class AnalyzeClassTest extends JUnitSuite with AssertionsForJUnit with Matchers {
- @Test def require_that_full_class_name_is_returned() {
- analyzeClass[Base].name should be(name[Base])
- }
-
- @Test def require_that_base_class_is_included() {
- analyzeClass[Derived].referencedClasses should contain (name[Base])
- }
-
- @Test def require_that_implemented_interfaces_are_included() {
- analyzeClass[Base].referencedClasses should (contain (name[Interface1]) and contain (name[Interface2]))
- }
-
- @Test def require_that_interface_can_be_analyzed() {
- val classMetaData = analyzeClass[Interface1]
-
- classMetaData.name should be(name[Interface1])
- classMetaData.referencedClasses should contain(name[Interface2])
- }
-
- @Test def require_that_return_type_is_included() {
- analyzeClass[Interface1].referencedClasses should contain (name[Image])
- }
-
- @Test def require_that_parameters_are_included() {
- analyzeClass[Interface1].referencedClasses should contain (name[Kernel])
- }
-
- @Test def require_that_exceptions_are_included() {
- analyzeClass[Interface1].referencedClasses should contain (name[ImagingOpException])
- }
-
- @Test def require_that_basic_types_ignored() {
- analyzeClass[Interface1].referencedClasses should not (contain ("int") or contain ("float"))
- }
-
- @Test def require_that_arrays_of_basic_types_ignored() {
- analyzeClass[Interface1].referencedClasses should not (contain ("int[]") or contain ("int[][]"))
- }
-
- @Test def require_that_instance_field_types_are_included() {
- analyzeClass[Fields].referencedClasses should contain (name[String])
- }
-
- @Test def require_that_static_field_types_are_included() {
- analyzeClass[Fields].referencedClasses should contain (name[java.util.List[_]])
- }
-
- @Test def require_that_field_annotation_is_included() {
- analyzeClass[Fields].referencedClasses should contain (name[DummyAnnotation])
- }
-
- @Test def require_that_class_annotation_is_included() {
- analyzeClass[ClassAnnotation].referencedClasses should contain(name[DummyAnnotation])
- }
-
- @Test def require_that_method_annotation_is_included() {
- analyzeClass[MethodAnnotation].referencedClasses should contain(name[DummyAnnotation])
- }
-
- @Test def require_that_export_package_annotations_are_ignored() {
- Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.package-info")).
- referencedClasses should not (contain (name[ExportPackage]) or contain (name[Version]))
- }
-
- @Test def require_that_export_annotations_are_processed() {
- Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.package-info")).
- exportPackage should be (Some(ExportPackageAnnotation(3, 1, 4, "TEST_QUALIFIER-2")))
- }
-
- @Test def require_that_export_annotations_are_validated() {
- val exception = intercept[RuntimeException] {
- Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.invalid.package-info"))
- }
-
- exception.getMessage should include ("invalid/package-info")
- exception.getCause.getMessage should include ("qualifier must follow the format")
- exception.getCause.getMessage should include ("'EXAMPLE INVALID QUALIFIER'")
- }
-
- @Test def require_that_catch_clauses_are_included() {
- Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.CatchException")).
- referencedClasses should contain(name[LoginException])
- }
-
- @Test def require_that_class_references_are_included() {
- Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.ClassReference")).
- referencedClasses should contain(name[Interface1])
- }
-
- @Test def require_that_return_type_of_method_invocations_are_included() {
- analyzeClass[MethodInvocation].referencedClasses should contain(name[Image])
- }
-
- @Test def require_that_attributes_are_included() {
- //Uses com/coremedia/iso/Utf8.class from com.googlecode.mp4parser:isoparser:1.0-RC-1
- Analyze.analyzeClass(classFile("class/Utf8")).referencedClasses should contain("org.aspectj.weaver.MethodDeclarationLineNumber")
- }
-}
diff --git a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodBodyTest.scala b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodBodyTest.scala
deleted file mode 100644
index 43f52884f39..00000000000
--- a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodBodyTest.scala
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.classanalysis
-
-import org.scalatest.junit.{AssertionsForJUnit, JUnitSuite}
-import sampleclasses._
-import TestUtilities._
-import org.junit.Test
-import java.io.PrintStream
-
-import org.scalatest.Matchers
-
-/**
- * Tests that classes used in method bodies are included in the imports list.
- * @author tonytv
- */
-class AnalyzeMethodBodyTest extends JUnitSuite with AssertionsForJUnit with Matchers {
- @Test def require_that_class_of_locals_are_included() {
- analyzeClass[Methods].referencedClasses should contain(name[Base])
- }
-
- @Test def require_that_class_of_locals_in_static_method_are_included() {
- analyzeClass[Methods].referencedClasses should contain(name[Derived])
- }
-
- @Test def require_that_field_references_are_included() {
- analyzeClass[Methods].referencedClasses should (contain (name[java.util.List[_]]) and contain (name[Fields]))
- }
-
- @Test def require_that_class_owning_field_is_included() {
- analyzeClass[Methods].referencedClasses should contain (name[System])
- }
-
- @Test def require_that_class_containing_method_is_included() {
- analyzeClass[Methods].referencedClasses should contain (name[PrintStream])
- }
-
- @Test def require_that_element_of_new_multidimensional_array_is_included() {
- analyzeClass[Methods].referencedClasses should contain (name[Interface1])
- }
-
- @Test def require_that_basic_arrays_are_not_included() {
- analyzeClass[Methods].referencedClasses should not (contain("int[]"))
- }
-
- @Test def require_that_container_generic_parameters_are_included() {
- analyzeClass[Methods].referencedClasses should contain(name[Dummy])
- }
-
- @Test def require_that_class_owning_method_handler_is_included() {
- analyzeClass[Methods].referencedClasses should contain(name[ClassWithMethod])
- }
-}
diff --git a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/TestUtilities.scala b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/TestUtilities.scala
deleted file mode 100644
index fbc3554e9e9..00000000000
--- a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/TestUtilities.scala
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.classanalysis
-
-import scala.reflect.ClassTag
-import java.io.File
-
-/**
- * @author tonytv
- */
-object TestUtilities {
- def analyzeClass[T](implicit m: ClassTag[T]) =
- Analyze.analyzeClass(classFile(name(m)))
-
- def classFile(className : String) =
- new File("target/test-classes/" + className.replace('.', '/') + ".class")
-
- def name[T](implicit m: ClassTag[T]) = m.runtimeClass.getName
-}
diff --git a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ExportPackageParserTest.scala b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ExportPackageParserTest.scala
deleted file mode 100644
index 5ddc7fef16d..00000000000
--- a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ExportPackageParserTest.scala
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.osgi
-
-import org.scalatest.junit.{AssertionsForJUnit, JUnitSuite}
-import org.junit.Test
-import ExportPackages.{Export, Parameter}
-import org.scalatest.Matchers
-
-/**
- * @author tonytv
- */
-class ExportPackageParserTest extends JUnitSuite with AssertionsForJUnit with Matchers {
- val versionParameter = Parameter("version", "1.2.3.sample")
-
- @Test
- def require_that_package_is_parsed_correctly() {
- ExportPackageParser.parseAll("sample.exported.package").get should
- be (List(Export(List("sample.exported.package"), List())))
- }
-
- @Test
- def require_that_version_is_parsed_correctly() {
- ExportPackageParser.parseAll("com.yahoo.sample.exported.package;version=\"1.2.3.sample\"").get should
- be (List(Export(List("com.yahoo.sample.exported.package"), List(versionParameter))))
- }
-
- @Test
- def require_that_multiple_packages_with_same_parameters_is_parsed_correctly() {
- ExportPackageParser.parseAll("exported.package1;exported.package2;version=\"1.2.3.sample\"").get should
- be (List(Export(List("exported.package1", "exported.package2"), List(versionParameter))))
- }
-
- @Test
- def require_that_multiple_parameters_for_a_package_is_parsed_correctly() {
- ExportPackageParser.parseAll("exported.package;version=\"1.2.3.sample\";param2=true").get.head.parameters should
- be (List(versionParameter, Parameter("param2", "true")))
- }
-
- @Test
- def require_that_multiple_exports_are_parsed_correctly() {
- ExportPackageParser.parseAll("exported.package1,exported.package2").get should
- be(List(
- Export(List("exported.package1"), List()),
- Export(List("exported.package2"), List())))
-
- ExportPackageParser.parseAll("exported.package1;version=\"1.2.3.sample\",exported.package2").get should
- be (List(
- Export(List("exported.package1"), List(versionParameter)),
- Export(List("exported.package2"), List())))
-
- ExportPackageParser.parseAll("exported.package1,exported.package2;version=\"1.2.3.sample\"").get should
- be(List(
- Export(List("exported.package1"), List()),
- Export(List("exported.package2"), List(versionParameter))))
- }
-
- @Test
- def require_that_long_string_literals_do_not_cause_stack_overflow_error() {
- //From jersey-server-1.13.jar
- val export = """com.sun.jersey.server.impl.wadl;uses:="com.sun.jersey.api.model,com.sun.research.ws.wadl,com.sun.jersey.api.wadl.config,com.sun.jersey.server.wadl,com.sun.jersey.api.core,javax.xml.bind,javax.ws.rs.core,com.sun.jersey.server.impl.uri,com.sun.jersey.core.spi.factory,com.sun.jersey.server.impl.model.method,com.sun.jersey.api.uri,com.sun.jersey.core.header,com.sun.jersey.spi.dispatch,javax.ws.rs,com.sun.jersey.spi.resource";version="1.13.0",com.sun.jersey.server.impl.model.parameter.multivalued;uses:="com.sun.jersey.spi,javax.ws.rs.core,com.sun.jersey.api.container,com.sun.jersey.impl,javax.xml.parsers,org.xml.sax,javax.xml.transform,javax.xml.bind.annotation,javax.xml.transform.sax,com.sun.jersey.spi.inject,javax.xml.bind,javax.ws.rs.ext,com.sun.jersey.api.model,com.sun.jersey.core.reflection,javax.ws.rs,com.sun.jersey.core.spi.component,com.sun.jersey.core.header";version="1.13.0",com.sun.jersey.server.impl.model.parameter;uses:="com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com.sun.jersey.server.impl.model.parameter.multivalued,com.sun.jersey.spi.inject,com.sun.jersey.api,com.sun.jersey.api.core,com.sun.jersey.server.impl.inject,javax.ws.rs.core,javax.ws.rs,com.sun.jersey.core.header,com.sun.jersey.api.representation";version="1.13.0",com.sun.jersey.server.impl.application;uses:="com.sun.jersey.core.spi.component,com.sun.jersey.api.core,com.sun.jersey.spi,com.sun.jersey.spi.inject,javax.ws.rs.core,com.sun.jersey.api.container,javax.ws.rs.ext,com.sun.jersey.spi.container,com.sun.jersey.core.reflection,com.sun.jersey.api.model,com.sun.jersey.impl,com.sun.jersey.spi.dispatch,com.sun.jersey.server.impl.model,com.sun.jersey.server.impl.wadl,com.sun.jersey.server.impl.uri,com.sun.jersey.core.spi.factory,com.sun.jersey.api.uri,com.sun.jersey.server.impl.uri.rules,com.sun.jersey.spi.uri.rules,com.sun.jersey.server.spi.component,com.sun.jersey.core.util,com.sun.jersey.core.header,com.sun.jersey.core.spi.component.ioc,javax.ws.rs,com.sun.jersey.server.impl,com.sun.jersey.server.wadl,com.sun.jersey.server.impl.inject,com.sun.jersey.server.impl.component,com.sun.jersey.spi.monitoring,com.sun.jersey.server.impl.monitoring,com.sun.jersey.api.container.filter,com.sun.jersey.server.impl.model.parameter.multivalued,com.sun.jersey.server.impl.model.parameter,com.sun.jersey.server.impl.template,com.sun.jersey.spi.template,com.sun.jersey.server.impl.resource,com.sun.jersey.server.impl.modelapi.annotation,com.sun.jersey.server.impl.container.filter,com.sun.jersey.server.impl.modelapi.validation,com.sun.jersey.api,com.sun.jersey.spi.service";version="1.13.0",com.sun.jersey.server.impl.component;uses:="com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com.sun.jersey.server.spi.component,com.sun.jersey.api.core,com.sun.jersey.core.spi.component.ioc,com.sun.jersey.server.impl.inject,com.sun.jersey.server.impl.resource,com.sun.jersey.api.container,com.sun.jersey.core.reflection,com.sun.jersey.spi.inject";version="1.13.0",com.sun.jersey.server.impl.provider;uses:="com.sun.jersey.core.spi.factory,com.sun.jersey.api.container,com.sun.jersey.api.core,javax.ws.rs.core";version="1.13.0",com.sun.jersey.server.impl.template;uses:="com.sun.jersey.core.spi.component,com.sun.jersey.api.view,com.sun.jersey.spi.template,javax.ws.rs.core,com.sun.jersey.core.header,com.sun.jersey.server.impl.model.method,com.sun.jersey.spi.dispatch,com.sun.jersey.api.uri,javax.ws.rs,com.sun.jersey.spi.inject,javax.ws.rs.ext,com.sun.jersey.server.impl.uri.rules,com.sun.jersey.server.probes,com.sun.jersey.core.reflection,com.sun.jersey.spi.uri.rules,com.sun.jersey.spi.container,com.sun.jersey.api.core";version="1.13.0",com.sun.jersey.server.osgi;uses:="com.sun.jersey.server.impl.provider,org.osgi.framework,javax.ws.rs.ext";version="1.13.0",com.sun.jersey.server.wadl.generators.resourcedoc.model;uses:="javax.xml.bind.annotation,javax.xml.namespace";version="1.13.0",com.sun.jersey.server.impl.resource;uses:="com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com.sun.jersey.server.spi.component,com.sun.jersey.api.core,com.sun.jersey.api.container,javax.ws.rs,com.sun.jersey.server.impl.inject,com.sun.jersey.core.spi.component.ioc,javax.ws.rs.core";version="1.13.0",com.sun.jersey.server.impl.monitoring;uses:="com.sun.jersey.spi.monitoring,com.sun.jersey.spi.service,com.sun.jersey.api.model,com.sun.jersey.spi.container,javax.ws.rs.ext,com.sun.jersey.core.spi.component";version="1.13.0",com.sun.jersey.server.impl.modelapi.annotation;uses:="com.sun.jersey.api.model,javax.ws.rs.core,javax.ws.rs,com.sun.jersey.core.reflection,com.sun.jersey.core.header,com.sun.jersey.impl";version="1.13.0",com.sun.jersey.server.impl.container;uses:="com.sun.jersey.server.impl.application,com.sun.jersey.spi.container,com.sun.jersey.api.container";version="1.13.0",com.sun.jersey.server.wadl;uses:="javax.ws.rs.core,com.sun.research.ws.wadl,javax.xml.namespace,com.sun.jersey.api.model,javax.xml.bind,javax.ws.rs,com.sun.jersey.server.wadl.generators,com.sun.jersey.server.impl.modelapi.annotation,com.sun.jersey.server.impl";version="1.13.0",com.sun.jersey.server.impl.model.method.dispatch;uses:="com.sun.jersey.api.model,com.sun.jersey.api.core,com.sun.jersey.spi.container,com.sun.jersey.server.impl.inject,com.sun.jersey.api,javax.ws.rs.core,com.sun.jersey.core.spi.factory,com.sun.jersey.spi.inject,com.sun.jersey.spi.dispatch,com.sun.jersey.core.spi.component,javax.ws.rs,com.sun.jersey.server.impl.model.parameter.multivalued,com.sun.jersey.api.representation,com.sun.jersey.api.container";version="1.13.0",com.sun.jersey.server.impl;uses:="javax.naming,com.sun.jersey.api.core,com.sun.jersey.core.header,javax.ws.rs.core,com.sun.jersey.server.impl.model,com.sun.jersey.spi.container";version="1.13.0",com.sun.jersey.server.wadl.generators.resourcedoc;uses:="com.sun.jersey.api.model,com.sun.jersey.server.wadl.generators.resourcedoc.model,com.sun.jersey.server.wadl.generators.resourcedoc.xhtml,com.sun.research.ws.wadl,javax.xml.namespace,com.sun.jersey.server.wadl,javax.xml.bind,javax.ws.rs.core";version="1.13.0",com.sun.jersey.server.impl.container.httpserver;uses:="com.sun.net.httpserver,com.sun.jersey.spi.container,javax.ws.rs.core,com.sun.jersey.core.header,com.sun.jersey.api.container,com.sun.jersey.core.util,com.sun.jersey.api.core";version="1.13.0",com.sun.jersey.server.impl.container.filter;uses:="com.sun.jersey.api.model,com.sun.jersey.spi.container,com.sun.jersey.core.spi.component,com.sun.jersey.api.core,javax.ws.rs,com.sun.jersey.server.impl.uri,javax.ws.rs.core";version="1.13.0",com.sun.jersey.server.wadl.generators.resourcedoc.xhtml;uses:="javax.xml.bind,javax.xml.namespace,javax.xml.bind.annotation";version="1.13.0",com.sun.jersey.server.impl.uri.rules;uses:="com.sun.jersey.spi.uri.rules,com.sun.jersey.api.uri,com.sun.jersey.api.core,com.sun.jersey.server.impl.model.method,com.sun.jersey.spi.dispatch,com.sun.jersey.core.header,javax.ws.rs.core,com.sun.jersey.api.model,com.sun.jersey.server.probes,com.sun.jersey.core.reflection,com.sun.jersey.server.impl.template,com.sun.jersey.spi.monitoring,com.sun.jersey.api,com.sun.jersey.spi.container,com.sun.jersey.server.impl.uri,javax.ws.rs,com.sun.jersey.api.container,com.sun.jersey.server.impl.inject,com.sun.jersey.spi.inject,com.sun.jersey.server.impl.uri.rules.automata";version="1.13.0",com.sun.jersey.server.spi.component;uses:="com.sun.jersey.spi.inject,com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com.sun.jersey.api.core,com.sun.jersey.server.impl.inject,com.sun.jersey.api.container,com.sun.jersey.core.spi.component.ioc";version="1.13.0",com.sun.jersey.server.probes;version="1.13.0",com.sun.jersey.server.wadl.generators;uses:="com.sun.research.ws.wadl,javax.xml.bind.annotation,com.sun.jersey.api.model,com.sun.jersey.server.wadl,javax.xml.bind,javax.ws.rs.core,com.sun.jersey.api,javax.xml.namespace,javax.xml.transform,javax.xml.transform.stream";version="1.13.0",com.sun.jersey.server.impl.modelapi.validation;uses:="com.sun.jersey.api.model,javax.ws.rs,com.sun.jersey.impl,com.sun.jersey.api.core,com.sun.jersey.core.reflection,javax.ws.rs.core";version="1.13.0",com.sun.jersey.server.impl.model.method;uses:="com.sun.jersey.api.container,com.sun.jersey.spi.dispatch,com.sun.jersey.api.uri,com.sun.jersey.api.model,com.sun.jersey.server.impl.container.filter,com.sun.jersey.impl,com.sun.jersey.spi.container,com.sun.jersey.spi.inject,com.sun.jersey.api.core,javax.ws.rs.core,com.sun.jersey.core.header";version="1.13.0",com.sun.jersey.server.impl.model;uses:="javax.ws.rs,com.sun.jersey.impl,com.sun.jersey.api.container,com.sun.jersey.core.header,com.sun.jersey.core.header.reader,com.sun.jersey.api.core,javax.ws.rs.core,com.sun.jersey.server.impl.model.method,com.sun.jersey.server.impl.container.filter,com.sun.jersey.api.model,com.sun.jersey.server.impl.wadl,com.sun.jersey.spi.monitoring,com.sun.jersey.server.impl.uri,com.sun.jersey.spi.container,com.sun.jersey.server.impl.inject,com.sun.jersey.spi.inject,com.sun.jersey.api.uri,com.sun.jersey.core.spi.component,com.sun.jersey.server.impl.uri.rules,com.sun.jersey.server.impl.template,com.sun.jersey.api.view,com.sun.jersey.spi.uri.rules";version="1.13.0",com.sun.jersey.server.impl.uri.rules.automata;uses:="com.sun.jersey.server.impl.uri,com.sun.jersey.spi.uri.rules,com.sun.jersey.server.impl.uri.rules,com.sun.jersey.api.uri";version="1.13.0",com.sun.jersey.server.impl.uri;uses:="com.sun.jersey.api.uri,javax.ws.rs.core";version="1.13.0",com.sun.jersey.server.impl.inject;uses:="com.sun.jersey.api.core,com.sun.jersey.spi.inject,javax.ws.rs,com.sun.jersey.api.container,com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com.sun.jersey.core.spi.factory";version="1.13.0",com.sun.jersey.spi.scanning;uses:="org.objectweb.asm,com.sun.jersey.core.reflection,com.sun.jersey.core.spi.scanning,javax.ws.rs,javax.ws.rs.ext";version="1.13.0",com.sun.jersey.spi.resource;uses:="com.sun.jersey.server.impl.resource,com.sun.jersey.server.spi.component";version="1.13.0",com.sun.jersey.spi.template;uses:="com.sun.jersey.api.view,javax.ws.rs.core,com.sun.jersey.api.container";version="1.13.0",com.sun.jersey.spi.dispatch;uses:="com.sun.jersey.api.core";version="1.13.0",com.sun.jersey.spi.uri.rules;uses:="com.sun.jersey.api.core,com.sun.jersey.api.model,com.sun.jersey.spi.container,com.sun.jersey.api.uri";version="1.13.0",com.sun.jersey.spi.container;uses:="javax.ws.rs,com.sun.jersey.api.representation,com.sun.jersey.core.header,com.sun.jersey.spi,javax.ws.rs.core,com.sun.jersey.api.container,com.sun.jersey.api.core,com.sun.jersey.core.util,com.sun.jersey.core.header.reader,com.sun.jersey.server.impl,com.sun.jersey.core.reflection,javax.ws.rs.ext,com.sun.jersey.server.impl.model,com.sun.jersey.api,com.sun.jersey.api.uri,com.sun.jersey.core.spi.factory,com.sun.jersey.spi.monitoring,com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com.sun.jersey.server.impl.application,com.sun.jersey.impl,com.sun.jersey.spi.inject,com.sun.jersey.spi.dispatch,com.sun.jersey.server.impl.inject,com.sun.jersey.core.spi.component.ioc,com.sun.jersey.spi.service";version="1.13.0",com.sun.jersey.spi.monitoring;uses:="com.sun.jersey.api.model,com.sun.jersey.spi.container,javax.ws.rs.ext";version="1.13.0",com.sun.jersey.api;uses:="javax.ws.rs,javax.ws.rs.core,com.sun.jersey.core.header,com.sun.jersey.core.spi.factory";version="1.13.0",com.sun.jersey.api.core;uses:="javax.ws.rs.core,com.sun.jersey.core.spi.scanning,com.sun.jersey.api.model,com.sun.jersey.api.uri,javax.ws.rs,com.sun.jersey.core.header,com.sun.jersey.api.representation,com.sun.jersey.core.util,javax.ws.rs.ext,com.sun.jersey.api.container,com.sun.jersey.spi.scanning,com.sun.jersey.spi.container,com.sun.jersey.server.impl.application";version="1.13.0",com.sun.jersey.api.wadl.config;uses:="com.sun.jersey.server.wadl,com.sun.jersey.api.core,com.sun.jersey.core.reflection,com.sun.jersey.server.wadl.generators";version="1.13.0",com.sun.jersey.api.model;uses:="javax.ws.rs.core,com.sun.jersey.spi.container";version="1.13.0",com.sun.jersey.api.view;version="1.13.0",com.sun.jersey.api.container.filter;uses:="javax.ws.rs,com.sun.jersey.spi.container,javax.ws.rs.core,com.sun.jersey.api.container,com.sun.jersey.api.core,com.sun.jersey.core.util,com.sun.jersey.core.header,com.sun.jersey.api.representation,com.sun.jersey.api.model,javax.annotation.security";version="1.13.0",com.sun.jersey.api.container;uses:="com.sun.jersey.api.core,com.sun.jersey.spi.container,com.sun.jersey.spi.service,com.sun.jersey.core.spi.component.ioc";version="1.13.0",com.sun.jersey.api.container.httpserver;uses:="com.sun.net.httpserver,com.sun.jersey.api.core,com.sun.jersey.api.container,com.sun.jersey.core.spi.component.ioc";version="1.13.0",com.sun.research.ws.wadl;uses:="javax.xml.bind.annotation,javax.xml.bind.annotation.adapters,javax.xml.namespace";version="1.13.0"""
- ExportPackageParser.parseAll(export) //throws StackOverflow exception on scala library 2.10.2
- }
-}
diff --git a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ImportPackageTest.scala b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ImportPackageTest.scala
deleted file mode 100644
index 4011f170da9..00000000000
--- a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ImportPackageTest.scala
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.osgi
-
-import org.scalatest.junit.{AssertionsForJUnit, JUnitSuite}
-import org.junit.Test
-import ImportPackages.Import
-import ExportPackages.{Export, Parameter}
-import org.scalatest.Matchers
-
-/**
- * @author tonytv
- */
-class ImportPackageTest extends JUnitSuite with AssertionsForJUnit with Matchers {
- val referencedPackages = Set("com.yahoo.exported")
- val exports = exportByPackageName(Export(List("com.yahoo.exported"), List()))
- val exportsWithVersion = exportByPackageName(exports.head._2.copy(parameters = List(Parameter("version", "1.3"))))
-
- def exportByPackageName(export : Export) = ExportPackages.exportsByPackageName(List(export))
- @Test
- def require_that_non_implemented_import_with_matching_export_is_included() {
- val imports = calculateImports(referencedPackages, implementedPackages = Set(), exportedPackages = exports)
- imports should be (Set(Import("com.yahoo.exported", None)))
- }
-
-
- @Test
- def require_that_non_implemented_import_without_matching_export_is_excluded() {
- val imports = calculateImports(referencedPackages, implementedPackages = Set(), exportedPackages = Map())
- imports should be (Set())
- }
-
- @Test
- def require_that_implemented_import_with_matching_export_is_excluded() {
- val imports = calculateImports(
- referencedPackages,
- implementedPackages = referencedPackages,
- exportedPackages = exports)
-
- imports should be (Set())
- }
-
- @Test
- def require_that_version_is_included() {
- val imports = calculateImports(referencedPackages, implementedPackages = Set(), exportedPackages = exportsWithVersion)
-
- imports should be (Set(Import("com.yahoo.exported", Some("1.3"))))
- }
-
- @Test
- def require_that_all_versions_up_to_the_next_major_version_is_in_range() {
- Import("foo", Some("1.2")).importVersionRange should be (Some("[1.2,2)"))
- }
-
- // TODO: Detecting guava packages should be based on bundle-symbolicName, not package name.
- @Test
- def require_that_for_guava_all_future_major_versions_are_in_range() {
- val rangeWithInfiniteUpperLimit = Some("[18.1," + ImportPackages.InfiniteVersion + ")")
- Import("com.google.common", Some("18.1")).importVersionRange should be (rangeWithInfiniteUpperLimit)
- Import("com.google.common.foo", Some("18.1")).importVersionRange should be (rangeWithInfiniteUpperLimit)
-
- Import("com.google.commonality", Some("18.1")).importVersionRange should be (Some("[18.1,19)"))
- }
-
- @Test
- def require_that_none_version_gives_non_version_range() {
- Import("foo", None).importVersionRange should be (None)
- }
-
- @Test
- def require_that_exception_is_thrown_when_major_component_is_non_numeric() {
- intercept[IllegalArgumentException](Import("foo", Some("1notValid.2")))
- }
-
- @Test
- def require_that_osgi_import_supports_missing_version() {
- Import("com.yahoo.exported", None).asOsgiImport should be ("com.yahoo.exported")
- }
-
- @Test
- def require_that_osgi_import_version_range_includes_all_versions_from_the_current_up_to_the_next_major_version() {
- Import("com.yahoo.exported", Some("1.2")).asOsgiImport should be ("com.yahoo.exported;version=\"[1.2,2)\"")
- }
-
- @Test
- def require_that_osgi_import_version_range_ignores_qualifier() {
- Import("com.yahoo.exported", Some("1.2.3.qualifier")).asOsgiImport should be ("com.yahoo.exported;version=\"[1.2.3,2)\"")
- }
-
-
- def calculateImports(referencedPackages : Set[String],
- implementedPackages : Set[String],
- exportedPackages : Map[String, Export]) : Set[Import] = {
- ImportPackages.calculateImports(referencedPackages, implementedPackages, exportedPackages).values.toSet
- }
-}
diff --git a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/util/IOTest.scala b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/util/IOTest.scala
deleted file mode 100644
index 7efb0392f5e..00000000000
--- a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/util/IOTest.scala
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.plugin.util
-
-import org.scalatest.junit.{AssertionsForJUnit, JUnitSuite}
-import org.junit.Test
-import IO.using
-import java.io.Closeable
-
-import org.scalatest.Matchers
-
-/**
- * @author tonytv
- */
-class IOTest extends JUnitSuite with AssertionsForJUnit with Matchers {
- class ClosingException extends RuntimeException
- class FunctionException extends RuntimeException
-
- object throwWhenClosingResource extends Closeable {
- def close() {
- throw new ClosingException()
- }
- }
-
- def throwFunction(r : throwWhenClosingResource.type) = throw new FunctionException
- def nonThrowingFunction(r : throwWhenClosingResource.type) = 42
-
- @Test
- def require_that_function_exception_is_prioritized_over_closing_exception() {
- intercept[FunctionException]{
- using(throwWhenClosingResource, readOnly = false)(throwFunction)
- }
- }
-
- @Test
- def require_that_closing_exception_is_ignored_when_read_only() {
- using(throwWhenClosingResource, readOnly = true)(nonThrowingFunction) should be (nonThrowingFunction(null))
- }
-
- @Test
- def require_that_closing_exception_is_rethrown_when_not_read_only() {
- intercept[ClosingException] {
- using(throwWhenClosingResource, readOnly = false)(nonThrowingFunction)
- }
- }
-}
diff --git a/jdisc_core/pom.xml b/jdisc_core/pom.xml
index 55da222994f..246265203cb 100644
--- a/jdisc_core/pom.xml
+++ b/jdisc_core/pom.xml
@@ -147,13 +147,6 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
- <dependency>
- <!-- This seems odd. Used for export-package parsing. Lazy stuff. Should be separated out. -->
- <groupId>com.yahoo.vespa</groupId>
- <artifactId>bundle-plugin</artifactId>
- <version>${project.version}</version>
- <scope>provided</scope>
- </dependency>
</dependencies>
<build>
<plugins>
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/core/ExportPackages.java b/jdisc_core/src/main/java/com/yahoo/jdisc/core/ExportPackages.java
index f2e9c1a6e53..8da1a4fad99 100644
--- a/jdisc_core/src/main/java/com/yahoo/jdisc/core/ExportPackages.java
+++ b/jdisc_core/src/main/java/com/yahoo/jdisc/core/ExportPackages.java
@@ -1,11 +1,8 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.core;
-import com.yahoo.container.plugin.bundle.TransformExportPackages;
-import com.yahoo.container.plugin.osgi.ExportPackages.Export;
import org.apache.felix.framework.util.Util;
import org.osgi.framework.Constants;
-import scala.collection.immutable.List;
import java.io.File;
import java.io.FileInputStream;
@@ -96,10 +93,4 @@ public class ExportPackages {
return jar.getManifest().getMainAttributes().getValue(Constants.EXPORT_PACKAGE);
}
}
-
- private static String transformExports(List<Export> exports, String newVersion) {
- return TransformExportPackages.toExportPackageProperty(
- TransformExportPackages.removeUses(
- TransformExportPackages.replaceVersions(exports, newVersion)));
- }
}