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/main | |
parent | eb80fb0d3a6004431ff13e36e9f480ccb32ec31f (diff) |
Bundle-plugin Scala code converted to Java
Diffstat (limited to 'bundle-plugin/src/main')
53 files changed, 2262 insertions, 1470 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) -} |