Skip to content

Commit dac90d2

Browse files
Simon Stonembwhite
authored andcommitted
[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 <sstone1@uk.ibm.com> Change-Id: I2e5938e08779607a4322446cbcb2e3263fb3ee21
1 parent f109d19 commit dac90d2

File tree

2 files changed

+68
-48
lines changed

2 files changed

+68
-48
lines changed

fabric-chaincode-shim/build.gradle

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ tasks.withType(org.gradle.api.tasks.testing.Test) {
1818
test {
1919
// Always run tests, even when nothing changed.
2020
dependsOn 'cleanTest'
21-
21+
2222
// Show test results. Potentially useful for debugging. Comment this block
2323
// testLogging {
2424
// events "passed", "skipped", "failed"
2525
// showExceptions true
2626
// showCauses true
2727
// showStandardStreams true
2828
// exceptionFormat "full"
29-
29+
3030
// }
3131
}
3232

@@ -35,7 +35,7 @@ dependencies {
3535
compile 'io.netty:netty-tcnative-boringssl-static:2.0.25.Final'
3636
compile 'org.bouncycastle:bcpkix-jdk15on:1.62'
3737
compile 'org.bouncycastle:bcprov-jdk15on:1.62'
38-
compile 'org.reflections:reflections:0.9.11'
38+
compile group: 'io.github.classgraph', name: 'classgraph', version: '4.8.47'
3939
implementation 'com.github.everit-org.json-schema:org.everit.json.schema:1.11.1'
4040
compile 'io.swagger.core.v3:swagger-annotations:2.0.0'
4141
implementation group: 'org.json', name: 'json', version: '20180813'
@@ -94,6 +94,7 @@ jacocoTestCoverageVerification {
9494
'org.hyperledger.fabric.contract.ContractRouter',
9595
'org.hyperledger.fabric.contract.routing.impl.ContractDefinitionImpl',
9696
'org.hyperledger.fabric.contract.routing.RoutingRegistry',
97+
'org.hyperledger.fabric.contract.routing.impl.RoutingRegistryImpl',
9798
'org.hyperledger.fabric.contract.execution.impl.ContractInvocationRequest',
9899
'org.hyperledger.fabric.contract.routing.TransactionType',
99100
'org.hyperledger.fabric.contract.metadata.MetadataBuilder',
@@ -117,6 +118,7 @@ jacocoTestCoverageVerification {
117118
'org.hyperledger.fabric.contract.execution.impl.ContractInvocationRequest',
118119
'org.hyperledger.fabric.contract.routing.impl.ContractDefinitionImpl',
119120
'org.hyperledger.fabric.contract.routing.RoutingRegistry',
121+
'org.hyperledger.fabric.contract.routing.impl.RoutingRegistryImpl',
120122
'org.hyperledger.fabric.shim.impl.Handler',
121123
'org.hyperledger.fabric.shim.ChaincodeBase',
122124
'org.hyperledger.fabric.contract.metadata.MetadataBuilder',

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

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

158-
final ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
159-
configurationBuilder.addUrls(urls);
160-
configurationBuilder.addUrls(ClasspathHelper.forJavaClassPath());
161-
configurationBuilder.addUrls(ClasspathHelper.forManifest());
162-
final Reflections ref = new Reflections(configurationBuilder);
163-
164-
logger.info("Searching chaincode class in urls: " + configurationBuilder.getUrls());
165-
166194
// set to ensure that we don't scan the same class twice
167195
final Set<String> seenClass = new HashSet<>();
168196

169197
// loop over all the classes that have the Contract annotation
170-
for (final Class<?> cl : ref.getTypesAnnotatedWith(Contract.class)) {
171-
logger.info("Found class: " + cl.getCanonicalName());
172-
if (ContractInterface.class.isAssignableFrom(cl)) {
173-
logger.fine("Inheritance ok");
174-
final String className = cl.getCanonicalName();
175-
176-
if (!seenClass.contains(className)) {
177-
final ContractDefinition contract = addNewContract((Class<ContractInterface>) cl);
198+
for (Class<ContractInterface> contractClass : contractClasses) {
199+
String className = contractClass.getCanonicalName();
200+
if (!seenClass.contains(className)) {
201+
ContractDefinition contract = addNewContract((Class<ContractInterface>) contractClass);
178202

179-
logger.fine("Searching annotated methods");
180-
for (final Method m : cl.getMethods()) {
181-
if (m.getAnnotation(Transaction.class) != null) {
182-
logger.fine("Found annotated method " + m.getName());
203+
logger.fine("Searching annotated methods");
204+
for (Method m : contractClass.getMethods()) {
205+
if (m.getAnnotation(Transaction.class) != null) {
206+
logger.fine("Found annotated method " + m.getName());
183207

184-
contract.addTxFunction(m);
208+
contract.addTxFunction(m);
185209

186-
}
187210
}
188-
189-
seenClass.add(className);
190211
}
191-
} else {
192-
logger.fine("Class is not assignabled from Contract");
212+
213+
seenClass.add(className);
193214
}
194215
}
195216

196217
// now need to look for the data types have been set with the
197-
logger.info("Looking for the data types");
198-
final Set<Class<?>> czs = ref.getTypesAnnotatedWith(DataType.class);
199-
logger.info("found " + czs.size());
200-
czs.forEach(typeRegistry::addDataType);
218+
dataTypeClasses.forEach(typeRegistry::addDataType);
201219

202220
}
203221

0 commit comments

Comments
 (0)