Skip to content
This repository has been archived by the owner on Jun 14, 2018. It is now read-only.

Commit

Permalink
Merge pull request #73 from paypal/develop
Browse files Browse the repository at this point in the history
Bringing in 2.3.2 changes to master
  • Loading branch information
fabiocarvalho777 authored Jun 5, 2017
2 parents 629cf7a + 4a3a6b5 commit 1fcd774
Show file tree
Hide file tree
Showing 17 changed files with 268 additions and 84 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Also, this RESTEasy Spring Boot starter integrates with Spring as expected, whic
* Supports automatic discovery and registration of multiple [JAX-RS Application](http://docs.oracle.com/javaee/7/api/javax/ws/rs/core/Application.html) classes as Spring beans
* Supports optional registration of [JAX-RS Application](http://docs.oracle.com/javaee/7/api/javax/ws/rs/core/Application.html) classes via class-path scanning, or manually, via configuration properties (or YAML) file
* Leverages and supports RESTEasy configuration
* Supports RESTEasy Asynchronous Job Service

## Quick start

Expand All @@ -28,7 +29,7 @@ Add the Maven dependency below to your Spring Boot application pom file.<br>
<dependency>
<groupId>com.paypal.springboot</groupId>
<artifactId>resteasy-spring-boot-starter</artifactId>
<version>2.3.1-RELEASE</version>
<version>2.3.2-RELEASE</version>
<scope>runtime</scope>
</dependency>
```
Expand Down
20 changes: 20 additions & 0 deletions mds/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Release notes

## 2.3.2-RELEASE

#### Release date
June 5th, 2017.

#### Third-party versions
- RESTEasy: 3.1.3.Final
- Spring Boot: 1.5.3.RELEASE

#### New features and enhancements
1. [61 - Adding support for RESTEasy Asynchronous Job Service](https://github.com/paypal/resteasy-spring-boot/issues/61)
1. [65 - Use Spring Framework scanning facility in JaxrsApplicationScanner](https://github.com/paypal/resteasy-spring-boot/issues/65)
1. [72 - Upgrade RESTEasy to version 3.1.3.Final](https://github.com/paypal/resteasy-spring-boot/issues/72)

#### Bug fixes
None

#### Important notes
1. Starting on version 3.0.0, the behavior of the `scanning` JAX-RS Application subclass registration method will change, being more restrictive. Instead of scanning the whole classpath, it will scan only packages registered to be scanned by Spring framework (regardless of the JAX-RS Application subclass being a Spring bean or not). The reason is to improve application startup performance. Having said that, it is recommended that every application use any method, other than `scanning`. Or, if using `scanning`, make sure your JAX-RS Application subclass is under a package to be scanned by Spring framework. If not, starting on version 3.0.0,it won't be found.

## 2.3.1-RELEASE

#### Release date
Expand Down
9 changes: 5 additions & 4 deletions mds/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Add the Maven dependency below to your Spring Boot application pom file.<br>
<dependency>
<groupId>com.paypal.springboot</groupId>
<artifactId>resteasy-spring-boot-starter</artifactId>
<version>2.3.1-RELEASE</version>
<version>2.3.2-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
```
Expand Down Expand Up @@ -41,7 +41,7 @@ JAX-RS applications are defined via sub-classes of [Application](http://docs.ora

1. By having them defined as Spring beans.
2. By setting property `resteasy.jaxrs.app.classes` via Spring Boot configuration file (properties or YAML). This property should contain a comma separated list of JAX-RS sub-classes.
3. Automatically by classpath scanning (looking for `javax.ws.rs.core.Application` sub-classes).
3. Automatically by classpath scanning (looking for `javax.ws.rs.core.Application` sub-classes). **See important note number 6 about this method**.

You can define the method you prefer by setting property `resteasy.jaxrs.app.registration` (via Spring Boot configuration file), although you don't have to, in that case the `auto` method is the default. The possible values are:

Expand All @@ -54,11 +54,12 @@ The first three values refer respectively to each one of the three methods descr

__Important notes__

1. If no JAX-RS application classes are found, a default one will be automatically created mapping to `/*` (_according to section 2.3.2 in the JAX-RS 2.0 specification_). Notice that, in this case, if you have any other Servlet in your application, their URL matching might conflict. For example, if you have SpringBoot actuator, its endpoints might not be reachable.
1. If no JAX-RS application classes are found, a default one will be automatically created mapping to `/*` (_according to section 2.3.2 in the JAX-RS 2.0 specification_). Notice that, in this case, if you have any other Servlet in your application, their URL matching might conflict. For example, if you have Spring Boot actuator and its mapped to `/`, its endpoints might not be reachable.
1. It is recommended to always have at least one JAX-RS application class.
1. A JAX-RS application class with no `javax.ws.rs.ApplicationPath` annotation will not be registered.
1. Avoid setting the JAX-RS application base URI to simply `/` to prevent URI conflicts, as explained in the first item.
1. Avoid setting the JAX-RS application base URI to simply `/` to prevent URI conflicts, as explained in item 1.
1. Property `resteasy.jaxrs.app` has been deprecated and replaced by `resteasy.jaxrs.app.classes` since version *2.2.0-RELEASE* (see [issue 35](https://github.com/paypal/resteasy-spring-boot/issues/35)). Property `resteasy.jaxrs.app` is going to be finally removed in version *3.0.0-RELEASE*.
1. Starting on version 3.0.0, the behavior of the `scanning` JAX-RS Application subclass registration method will change, being more restrictive. Instead of scanning the whole classpath, it will scan only packages registered to be scanned by Spring framework (regardless of the JAX-RS Application subclass being a Spring bean or not). The reason is to improve application startup performance. Having said that, it is recommended that every application use any method, other than `scanning`. Or, if using `scanning`, make sure your JAX-RS Application subclass is under a package to be scanned by Spring framework. If not, starting on version 3.0.0,it won't be found.

#### RESTEasy configuration
RESTEasy offers a few configuration switches, [as seen here](http://docs.jboss.org/resteasy/docs/3.1.0.Final/userguide/html_single/index.html#configuration_switches), and they are set as Servlet context init parameters. In Spring Boot, Servlet context init parameters are defined via Spring Boot `application.properties` file, using the property prefix `server.context-parameters.*` (search for it in [Spring Boot reference guide](http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/)).</br>
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<groupId>com.paypal.springboot</groupId>
<artifactId>resteasy-spring-boot-starter-parent</artifactId>
<version>2.3.1-RELEASE</version>
<version>2.3.2-RELEASE</version>
<packaging>pom</packaging>

<modules>
Expand Down
2 changes: 1 addition & 1 deletion resteasy-spring-boot-starter-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.paypal.springboot</groupId>
<artifactId>resteasy-spring-boot-starter-test</artifactId>
<version>2.3.1-RELEASE</version>
<version>2.3.2-RELEASE</version>
<name>${project.artifactId}</name>

<description>Integration test project for RESTEasy Spring Boot starter</description>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.paypal.springboot.resteasy;

import com.sample.app.Application;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import org.springframework.boot.SpringApplication;
import org.springframework.util.SocketUtils;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.util.Properties;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.isEmptyString;

/**
* Integration tests for RESTEasy Asynchronous Job Service
*
* @author facarvalho
*/
public class AsyncJobIT {

@BeforeClass
public void setUp() {
int appPort = SocketUtils.findAvailableTcpPort();

RestAssured.basePath = "sample-app";
RestAssured.port = appPort;

Properties properties = new Properties();
properties.put("server.context-parameters.resteasy.async.job.service.enabled", true);

SpringApplication app = new SpringApplication(Application.class);
app.setDefaultProperties(properties);
app.addListeners(new LogbackTestApplicationListener());
app.run("--server.port=" + appPort).registerShutdownHook();
}

@Test
public void regularRequestTest() {
Response response = given().body("is there anybody out there?").post("/echo");
response.then().statusCode(200).body("timestamp", notNullValue()).body("echoText", equalTo("is there anybody out there?"));
}

@Test
public void asyncRequestTest() {
Response response = given().body("is there anybody out there?").post("/echo?asynch=true");
response.then().statusCode(202).body(isEmptyString());

String location = response.getHeader("Location");
response = given().get(location + "?wait=50");
response.then().statusCode(200).body("timestamp", notNullValue()).body("echoText", equalTo("is there anybody out there?"));
}

@Test
public void fireAndForgetRequestTest() {
Response response = given().body("is there anybody out there?").post("/echo?oneway=true");
response.then().statusCode(202).body(isEmptyString());
}

@AfterClass
public void shuttingDownApplication() {
Response response = given().basePath("/").post("/shutdown");
response.then().statusCode(200).body("message", equalTo("Shutting down, bye..."));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ public void scanningTest() {

assertResourceFound(port, "sample-app");
assertResourceFound(port, "sample-app-test");
assertResourceFound(port, "sample-app-test-two");
assertResourceNotFound(port, "/");

appShutdown(port);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,18 @@ public class LogbackTestApplicationListener implements SmartApplicationListener
private boolean warningOrErrorFound = false;

private Appender<ILoggingEvent> appender = new AppenderBase<ILoggingEvent>() {

// TODO
// Remove this after implementing https://github.com/paypal/resteasy-spring-boot/issues/69
private static final java.lang.String SCANNING_WARNING = "\n-------------\nStarting on version 3.0.0, the behavior of the `scanning`";

@Override
protected void append(ILoggingEvent event) {
if (event == null || warningOrErrorFound) {
return;
}
Level level = event.getLevel();
if (level.equals(Level.WARN) || level.equals(Level.ERROR)) {
if ((level.equals(Level.WARN) || level.equals(Level.ERROR)) && !event.getMessage().startsWith(SCANNING_WARNING)) {
warningOrErrorFound = true;
Assert.fail(event.getFormattedMessage());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.sample.app.test;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("sample-app-test-two")
public class NonSpringBeanJaxrsApplication extends Application {
}
39 changes: 32 additions & 7 deletions resteasy-spring-boot-starter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.paypal.springboot</groupId>
<artifactId>resteasy-spring-boot-starter</artifactId>
<version>2.3.1-RELEASE</version>
<version>2.3.2-RELEASE</version>
<name>${project.artifactId}</name>

<description>A Spring Boot starter for RESTEasy</description>
Expand Down Expand Up @@ -45,7 +45,7 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<springboot.version>1.5.3.RELEASE</springboot.version>
<resteasy.version>3.1.0.Final</resteasy.version>
<resteasy.version>3.1.3.Final</resteasy.version>
<coverage.line>80</coverage.line>
<coverage.branch>80</coverage.branch>
</properties>
Expand Down Expand Up @@ -92,11 +92,6 @@
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
Expand All @@ -115,6 +110,36 @@
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>1.6.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.6.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-testng-common</artifactId>
<version>1.6.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-testng</artifactId>
<version>1.6.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-reflect</artifactId>
<version>1.6.5</version>
<scope>test</scope>
</dependency>
</dependencies>

<profiles>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
package com.paypal.springboot.resteasy;

import org.apache.commons.io.FilenameUtils;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.util.ClassUtils;

import javax.ws.rs.core.Application;
import java.io.File;
import java.net.URL;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* Helper class to scan the classpath searching for
* JAX-RS Application sub-classes
* Helper class to scan the classpath under the specified packages
* searching for JAX-RS Application sub-classes
*
* @author Fabio Carvalho ([email protected] or [email protected])
*/
Expand All @@ -26,58 +24,47 @@ public abstract class JaxrsApplicationScanner {

private static Set<Class<? extends Application>> applications;

public static Set<Class<? extends Application>> getApplications() {
public static Set<Class<? extends Application>> getApplications(List<String> packagesToBeScanned) {
if(applications == null) {
applications = findJaxrsApplicationClasses();
applications = findJaxrsApplicationClasses(packagesToBeScanned);
}

return applications;
}

/*
* Scan the classpath looking for JAX-RS Application sub-classes
* Scan the classpath under the specified packages looking for JAX-RS Application sub-classes
*/
private static Set<Class<? extends Application>> findJaxrsApplicationClasses() {
private static Set<Class<? extends Application>> findJaxrsApplicationClasses(List<String> packagesToBeScanned) {
logger.info("Scanning classpath to find JAX-RS Application classes");

final Collection<URL> systemPropertyURLs = ClasspathHelper.forJavaClassPath();
final Collection<URL> classLoaderURLs = ClasspathHelper.forClassLoader();
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AssignableTypeFilter(Application.class));

Set<URL> classpathURLs = new HashSet<URL>();
Set<BeanDefinition> candidates = new HashSet<BeanDefinition>();
Set<BeanDefinition> candidatesSubSet;

copyValidClasspathEntries(systemPropertyURLs, classpathURLs);
copyValidClasspathEntries(classLoaderURLs, classpathURLs);

logger.debug("Classpath URLs to be scanned: " + classpathURLs);

Reflections reflections = new Reflections(classpathURLs, new SubTypesScanner());

return reflections.getSubTypesOf(Application.class);
}

/*
* Copy all entries that are a JAR file or a directory
*/
private static void copyValidClasspathEntries(Collection<URL> source, Set<URL> destination) {
String fileName;
boolean isJarFile;
boolean isDirectory;
for (String packageToScan : packagesToBeScanned) {
candidatesSubSet = scanner.findCandidateComponents(packageToScan);
candidates.addAll(candidatesSubSet);
}

for (URL url : source) {
if(destination.contains(url)) {
continue;
Set<Class<? extends Application>> classes = new HashSet<Class<? extends Application>>();
ClassLoader classLoader = JaxrsApplicationScanner.class.getClassLoader();
Class<? extends Application> type;
for (BeanDefinition candidate : candidates) {
try {
type = (Class<? extends Application>) ClassUtils.forName(candidate.getBeanClassName(), classLoader);
classes.add(type);
} catch (ClassNotFoundException e) {
logger.error("JAX-RS Application subclass could not be loaded", e);
}
}

fileName = url.getFile();
isJarFile = FilenameUtils.isExtension(fileName, "jar");
isDirectory = new File(fileName).isDirectory();
// We don't want the JAX-RS Application class itself in there
classes.remove(Application.class);

if (isJarFile || isDirectory) {
destination.add(url);
} else if (logger.isDebugEnabled()) {
logger.debug("Ignored classpath entry: " + fileName);
}
}
return classes;
}

}
Loading

0 comments on commit 1fcd774

Please sign in to comment.