diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle index 0a24ad13b0e..79a77d68b7e 100644 --- a/fineract-provider/dependencies.gradle +++ b/fineract-provider/dependencies.gradle @@ -86,8 +86,6 @@ dependencies { // Although fineract (at the time of writing) doesn't have any compile time dep. on this, // it's useful to have this for the Spring Boot TestRestTemplate http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-rest-templates-test-utility [group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3.5'], - // Once we've switched to Java 8 this dep can be removed. - //[group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.0'] [group: 'io.swagger', name: 'swagger-jersey-jaxrs', version: '1.5.15'], [group: 'org.springframework', name:'spring-jms', version: '4.0.7.RELEASE'], @@ -100,9 +98,9 @@ dependencies { testCompile 'junit:junit:4.11', 'junit:junit-dep:4.11', 'org.mockito:mockito-core:1.9.5', + 'io.github.classgraph:classgraph:4.8.43', 'com.jayway.restassured:rest-assured:2.3.3', [group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springBootVersion], [group: 'com.mockrunner', name: 'mockrunner-jms', version: '1.0.6'], [group: 'com.mockrunner', name: 'mockrunner-jdbc', version: '1.0.6'] - } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesCheckRule.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesCheckRule.java new file mode 100644 index 00000000000..62efb3e7efc --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesCheckRule.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.fineract.infrastructure.classdupes; + +import java.util.List; +import java.util.Map; +import junit.framework.AssertionFailedError; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * JUnit Rule to run detect duplicate entries on the classpath. Usage: + * + *
public static {@literal @}ClassRule ClasspathHellDuplicatesCheckRule
+ *     dupes = new ClasspathHellDuplicatesCheckRule();
+ * + *

NB that the basepom/duplicate-finder-maven-plugin already runs as part of odlparent. + * It has a similar purpose, but covers build time instead of runtime testing. This JUnit Rule class is + * thus recommended to be used in particular in tests which previously ran into JAR Hell issues, and for + * which non-regression with a clear failure message in case of future similar problems is important. + * (This provides more details at runtime than duplicate-finder-maven-plugin does at build time.) + * + * @author Michael Vorburger.ch + */ +public class ClasspathHellDuplicatesCheckRule implements TestRule { + + private final ClasspathHellDuplicatesChecker checker; + + public ClasspathHellDuplicatesCheckRule(ClasspathHellDuplicatesChecker checker) { + this.checker = checker; + } + + public ClasspathHellDuplicatesCheckRule() { + this(ClasspathHellDuplicatesChecker.INSTANCE); + } + + @Override + public Statement apply(Statement base, Description description) { + checkClasspath(); + return base; + } + + protected void checkClasspath() { + Map> dupes = checker.getDuplicates(); + if (!dupes.isEmpty()) { + throw new AssertionFailedError("Classpath duplicates detected: " + checker.toString(dupes)); + } + } +} + diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesCheckRuleTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesCheckRuleTest.java new file mode 100644 index 00000000000..48639f34d01 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesCheckRuleTest.java @@ -0,0 +1,35 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.fineract.infrastructure.classdupes; + +import org.junit.ClassRule; +import org.junit.Test; + +/** + * {@link ClasspathHellDuplicatesCheckRule} test. + * + * @author Michael Vorburger + */ +public class ClasspathHellDuplicatesCheckRuleTest { + + @ClassRule public static ClasspathHellDuplicatesCheckRule jHades = new ClasspathHellDuplicatesCheckRule(); + + @Test // we just need this because JUnit doesn't like a *Test class with only a Rule + public void testAnything() throws Exception { } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesChecker.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesChecker.java new file mode 100644 index 00000000000..ed7d9c26f23 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/classdupes/ClasspathHellDuplicatesChecker.java @@ -0,0 +1,143 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.fineract.infrastructure.classdupes; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ResourceList; +import io.github.classgraph.ScanResult; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +/** + * Check classpath for duplicates. + * + * @author Michael Vorburger.ch + */ +public class ClasspathHellDuplicatesChecker { + + public static final ClasspathHellDuplicatesChecker INSTANCE = new ClasspathHellDuplicatesChecker(); + + private final Map> duplicates; + + public ClasspathHellDuplicatesChecker() { + duplicates = recheck(); + } + + public Map> getDuplicates() { + return duplicates; + } + + public String toString(Map> map) { + StringBuilder sb = new StringBuilder(); + for (Entry> entry : map.entrySet()) { + sb.append(entry.getKey()); + sb.append('\n'); + for (String location : entry.getValue()) { + sb.append(" "); + sb.append(location); + sb.append('\n'); + } + } + return sb.toString(); + } + + private Map> recheck() { + Map> dupes = new HashMap<>(); + // To debug this scanner, use ClassGraph().verbose() + // We intentionally do not use .classFilesOnly(), or .nonClassFilesOnly(), to check both + try (ScanResult scanResult = new ClassGraph().scan()) { + for (Entry dupe : scanResult.getAllResources().findDuplicatePaths()) { + String path = dupe.getKey(); + if (!isHarmlessDuplicate(path)) { + dupes.put(path, dupe.getValue().stream() + .map(resource -> resource.getURL().toExternalForm()).collect(Collectors.toList())); + } + } + return dupes; + } + } + + protected boolean isHarmlessDuplicate(String resourcePath) { + // list from org.jhades.reports.DuplicatesReport + return resourcePath.equals("META-INF/MANIFEST.MF") + || resourcePath.equals("META-INF/INDEX.LIST") + || resourcePath.equals("META-INF/ORACLE_J.SF") + || resourcePath.toUpperCase().startsWith("META-INF/NOTICE") + || resourcePath.toUpperCase().startsWith("META-INF/LICENSE") + || resourcePath.toUpperCase().startsWith("LICENSE") + || resourcePath.toUpperCase().startsWith("LICENSE/NOTICE") + // list formerly in ClasspathHellDuplicatesCheckRule (moved here in INFRAUTILS-52) + || resourcePath.endsWith(".txt") + || resourcePath.endsWith("LICENSE") + || resourcePath.endsWith("license.html") + || resourcePath.endsWith("about.html") + || resourcePath.endsWith("readme.html") + || resourcePath.startsWith("META-INF/services") + || resourcePath.equals("META-INF/DEPENDENCIES") + || resourcePath.equals("META-INF/git.properties") + || resourcePath.equals("META-INF/io.netty.versions.properties") + || resourcePath.equals("META-INF/jersey-module-version") + || resourcePath.startsWith("OSGI-INF/blueprint/") + || resourcePath.startsWith("org/opendaylight/blueprint/") + || resourcePath.equals("WEB-INF/web.xml") + || resourcePath.endsWith("reference.conf") // in Akka's JARs + || resourcePath.equals("META-INF/eclipse.inf") + || resourcePath.equals("META-INF/ECLIPSE_.SF") + || resourcePath.equals("META-INF/ECLIPSE_.RSA") + || resourcePath.equals("META-INF/BC2048KE.DSA") + || resourcePath.equals("META-INF/BC2048KE.SF") + || resourcePath.equals("META-INF/BC1024KE.SF") + || resourcePath.equals("OSGI-INF/bundle.info") + // Something doesn't to be a perfectly clean in Maven Surefire: + || resourcePath.startsWith("META-INF/maven/") + || resourcePath.contains("surefire") + // org.slf4j.impl.StaticLoggerBinder.class in testutils for the LogCaptureRule + || resourcePath.equals("org/slf4j/impl/StaticLoggerBinder.class") + // INFRAUTILS-35: JavaLaunchHelper is both in java and libinstrument.dylib (?) on Mac OS X + || resourcePath.contains("JavaLaunchHelper") + // javax.annotation is a big mess... :( E.g. javax.annotation.Resource (and some others) + // are present both in rt.jar AND javax.annotation-api-1.3.2.jar and similar - BUT those + // JARs cannot just be excluded, because they contain some additional annotations, in the + // (reserved!) package javax.annotation, such as javax.annotation.Priority et al. The + // super proper way to address this cleanly would be to make our own JAR for javax.annotation + // and have it contain ONLY what is not already in package javax.annotation in rt.jar.. but for now: + || resourcePath.equals("javax/annotation/Resource$AuthenticationType.class") + // NEUTRON-205: javax.inject is a mess :( because of javax.inject:javax.inject (which we widely use in ODL) + // VS. org.glassfish.hk2.external:javax.inject (which Glassfish Jersey has dependencies on). Attempts to + // cleanly exclude glassfish.hk2's javax.inject and align everything on only depending on + // javax.inject:javax.inject have failed, because the OSGi bundle + // org.glassfish.jersey.containers.jersey-container-servlet-core (2.25.1) has a non-optional Package-Import + // for javax.inject, but we made javax.inject:javax.inject true in odlparent, and don't bundle it. + || resourcePath.startsWith("javax/inject/") + // Java 9 modules + || resourcePath.endsWith("module-info.class") + || resourcePath.contains("findbugs") + // list newly introduced in INFRAUTILS-52, because classgraph scans more than JHades did + || resourcePath.equals("plugin.properties") + || resourcePath.equals(".api_description") + // errorprone with Java 11 integration leaks to classpath, which causes a conflict between + // checkerframework/checker-qual and checkerframework/dataflow + || resourcePath.startsWith("org/checkerframework/dataflow/qual/") + ; + } +} +