Extract jar scanning part from AutoRegisterModules
Extract scanning code from AutoRegisterModules so it can be used in SecureStore implementation. Change-Id: I975cbd35b9ad2b570f723f1a314bb036729ed4b3 Signed-off-by: Dariusz Luksza <dariusz@luksza.org>
This commit is contained in:
committed by
David Pursehouse
parent
19e51a5e33
commit
07e42d5ec4
@@ -23,34 +23,20 @@ import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.extensions.annotations.Export;
|
||||
import com.google.gerrit.extensions.annotations.ExtensionPoint;
|
||||
import com.google.gerrit.extensions.annotations.Listen;
|
||||
import com.google.gerrit.server.plugins.JarScanner.ExtensionMetaData;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Scopes;
|
||||
import com.google.inject.TypeLiteral;
|
||||
|
||||
import org.eclipse.jgit.util.IO;
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.Attribute;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
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.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
class AutoRegisterModules {
|
||||
private static final int SKIP_ALL = ClassReader.SKIP_CODE
|
||||
| ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
|
||||
private final String pluginName;
|
||||
private final PluginGuiceEnvironment env;
|
||||
private final JarFile jarFile;
|
||||
@@ -124,60 +110,31 @@ class AutoRegisterModules {
|
||||
}
|
||||
|
||||
private void scan() throws InvalidPluginException {
|
||||
Enumeration<JarEntry> e = jarFile.entries();
|
||||
while (e.hasMoreElements()) {
|
||||
JarEntry entry = e.nextElement();
|
||||
if (skip(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ClassData def = new ClassData();
|
||||
try {
|
||||
new ClassReader(read(entry)).accept(def, SKIP_ALL);
|
||||
} catch (IOException err) {
|
||||
throw new InvalidPluginException("Cannot auto-register", err);
|
||||
} catch (RuntimeException err) {
|
||||
PluginLoader.log.warn(String.format(
|
||||
"Plugin %s has invaild class file %s inside of %s",
|
||||
pluginName, entry.getName(), jarFile.getName()), err);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (def.exportedAsName != null) {
|
||||
if (def.isConcrete()) {
|
||||
export(def);
|
||||
} else {
|
||||
PluginLoader.log.warn(String.format(
|
||||
"Plugin %s tries to @Export(\"%s\") abstract class %s",
|
||||
pluginName, def.exportedAsName, def.className));
|
||||
}
|
||||
} else if (def.listen) {
|
||||
if (def.isConcrete()) {
|
||||
listen(def);
|
||||
} else {
|
||||
PluginLoader.log.warn(String.format(
|
||||
"Plugin %s tries to @Listen abstract class %s",
|
||||
pluginName, def.className));
|
||||
}
|
||||
}
|
||||
Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> extensions =
|
||||
JarScanner.scan(jarFile, pluginName, Arrays.asList(Export.class, Listen.class));
|
||||
for (ExtensionMetaData export : extensions.get(Export.class)) {
|
||||
export(export);
|
||||
}
|
||||
for (ExtensionMetaData listener : extensions.get(Listen.class)) {
|
||||
listen(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void export(ClassData def) throws InvalidPluginException {
|
||||
private void export(ExtensionMetaData def) throws InvalidPluginException {
|
||||
Class<?> clazz;
|
||||
try {
|
||||
clazz = Class.forName(def.className, false, classLoader);
|
||||
clazz = Class.forName(def.getClassName(), false, classLoader);
|
||||
} catch (ClassNotFoundException err) {
|
||||
throw new InvalidPluginException(String.format(
|
||||
"Cannot load %s with @Export(\"%s\")",
|
||||
def.className, def.exportedAsName), err);
|
||||
def.getClassName(), def.getAnnotationValue()), err);
|
||||
}
|
||||
|
||||
Export export = clazz.getAnnotation(Export.class);
|
||||
if (export == null) {
|
||||
PluginLoader.log.warn(String.format(
|
||||
"In plugin %s asm incorrectly parsed %s with @Export(\"%s\")",
|
||||
pluginName, clazz.getName(), def.exportedAsName));
|
||||
pluginName, clazz.getName(), def.getAnnotationValue()));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,14 +159,14 @@ class AutoRegisterModules {
|
||||
}
|
||||
}
|
||||
|
||||
private void listen(ClassData def) throws InvalidPluginException {
|
||||
private void listen(ExtensionMetaData def) throws InvalidPluginException {
|
||||
Class<?> clazz;
|
||||
try {
|
||||
clazz = Class.forName(def.className, false, classLoader);
|
||||
clazz = Class.forName(def.getClassName(), false, classLoader);
|
||||
} catch (ClassNotFoundException err) {
|
||||
throw new InvalidPluginException(String.format(
|
||||
"Cannot load %s with @Listen",
|
||||
def.className), err);
|
||||
def.getClassName()), err);
|
||||
}
|
||||
|
||||
Listen listen = clazz.getAnnotation(Listen.class);
|
||||
@@ -274,128 +231,4 @@ class AutoRegisterModules {
|
||||
type = rawType.getGenericSuperclass();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean skip(JarEntry entry) {
|
||||
if (!entry.getName().endsWith(".class")) {
|
||||
return true; // Avoid non-class resources.
|
||||
}
|
||||
if (entry.getSize() <= 0) {
|
||||
return true; // Directories have 0 size.
|
||||
}
|
||||
if (entry.getSize() >= 1024 * 1024) {
|
||||
return true; // Do not scan huge class files.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private byte[] read(JarEntry entry) throws IOException {
|
||||
byte[] data = new byte[(int) entry.getSize()];
|
||||
InputStream in = jarFile.getInputStream(entry);
|
||||
try {
|
||||
IO.readFully(in, data, 0, data.length);
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private static class ClassData extends ClassVisitor {
|
||||
private static final String EXPORT = Type.getType(Export.class).getDescriptor();
|
||||
private static final String LISTEN = Type.getType(Listen.class).getDescriptor();
|
||||
|
||||
String className;
|
||||
int access;
|
||||
String exportedAsName;
|
||||
boolean listen;
|
||||
|
||||
ClassData() {
|
||||
super(Opcodes.ASM4);
|
||||
}
|
||||
|
||||
boolean isConcrete() {
|
||||
return (access & Opcodes.ACC_ABSTRACT) == 0
|
||||
&& (access & Opcodes.ACC_INTERFACE) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature,
|
||||
String superName, String[] interfaces) {
|
||||
this.className = Type.getObjectType(name).getClassName();
|
||||
this.access = access;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||
if (visible && EXPORT.equals(desc)) {
|
||||
return new AbstractAnnotationVisitor() {
|
||||
@Override
|
||||
public void visit(String name, Object value) {
|
||||
exportedAsName = (String) value;
|
||||
}
|
||||
};
|
||||
}
|
||||
if (visible && LISTEN.equals(desc)) {
|
||||
listen = true;
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSource(String arg0, String arg1) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitOuterClass(String arg0, String arg1, String arg2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int arg0, String arg1, String arg2,
|
||||
String arg3, String[] arg4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldVisitor visitField(int arg0, String arg1, String arg2,
|
||||
String arg3, Object arg4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAttribute(Attribute arg0) {
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class AbstractAnnotationVisitor extends
|
||||
AnnotationVisitor {
|
||||
AbstractAnnotationVisitor() {
|
||||
super(Opcodes.ASM4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String arg0, String arg1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitArray(String arg0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnum(String arg0, String arg1, String arg2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
// Copyright (C) 2014 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.plugins;
|
||||
|
||||
import static com.google.common.base.Objects.firstNonNull;
|
||||
import static com.google.common.collect.Iterables.transform;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import org.eclipse.jgit.util.IO;
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.Attribute;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
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.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
public class JarScanner {
|
||||
private static final int SKIP_ALL = ClassReader.SKIP_CODE
|
||||
| ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
|
||||
private static final Function<ClassData, ExtensionMetaData> CLASS_DATA_TO_EXTENSION_META_DATA =
|
||||
new Function<ClassData, ExtensionMetaData>() {
|
||||
@Override
|
||||
public ExtensionMetaData apply(ClassData classData) {
|
||||
return new ExtensionMetaData(classData.className,
|
||||
classData.annotationValue);
|
||||
}
|
||||
};
|
||||
|
||||
public static class ExtensionMetaData {
|
||||
private final String className;
|
||||
private final String annotationValue;
|
||||
|
||||
private ExtensionMetaData(String className, String annotationValue) {
|
||||
this.className = className;
|
||||
this.annotationValue = annotationValue;
|
||||
}
|
||||
|
||||
public String getAnnotationValue() {
|
||||
return annotationValue;
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> scan(
|
||||
JarFile jarFile, String pluginName,
|
||||
Iterable<Class<? extends Annotation>> annotations)
|
||||
throws InvalidPluginException {
|
||||
Set<String> descriptors = Sets.newHashSet();
|
||||
Multimap<String, JarScanner.ClassData> rawMap = ArrayListMultimap.create();
|
||||
Map<Class<? extends Annotation>, String> classObjToClassDescr =
|
||||
Maps.newHashMap();
|
||||
|
||||
for (Class<? extends Annotation> annotation : annotations) {
|
||||
String descriptor = Type.getType(annotation).getDescriptor();
|
||||
descriptors.add(descriptor);
|
||||
classObjToClassDescr.put(annotation, descriptor);
|
||||
}
|
||||
|
||||
Enumeration<JarEntry> e = jarFile.entries();
|
||||
while (e.hasMoreElements()) {
|
||||
JarEntry entry = e.nextElement();
|
||||
if (skip(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ClassData def = new ClassData(descriptors);
|
||||
try {
|
||||
new ClassReader(read(jarFile, entry)).accept(def, SKIP_ALL);
|
||||
} catch (IOException err) {
|
||||
throw new InvalidPluginException("Cannot auto-register", err);
|
||||
} catch (RuntimeException err) {
|
||||
PluginLoader.log.warn(String.format(
|
||||
"Plugin %s has invaild class file %s inside of %s", pluginName,
|
||||
entry.getName(), jarFile.getName()), err);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (def.isConcrete()) {
|
||||
if (!Strings.isNullOrEmpty(def.annotationName)) {
|
||||
rawMap.put(def.annotationName, def);
|
||||
}
|
||||
} else {
|
||||
PluginLoader.log.warn(String.format(
|
||||
"Plugin %s tries to @%s(\"%s\") abstract class %s", pluginName,
|
||||
def.annotationName, def.annotationValue, def.className));
|
||||
}
|
||||
}
|
||||
|
||||
ImmutableMap.Builder<Class<? extends Annotation>, Iterable<ExtensionMetaData>> result =
|
||||
ImmutableMap.builder();
|
||||
|
||||
for (Class<? extends Annotation> annotoation : annotations) {
|
||||
String descr = classObjToClassDescr.get(annotoation);
|
||||
Collection<ClassData> discoverdData = rawMap.get(descr);
|
||||
Collection<ClassData> values =
|
||||
firstNonNull(discoverdData, Collections.<ClassData> emptySet());
|
||||
|
||||
result.put(annotoation,
|
||||
transform(values, CLASS_DATA_TO_EXTENSION_META_DATA));
|
||||
}
|
||||
|
||||
return result.build();
|
||||
}
|
||||
|
||||
private static boolean skip(JarEntry entry) {
|
||||
if (!entry.getName().endsWith(".class")) {
|
||||
return true; // Avoid non-class resources.
|
||||
}
|
||||
if (entry.getSize() <= 0) {
|
||||
return true; // Directories have 0 size.
|
||||
}
|
||||
if (entry.getSize() >= 1024 * 1024) {
|
||||
return true; // Do not scan huge class files.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static byte[] read(JarFile jarFile, JarEntry entry)
|
||||
throws IOException {
|
||||
byte[] data = new byte[(int) entry.getSize()];
|
||||
InputStream in = jarFile.getInputStream(entry);
|
||||
try {
|
||||
IO.readFully(in, data, 0, data.length);
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public static class ClassData extends ClassVisitor {
|
||||
int access;
|
||||
String className;
|
||||
String annotationName;
|
||||
String annotationValue;
|
||||
Iterable<String> exports;
|
||||
|
||||
private ClassData(Iterable<String> exports) {
|
||||
super(Opcodes.ASM4);
|
||||
this.exports = exports;
|
||||
}
|
||||
|
||||
boolean isConcrete() {
|
||||
return (access & Opcodes.ACC_ABSTRACT) == 0
|
||||
&& (access & Opcodes.ACC_INTERFACE) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature,
|
||||
String superName, String[] interfaces) {
|
||||
this.className = Type.getObjectType(name).getClassName();
|
||||
this.access = access;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||
Optional<String> found =
|
||||
Iterables.tryFind(exports, Predicates.equalTo(desc));
|
||||
if (visible && found.isPresent()) {
|
||||
annotationName = desc;
|
||||
return new AbstractAnnotationVisitor() {
|
||||
@Override
|
||||
public void visit(String name, Object value) {
|
||||
annotationValue = (String) value;
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSource(String arg0, String arg1) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitOuterClass(String arg0, String arg1, String arg2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int arg0, String arg1, String arg2,
|
||||
String arg3, String[] arg4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldVisitor visitField(int arg0, String arg1, String arg2,
|
||||
String arg3, Object arg4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAttribute(Attribute arg0) {
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class AbstractAnnotationVisitor extends
|
||||
AnnotationVisitor {
|
||||
AbstractAnnotationVisitor() {
|
||||
super(Opcodes.ASM4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String arg0, String arg1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitArray(String arg0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnum(String arg0, String arg1, String arg2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user