diff options
author | Olli Virtanen <olli.virtanen@oath.com> | 2018-06-13 13:38:26 +0200 |
---|---|---|
committer | Olli Virtanen <olli.virtanen@oath.com> | 2018-06-13 13:38:26 +0200 |
commit | d5836f186c269986cb9d86374b54b540bf600930 (patch) | |
tree | e95e2ec309835ef1d285630e87bae5d177e9934b /bundle-plugin/src | |
parent | eb80fb0d3a6004431ff13e36e9f480ccb32ec31f (diff) |
Bundle-plugin Scala code converted to Java
Diffstat (limited to 'bundle-plugin/src')
69 files changed, 3055 insertions, 1902 deletions
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 -> [.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) - } - } -} |