Skip to content

Commit 5dbbea7

Browse files
author
Simon Stone
committed
[FAB-6415] Replace org.reflections with classgraph
The org.reflections package we use for finding contract classes does not work with Java 9+, because in Java 9+ classloaders are no longer instances of URLClassLoader. The classgraph package is a heavily maintained, MIT licensed package that does work with Java 9+. Signed-off-by: Simon Stone <[email protected]> Change-Id: I2e5938e08779607a4322446cbcb2e3263fb3ee21
1 parent 059d043 commit 5dbbea7

File tree

2 files changed

+64
-44
lines changed

2 files changed

+64
-44
lines changed

fabric-chaincode-shim/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ dependencies {
2121
compile 'io.netty:netty-tcnative-boringssl-static:2.0.7.Final'
2222
compile 'org.bouncycastle:bcpkix-jdk15on:1.62'
2323
compile 'org.bouncycastle:bcprov-jdk15on:1.62'
24-
compile 'org.reflections:reflections:0.9.11'
24+
compile group: 'io.github.classgraph', name: 'classgraph', version: '4.8.47'
2525
compile group: 'cglib', name: 'cglib', version: '3.2.10'
2626
implementation 'com.github.everit-org.json-schema:org.everit.json.schema:1.11.1'
2727
implementation group: 'org.json', name: 'json', version: '20180813'
@@ -57,7 +57,7 @@ publishing {
5757
}
5858

5959
jacoco {
60-
toolVersion = "0.7.9"
60+
toolVersion = "0.8.4"
6161
}
6262

6363
jacocoTestReport {

fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/RoutingRegistryImpl.java

Lines changed: 62 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,11 @@
66
package org.hyperledger.fabric.contract.routing.impl;
77

88
import java.lang.reflect.Method;
9-
import java.net.URL;
10-
import java.net.URLClassLoader;
119
import java.util.ArrayList;
12-
import java.util.Arrays;
1310
import java.util.Collection;
1411
import java.util.HashMap;
1512
import java.util.HashSet;
13+
import java.util.List;
1614
import java.util.Map;
1715
import java.util.Set;
1816

@@ -27,9 +25,10 @@
2725
import org.hyperledger.fabric.contract.routing.RoutingRegistry;
2826
import org.hyperledger.fabric.contract.routing.TxFunction;
2927
import org.hyperledger.fabric.contract.routing.TypeRegistry;
30-
import org.reflections.Reflections;
31-
import org.reflections.util.ClasspathHelper;
32-
import org.reflections.util.ConfigurationBuilder;
28+
29+
import io.github.classgraph.ClassGraph;
30+
import io.github.classgraph.ClassInfo;
31+
import io.github.classgraph.ScanResult;
3332

3433
/**
3534
* Registry to hold permit access to the routing definitions. This is the
@@ -143,58 +142,79 @@ public Collection<ContractDefinition> getAllDefinitions() {
143142
*/
144143
@Override
145144
public void findAndSetContracts(TypeRegistry typeRegistry) {
146-
ArrayList<URL> urls = new ArrayList<>();
147-
ClassLoader[] classloaders = { getClass().getClassLoader(), Thread.currentThread().getContextClassLoader() };
148-
for (int i = 0; i < classloaders.length; i++) {
149-
if (classloaders[i] instanceof URLClassLoader) {
150-
urls.addAll(Arrays.asList(((URLClassLoader) classloaders[i]).getURLs()));
151-
} else {
152-
throw new RuntimeException("classLoader is not an instanceof URLClassLoader");
145+
146+
// Find all classes that are valid contract or data type instances.
147+
ClassGraph classGraph = new ClassGraph()
148+
.enableClassInfo()
149+
.enableAnnotationInfo();
150+
List<Class<ContractInterface>> contractClasses = new ArrayList<>();
151+
List<Class<?>> dataTypeClasses = new ArrayList<>();
152+
try (ScanResult scanResult = classGraph.scan()) {
153+
for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(Contract.class.getCanonicalName())) {
154+
logger.debug("Found class with contract annotation: " + classInfo.getName());
155+
try {
156+
Class<?> contractClass = classInfo.loadClass();
157+
logger.debug("Loaded class");
158+
Contract annotation = contractClass.getAnnotation(Contract.class);
159+
if (annotation == null) {
160+
// Since we check by name above, it makes sense to check it's actually compatible,
161+
// and not some random class with the same name.
162+
logger.debug("Class does not have compatible contract annotation");
163+
} else if (!ContractInterface.class.isAssignableFrom(contractClass)) {
164+
logger.debug("Class is not assignable from ContractInterface");
165+
} else {
166+
logger.debug("Class is assignable from ContractInterface");
167+
contractClasses.add((Class<ContractInterface>) contractClass);
168+
}
169+
} catch (IllegalArgumentException e) {
170+
logger.debug("Failed to load class: " + e);
171+
}
172+
}
173+
for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(DataType.class.getCanonicalName())) {
174+
logger.debug("Found class with data type annotation: " + classInfo.getName());
175+
try {
176+
Class<?> dataTypeClass = classInfo.loadClass();
177+
logger.debug("Loaded class");
178+
DataType annotation = dataTypeClass.getAnnotation(DataType.class);
179+
if (annotation == null) {
180+
// Since we check by name above, it makes sense to check it's actually compatible,
181+
// and not some random class with the same name.
182+
logger.debug("Class does not have compatible data type annotation");
183+
} else {
184+
logger.debug("Class has compatible data type annotation");
185+
dataTypeClasses.add(dataTypeClass);
186+
}
187+
} catch (IllegalArgumentException e) {
188+
logger.debug("Failed to load class: " + e);
189+
}
153190
}
154191
}
155192

156-
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
157-
configurationBuilder.addUrls(urls);
158-
configurationBuilder.addUrls(ClasspathHelper.forJavaClassPath());
159-
Reflections ref = new Reflections(configurationBuilder);
160-
161-
logger.info("Searching chaincode class in urls: " + urls);
162-
163193
// set to ensure that we don't scan the same class twice
164194
Set<String> seenClass = new HashSet<>();
165195

166196
// loop over all the classes that have the Contract annotation
167-
for (Class<?> cl : ref.getTypesAnnotatedWith(Contract.class)) {
168-
logger.info("Found class: " + cl.getCanonicalName());
169-
if (ContractInterface.class.isAssignableFrom(cl)) {
170-
logger.debug("Inheritance ok");
171-
String className = cl.getCanonicalName();
197+
for (Class<ContractInterface> contractClass : contractClasses) {
198+
String className = contractClass.getCanonicalName();
199+
if (!seenClass.contains(className)) {
200+
ContractDefinition contract = addNewContract((Class<ContractInterface>) contractClass);
172201

173-
if (!seenClass.contains(className)) {
174-
ContractDefinition contract = addNewContract((Class<ContractInterface>) cl);
202+
logger.debug("Searching annotated methods");
203+
for (Method m : contractClass.getMethods()) {
204+
if (m.getAnnotation(Transaction.class) != null) {
205+
logger.debug("Found annotated method " + m.getName());
175206

176-
logger.debug("Searching annotated methods");
177-
for (Method m : cl.getMethods()) {
178-
if (m.getAnnotation(Transaction.class) != null) {
179-
logger.debug("Found annotated method " + m.getName());
207+
contract.addTxFunction(m);
180208

181-
contract.addTxFunction(m);
182-
183-
}
184209
}
185-
186-
seenClass.add(className);
187210
}
188-
} else {
189-
logger.debug("Class is not assignabled from Contract");
211+
212+
seenClass.add(className);
190213
}
191214
}
192215

193216
// now need to look for the data types have been set with the
194-
logger.info("Looking for the data types");
195-
Set<Class<?>> czs = ref.getTypesAnnotatedWith(DataType.class);
196-
logger.info("found " + czs.size());
197-
czs.forEach(typeRegistry::addDataType);
217+
dataTypeClasses.forEach(typeRegistry::addDataType);
198218

199219
}
200220

0 commit comments

Comments
 (0)