Skip to content

Commit

Permalink
#6 Got completely rid of fast-classpath-scanner and went for the Spri…
Browse files Browse the repository at this point in the history
…ng variant (https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java). New tests for the WebServiceScanner.

This was only possible, because Spring is after all able to scan for Interfaces which have specific Annotations (http://stackoverflow.com/a/41504372/4964553). Therefore Spring can be used and all problems with java -jar and mvn spring-boot:run not finding classes or CXF gone wild are completely gone now!
  • Loading branch information
jonashackt committed Jan 7, 2017
1 parent dbc71e7 commit 08bcb2c
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 36 deletions.
8 changes: 1 addition & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- Ease development - e.g. with autorestart, see https://spring.io/blog/2015/06/17/devtools-in-spring-boot-1-3 -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down Expand Up @@ -142,13 +143,6 @@
<version>${fluent-hc.version}</version>
</dependency>

<!-- Autodetection of WebService and WebServiceClient classes for Endpoint Initialization -->
<dependency>
<groupId>io.github.lukehutch</groupId>
<artifactId>fast-classpath-scanner</artifactId>
<version>${fast-classpath-scanner.version}</version>
</dependency>

<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package de.codecentric.cxf.autodetection;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;

/**
* Because org.springframework.core.type.classreading.ClassMetadataReadingVisitor.isConcrete() does exclude Interfaces from beeing returned,
* we have to override isCandidateComponent(AnnotatedBeanDefinition beanDefinition) to return true in every case.
* Now our Interface is also returned.
*/
class InterfaceIncludingClassPathScanningCandidateComponentProvider extends ClassPathScanningCandidateComponentProvider {
public InterfaceIncludingClassPathScanningCandidateComponentProvider() {
// false => Don't use default filters (@Component, etc.)
super(false);
}

@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import de.codecentric.cxf.diagnostics.WebServiceClientNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.jws.WebService;
import javax.xml.ws.Service;
Expand All @@ -14,6 +15,7 @@
import java.lang.reflect.InvocationTargetException;


@Component
public class WebServiceAutoDetector {

private static final Logger LOG = LoggerFactory.getLogger(WebServiceAutoDetector.class);
Expand All @@ -27,8 +29,19 @@ public WebServiceAutoDetector(WebServiceScanner webServiceScanner) {
this.webServiceScanner = webServiceScanner;
}

/**
* Detects & instantiates the SEI-Implementation. Therefore it detects the SEI itself first.
*
* @param <T>
* @return
* @throws BootStarterCxfException
*/
public <T> T searchAndInstantiateSeiImplementation() throws BootStarterCxfException {
return searchAndInstantiateSeiImplementation(searchServiceEndpointInterface());
}

@SuppressWarnings("unchecked")
public <T> T searchAndInstantiateSeiImplementation(Class seiName) throws BootStarterCxfException {
protected <T> T searchAndInstantiateSeiImplementation(Class seiName) throws BootStarterCxfException {
Class<T> implementingClass = null;
try {
implementingClass = webServiceScanner.scanForClassWhichImplementsAndPickFirst(seiName);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,58 @@
package de.codecentric.cxf.autodetection;

import de.codecentric.cxf.common.BootStarterCxfException;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;

public class WebServiceScanner {

protected static final String NO_CLASS_FOUND = "No class found";

protected <T> Class scanForClassWhichImplementsAndPickFirst(Class<T> interfaceName) throws BootStarterCxfException {
List<String> namesOfClassesImplementing = initScannerAndScan().getNamesOfClassesImplementing(interfaceName);
if (namesOfClassesImplementing.isEmpty()) {
Set<BeanDefinition> beans = scanForClasses(new AssignableTypeFilter(interfaceName),"de.codecentric");
Optional<BeanDefinition> bean = beans.stream().findFirst();

if (!bean.isPresent()) {
throw new BootStarterCxfException(WebServiceAutoDetector.NO_CLASS_FOUND);
}
return classForName(namesOfClassesImplementing.get(0));

return classForName(bean.get().getBeanClassName());
}

protected <T> Class scanForClassWithAnnotationAndPickTheFirstOneFound(Class<T> annotationName) throws BootStarterCxfException {
private Set<BeanDefinition> scanForClasses(TypeFilter typeFilter, String basePackage) {
ClassPathScanningCandidateComponentProvider scanningProvider = new InterfaceIncludingClassPathScanningCandidateComponentProvider();
scanningProvider.addIncludeFilter(typeFilter);
return scanningProvider.findCandidateComponents(basePackage);
}

protected <T extends Annotation> Class scanForClassWithAnnotationAndPickTheFirstOneFound(Class<T> annotationName) throws BootStarterCxfException {
return classForName(scanForClassNamesWithAnnotation(annotationName).get(0));
}

protected <T> List<String> scanForClassNamesWithAnnotation(Class<T> annotationName) throws BootStarterCxfException {
List<String> namesOfClassesWithAnnotation = initScannerAndScan().getNamesOfClassesWithAnnotation(annotationName);
protected <T extends Annotation> List<String> scanForClassNamesWithAnnotation(Class<T> annotation) throws BootStarterCxfException {
List<String> namesOfClassesWithAnnotation = new ArrayList<>();

Set<BeanDefinition> beans = scanForClasses(new AnnotationTypeFilter(annotation), "de.codecentric");

if(namesOfClassesWithAnnotation.isEmpty()) {
if(beans.isEmpty()) {
throw new BootStarterCxfException(NO_CLASS_FOUND);
}

beans.stream().forEach((BeanDefinition bean) -> namesOfClassesWithAnnotation.add(bean.getBeanClassName()));
return namesOfClassesWithAnnotation;
}

protected <T> Class scanForClassWithAnnotationAndIsAnInterface(Class<T> annotationName) throws BootStarterCxfException {
protected <T extends Annotation> Class scanForClassWithAnnotationAndIsAnInterface(Class<T> annotationName) throws BootStarterCxfException {
List<String> namesOfClassesWithAnnotation = scanForClassNamesWithAnnotation(annotationName);

if(namesOfClassesWithAnnotation.size() > 1) {
Expand All @@ -54,16 +75,12 @@ protected boolean isInterface(String className) throws BootStarterCxfException {
return classForName(className).isInterface();
}

private ScanResult initScannerAndScan() {
return new FastClasspathScanner().scan();
}


protected Class<?> classForName(String className) throws BootStarterCxfException {
try {
return Class.forName(className);
} catch (ClassNotFoundException exception) {
throw new BootStarterCxfException(NO_CLASS_FOUND, exception);
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.codecentric.cxf.configuration;

import java.lang.reflect.Method;
import java.util.Map;

import javax.annotation.PostConstruct;
Expand Down Expand Up @@ -51,21 +52,16 @@ public class CxfAutoConfiguration {
@Value("${cxf.servicelist.title:CXF SpringBoot Starter - service list}")
private String serviceListTitle;

private Object seiImplementation;
private Service webServiceClient;

private String serviceUrlEnding = "";

@PostConstruct
public void setUp() throws BootStarterCxfException {
// Try to autodetect all necessary classes for Endpoint initialization
WebServiceAutoDetector webServiceAutoDetector = new WebServiceAutoDetector(new WebServiceScanner());

Class serviceEndpointInterface = webServiceAutoDetector.searchServiceEndpointInterface();
seiImplementation = webServiceAutoDetector.searchAndInstantiateSeiImplementation(serviceEndpointInterface);

webServiceClient = webServiceAutoDetector.searchAndInstantiateWebServiceClient();
@Bean
public WebServiceAutoDetector webServiceAutoDetector() {
return new WebServiceAutoDetector(new WebServiceScanner());
}

@PostConstruct
public void setUp() throws BootStarterCxfException {
serviceUrlEnding = "/" + webServiceClient().getServiceName().getLocalPart();
}

Expand All @@ -90,7 +86,7 @@ public SpringBus springBus() {

@Bean
public Object seiImplementation() throws BootStarterCxfException {
return seiImplementation;
return webServiceAutoDetector().searchAndInstantiateSeiImplementation();
}

@Bean
Expand All @@ -113,7 +109,7 @@ public Endpoint endpoint() throws BootStarterCxfException {
@Bean
public Service webServiceClient() throws BootStarterCxfException {
// Needed for correct ServiceName & WSDLLocation to publish contract first incl. original WSDL
return webServiceClient;
return webServiceAutoDetector().searchAndInstantiateWebServiceClient();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.interceptor.AbstractLoggingInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
Expand All @@ -24,6 +25,7 @@
*/
@Configuration
@Conditional(SoapMessageLoggerConfiguration.SoapMessageLoggerPropertyCondition.class)
@AutoConfigureAfter(CxfAutoConfiguration.class)
public class SoapMessageLoggerConfiguration {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.jaxws.EndpointImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -25,6 +26,7 @@
*/
@Configuration
@ConditionalOnBean(CustomFaultBuilder.class)
@AutoConfigureAfter(CxfAutoConfiguration.class)
public class XmlValidationConfiguration {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
package de.codecentric.cxf.autodetection;


import de.codecentric.cxf.TestServiceEndpoint;
import de.codecentric.cxf.common.BootStarterCxfException;
import de.codecentric.namespace.weatherservice.Weather;
import de.codecentric.namespace.weatherservice.WeatherService;
import org.junit.Test;

import javax.jws.WebService;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import java.util.List;

import static de.codecentric.cxf.autodetection.WebServiceAutoDetector.WEB_SERVICE_CLIENT_ANNOTATION;
import static de.codecentric.cxf.autodetection.WebServiceAutoDetectorTest.generateListWithSeiAndSeiImplNameWithBothWebServiceAnnotation;
import static junit.framework.TestCase.fail;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class WebServiceScannerTest {

private static final Class WEATHER_SERVICE_ENDPOINT_INTERFACE = WeatherService.class;
private static final Class WEATHER_WEBSERVICE_CLIENT = Weather.class;
private static final Class WEATHER_SEI_IMPLEMENTING_CLASS = TestServiceEndpoint.class;

public static final Class<WebService> SEI_ANNOTATION = WebService.class;

private WebServiceScanner webServiceScanner = new WebServiceScanner();

@Test public void
Expand All @@ -33,6 +46,30 @@ public class WebServiceScannerTest {
assertThat(weather, is(equalTo(WeatherService.class)));
}

@Test public void
is_Class_With_Annotation_Which_is_also_an_Interface_Successfully_detected() throws BootStarterCxfException {
Class serviceEndpointInterface = webServiceScanner.scanForClassWithAnnotationAndIsAnInterface(SEI_ANNOTATION);

assertThat(serviceEndpointInterface, is(notNullValue()));
assertThat(serviceEndpointInterface.getSimpleName(), is(equalTo(WEATHER_SERVICE_ENDPOINT_INTERFACE.getSimpleName())));
}

@Test public void
is_Class_which_Implementes_another_Class_successfully_found() throws NoSuchFieldException, BootStarterCxfException {
Class weatherServiceEndpointImpl = webServiceScanner.scanForClassWhichImplementsAndPickFirst(WEATHER_SERVICE_ENDPOINT_INTERFACE);

assertThat(weatherServiceEndpointImpl, is(notNullValue()));
assertThat(weatherServiceEndpointImpl.getSimpleName(), is(equalTo(WEATHER_SEI_IMPLEMENTING_CLASS.getSimpleName())));
}

@Test public void
is_Class_with_Annotation_successfully_found() throws BootStarterCxfException {
Class webServiceClient = webServiceScanner.scanForClassWithAnnotationAndPickTheFirstOneFound(WEB_SERVICE_CLIENT_ANNOTATION);

assertThat(webServiceClient, is(notNullValue()));
assertThat(webServiceClient.getSimpleName(), is(equalTo(WEATHER_WEBSERVICE_CLIENT.getSimpleName())));
}

@Test public void
picks_the_interface_from_two_classes_where_one_is_an_interface_and_the_other_not() throws BootStarterCxfException, ClassNotFoundException {
List<String> twoWebServices = generateListWithSeiAndSeiImplNameWithBothWebServiceAnnotation();
Expand Down

0 comments on commit 08bcb2c

Please sign in to comment.