|
6 | 6 | package org.hyperledger.fabric.contract.routing.impl; |
7 | 7 |
|
8 | 8 | import java.lang.reflect.Method; |
9 | | -import java.net.URL; |
10 | | -import java.net.URLClassLoader; |
11 | 9 | import java.util.ArrayList; |
12 | | -import java.util.Arrays; |
13 | 10 | import java.util.Collection; |
14 | 11 | import java.util.HashMap; |
15 | 12 | import java.util.HashSet; |
| 13 | +import java.util.List; |
16 | 14 | import java.util.Map; |
17 | 15 | import java.util.Set; |
18 | 16 | import java.util.logging.Logger; |
|
27 | 25 | import org.hyperledger.fabric.contract.routing.RoutingRegistry; |
28 | 26 | import org.hyperledger.fabric.contract.routing.TxFunction; |
29 | 27 | 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; |
33 | 32 |
|
34 | 33 | /** |
35 | 34 | * Registry to hold permit access to the routing definitions. This is the |
@@ -143,61 +142,80 @@ public Collection<ContractDefinition> getAllDefinitions() { |
143 | 142 | */ |
144 | 143 | @SuppressWarnings("unchecked") |
145 | 144 | @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 | + } |
155 | 191 | } |
156 | 192 | } |
157 | 193 |
|
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 | | - |
166 | 194 | // set to ensure that we don't scan the same class twice |
167 | 195 | final Set<String> seenClass = new HashSet<>(); |
168 | 196 |
|
169 | 197 | // 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); |
178 | 202 |
|
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()); |
183 | 207 |
|
184 | | - contract.addTxFunction(m); |
| 208 | + contract.addTxFunction(m); |
185 | 209 |
|
186 | | - } |
187 | 210 | } |
188 | | - |
189 | | - seenClass.add(className); |
190 | 211 | } |
191 | | - } else { |
192 | | - logger.fine("Class is not assignabled from Contract"); |
| 212 | + |
| 213 | + seenClass.add(className); |
193 | 214 | } |
194 | 215 | } |
195 | 216 |
|
196 | 217 | // 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); |
201 | 219 |
|
202 | 220 | } |
203 | 221 |
|
|
0 commit comments