├── .gitignore ├── .travis.yml ├── License.html ├── README.md ├── mds ├── CONTRIBUTING.md ├── RELEASE_NOTES.md └── USAGE.md ├── pom.xml ├── release_steps.txt ├── resteasy-spring-boot-starter-test ├── pom.xml └── src │ └── test │ ├── java │ └── com │ │ ├── paypal │ │ └── springboot │ │ │ └── resteasy │ │ │ ├── AsyncJobIT.java │ │ │ ├── CommonUseCasesIT.java │ │ │ ├── ConfigurationIT.java │ │ │ ├── LogbackTestApplicationListener.java │ │ │ └── MultipleContextsIT.java │ │ ├── sample │ │ └── app │ │ │ └── test │ │ │ └── NonSpringBeanJaxrsApplication.java │ │ └── test │ │ ├── NonSpringBeanJaxrsApplication.java │ │ └── multicontexttest │ │ ├── Echo.java │ │ ├── EchoMessage.java │ │ ├── EchoMessageCreator.java │ │ ├── JaxrsApplication.java │ │ └── MultiContextTestApp.java │ └── resources │ └── application.yaml ├── resteasy-spring-boot-starter ├── pom.xml ├── settings.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── paypal │ │ │ └── springboot │ │ │ └── resteasy │ │ │ ├── JaxrsApplicationScanner.java │ │ │ ├── ResteasyApplicationBuilder.java │ │ │ ├── ResteasyAutoConfiguration.java │ │ │ └── ResteasyEmbeddedServletInitializer.java │ └── resources │ │ └── META-INF │ │ └── spring.factories │ └── test │ ├── java │ └── com │ │ └── paypal │ │ └── springboot │ │ └── resteasy │ │ ├── JaxrsAppRegistrationTest.java │ │ ├── ResteasyAutoConfigurationTest.java │ │ ├── ResteasyEmbeddedServletInitializerTest.java │ │ └── sample │ │ ├── SampleApp.java │ │ ├── TestApplication1.java │ │ ├── TestApplication2.java │ │ ├── TestApplication3.java │ │ ├── TestApplication4.java │ │ ├── TestApplication5.java │ │ ├── TestProvider1.java │ │ ├── TestResource1.java │ │ └── TestResource2.java │ └── resources │ ├── logback.xml │ └── test-config.xml └── sample-app ├── README.md ├── pom.xml └── src └── main ├── java └── com │ └── sample │ └── app │ ├── Application.java │ ├── CustomContainerResponseFilter.java │ ├── CustomExceptionMapper.java │ ├── CustomSingletonBean.java │ ├── Echo.java │ ├── EchoMessage.java │ ├── EchoMessageCreator.java │ └── JaxrsApplication.java └── resources ├── application.yaml └── logback.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | target/ 3 | test-output/ 4 | 5 | # Package Files # 6 | *.jar 7 | *.war 8 | *.ear 9 | 10 | # IDE Files # 11 | .classpath 12 | .project 13 | .settings/ 14 | .idea/ 15 | *.iml 16 | 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: required 3 | 4 | jdk: 5 | - oraclejdk8 6 | 7 | install: mvn install 8 | 9 | after_success: 10 | # Installing Codacy code coverage reporter upload tool 11 | # This is a workaround until [this issue here](https://github.com/codacy/codacy-coverage-reporter/issues/49) is not solved. 12 | - wget https://github.com/codacy/codacy-coverage-reporter/releases/download/1.0.13/codacy-coverage-reporter-1.0.13-assembly.jar -O ccr.jar 13 | # Uploading Cobertura report to Codacy 14 | - java -cp ccr.jar com.codacy.CodacyCoverageReporter -l Java -r ./resteasy-spring-boot-starter/target/site/cobertura/coverage.xml --projectToken $CODACY_PROJECT_TOKEN 15 | # Deploying SNAPSHOT artifacts to Maven Central 16 | - mvn -B -f ./resteasy-spring-boot-starter/pom.xml -s ./resteasy-spring-boot-starter/settings.xml -DskipTests=true -Dcobertura.skip deploy -------------------------------------------------------------------------------- /License.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
  6 | Apache License
  7 |   Version 2.0, January 2004
  8 |    http://www.apache.org/licenses/
  9 | 
 10 |   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 11 | 
 12 |   1. Definitions.
 13 | 
 14 |  "License" shall mean the terms and conditions for use, reproduction,
 15 |  and distribution as defined by Sections 1 through 9 of this document.
 16 | 
 17 |  "Licensor" shall mean the copyright owner or entity authorized by
 18 |  the copyright owner that is granting the License.
 19 | 
 20 |  "Legal Entity" shall mean the union of the acting entity and all
 21 |  other entities that control, are controlled by, or are under common
 22 |  control with that entity. For the purposes of this definition,
 23 |  "control" means (i) the power, direct or indirect, to cause the
 24 |  direction or management of such entity, whether by contract or
 25 |  otherwise, or (ii) ownership of fifty percent (50%) or more of the
 26 |  outstanding shares, or (iii) beneficial ownership of such entity.
 27 | 
 28 |  "You" (or "Your") shall mean an individual or Legal Entity
 29 |  exercising permissions granted by this License.
 30 | 
 31 |  "Source" form shall mean the preferred form for making modifications,
 32 |  including but not limited to software source code, documentation
 33 |  source, and configuration files.
 34 | 
 35 |  "Object" form shall mean any form resulting from mechanical
 36 |  transformation or translation of a Source form, including but
 37 |  not limited to compiled object code, generated documentation,
 38 |  and conversions to other media types.
 39 | 
 40 |  "Work" shall mean the work of authorship, whether in Source or
 41 |  Object form, made available under the License, as indicated by a
 42 |  copyright notice that is included in or attached to the work
 43 |  (an example is provided in the Appendix below).
 44 | 
 45 |  "Derivative Works" shall mean any work, whether in Source or Object
 46 |  form, that is based on (or derived from) the Work and for which the
 47 |  editorial revisions, annotations, elaborations, or other modifications
 48 |  represent, as a whole, an original work of authorship. For the purposes
 49 |  of this License, Derivative Works shall not include works that remain
 50 |  separable from, or merely link (or bind by name) to the interfaces of,
 51 |  the Work and Derivative Works thereof.
 52 | 
 53 |  "Contribution" shall mean any work of authorship, including
 54 |  the original version of the Work and any modifications or additions
 55 |  to that Work or Derivative Works thereof, that is intentionally
 56 |  submitted to Licensor for inclusion in the Work by the copyright owner
 57 |  or by an individual or Legal Entity authorized to submit on behalf of
 58 |  the copyright owner. For the purposes of this definition, "submitted"
 59 |  means any form of electronic, verbal, or written communication sent
 60 |  to the Licensor or its representatives, including but not limited to
 61 |  communication on electronic mailing lists, source code control systems,
 62 |  and issue tracking systems that are managed by, or on behalf of, the
 63 |  Licensor for the purpose of discussing and improving the Work, but
 64 |  excluding communication that is conspicuously marked or otherwise
 65 |  designated in writing by the copyright owner as "Not a Contribution."
 66 | 
 67 |  "Contributor" shall mean Licensor and any individual or Legal Entity
 68 |  on behalf of whom a Contribution has been received by Licensor and
 69 |  subsequently incorporated within the Work.
 70 | 
 71 |   2. Grant of Copyright License. Subject to the terms and conditions of
 72 |  this License, each Contributor hereby grants to You a perpetual,
 73 |  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 74 |  copyright license to reproduce, prepare Derivative Works of,
 75 |  publicly display, publicly perform, sublicense, and distribute the
 76 |  Work and such Derivative Works in Source or Object form.
 77 | 
 78 |   3. Grant of Patent License. Subject to the terms and conditions of
 79 |  this License, each Contributor hereby grants to You a perpetual,
 80 |  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 81 |  (except as stated in this section) patent license to make, have made,
 82 |  use, offer to sell, sell, import, and otherwise transfer the Work,
 83 |  where such license applies only to those patent claims licensable
 84 |  by such Contributor that are necessarily infringed by their
 85 |  Contribution(s) alone or by combination of their Contribution(s)
 86 |  with the Work to which such Contribution(s) was submitted. If You
 87 |  institute patent litigation against any entity (including a
 88 |  cross-claim or counterclaim in a lawsuit) alleging that the Work
 89 |  or a Contribution incorporated within the Work constitutes direct
 90 |  or contributory patent infringement, then any patent licenses
 91 |  granted to You under this License for that Work shall terminate
 92 |  as of the date such litigation is filed.
 93 | 
 94 |   4. Redistribution. You may reproduce and distribute copies of the
 95 |  Work or Derivative Works thereof in any medium, with or without
 96 |  modifications, and in Source or Object form, provided that You
 97 |  meet the following conditions:
 98 | 
 99 |  (a) You must give any other recipients of the Work or
100 |  Derivative Works a copy of this License; and
101 | 
102 |  (b) You must cause any modified files to carry prominent notices
103 |  stating that You changed the files; and
104 | 
105 |  (c) You must retain, in the Source form of any Derivative Works
106 |  that You distribute, all copyright, patent, trademark, and
107 |  attribution notices from the Source form of the Work,
108 |  excluding those notices that do not pertain to any part of
109 |  the Derivative Works; and
110 | 
111 |  (d) If the Work includes a "NOTICE" text file as part of its
112 |  distribution, then any Derivative Works that You distribute must
113 |  include a readable copy of the attribution notices contained
114 |  within such NOTICE file, excluding those notices that do not
115 |  pertain to any part of the Derivative Works, in at least one
116 |  of the following places: within a NOTICE text file distributed
117 |  as part of the Derivative Works; within the Source form or
118 |  documentation, if provided along with the Derivative Works; or,
119 |  within a display generated by the Derivative Works, if and
120 |  wherever such third-party notices normally appear. The contents
121 |  of the NOTICE file are for informational purposes only and
122 |  do not modify the License. You may add Your own attribution
123 |  notices within Derivative Works that You distribute, alongside
124 |  or as an addendum to the NOTICE text from the Work, provided
125 |  that such additional attribution notices cannot be construed
126 |  as modifying the License.
127 | 
128 |  You may add Your own copyright statement to Your modifications and
129 |  may provide additional or different license terms and conditions
130 |  for use, reproduction, or distribution of Your modifications, or
131 |  for any such Derivative Works as a whole, provided Your use,
132 |  reproduction, and distribution of the Work otherwise complies with
133 |  the conditions stated in this License.
134 | 
135 |   5. Submission of Contributions. Unless You explicitly state otherwise,
136 |  any Contribution intentionally submitted for inclusion in the Work
137 |  by You to the Licensor shall be under the terms and conditions of
138 |  this License, without any additional terms or conditions.
139 |  Notwithstanding the above, nothing herein shall supersede or modify
140 |  the terms of any separate license agreement you may have executed
141 |  with Licensor regarding such Contributions.
142 | 
143 |   6. Trademarks. This License does not grant permission to use the trade
144 |  names, trademarks, service marks, or product names of the Licensor,
145 |  except as required for reasonable and customary use in describing the
146 |  origin of the Work and reproducing the content of the NOTICE file.
147 | 
148 |   7. Disclaimer of Warranty. Unless required by applicable law or
149 |  agreed to in writing, Licensor provides the Work (and each
150 |  Contributor provides its Contributions) on an "AS IS" BASIS,
151 |  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
152 |  implied, including, without limitation, any warranties or conditions
153 |  of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
154 |  PARTICULAR PURPOSE. You are solely responsible for determining the
155 |  appropriateness of using or redistributing the Work and assume any
156 |  risks associated with Your exercise of permissions under this License.
157 | 
158 |   8. Limitation of Liability. In no event and under no legal theory,
159 |  whether in tort (including negligence), contract, or otherwise,
160 |  unless required by applicable law (such as deliberate and grossly
161 |  negligent acts) or agreed to in writing, shall any Contributor be
162 |  liable to You for damages, including any direct, indirect, special,
163 |  incidental, or consequential damages of any character arising as a
164 |  result of this License or out of the use or inability to use the
165 |  Work (including but not limited to damages for loss of goodwill,
166 |  work stoppage, computer failure or malfunction, or any and all
167 |  other commercial damages or losses), even if such Contributor
168 |  has been advised of the possibility of such damages.
169 | 
170 |   9. Accepting Warranty or Additional Liability. While redistributing
171 |  the Work or Derivative Works thereof, You may choose to offer,
172 |  and charge a fee for, acceptance of support, warranty, indemnity,
173 |  or other liability obligations and/or rights consistent with this
174 |  License. However, in accepting such obligations, You may act only
175 |  on Your own behalf and on Your sole responsibility, not on behalf
176 |  of any other Contributor, and only if You agree to indemnify,
177 |  defend, and hold each Contributor harmless for any liability
178 |  incurred by, or claims asserted against, such Contributor by reason
179 |  of your accepting any such warranty or additional liability.
180 | 
181 |   END OF TERMS AND CONDITIONS
182 | 
183 |   APPENDIX: How to apply the Apache License to your work.
184 | 
185 |  To apply the Apache License to your work, attach the following
186 |  boilerplate notice, with the fields enclosed by brackets "[]"
187 |  replaced with your own identifying information. (Don't include
188 |  the brackets!)  The text should be enclosed in the appropriate
189 |  comment syntax for the file format. We also recommend that a
190 |  file or class name and description of purpose be included on the
191 |  same "printed page" as the copyright notice for easier
192 |  identification within third-party archives.
193 | 
194 |   Copyright [yyyy] [name of copyright owner]
195 | 
196 |   Licensed under the Apache License, Version 2.0 (the "License");
197 |   you may not use this file except in compliance with the License.
198 |   You may obtain a copy of the License at
199 | 
200 |   http://www.apache.org/licenses/LICENSE-2.0
201 | 
202 |   Unless required by applicable law or agreed to in writing, software
203 |   distributed under the License is distributed on an "AS IS" BASIS,
204 |   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
205 |   See the License for the specific language governing permissions and
206 |   limitations under the License.
207 |     
208 | 
209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/paypal/resteasy-spring-boot.svg?branch=master)](https://travis-ci.org/paypal/resteasy-spring-boot) 2 | [![Codacy Badge](https://api.codacy.com/project/badge/grade/4d23b74b13c3464b95f1acdb40b35cd7)](https://www.codacy.com/app/fabiocarvalho777/resteasy-spring-boot) 3 | [![Codacy Coverage](https://api.codacy.com/project/badge/coverage/4d23b74b13c3464b95f1acdb40b35cd7)](https://www.codacy.com/app/fabiocarvalho777/resteasy-spring-boot) 4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.paypal.springboot/resteasy-spring-boot-starter/badge.svg?style=flat)](http://search.maven.org/#search|ga|1|g:com.paypal.springboot) 5 | [![License](http://img.shields.io/:license-Apache%202-red.svg)](http://www.apache.org/licenses/LICENSE-2.0.txt) 6 | 7 | # RESTEasy Spring Boot Starter 8 | 9 | --- 10 | 11 | **Important: this project has been transfered from PayPal team to RESTEasy team.** 12 | 13 | **All future versions are going to be managed and released from https://github.com/resteasy/resteasy-spring-boot.** 14 | 15 | **This Git repo will be kept here for historical purposes only.** 16 | 17 | --- 18 | 19 | There was no RESTEasy Spring Boot starter out there, so PayPal team decided to create one and share it with the community.
20 | 21 | This Spring Boot starter is fully functional, has ZERO PayPal specific code on it, and can be used normally by any regular Spring Boot application that wants to have REST endpoints and prefers RESTEasy as the JAX-RS implementation. 22 | 23 | Also, this RESTEasy Spring Boot starter integrates with Spring as expected, which means every JAX-RS REST resource that is also a Spring bean will be automatically auto-scanned, integrated, and available. 24 | 25 | ## Features 26 | * Enables RESTEasy for Spring Boot applications 27 | * Supports JAX-RS providers, resources and sub-resources as Spring beans 28 | * 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 29 | * 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 30 | * Leverages and supports RESTEasy configuration 31 | * Supports RESTEasy Asynchronous Job Service 32 | 33 | ## Quick start 34 | 35 | ### Adding POM dependency 36 | Add the Maven dependency below to your Spring Boot application pom file.
37 | 38 | ``` xml 39 | 40 | com.paypal.springboot 41 | resteasy-spring-boot-starter 42 | 2.3.4-RELEASE 43 | runtime 44 | 45 | ``` 46 | 47 | ### Registering JAX-RS application classes 48 | Just define your JAX-RS application class (a subclass of [Application](http://docs.oracle.com/javaee/7/api/javax/ws/rs/core/Application.html)) as a Spring bean, and it will be automatically registered. See the example below. 49 | See section _JAX-RS application registration methods_ in [How to use RESTEasy Spring Boot Starter](mds/USAGE.md) for further information. 50 | 51 | ``` java 52 | package com.sample.app; 53 | 54 | import org.springframework.stereotype.Component; 55 | import javax.ws.rs.ApplicationPath; 56 | import javax.ws.rs.core.Application; 57 | 58 | @Component 59 | @ApplicationPath("/sample-app/") 60 | public class JaxrsApplication extends Application { 61 | } 62 | ``` 63 | 64 | ### Registering JAX-RS resources and providers 65 | Just define them as Spring beans, and they will be automatically registered. 66 | Notice that JAX-RS resources can be singleton or request scoped, while JAX-RS providers must be singletons. 67 | 68 | ### Further information 69 | See [How to use RESTEasy Spring Boot Starter](mds/USAGE.md). 70 | 71 | ## Release notes 72 | See [RESTEasy Spring Boot starter release notes](mds/RELEASE_NOTES.md). 73 | 74 | ## Projects 75 | 76 | - **sample-app**: A simple Spring Boot application that exposes JAX-RS endpoints as Spring beans using RESTEasy via this RESTEasy Spring Boot starter. 77 | - **resteasy-spring-boot-starter**: The RESTEasy Spring Boot Starter project. 78 | - **resteasy-spring-boot-starter-test**: Integration tests for the RESTEasy Spring Boot Starter project. 79 | 80 | ## Reporting an issue 81 | Please open an issue using our [GitHub issues](https://github.com/paypal/resteasy-spring-boot/issues) page. 82 | 83 | ## Contributing 84 | You are very welcome to contribute to RESTEasy Spring Boot starter! Read our [Contribution guidelines](mds/CONTRIBUTING.md). 85 | 86 | ## Contacting us 87 | To contact us, please send an email to fabiocarvalho777@gmail.com. 88 | 89 | ## License 90 | This project is licensed under the [Apache 2 License](License.html). 91 | -------------------------------------------------------------------------------- /mds/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guidelines 2 | 3 | ## Basic 4 | - If there is not one open already, open an issue in [GitHub issues](https://github.com/paypal/resteasy-spring-boot/issues) 5 | - Fork this repo 6 | - Checkout `develop` branch 7 | - Apply your changes 8 | - Make sure all projects build and all unit tests pass 9 | - Make sure code coverage doesn't drop (add extra unit tests if necessary) 10 | - If fixing a bug, make sure you add an unit or integration test to expose the issue 11 | - If adding a new feature, make sure you add an unit or integration test to test the feature 12 | - If adding a new feature, add end user documentation as well 13 | - Add comments to the code explaining your changes if necessary 14 | - Create a pull request to the correct development branch (mention the issue id in the PR comments) 15 | 16 | ## Code style 17 | Make sure to follow the code style of the existing code. That means for example four spaces for indentation. 18 | 19 | ## Commit messages 20 | When committing, make sure the commit message is describing what is changed and why. See the example below. 21 | 22 | [issue id] a short description 23 | A more detailed description 24 | 25 | ## Communication 26 | Please send an email to facarvalho@paypal.com, or fabiocarvalho777@gmail.com. 27 | 28 | ## More information 29 | Read more about best practices in [this github guide](https://guides.github.com/activities/contributing-to-open-source/). 30 | -------------------------------------------------------------------------------- /mds/RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | # Release notes 2 | 3 | ## 2.3.4-RELEASE 4 | 5 | #### Release date 6 | November 6th, 2017. 7 | 8 | #### Third-party versions 9 | - RESTEasy: 3.1.4.Final 10 | - Spring Boot: 1.5.8.RELEASE 11 | 12 | #### New features and enhancements 13 | 1. [83 - Upgrade to Spring Boot version 1.5.8](https://github.com/paypal/resteasy-spring-boot/issues/83) 14 | 15 | #### Bug fixes 16 | None 17 | 18 | ## 2.3.3-RELEASE 19 | 20 | #### Release date 21 | August 1st, 2017. 22 | 23 | #### Third-party versions 24 | - RESTEasy: 3.1.4.Final 25 | - Spring Boot: 1.5.6.RELEASE 26 | 27 | #### New features and enhancements 28 | 1. [76 - Upgrade to Spring Boot version 1.5.6](https://github.com/paypal/resteasy-spring-boot/issues/76) 29 | 1. [77 - Upgrade to RESTEasy version 3.1.4.Final](https://github.com/paypal/resteasy-spring-boot/issues/77) 30 | 31 | #### Bug fixes 32 | None 33 | 34 | ## 2.3.2-RELEASE 35 | 36 | #### Release date 37 | June 5th, 2017. 38 | 39 | #### Third-party versions 40 | - RESTEasy: 3.1.3.Final 41 | - Spring Boot: 1.5.3.RELEASE 42 | 43 | #### New features and enhancements 44 | 1. [61 - Adding support for RESTEasy Asynchronous Job Service](https://github.com/paypal/resteasy-spring-boot/issues/61) 45 | 1. [65 - Use Spring Framework scanning facility in JaxrsApplicationScanner](https://github.com/paypal/resteasy-spring-boot/issues/65) 46 | 1. [72 - Upgrade RESTEasy to version 3.1.3.Final](https://github.com/paypal/resteasy-spring-boot/issues/72) 47 | 48 | #### Bug fixes 49 | None 50 | 51 | #### Important notes 52 | 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. 53 | 54 | ## 2.3.1-RELEASE 55 | 56 | #### Release date 57 | May 12th, 2017. 58 | 59 | #### Third-party versions 60 | - RESTEasy: 3.1.0.Final 61 | - Spring Boot: 1.5.3.RELEASE 62 | 63 | #### New features and enhancements 64 | 1. [66 - Upgrade to Spring Boot 1.5.3.RELEASE](https://github.com/paypal/resteasy-spring-boot/issues/66) 65 | 1. [64 - Renaming `ResteasySpringBootConfig` to `RestEasyAutoConfiguration`](https://github.com/paypal/resteasy-spring-boot/issues/64) 66 | 1. [54 - Add a test case to integration tests to reproduce issue with multiple applications](https://github.com/paypal/resteasy-spring-boot/issues/54) 67 | 1. [57 - Add a test case to check for warning or error messages](https://github.com/paypal/resteasy-spring-boot/issues/57) 68 | 69 | #### Bug fixes 70 | 1. [67 - Remove all warnings during build](https://github.com/paypal/resteasy-spring-boot/issues/67) 71 | 72 | ## 2.3.0-RELEASE 73 | 74 | #### Release date 75 | February 22nd, 2017. 76 | 77 | #### Third-party versions 78 | - RESTEasy: 3.1.0.Final 79 | - Spring Boot: 1.5.1.RELEASE 80 | 81 | #### New features and enhancements 82 | 1. [49 - Upgrade to RESTEasy version 3.1.0](https://github.com/paypal/resteasy-spring-boot/issues/49) 83 | 1. [50 - Upgrade to Spring Boot version 1.5.1](https://github.com/paypal/resteasy-spring-boot/issues/50) 84 | 85 | #### Bug fixes 86 | 1. [56 - Warning messages during startup](https://github.com/paypal/resteasy-spring-boot/issues/56) 87 | 88 | ## 2.2.2-RELEASE 89 | 90 | #### Release date 91 | February 1st, 2017. 92 | 93 | #### Third-party versions 94 | - RESTEasy: 3.0.19.Final 95 | - Spring Boot: 1.4.2.RELEASE 96 | 97 | #### New features and enhancements 98 | 1. [13 - Add automated integration tests](https://github.com/paypal/resteasy-spring-boot/issues/13) 99 | 100 | #### Bug fixes 101 | 1. [51 - Do not share ResteasyProviderFactory and Registry among applications](https://github.com/paypal/resteasy-spring-boot/issues/51) 102 | 1. [44 - Fix CI travis jpm usage](https://github.com/paypal/resteasy-spring-boot/issues/44) 103 | 104 | ## 2.2.1-RELEASE 105 | 106 | #### Release date 107 | November 20th, 2016. 108 | 109 | #### Third-party versions 110 | - RESTEasy: 3.0.19.Final 111 | - Spring Boot: 1.4.2.RELEASE 112 | 113 | #### New features and enhancements 114 | 1. [43 - Upgrade Spring Boot to version 1.4.2](https://github.com/paypal/resteasy-spring-boot/issues/43) 115 | 1. [40 - Spring 4.1 is not supported although it easily can be](https://github.com/paypal/resteasy-spring-boot/issues/40) 116 | 117 | #### Bug fixes 118 | 1. [42 - Remove deprecated code](https://github.com/paypal/resteasy-spring-boot/issues/42) 119 | 120 | ## 2.2.0-RELEASE 121 | 122 | #### Release date 123 | August 31st, 2016. 124 | 125 | #### Third-party versions 126 | - RESTEasy: 3.0.19.Final 127 | - Spring Boot: 1.4.0.RELEASE 128 | 129 | #### New features and enhancements 130 | 1. [33 - Upgrade to the latest Spring Boot and the RESTEasy versions](https://github.com/paypal/resteasy-spring-boot/issues/33) 131 | 132 | #### Bug fixes 133 | 1. [35 - Not possible to use application property in application.yml](https://github.com/paypal/resteasy-spring-boot/issues/35) 134 | 1. [34 - Mockito should be set to test scope](https://github.com/paypal/resteasy-spring-boot/issues/34) 135 | 1. [32 - Documentation has a contradiction about registering Application subclasses](https://github.com/paypal/resteasy-spring-boot/issues/32) 136 | 137 | ## 2.1.1-RELEASE 138 | 139 | #### Release date 140 | July 22nd, 2016. 141 | 142 | #### Third-party versions 143 | - RESTEasy: 3.0.18.Final 144 | - Spring Boot: 1.3.6.RELEASE 145 | 146 | #### New features and enhancements 147 | None 148 | 149 | #### Bug fixes 150 | 151 | 1. [23 - JAX-RS application fails to register after upgrade to 2.1.0](https://github.com/paypal/resteasy-spring-boot/issues/30) 152 | 153 | ## 2.1.0-RELEASE 154 | 155 | #### Release date 156 | July 21st, 2016. 157 | 158 | #### Third-party versions 159 | - RESTEasy: 3.0.18.Final 160 | - Spring Boot: 1.3.6.RELEASE 161 | 162 | #### New features and enhancements 163 | 164 | 1. [22 - Add configuration to turn OFF scanning for JAX-RS Application sub-classes](https://github.com/paypal/resteasy-spring-boot/issues/22) 165 | 1. [10 - Support RESTEasy configuration](https://github.com/paypal/resteasy-spring-boot/issues/10) 166 | 1. [20 - Add documentation](https://github.com/paypal/resteasy-spring-boot/issues/20) 167 | 1. [25 - Add Cobertura code coverage](https://github.com/paypal/resteasy-spring-boot/issues/25) 168 | 1. [24 - Automate Travis CI](https://github.com/paypal/resteasy-spring-boot/issues/24) 169 | 170 | #### Bug fixes 171 | 172 | 1. [23 - Make sure section 2.3.2 in JAX-RS 2.0 spec is followed strictly in regard to the absence of JAX-RS Application classes](https://github.com/paypal/resteasy-spring-boot/issues/23) 173 | 174 | ## 2.0.1-RELEASE 175 | 176 | #### Release date 177 | June 9th, 2016. 178 | 179 | #### Third-party versions 180 | - RESTEasy: 3.0.17.Final 181 | - Spring Boot: 1.3.5.RELEASE 182 | 183 | #### New features and enhancements 184 | 185 | 1. [18 - Component annotation removal from ResteasyEmbeddedServletInitializer](https://github.com/paypal/resteasy-spring-boot/issues/18) 186 | 1. [17 - Upgrade Sprig Boot to version 1.3.5](https://github.com/paypal/resteasy-spring-boot/issues/17) 187 | 1. [16 - Upgrade RESTEasy to version 3.0.17.Final](https://github.com/paypal/resteasy-spring-boot/issues/16) 188 | 1. [7 - Remove custom RESTEasy classes as soon as RESTEasy 3.0.17.Final is released](https://github.com/paypal/resteasy-spring-boot/issues/7) 189 | 190 | #### Bug fixes 191 | 192 | 1. [19 - JAX-RS resources resolution should be targeted towards Spring beans only](https://github.com/paypal/resteasy-spring-boot/issues/19) 193 | 194 | ## 2.0.0-RELEASE 195 | 196 | #### Important note 197 | The starter artifact id is being renamed to **resteasy-spring-boot-starter**, due to [issue 2](https://github.com/paypal/resteasy-spring-boot/issues/2). 198 | 199 | #### Release date 200 | April 27th, 2016. 201 | 202 | #### Third-party versions 203 | - RESTEasy: 3.0.16.Final 204 | - Spring Boot: 1.3.3.RELEASE 205 | 206 | #### New features and enhancements 207 | 208 | 1. [2 - Renaming artifact id due to Spring team request](https://github.com/paypal/resteasy-spring-boot/issues/2) 209 | 1. [4 - Replace ComponentScan by Import annotations](https://github.com/paypal/resteasy-spring-boot/issues/4) 210 | 211 | #### Bug fixes 212 | 213 | 1. [5 - Classpath scanner prints warning when a non jar file is scanned](https://github.com/paypal/resteasy-spring-boot/issues/5) 214 | 1. [6 - JAX-RS application and endpoints don't get registered when running mvn spring-boot:run](https://github.com/paypal/resteasy-spring-boot/issues/6) 215 | 1. [8 - Don't skip all applications if one has no path bug ](https://github.com/paypal/resteasy-spring-boot/issues/8) 216 | 217 | ## 1.0.0-RELEASE 218 | 219 | #### Release date 220 | February 26th, 2016. 221 | 222 | #### Third-party versions 223 | - RESTEasy: 3.0.16.Final 224 | - Spring Boot: 1.3.2.RELEASE 225 | 226 | #### New features and enhancements 227 | 228 | 1. Enable RESTEasy and Spring integration for Spring Boot applications 229 | 1. Support JAX-RS sub-resources 230 | 1. Support automatic discovery of JAX-RS Application classes 231 | 232 | #### Bug fixes 233 | None -------------------------------------------------------------------------------- /mds/USAGE.md: -------------------------------------------------------------------------------- 1 | # How to use RESTEasy Spring Boot Starter 2 | 3 | #### Adding POM dependency 4 | Add the Maven dependency below to your Spring Boot application pom file.
5 | 6 | ``` xml 7 | 8 | com.paypal.springboot 9 | resteasy-spring-boot-starter 10 | 2.3.4-RELEASE 11 | runtime 12 | 13 | ``` 14 | 15 | #### Registering JAX-RS application classes 16 | Just define your JAX-RS application class (a subclass of [Application](http://docs.oracle.com/javaee/7/api/javax/ws/rs/core/Application.html)) as a Spring bean, and it will be automatically registered. See the example below. 17 | See section [JAX-RS application registration methods](#jax-rs-application-registration-methods) for further information. 18 | 19 | ``` java 20 | package com.sample.app; 21 | 22 | import org.springframework.stereotype.Component; 23 | import javax.ws.rs.ApplicationPath; 24 | import javax.ws.rs.core.Application; 25 | 26 | @Component 27 | @ApplicationPath("/sample-app/") 28 | public class JaxrsApplication extends Application { 29 | } 30 | ``` 31 | 32 | #### Registering JAX-RS resources and providers 33 | Just define them as Spring beans, and they will be automatically registered. 34 | Notice that JAX-RS resources can be singleton or request scoped, while JAX-RS providers must be singletons. 35 | 36 | ## Advanced topics 37 | 38 | #### JAX-RS application registration methods 39 | 40 | JAX-RS applications are defined via sub-classes of [Application](http://docs.oracle.com/javaee/7/api/javax/ws/rs/core/Application.html). One or more JAX-RS applications can be registered, and there are three different methods to do so: 41 | 42 | 1. By having them defined as Spring beans. 43 | 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. 44 | 3. Automatically by classpath scanning (looking for `javax.ws.rs.core.Application` sub-classes). **See important note number 6 about this method**. 45 | 46 | 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: 47 | 48 | 1. `beans` 49 | 1. `property` 50 | 1. `scanning` 51 | 1. `auto` (default) 52 | 53 | The first three values refer respectively to each one of the three methods described earlier. The last one, `auto`, when set (or when property `resteasy.jaxrs.app.registration` is not present), attempts first to find JAX-RS application classes by searching them as Spring beans. If any is found, the search stops, and those are the only JAX-RS applications to be registered. If no JAX-RS application Spring beans are found, then the `property` approach is tried. If still no JAX-RS application classes could be found, then the last method, `scanning`, is attempted. If after that still no JAX-RS application class could be registered, then a default one will be automatically created mapping to `/*` (_according to section 2.3.2 in the JAX-RS 2.0 specification_). 54 | 55 | __Important notes__ 56 | 57 | 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. 58 | 1. It is recommended to always have at least one JAX-RS application class. 59 | 1. A JAX-RS application class with no `javax.ws.rs.ApplicationPath` annotation will not be registered. 60 | 1. Avoid setting the JAX-RS application base URI to simply `/` to prevent URI conflicts, as explained in item 1. 61 | 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*. 62 | 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. 63 | 64 | #### RESTEasy configuration 65 | 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/)).
66 | 67 | As an example, to set RESTEasy property `resteasy.role.based.security` to `true`, just add the property bellow to Spring Boot `application.properties` file. 68 | 69 | ``` 70 | server.context-parameters.resteasy.role.based.security=true 71 | ``` 72 | 73 | It is important to mention that the following RESTEasy configuration options are NOT applicable to an application using RESTEasy Spring Boot starter. 74 | All other RESTEasy configuration options are supported normally. 75 | 76 | | Configuration option | Why it is not applicable | 77 | |---|---| 78 | |`javax.ws.rs.Application`|JAX-RS application classes are registered as explained in section _"JAX-RS application registration methods"_ above| 79 | |`resteasy.servlet.mapping.prefix`|The url-pattern for the Resteasy servlet-mapping is always based on the `ApplicationPath` annotation in the JAX-RS application class| 80 | |`resteasy.scan`
`resteasy.scan.providers`
`resteasy.scan.resources`
`resteasy.providers`
`resteasy.use.builtin.providers`
`resteasy.resources`
`resteasy.jndi.resources`|All JAX-RS resources and providers are always supposed to be Spring beans, and they are automatically discovered| 81 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 4.0.0 7 | 8 | com.paypal.springboot 9 | resteasy-spring-boot-starter-parent 10 | 2.3.4-RELEASE 11 | pom 12 | 13 | 14 | resteasy-spring-boot-starter 15 | sample-app 16 | resteasy-spring-boot-starter-test 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /release_steps.txt: -------------------------------------------------------------------------------- 1 | changes in develop branch 2 | rev all 4 poms to release 3 | update /README 4 | set the new RELEASE version in Quick Start 5 | update /mds/USAGE 6 | set the new RELEASE version 7 | update /mds/RELEASE_NOTES 8 | check if it has all contributions (improvements and fixes) 9 | set delivery date 10 | set 3rd party versions 11 | build everything to make sure it is Ok 12 | check in changes to develop branch (Revving up to version 2.1.0-RELEASE) 13 | merge all changes from develop branch to master (Bringing in 2.1.0 changes to master) 14 | 15 | checkout master branch 16 | git pull 17 | build everything to make sure it is Ok 18 | release from master 19 | comment out cobertura plugin and cobertura reporting plugin in resteasy-spring-boot-starter/pom.xml (BUT NOT CHECK IT IN!) 20 | deploy to MavenCentral(mvn clean install deploy -Possrh) 21 | remove comment marks for cobertura plugin and cobertura reporting plugin 22 | manual sonatype release 23 | Staging Repositories 24 | Close (the one with sources and everything) 25 | Release 26 | tag new release from master 27 | git tag -a 2.1.0-RELEASE 28 | git push origin 2.1.0-RELEASE 29 | 30 | Close GitHub Milestone that has just been released 31 | 32 | git checkout develop 33 | rev develop branch to next snapshot version 34 | rev all 4 poms in to next snapshot version 35 | build everything to make sure it is Ok 36 | update /README 37 | set the new SNAPSHOT version in Quick Start 38 | update /mds/USAGE 39 | set the new SNAPSHOT version 40 | update /mds/RELEASE NOTES 41 | create new version section by copying latest one 42 | set delivery date to TBD 43 | set third-parties to TBD 44 | set contributions (improvements and fixes) to TBD 45 | check in changes to develop branch (Preparing for version 2.1.1-SNAPSHOT) 46 | 47 | Create a new GitHub Milestone and assign issues to it (if any) 48 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter-test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.paypal.springboot 7 | resteasy-spring-boot-starter-test 8 | 2.3.4-RELEASE 9 | ${project.artifactId} 10 | 11 | Integration test project for RESTEasy Spring Boot starter 12 | https://github.com/paypal/resteasy-spring-boot 13 | 14 | 15 | 16 | The Apache License, Version 2.0 17 | http://www.apache.org/licenses/LICENSE-2.0.txt 18 | 19 | 20 | 21 | 22 | Fabio Carvalho 23 | facarvalho@paypal.com 24 | PayPal 25 | http://www.paypal.com 26 | 27 | 28 | 29 | scm:git:git@github.com:paypal/resteasy-spring-boot.git 30 | scm:git:git@github.com:paypal/resteasy-spring-boot.git 31 | git@github.com:paypal/resteasy-spring-boot.git 32 | 33 | 34 | 35 | ossrh 36 | https://oss.sonatype.org/content/repositories/snapshots 37 | 38 | 39 | ossrh 40 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 41 | 42 | 43 | 44 | 45 | UTF-8 46 | UTF-8 47 | 1.8 48 | 49 | 50 | 51 | 52 | javax.ws.rs 53 | javax.ws.rs-api 54 | 2.0 55 | test 56 | 57 | 58 | com.test 59 | sample-app 60 | ${project.version} 61 | test 62 | 63 | 64 | io.rest-assured 65 | rest-assured 66 | 3.0.1 67 | test 68 | 69 | 70 | org.testng 71 | testng 72 | 6.1.1 73 | test 74 | 75 | 76 | org.yaml 77 | snakeyaml 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | fortify 87 | 88 | 89 | 90 | 91 | com.fortify.ps.maven.plugin 92 | sca-maven-plugin 93 | 4.22 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | com.fortify.ps.maven.plugin 106 | sca-maven-plugin 107 | 108 | 1.8 109 | 1G 110 | 111 | 112 | 113 | sca-clean 114 | 115 | clean 116 | 117 | 118 | 119 | sca-translate-exec 120 | 121 | translate 122 | 123 | 124 | 125 | 4.22 126 | 127 | 128 | 129 | 130 | 131 | 132 | org.apache.maven.plugins 133 | maven-failsafe-plugin 134 | 2.20 135 | 136 | 137 | failsafe-integration-tests 138 | integration-test 139 | 140 | integration-test 141 | verify 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter-test/src/test/java/com/paypal/springboot/resteasy/AsyncJobIT.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy; 2 | 3 | import com.sample.app.Application; 4 | import io.restassured.RestAssured; 5 | import io.restassured.response.Response; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.util.SocketUtils; 8 | import org.testng.annotations.AfterClass; 9 | import org.testng.annotations.BeforeClass; 10 | import org.testng.annotations.Test; 11 | 12 | import java.util.Properties; 13 | 14 | import static io.restassured.RestAssured.given; 15 | import static org.hamcrest.CoreMatchers.notNullValue; 16 | import static org.hamcrest.Matchers.empty; 17 | import static org.hamcrest.Matchers.equalTo; 18 | import static org.hamcrest.Matchers.isEmptyString; 19 | 20 | /** 21 | * Integration tests for RESTEasy Asynchronous Job Service 22 | * 23 | * @author facarvalho 24 | */ 25 | public class AsyncJobIT { 26 | 27 | @BeforeClass 28 | public void setUp() { 29 | int appPort = SocketUtils.findAvailableTcpPort(); 30 | 31 | RestAssured.basePath = "sample-app"; 32 | RestAssured.port = appPort; 33 | 34 | Properties properties = new Properties(); 35 | properties.put("server.context-parameters.resteasy.async.job.service.enabled", true); 36 | 37 | SpringApplication app = new SpringApplication(Application.class); 38 | app.setDefaultProperties(properties); 39 | app.addListeners(new LogbackTestApplicationListener()); 40 | app.run("--server.port=" + appPort).registerShutdownHook(); 41 | } 42 | 43 | @Test 44 | public void regularRequestTest() { 45 | Response response = given().body("is there anybody out there?").post("/echo"); 46 | response.then().statusCode(200).body("timestamp", notNullValue()).body("echoText", equalTo("is there anybody out there?")); 47 | } 48 | 49 | @Test 50 | public void asyncRequestTest() { 51 | Response response = given().body("is there anybody out there?").post("/echo?asynch=true"); 52 | response.then().statusCode(202).body(isEmptyString()); 53 | 54 | String location = response.getHeader("Location"); 55 | response = given().get(location + "?wait=1000"); 56 | response.then().statusCode(200).body("timestamp", notNullValue()).body("echoText", equalTo("is there anybody out there?")); 57 | } 58 | 59 | @Test 60 | public void fireAndForgetRequestTest() { 61 | Response response = given().body("is there anybody out there?").post("/echo?oneway=true"); 62 | response.then().statusCode(202).body(isEmptyString()); 63 | } 64 | 65 | @AfterClass 66 | public void shuttingDownApplication() { 67 | Response response = given().basePath("/").post("/shutdown"); 68 | response.then().statusCode(200).body("message", equalTo("Shutting down, bye...")); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter-test/src/test/java/com/paypal/springboot/resteasy/CommonUseCasesIT.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy; 2 | 3 | import com.sample.app.Application; 4 | import io.restassured.RestAssured; 5 | import io.restassured.response.Response; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.util.SocketUtils; 8 | import org.testng.annotations.AfterClass; 9 | import org.testng.annotations.BeforeClass; 10 | import org.testng.annotations.Test; 11 | 12 | import static io.restassured.RestAssured.given; 13 | import static org.hamcrest.CoreMatchers.equalTo; 14 | import static org.hamcrest.CoreMatchers.notNullValue; 15 | 16 | /** 17 | * This is an integration test based on a simple sample application and 18 | * very common use cases (see sample-app project) 19 | * 20 | * @author facarvalho 21 | */ 22 | public class CommonUseCasesIT { 23 | 24 | @BeforeClass 25 | public void startingApplicationUp() { 26 | RestAssured.basePath = "sample-app"; 27 | int port = SocketUtils.findAvailableTcpPort(); 28 | RestAssured.port = port; 29 | 30 | SpringApplication springApplication = new SpringApplication(Application.class); 31 | springApplication.addListeners(new LogbackTestApplicationListener()); 32 | springApplication.run("--server.port=" + port).registerShutdownHook(); 33 | } 34 | 35 | @AfterClass 36 | public void shuttingDownApplication() { 37 | Response response = given().basePath("/").post("/shutdown"); 38 | response.then().statusCode(200).body("message", equalTo("Shutting down, bye...")); 39 | } 40 | 41 | @Test 42 | public void happyPathTest() { 43 | Response response = given().body("is there anybody out there?").post("/echo"); 44 | response.then().statusCode(200).body("timestamp", notNullValue()).body("echoText", equalTo("is there anybody out there?")); 45 | } 46 | 47 | @Test 48 | public void filterTest() { 49 | Response response = given().body("is there anybody out there?").header("ping", "ping").post("/echo"); 50 | response.then().statusCode(200).body("timestamp", notNullValue()).body("echoText", equalTo("is there anybody out there?")).header("pong", equalTo("pong")); 51 | } 52 | 53 | @Test 54 | public void invalidUriPathTest() { 55 | // Notice "eco" is supposed to result in 404 56 | Response response = given().body("is there anybody out there?").post("/eco"); 57 | response.then().statusCode(404).body(equalTo("The resource you've requested, has not been found!")); 58 | } 59 | 60 | @Test 61 | public void invalidBaseUrlTest() { 62 | // Notice "sampl-ap" is supposed to result in 404 63 | Response response = given().basePath("sampl-app").body("is there anybody out there?").post("/echo"); 64 | response.then().statusCode(404).body("status", equalTo(404)).body("error", equalTo("Not Found")); 65 | } 66 | 67 | @Test 68 | public void invalidNoPayloadTest() { 69 | // Notice that the endpoint we are sending a request to uses Bean Validations to assure 70 | // the request message payload is valid. If that is not the case (a blank payload for example), 71 | // then the server is expected to return a 400 response message 72 | Response response = given().body("").post("/echo"); 73 | response.then().statusCode(400).body(equalTo("[PARAMETER]\r[echo.arg0]\r[may not be empty]\r[]\r\r")); 74 | } 75 | 76 | @Test 77 | public void actuatorTest() throws InterruptedException { 78 | Response response = given().basePath("/").get("/health"); 79 | response.then().statusCode(200).body("status", equalTo("UP")); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter-test/src/test/java/com/paypal/springboot/resteasy/ConfigurationIT.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy; 2 | 3 | import com.sample.app.Application; 4 | import io.restassured.response.Response; 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.util.SocketUtils; 8 | import org.testng.Assert; 9 | import org.testng.annotations.Test; 10 | 11 | import java.util.Properties; 12 | 13 | import static io.restassured.RestAssured.given; 14 | import static org.hamcrest.CoreMatchers.equalTo; 15 | import static org.hamcrest.CoreMatchers.notNullValue; 16 | 17 | /** 18 | * This is an integration test based on a simple sample application (see sample-app project). 19 | * This class test possible configurations to register JAX-RS application classes. 20 | * 21 | * @author facarvalho 22 | */ 23 | public class ConfigurationIT { 24 | 25 | private int configureAndStartApp(Properties properties) { 26 | return configureAndStartApp(properties, true); 27 | } 28 | 29 | private int configureAndStartApp(Properties properties, boolean assertPerfectLog) { 30 | SpringApplication springApplication = new SpringApplication(Application.class); 31 | if (assertPerfectLog) { 32 | springApplication.addListeners(new LogbackTestApplicationListener()); 33 | } 34 | if (properties != null) { 35 | springApplication.setDefaultProperties(properties); 36 | } 37 | 38 | int port = SocketUtils.findAvailableTcpPort(); 39 | springApplication.run("--server.port=" + port).registerShutdownHook(); 40 | 41 | return port; 42 | } 43 | 44 | private void appShutdown(int port) { 45 | Response response = given().basePath("/").port(port).post("/shutdown"); 46 | response.then().statusCode(200).body("message", equalTo("Shutting down, bye...")); 47 | } 48 | 49 | private void assertResourceFound(int port, String basePath) { 50 | Response response = given().basePath(basePath).port(port).body("is there anybody out there?").post("/echo"); 51 | response.then().statusCode(200).body("timestamp", notNullValue()).body("echoText", equalTo("is there anybody out there?")); 52 | } 53 | 54 | private void assertResourceNotFound(int port, String basePath) { 55 | Response response = given().basePath(basePath).port(port).body("is there anybody out there?").post("/echo"); 56 | response.then().statusCode(404).body("status", equalTo(404)).body("error", equalTo("Not Found")); 57 | } 58 | 59 | @Test 60 | public void implicitAutoTest() { 61 | int port = configureAndStartApp(null); 62 | 63 | assertResourceFound(port, "sample-app"); 64 | assertResourceNotFound(port, "sample-app-test"); 65 | assertResourceNotFound(port, "/"); 66 | 67 | appShutdown(port); 68 | } 69 | 70 | @Test 71 | public void explicitAutoTest() { 72 | Properties properties = new Properties(); 73 | properties.put("resteasy.jaxrs.app.registration", "auto"); 74 | 75 | int port = configureAndStartApp(properties); 76 | 77 | assertResourceFound(port, "sample-app"); 78 | assertResourceNotFound(port, "sample-app-test"); 79 | assertResourceNotFound(port, "/"); 80 | 81 | appShutdown(port); 82 | } 83 | 84 | @Test 85 | public void beansTest() { 86 | Properties properties = new Properties(); 87 | properties.put("resteasy.jaxrs.app.registration", "beans"); 88 | 89 | int port = configureAndStartApp(properties); 90 | 91 | assertResourceFound(port, "sample-app"); 92 | assertResourceNotFound(port, "sample-app-test"); 93 | assertResourceNotFound(port, "/"); 94 | 95 | appShutdown(port); 96 | } 97 | 98 | @Test 99 | public void propertySpringBeanClassTest() { 100 | Properties properties = new Properties(); 101 | properties.put("resteasy.jaxrs.app.registration", "property"); 102 | properties.put("resteasy.jaxrs.app.classes", "com.sample.app.JaxrsApplication"); 103 | 104 | int port = configureAndStartApp(properties); 105 | 106 | assertResourceFound(port, "sample-app"); 107 | assertResourceNotFound(port, "sample-app-test"); 108 | assertResourceNotFound(port, "/"); 109 | 110 | appShutdown(port); 111 | } 112 | 113 | @Test 114 | public void propertyNonSpringBeanClassTest() { 115 | Properties properties = new Properties(); 116 | properties.put("resteasy.jaxrs.app.registration", "property"); 117 | properties.put("resteasy.jaxrs.app.classes", "com.test.NonSpringBeanJaxrsApplication"); 118 | 119 | int port = configureAndStartApp(properties); 120 | 121 | assertResourceNotFound(port, "sample-app"); 122 | assertResourceFound(port, "sample-app-test"); 123 | assertResourceNotFound(port, "/"); 124 | 125 | appShutdown(port); 126 | } 127 | 128 | @Test 129 | public void invalidClassTest() { 130 | Properties properties = new Properties(); 131 | properties.put("resteasy.jaxrs.app.registration", "property"); 132 | properties.put("resteasy.jaxrs.app.classes", "com.foor.bar.NonExistentApplicationClass"); 133 | 134 | try { 135 | configureAndStartApp(properties, false); 136 | 137 | Assert.fail("Expected exception, due to class not found, has not been thrown"); 138 | } catch (BeansException ex) { 139 | Assert.assertEquals(ex.getCause().getClass(), ClassNotFoundException.class); 140 | Assert.assertEquals(ex.getCause().getMessage(), "com.foor.bar.NonExistentApplicationClass"); 141 | } 142 | } 143 | 144 | @Test 145 | public void scanningTest() { 146 | Properties properties = new Properties(); 147 | properties.put("resteasy.jaxrs.app.registration", "scanning"); 148 | 149 | int port = configureAndStartApp(properties); 150 | 151 | assertResourceFound(port, "sample-app"); 152 | assertResourceFound(port, "sample-app-test"); 153 | assertResourceFound(port, "sample-app-test-two"); 154 | assertResourceNotFound(port, "/"); 155 | 156 | appShutdown(port); 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter-test/src/test/java/com/paypal/springboot/resteasy/LogbackTestApplicationListener.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy; 2 | 3 | import ch.qos.logback.classic.Level; 4 | import ch.qos.logback.classic.Logger; 5 | import ch.qos.logback.classic.LoggerContext; 6 | import ch.qos.logback.classic.spi.ILoggingEvent; 7 | import ch.qos.logback.core.Appender; 8 | import ch.qos.logback.core.AppenderBase; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; 11 | import org.springframework.context.ApplicationEvent; 12 | import org.springframework.context.event.ContextClosedEvent; 13 | import org.springframework.context.event.SmartApplicationListener; 14 | import org.springframework.core.Ordered; 15 | import org.testng.Assert; 16 | 17 | /** 18 | * The Spring application listener registers a Logback appender 19 | * which allows inspecting every log statement looking for warning 20 | * or error messages. If any is found, the test will fail. 21 | * 22 | * @author facarvalho 23 | */ 24 | public class LogbackTestApplicationListener implements SmartApplicationListener { 25 | 26 | private boolean warningOrErrorFound = false; 27 | 28 | private Appender appender = new AppenderBase() { 29 | 30 | // TODO 31 | // Remove this after implementing https://github.com/paypal/resteasy-spring-boot/issues/69 32 | private static final java.lang.String SCANNING_WARNING = "\n-------------\nStarting on version 3.0.0, the behavior of the `scanning`"; 33 | 34 | @Override 35 | protected void append(ILoggingEvent event) { 36 | if (event == null || warningOrErrorFound) { 37 | return; 38 | } 39 | Level level = event.getLevel(); 40 | if ((level.equals(Level.WARN) || level.equals(Level.ERROR)) && !event.getMessage().startsWith(SCANNING_WARNING)) { 41 | warningOrErrorFound = true; 42 | Assert.fail(event.getFormattedMessage()); 43 | } 44 | } 45 | }; 46 | 47 | private void addTestAppender() { 48 | LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); 49 | appender.setContext(loggerContext); 50 | appender.start(); 51 | Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); 52 | rootLogger.addAppender(appender); 53 | } 54 | 55 | private void detachTestAppender() { 56 | if (appender != null) { 57 | appender.stop(); 58 | LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); 59 | Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); 60 | rootLogger.detachAppender(appender); 61 | } 62 | } 63 | 64 | public boolean supportsEventType(Class eventType) { 65 | return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType); 66 | } 67 | 68 | public boolean supportsSourceType(Class sourceType) { 69 | return true; 70 | } 71 | 72 | public void onApplicationEvent(ApplicationEvent event) { 73 | if(event instanceof ApplicationEnvironmentPreparedEvent) { 74 | addTestAppender(); 75 | } else if(event instanceof ContextClosedEvent && ((ContextClosedEvent)event).getApplicationContext().getParent() == null) { 76 | detachTestAppender(); 77 | } 78 | } 79 | 80 | public int getOrder() { 81 | return Ordered.HIGHEST_PRECEDENCE - 12; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter-test/src/test/java/com/paypal/springboot/resteasy/MultipleContextsIT.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy; 2 | 3 | import com.sample.app.Application; 4 | import com.test.multicontexttest.MultiContextTestApp; 5 | import io.restassured.RestAssured; 6 | import io.restassured.response.Response; 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.util.SocketUtils; 9 | import org.testng.annotations.AfterClass; 10 | import org.testng.annotations.BeforeClass; 11 | import org.testng.annotations.Test; 12 | 13 | import java.util.Properties; 14 | 15 | import static io.restassured.RestAssured.given; 16 | import static org.hamcrest.CoreMatchers.notNullValue; 17 | import static org.hamcrest.Matchers.equalTo; 18 | 19 | /** 20 | * This test assures that the RESTEasy and Spring integration, promoted by the starter, 21 | * does not cause any conflicts when there is more than one application context 22 | * registered, as reported in the past by this bug (already fixed): 23 | * https://github.com/paypal/resteasy-spring-boot/issues/51 24 | * 25 | * @author facarvalho 26 | */ 27 | public class MultipleContextsIT { 28 | 29 | private int app1Port, app2Port; 30 | 31 | @BeforeClass 32 | public void setUp() { 33 | app1Port = SocketUtils.findAvailableTcpPort(); 34 | app2Port = SocketUtils.findAvailableTcpPort(); 35 | 36 | RestAssured.basePath = "sample-app"; 37 | } 38 | 39 | @Test 40 | public void test() { 41 | Properties properties = new Properties(); 42 | properties.put("spring.jmx.enabled", false); 43 | 44 | SpringApplication app1 = new SpringApplication(Application.class); 45 | app1.setDefaultProperties(properties); 46 | app1.addListeners(new LogbackTestApplicationListener()); 47 | app1.run("--server.port=" + app1Port).registerShutdownHook(); 48 | 49 | SpringApplication app2 = new SpringApplication(MultiContextTestApp.class); 50 | app2.setDefaultProperties(properties); 51 | app2.addListeners(new LogbackTestApplicationListener()); 52 | app2.run("--server.port=" + app2Port).registerShutdownHook(); 53 | 54 | Response response; 55 | 56 | response = given().port(app1Port).body("is there anybody out there?").post("/echo"); 57 | response.then().statusCode(200).body("timestamp", notNullValue()).body("echoText", equalTo("is there anybody out there?")); 58 | 59 | response = given().port(app2Port).body("is there anybody out there?").post("/echo"); 60 | response.then().statusCode(200).body("timestamp", notNullValue()).body("echoText", equalTo("I don't want to echo anything today")); 61 | } 62 | 63 | @AfterClass 64 | public void shuttingDownApplication() { 65 | Response response; 66 | 67 | response = given().port(app1Port).basePath("/").post("/shutdown"); 68 | response.then().statusCode(200).body("message", equalTo("Shutting down, bye...")); 69 | 70 | response = given().port(app2Port).basePath("/").post("/shutdown"); 71 | response.then().statusCode(200).body("message", equalTo("Shutting down, bye...")); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter-test/src/test/java/com/sample/app/test/NonSpringBeanJaxrsApplication.java: -------------------------------------------------------------------------------- 1 | package com.sample.app.test; 2 | 3 | import javax.ws.rs.ApplicationPath; 4 | import javax.ws.rs.core.Application; 5 | 6 | @ApplicationPath("sample-app-test-two") 7 | public class NonSpringBeanJaxrsApplication extends Application { 8 | } -------------------------------------------------------------------------------- /resteasy-spring-boot-starter-test/src/test/java/com/test/NonSpringBeanJaxrsApplication.java: -------------------------------------------------------------------------------- 1 | package com.test; 2 | 3 | import javax.ws.rs.ApplicationPath; 4 | import javax.ws.rs.core.Application; 5 | 6 | @ApplicationPath("sample-app-test") 7 | public class NonSpringBeanJaxrsApplication extends Application { 8 | } -------------------------------------------------------------------------------- /resteasy-spring-boot-starter-test/src/test/java/com/test/multicontexttest/Echo.java: -------------------------------------------------------------------------------- 1 | package com.test.multicontexttest; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | 6 | import javax.ws.rs.Consumes; 7 | import javax.ws.rs.POST; 8 | import javax.ws.rs.Path; 9 | import javax.ws.rs.Produces; 10 | import javax.ws.rs.core.MediaType; 11 | 12 | /** 13 | * Echo REST endpoint class 14 | * 15 | * Created by facarvalho on 12/7/15. 16 | */ 17 | @Path("/echo") 18 | @Component 19 | public class Echo { 20 | 21 | @Autowired 22 | private EchoMessageCreator echoer; 23 | 24 | /** 25 | * Receives a simple POST request message containing as payload 26 | * a text, in text plain format, to be echoed by the service. 27 | * It returns as response, in JSON, the text to be echoed plus a timestamp of the 28 | * moment the echo response was created on the server side 29 | * 30 | * @param echoText 31 | * @return 32 | */ 33 | @POST 34 | @Consumes({ MediaType.TEXT_PLAIN }) 35 | @Produces({ MediaType.APPLICATION_JSON }) 36 | public EchoMessage echo(String echoText) { 37 | return echoer.createEchoMessage(echoText); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter-test/src/test/java/com/test/multicontexttest/EchoMessage.java: -------------------------------------------------------------------------------- 1 | package com.test.multicontexttest; 2 | 3 | /** 4 | * A simple echo message, containing the text to be echoed 5 | * and timestamp of the moment the message was created 6 | * 7 | * @author facarvalho 8 | */ 9 | public class EchoMessage { 10 | 11 | private long timestamp; 12 | private String echoText; 13 | 14 | public EchoMessage(String echoText) { 15 | timestamp = System.currentTimeMillis(); 16 | this.echoText = echoText; 17 | } 18 | 19 | public long getTimestamp() { 20 | return timestamp; 21 | } 22 | 23 | public String getEchoText() { 24 | return echoText; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /resteasy-spring-boot-starter-test/src/test/java/com/test/multicontexttest/EchoMessageCreator.java: -------------------------------------------------------------------------------- 1 | package com.test.multicontexttest; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | /** 6 | * This bean creates {@link EchoMessage} objects but, 7 | * different than the original application it was copied from, 8 | * ignoring echo texts received as input 9 | * 10 | * @author facarvalho 11 | */ 12 | @Component 13 | public class EchoMessageCreator { 14 | 15 | public EchoMessage createEchoMessage(String echoText) { 16 | return new EchoMessage("I don't want to echo anything today"); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter-test/src/test/java/com/test/multicontexttest/JaxrsApplication.java: -------------------------------------------------------------------------------- 1 | package com.test.multicontexttest; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import javax.ws.rs.ApplicationPath; 6 | import javax.ws.rs.core.Application; 7 | 8 | /** 9 | * JAX-RS application 10 | * 11 | * Created by facarvalho on 12/7/15. 12 | */ 13 | @Component 14 | @ApplicationPath("/sample-app/") 15 | public class JaxrsApplication extends Application { 16 | } 17 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter-test/src/test/java/com/test/multicontexttest/MultiContextTestApp.java: -------------------------------------------------------------------------------- 1 | package com.test.multicontexttest; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.web.support.SpringBootServletInitializer; 6 | 7 | /** 8 | * SpringBoot entry point application 9 | * 10 | * Created by facarvalho on 12/7/15. 11 | */ 12 | @SpringBootApplication 13 | public class MultiContextTestApp extends SpringBootServletInitializer { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(MultiContextTestApp.class, args); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter-test/src/test/resources/application.yaml: -------------------------------------------------------------------------------- 1 | endpoints: 2 | shutdown: 3 | enabled: true 4 | management: 5 | security: 6 | enabled: false -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.paypal.springboot 7 | resteasy-spring-boot-starter 8 | 2.3.4-RELEASE 9 | ${project.artifactId} 10 | 11 | A Spring Boot starter for RESTEasy 12 | https://github.com/paypal/resteasy-spring-boot 13 | 14 | 15 | 16 | The Apache License, Version 2.0 17 | http://www.apache.org/licenses/LICENSE-2.0.txt 18 | 19 | 20 | 21 | 22 | Fabio Carvalho 23 | facarvalho@paypal.com 24 | PayPal 25 | http://www.paypal.com 26 | 27 | 28 | 29 | scm:git:git@github.com:paypal/resteasy-spring-boot.git 30 | scm:git:git@github.com:paypal/resteasy-spring-boot.git 31 | git@github.com:paypal/resteasy-spring-boot.git 32 | 33 | 34 | 35 | ossrh 36 | https://oss.sonatype.org/content/repositories/snapshots 37 | 38 | 39 | ossrh 40 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 41 | 42 | 43 | 44 | 45 | UTF-8 46 | UTF-8 47 | 1.5.8.RELEASE 48 | 3.1.4.Final 49 | 80 50 | 80 51 | 52 | 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-starter 57 | ${springboot.version} 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-starter-web 62 | ${springboot.version} 63 | 64 | 65 | org.jboss.resteasy 66 | resteasy-jaxrs 67 | ${resteasy.version} 68 | 69 | 70 | org.jboss.resteasy 71 | resteasy-servlet-initializer 72 | ${resteasy.version} 73 | 74 | 75 | org.jboss.resteasy 76 | resteasy-spring 77 | ${resteasy.version} 78 | 79 | 80 | org.jboss.resteasy 81 | resteasy-jettison-provider 82 | 83 | 84 | 85 | 86 | org.jboss.resteasy 87 | resteasy-jackson2-provider 88 | ${resteasy.version} 89 | 90 | 91 | javax.servlet 92 | javax.servlet-api 93 | 3.1.0 94 | 95 | 96 | 97 | 98 | 99 | org.springframework 100 | spring-test 101 | 4.1.0.RELEASE 102 | test 103 | 104 | 105 | org.testng 106 | testng 107 | 6.1.1 108 | test 109 | 110 | 111 | org.mockito 112 | mockito-core 113 | 1.10.19 114 | test 115 | 116 | 117 | org.powermock 118 | powermock-core 119 | 1.6.6 120 | test 121 | 122 | 123 | org.powermock 124 | powermock-api-mockito 125 | 1.6.5 126 | test 127 | 128 | 129 | org.powermock 130 | powermock-module-testng-common 131 | 1.6.5 132 | test 133 | 134 | 135 | org.powermock 136 | powermock-module-testng 137 | 1.6.5 138 | test 139 | 140 | 141 | org.powermock 142 | powermock-reflect 143 | 1.6.5 144 | test 145 | 146 | 147 | 148 | 149 | 150 | 151 | fortify 152 | 153 | 154 | 155 | 156 | com.fortify.ps.maven.plugin 157 | sca-maven-plugin 158 | 4.22 159 | 160 | 161 | 162 | 163 | 164 | ossrh 165 | 166 | 167 | 168 | org.apache.maven.plugins 169 | maven-source-plugin 170 | 171 | 172 | attach-sources 173 | 174 | jar 175 | 176 | 177 | 178 | 179 | 180 | org.apache.maven.plugins 181 | maven-javadoc-plugin 182 | 183 | 184 | attach-javadocs 185 | 186 | jar 187 | 188 | 189 | 190 | 191 | 192 | org.apache.maven.plugins 193 | maven-gpg-plugin 194 | 1.6 195 | 196 | 197 | sign-artifacts 198 | verify 199 | 200 | sign 201 | 202 | 203 | ${gpg.keyname} 204 | ${gpg.passphrase} 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | com.fortify.ps.maven.plugin 220 | sca-maven-plugin 221 | 222 | 1.8 223 | 1G 224 | 225 | 226 | 227 | sca-clean 228 | 229 | clean 230 | 231 | 232 | 233 | sca-translate-exec 234 | 235 | translate 236 | 237 | 238 | 239 | 4.22 240 | 241 | 242 | 243 | 244 | 245 | 246 | org.codehaus.mojo 247 | cobertura-maven-plugin 248 | 2.7 249 | 250 | 251 | ${coverage.line} 252 | ${coverage.line} 253 | ${coverage.line} 254 | ${coverage.branch} 255 | ${coverage.branch} 256 | ${coverage.branch} 257 | true 258 | 259 | 260 | com.paypal.springboot.resteasy.JaxrsApplicationScanner 261 | 80 262 | 65 263 | 264 | 265 | 266 | 267 | html 268 | xml 269 | 270 | 271 | 272 | 273 | package 274 | 275 | clean 276 | cobertura 277 | check 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | org.codehaus.mojo 289 | cobertura-maven-plugin 290 | 2.7 291 | 292 | 293 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ossrh 6 | ${env.SONATYPE_USER} 7 | ${env.SONATYPE_PASSWORD} 8 | 9 | 10 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/main/java/com/paypal/springboot/resteasy/JaxrsApplicationScanner.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.config.BeanDefinition; 6 | import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; 7 | import org.springframework.core.type.filter.AssignableTypeFilter; 8 | import org.springframework.util.ClassUtils; 9 | 10 | import javax.ws.rs.core.Application; 11 | import java.util.HashSet; 12 | import java.util.List; 13 | import java.util.Set; 14 | 15 | /** 16 | * Helper class to scan the classpath under the specified packages 17 | * searching for JAX-RS Application sub-classes 18 | * 19 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 20 | */ 21 | public abstract class JaxrsApplicationScanner { 22 | 23 | private static final Logger logger = LoggerFactory.getLogger(JaxrsApplicationScanner.class); 24 | 25 | private static Set> applications; 26 | 27 | public static Set> getApplications(List packagesToBeScanned) { 28 | if(applications == null) { 29 | applications = findJaxrsApplicationClasses(packagesToBeScanned); 30 | } 31 | 32 | return applications; 33 | } 34 | 35 | /* 36 | * Scan the classpath under the specified packages looking for JAX-RS Application sub-classes 37 | */ 38 | private static Set> findJaxrsApplicationClasses(List packagesToBeScanned) { 39 | logger.info("Scanning classpath to find JAX-RS Application classes"); 40 | 41 | ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); 42 | scanner.addIncludeFilter(new AssignableTypeFilter(Application.class)); 43 | 44 | Set candidates = new HashSet(); 45 | Set candidatesSubSet; 46 | 47 | for (String packageToScan : packagesToBeScanned) { 48 | candidatesSubSet = scanner.findCandidateComponents(packageToScan); 49 | candidates.addAll(candidatesSubSet); 50 | } 51 | 52 | Set> classes = new HashSet>(); 53 | ClassLoader classLoader = JaxrsApplicationScanner.class.getClassLoader(); 54 | Class type; 55 | for (BeanDefinition candidate : candidates) { 56 | try { 57 | type = (Class) ClassUtils.forName(candidate.getBeanClassName(), classLoader); 58 | classes.add(type); 59 | } catch (ClassNotFoundException e) { 60 | logger.error("JAX-RS Application subclass could not be loaded", e); 61 | } 62 | } 63 | 64 | // We don't want the JAX-RS Application class itself in there 65 | classes.remove(Application.class); 66 | 67 | return classes; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/main/java/com/paypal/springboot/resteasy/ResteasyApplicationBuilder.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy; 2 | 3 | import java.util.Set; 4 | 5 | import javax.servlet.Servlet; 6 | import javax.servlet.ServletContainerInitializer; 7 | 8 | import org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher; 9 | import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters; 10 | import org.jboss.resteasy.plugins.servlet.ResteasyServletInitializer; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 14 | 15 | /** 16 | * This class is the Spring Boot equivalent of {@link ResteasyServletInitializer}, 17 | * which implements the Servlet API {@link ServletContainerInitializer} interface 18 | * to find all JAX-RS Application, Provider and Path classes in the classpath. 19 | * 20 | * As we all know, in Spring Boot we use an embedded servlet container. However, 21 | * the Servlet spec does not support embedded containers, and many portions of it 22 | * do not apply to embedded containers, and ServletContainerInitializer is one of them. 23 | * 24 | * This class fills in this gap. 25 | * 26 | * Notice that the JAX-RS Application classes are found in this RESTEasy starter by class 27 | * ResteasyEmbeddedServletInitializer, and that is done by scanning the classpath. 28 | * 29 | * The Path and Provider annotated classes are found by using Spring framework (instead of 30 | * scanning the classpath), since it is assumed those classes are ALWAYS necessarily 31 | * Spring beans (this starter is meant for Spring Boot applications that use RESTEasy 32 | * as the JAX-RS implementation) 33 | * 34 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 35 | */ 36 | public class ResteasyApplicationBuilder { 37 | 38 | public static final String BEAN_NAME = "JaxrsApplicationServletBuilder"; 39 | 40 | private static final Logger logger = LoggerFactory.getLogger(ResteasyApplicationBuilder.class); 41 | 42 | public ServletRegistrationBean build(String applicationClassName, String path, Set> resources, Set> providers) { 43 | Servlet servlet = new HttpServlet30Dispatcher(); 44 | 45 | ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(servlet); 46 | 47 | servletRegistrationBean.setName(applicationClassName); 48 | servletRegistrationBean.setLoadOnStartup(1); 49 | servletRegistrationBean.setAsyncSupported(true); 50 | servletRegistrationBean.addInitParameter("javax.ws.rs.Application", applicationClassName); 51 | 52 | if (path != null) { 53 | String mapping = path; 54 | if (!mapping.startsWith("/")) 55 | mapping = "/" + mapping; 56 | String prefix = mapping; 57 | if (!"/".equals(prefix) && prefix.endsWith("/")) 58 | prefix = prefix.substring(0, prefix.length() - 1); 59 | if (mapping.endsWith("/")) 60 | mapping += "*"; 61 | else 62 | mapping += "/*"; 63 | // resteasy.servlet.mapping.prefix 64 | servletRegistrationBean.addInitParameter("resteasy.servlet.mapping.prefix", prefix); 65 | servletRegistrationBean.addUrlMappings(mapping); 66 | } 67 | 68 | if (resources.size() > 0) { 69 | StringBuilder builder = new StringBuilder(); 70 | boolean first = true; 71 | for (Class resource : resources) { 72 | if (first) { 73 | first = false; 74 | } else { 75 | builder.append(","); 76 | } 77 | 78 | builder.append(resource.getName()); 79 | } 80 | servletRegistrationBean.addInitParameter(ResteasyContextParameters.RESTEASY_SCANNED_RESOURCES, builder.toString()); 81 | } 82 | if (providers.size() > 0) { 83 | StringBuilder builder = new StringBuilder(); 84 | boolean first = true; 85 | for (Class provider : providers) { 86 | if (first) { 87 | first = false; 88 | } else { 89 | builder.append(","); 90 | } 91 | builder.append(provider.getName()); 92 | } 93 | servletRegistrationBean.addInitParameter(ResteasyContextParameters.RESTEASY_SCANNED_PROVIDERS, builder.toString()); 94 | } 95 | 96 | logger.debug("ServletRegistrationBean has just bean created for JAX-RS class " + applicationClassName); 97 | 98 | return servletRegistrationBean; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/main/java/com/paypal/springboot/resteasy/ResteasyAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy; 2 | 3 | import org.jboss.resteasy.core.AsynchronousDispatcher; 4 | import org.jboss.resteasy.core.Dispatcher; 5 | import org.jboss.resteasy.core.ResourceMethodRegistry; 6 | import org.jboss.resteasy.core.SynchronousDispatcher; 7 | import org.jboss.resteasy.plugins.server.servlet.ListenerBootstrap; 8 | import org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap; 9 | import org.jboss.resteasy.plugins.spring.SpringBeanProcessor; 10 | import org.jboss.resteasy.spi.Registry; 11 | import org.jboss.resteasy.spi.ResteasyDeployment; 12 | import org.jboss.resteasy.spi.ResteasyProviderFactory; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.beans.factory.annotation.Qualifier; 16 | import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 17 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 18 | import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; 19 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Configuration; 22 | 23 | import javax.servlet.ServletContext; 24 | import javax.servlet.ServletContextEvent; 25 | import javax.servlet.ServletContextListener; 26 | 27 | /** 28 | * This is the main class that configures this Resteasy Sring Boot starter 29 | * 30 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 31 | */ 32 | @Configuration 33 | @AutoConfigureAfter(WebMvcAutoConfiguration.class) 34 | @EnableConfigurationProperties 35 | public class ResteasyAutoConfiguration { 36 | 37 | private static Logger logger = LoggerFactory.getLogger(ResteasyAutoConfiguration.class); 38 | 39 | @Bean 40 | @Qualifier("ResteasyProviderFactory") 41 | public static BeanFactoryPostProcessor springBeanProcessor() { 42 | ResteasyProviderFactory resteasyProviderFactory = new ResteasyProviderFactory(); 43 | ResourceMethodRegistry resourceMethodRegistry = new ResourceMethodRegistry(resteasyProviderFactory); 44 | 45 | SpringBeanProcessor springBeanProcessor = new SpringBeanProcessor(); 46 | springBeanProcessor.setProviderFactory(resteasyProviderFactory); 47 | springBeanProcessor.setRegistry(resourceMethodRegistry); 48 | 49 | logger.debug("SpringBeanProcessor has been created"); 50 | 51 | return springBeanProcessor; 52 | } 53 | 54 | /** 55 | * This is a modified version of {@link ResteasyBootstrap} 56 | * 57 | * @return a ServletContextListener object that configures and start a ResteasyDeployment 58 | */ 59 | @Bean 60 | public ServletContextListener resteasyBootstrapListener(@Qualifier("ResteasyProviderFactory") final BeanFactoryPostProcessor beanFactoryPostProcessor) { 61 | ServletContextListener servletContextListener = new ServletContextListener() { 62 | 63 | private SpringBeanProcessor springBeanProcessor = (SpringBeanProcessor) beanFactoryPostProcessor; 64 | 65 | protected ResteasyDeployment deployment; 66 | 67 | public void contextInitialized(ServletContextEvent sce) { 68 | ServletContext servletContext = sce.getServletContext(); 69 | 70 | ListenerBootstrap config = new ListenerBootstrap(servletContext); 71 | 72 | ResteasyProviderFactory resteasyProviderFactory = springBeanProcessor.getProviderFactory(); 73 | ResourceMethodRegistry resourceMethodRegistry = (ResourceMethodRegistry) springBeanProcessor.getRegistry(); 74 | 75 | deployment = config.createDeployment(); 76 | 77 | deployment.setProviderFactory(resteasyProviderFactory); 78 | deployment.setRegistry(resourceMethodRegistry); 79 | 80 | if (deployment.isAsyncJobServiceEnabled()) { 81 | AsynchronousDispatcher dispatcher = new AsynchronousDispatcher(resteasyProviderFactory, resourceMethodRegistry); 82 | deployment.setDispatcher(dispatcher); 83 | } else { 84 | SynchronousDispatcher dispatcher = new SynchronousDispatcher(resteasyProviderFactory, resourceMethodRegistry); 85 | deployment.setDispatcher(dispatcher); 86 | } 87 | 88 | deployment.start(); 89 | 90 | servletContext.setAttribute(ResteasyProviderFactory.class.getName(), deployment.getProviderFactory()); 91 | servletContext.setAttribute(Dispatcher.class.getName(), deployment.getDispatcher()); 92 | servletContext.setAttribute(Registry.class.getName(), deployment.getRegistry()); 93 | } 94 | 95 | public void contextDestroyed(ServletContextEvent sce) { 96 | if (deployment != null) { 97 | deployment.stop(); 98 | } 99 | } 100 | }; 101 | 102 | logger.debug("ServletContextListener has been created"); 103 | 104 | return servletContextListener; 105 | } 106 | 107 | @Bean(name = ResteasyApplicationBuilder.BEAN_NAME) 108 | public ResteasyApplicationBuilder resteasyApplicationBuilder() { 109 | return new ResteasyApplicationBuilder(); 110 | } 111 | 112 | @Bean 113 | public static ResteasyEmbeddedServletInitializer resteasyEmbeddedServletInitializer() { 114 | return new ResteasyEmbeddedServletInitializer(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/main/java/com/paypal/springboot/resteasy/ResteasyEmbeddedServletInitializer.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy; 2 | 3 | import org.jboss.resteasy.plugins.servlet.ResteasyServletInitializer; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.BeansException; 7 | import org.springframework.beans.factory.BeanFactory; 8 | import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 9 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 10 | import org.springframework.beans.factory.config.ConstructorArgumentValues; 11 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 12 | import org.springframework.beans.factory.support.GenericBeanDefinition; 13 | import org.springframework.core.annotation.AnnotationUtils; 14 | import org.springframework.core.env.ConfigurableEnvironment; 15 | 16 | import javax.ws.rs.ApplicationPath; 17 | import javax.ws.rs.Path; 18 | import javax.ws.rs.core.Application; 19 | import javax.ws.rs.ext.Provider; 20 | import java.util.*; 21 | 22 | /** 23 | * This is a Spring version of {@link ResteasyServletInitializer}. 24 | * It does not register the servlets though, that is done by {@link ResteasyApplicationBuilder} 25 | * It only finds the JAX-RS Application classes (by scanning the classpath), and 26 | * the JAX-RS Path and Provider annotated Spring beans, and then register the 27 | * Spring bean definitions that represent each servlet registration. 28 | * 29 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 30 | */ 31 | public class ResteasyEmbeddedServletInitializer implements BeanFactoryPostProcessor { 32 | 33 | private static final String JAXRS_APP_CLASSES_DEFINITION_PROPERTY = "resteasy.jaxrs.app.registration"; 34 | private static final String JAXRS_APP_CLASSES_PROPERTY = "resteasy.jaxrs.app.classes"; 35 | 36 | // This is how JAXRS_APP_CLASSES_PROPERTY was named originally. It conflicted with "resteasy.jaxrs.app.registration" 37 | // in case of YAML files, since registration was a child of app from an YAML perspective, which is not allowed. 38 | // Because of that its name was changed (the ".classes" suffix was added). 39 | // This legacy property has not been removed though, to keep backward compatibility, but it is marked as deprecated. It will be 40 | // available only for .properties files, but not for YAML files. It should be finally removed in a future major release. 41 | private static final String JAXRS_APP_CLASSES_PROPERTY_LEGACY = "resteasy.jaxrs.app"; 42 | 43 | private Set> applications = new HashSet>(); 44 | private Set> allResources = new HashSet>(); 45 | private Set> providers = new HashSet>(); 46 | 47 | private static final Logger logger = LoggerFactory.getLogger(ResteasyEmbeddedServletInitializer.class); 48 | 49 | private enum JaxrsAppClassesRegistration { 50 | BEANS, PROPERTY, SCANNING, AUTO 51 | } 52 | 53 | /* 54 | * Find the JAX-RS application classes. 55 | * This is done by one of these three options in this order: 56 | * 57 | * 1- By having them defined as Spring beans 58 | * 2- By setting property {@code resteasy.jaxrs.app.classes} via Spring Boot application properties file. 59 | * This property should contain a comma separated list of JAX-RS sub-classes 60 | * 3- Via classpath scanning (looking for javax.ws.rs.core.Application sub-classes) 61 | * 62 | * First try to find JAX-RS Application sub-classes defined as Spring beans. If that is existent, 63 | * the search stops, and those are the only JAX-RS applications to be registered. 64 | * If no JAX-RS application Spring beans are found, then see if Spring Boot property {@code resteasy.jaxrs.app.classes} 65 | * has been set. If it has, the search stops, and those are the only JAX-RS applications to be registered. 66 | * If not, then scan the classpath searching for JAX-RS applications. 67 | * 68 | * There is a way though to force one of the options above, which is by setting property 69 | * {@code resteasy.jaxrs.app.registration} via Spring Boot application properties file. The possible valid 70 | * values are {@code beans}, {@code property}, {@code scanning} or {@code auto}. If this property is not 71 | * present, the default value is {@code auto}, which means every approach will be tried in the order and way 72 | * explained earlier. 73 | * 74 | * @param beanFactory 75 | */ 76 | private void findJaxrsApplications(ConfigurableListableBeanFactory beanFactory) { 77 | logger.info("Finding JAX-RS Application classes"); 78 | 79 | JaxrsAppClassesRegistration registration = getJaxrsAppClassesRegistration(beanFactory); 80 | 81 | switch (registration) { 82 | case AUTO: 83 | findJaxrsApplicationBeans(beanFactory); 84 | if(applications.size() == 0) findJaxrsApplicationProperty(beanFactory); 85 | if(applications.size() == 0) findJaxrsApplicationScanning(beanFactory); 86 | break; 87 | case BEANS: 88 | findJaxrsApplicationBeans(beanFactory); 89 | break; 90 | case PROPERTY: 91 | findJaxrsApplicationProperty(beanFactory); 92 | break; 93 | case SCANNING: 94 | // TODO 95 | // Remove this warning after implementing 96 | // https://github.com/paypal/resteasy-spring-boot/issues/69 97 | logger.warn("\n-------------\nStarting on version 3.0.0, the behavior of the `scanning` JAX-RS Application subclass registration method will change, being more restrictive.\nInstead 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.\nHaving 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.\n-------------"); 98 | findJaxrsApplicationScanning(beanFactory); 99 | break; 100 | default: 101 | logger.error("JAX-RS application registration method (%s) not known, no application will be registered", registration.name()); 102 | break; 103 | } 104 | 105 | for (Object appClass : applications.toArray()) { 106 | logger.info("JAX-RS Application class found: {}", ((Class) appClass).getName()); 107 | } 108 | } 109 | 110 | private JaxrsAppClassesRegistration getJaxrsAppClassesRegistration(ConfigurableListableBeanFactory beanFactory) { 111 | ConfigurableEnvironment configurableEnvironment = beanFactory.getBean(ConfigurableEnvironment.class); 112 | String jaxrsAppClassesRegistration = configurableEnvironment.getProperty(JAXRS_APP_CLASSES_DEFINITION_PROPERTY); 113 | JaxrsAppClassesRegistration registration = JaxrsAppClassesRegistration.AUTO; 114 | 115 | if(jaxrsAppClassesRegistration == null) { 116 | logger.info("Property {} has not been set, JAX-RS Application classes registration is being set to AUTO", JAXRS_APP_CLASSES_DEFINITION_PROPERTY); 117 | } else { 118 | logger.info("Property {} has been set to {}", JAXRS_APP_CLASSES_DEFINITION_PROPERTY, jaxrsAppClassesRegistration); 119 | try { 120 | registration = JaxrsAppClassesRegistration.valueOf(jaxrsAppClassesRegistration.toUpperCase()); 121 | } catch(IllegalArgumentException ex) { 122 | String errorMesage = String.format("Property %s has not been properly set, value %s is invalid. JAX-RS Application classes registration is being set to AUTO.", JAXRS_APP_CLASSES_DEFINITION_PROPERTY, jaxrsAppClassesRegistration); 123 | logger.error(errorMesage); 124 | throw new IllegalArgumentException(errorMesage, ex); 125 | } 126 | } 127 | 128 | return registration; 129 | } 130 | 131 | /* 132 | * Find JAX-RS application classes by searching for their related 133 | * Spring beans 134 | * 135 | * @param beanFactory 136 | */ 137 | private void findJaxrsApplicationBeans(ConfigurableListableBeanFactory beanFactory) { 138 | logger.info("Searching for JAX-RS Application Spring beans"); 139 | 140 | Map applicationBeans = beanFactory.getBeansOfType(Application.class, true, false); 141 | if(applicationBeans == null || applicationBeans.size() == 0) { 142 | logger.info("No JAX-RS Application Spring beans found"); 143 | return; 144 | } 145 | 146 | for (Application application : applicationBeans.values()) { 147 | applications.add(application.getClass()); 148 | } 149 | } 150 | 151 | /* 152 | * Find JAX-RS application classes via property {@code resteasy.jaxrs.app.classes} 153 | */ 154 | private void findJaxrsApplicationProperty(ConfigurableListableBeanFactory beanFactory) { 155 | ConfigurableEnvironment configurableEnvironment = beanFactory.getBean(ConfigurableEnvironment.class); 156 | String jaxrsAppsProperty = configurableEnvironment.getProperty(JAXRS_APP_CLASSES_PROPERTY); 157 | if(jaxrsAppsProperty == null) { 158 | jaxrsAppsProperty = configurableEnvironment.getProperty(JAXRS_APP_CLASSES_PROPERTY_LEGACY); 159 | if(jaxrsAppsProperty == null) { 160 | logger.info("No JAX-RS Application set via property {}", JAXRS_APP_CLASSES_PROPERTY); 161 | return; 162 | } 163 | logger.warn("Property {} has been set. Notice that this property has been deprecated and will be removed soon. Please replace it by property {}", JAXRS_APP_CLASSES_PROPERTY_LEGACY, JAXRS_APP_CLASSES_PROPERTY); 164 | } else { 165 | logger.info("Property {} has been set to {}", JAXRS_APP_CLASSES_PROPERTY, jaxrsAppsProperty); 166 | } 167 | 168 | String[] jaxrsClassNames = jaxrsAppsProperty.split(","); 169 | 170 | for(String jaxrsClassName : jaxrsClassNames) { 171 | Class jaxrsClass = null; 172 | try { 173 | jaxrsClass = (Class) Class.forName(jaxrsClassName.trim()); 174 | } catch (ClassNotFoundException e) { 175 | String exceptionMessage = String.format("JAX-RS Application class %s has not been found", jaxrsClassName.trim()); 176 | logger.error(exceptionMessage, e); 177 | throw new BeansException(exceptionMessage, e){}; 178 | } 179 | applications.add(jaxrsClass); 180 | } 181 | } 182 | 183 | /* 184 | * Find JAX-RS application classes by scanning the classpath under 185 | * packages already marked to be scanned by Spring framework 186 | */ 187 | private void findJaxrsApplicationScanning(BeanFactory beanFactory) { 188 | List packagesToBeScanned = getSpringApplicationPackages(beanFactory); 189 | 190 | Set> applications = JaxrsApplicationScanner.getApplications(packagesToBeScanned); 191 | if(applications == null || applications.size() == 0) { 192 | return; 193 | } 194 | this.applications.addAll(applications); 195 | } 196 | 197 | /* 198 | * Return the name of the packages to be scanned by Spring framework 199 | */ 200 | private List getSpringApplicationPackages(BeanFactory beanFactory) { 201 | // TODO 202 | // See https://github.com/paypal/resteasy-spring-boot/issues/69 203 | 204 | List packages = new ArrayList(); 205 | packages.add(""); 206 | return packages; 207 | } 208 | 209 | /* 210 | * Search for JAX-RS resource and provider Spring beans, 211 | * which are the ones whose classes are annotated with 212 | * {@link Path} or {@link Provider} respectively 213 | * 214 | * @param beanFactory 215 | */ 216 | private void findJaxrsResourcesAndProviderClasses(ConfigurableListableBeanFactory beanFactory) { 217 | logger.debug("Finding JAX-RS resources and providers Spring bean classes"); 218 | 219 | String[] resourceBeans = beanFactory.getBeanNamesForAnnotation(Path.class); 220 | String[] providerBeans = beanFactory.getBeanNamesForAnnotation(Provider.class); 221 | 222 | if(resourceBeans != null) { 223 | for(String resourceBean : resourceBeans) { 224 | allResources.add(beanFactory.getType(resourceBean)); 225 | } 226 | } 227 | 228 | if(providerBeans != null) { 229 | for(String providerBean : providerBeans) { 230 | providers.add(beanFactory.getType(providerBean)); 231 | } 232 | } 233 | 234 | if(logger.isDebugEnabled()) { 235 | for (Object resourceClass : allResources.toArray()) { 236 | logger.debug("JAX-RS resource class found: {}", ((Class) resourceClass).getName()); 237 | } 238 | } 239 | if(logger.isDebugEnabled()) { 240 | for (Object providerClass: providers.toArray()) { 241 | logger.debug("JAX-RS provider class found: {}", ((Class) providerClass).getName()); 242 | } 243 | } 244 | } 245 | 246 | public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 247 | logger.debug("Post process bean factory has been called"); 248 | 249 | findJaxrsApplications(beanFactory); 250 | 251 | // This is done by finding their related Spring beans 252 | findJaxrsResourcesAndProviderClasses(beanFactory); 253 | 254 | if (allResources.size() == 0) { 255 | logger.warn("No JAX-RS resource Spring beans have been found"); 256 | } 257 | if (applications.size() == 0) { 258 | logger.info("No JAX-RS Application classes have been found. A default, one mapped to '/', will be registered."); 259 | registerDefaultJaxrsApp(beanFactory); 260 | return; 261 | } 262 | 263 | BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; 264 | 265 | for (Class applicationClass : applications) { 266 | ApplicationPath path = AnnotationUtils.findAnnotation(applicationClass, ApplicationPath.class); 267 | if (path == null) { 268 | logger.warn("JAX-RS Application class {} has no ApplicationPath annotation, so it will not be registered", applicationClass.getName()); 269 | continue; 270 | } 271 | 272 | logger.debug("registering JAX-RS application class " + applicationClass.getName()); 273 | 274 | GenericBeanDefinition applicationServletBean = createApplicationServlet(applicationClass, path.value()); 275 | registry.registerBeanDefinition(applicationClass.getName(), applicationServletBean); 276 | } 277 | 278 | } 279 | 280 | /** 281 | * Register a default JAX-RS application, in case no other is present in the application. 282 | * Read section 2.3.2 in JAX-RS 2.0 specification. 283 | * 284 | * @param beanFactory 285 | */ 286 | private void registerDefaultJaxrsApp(ConfigurableListableBeanFactory beanFactory) { 287 | BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; 288 | GenericBeanDefinition applicationServletBean = createApplicationServlet(Application.class, "/"); 289 | registry.registerBeanDefinition(Application.class.getName(), applicationServletBean); 290 | } 291 | 292 | /** 293 | * Creates a Servlet bean definition for the given JAX-RS application 294 | * 295 | * @param applicationClass 296 | * @param path 297 | * @return a Servlet bean definition for the given JAX-RS application 298 | */ 299 | private GenericBeanDefinition createApplicationServlet(Class applicationClass, String path) { 300 | GenericBeanDefinition applicationServletBean = new GenericBeanDefinition(); 301 | applicationServletBean.setFactoryBeanName(ResteasyApplicationBuilder.BEAN_NAME); 302 | applicationServletBean.setFactoryMethodName("build"); 303 | 304 | Set> resources = allResources; 305 | 306 | ConstructorArgumentValues values = new ConstructorArgumentValues(); 307 | values.addIndexedArgumentValue(0, applicationClass.getName()); 308 | values.addIndexedArgumentValue(1, path); 309 | values.addIndexedArgumentValue(2, resources); 310 | values.addIndexedArgumentValue(3, providers); 311 | applicationServletBean.setConstructorArgumentValues(values); 312 | 313 | applicationServletBean.setAutowireCandidate(false); 314 | applicationServletBean.setScope("singleton"); 315 | 316 | return applicationServletBean; 317 | } 318 | 319 | } 320 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.paypal.springboot.resteasy.ResteasyAutoConfiguration -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/test/java/com/paypal/springboot/resteasy/JaxrsAppRegistrationTest.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy; 2 | 3 | import com.paypal.springboot.resteasy.sample.*; 4 | import org.mockito.internal.verification.VerificationModeFactory; 5 | import org.powermock.api.mockito.PowerMockito; 6 | import org.powermock.api.mockito.mockpolicies.Slf4jMockPolicy; 7 | import org.powermock.core.classloader.annotations.MockPolicy; 8 | import org.powermock.core.classloader.annotations.PrepareForTest; 9 | import org.powermock.modules.testng.PowerMockTestCase; 10 | import org.springframework.beans.BeansException; 11 | import org.springframework.beans.factory.BeanFactory; 12 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 13 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 14 | import org.springframework.beans.factory.support.GenericBeanDefinition; 15 | import org.springframework.boot.autoconfigure.AutoConfigurationPackages; 16 | import org.springframework.core.env.ConfigurableEnvironment; 17 | import org.testng.annotations.BeforeMethod; 18 | import org.testng.annotations.Test; 19 | 20 | import javax.ws.rs.Path; 21 | import javax.ws.rs.core.Application; 22 | import javax.ws.rs.ext.Provider; 23 | import java.util.*; 24 | 25 | import static org.mockito.Mockito.*; 26 | 27 | /** 28 | * Created by facarvalho on 7/19/16. 29 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 30 | */ 31 | @PrepareForTest(AutoConfigurationPackages.class) 32 | @MockPolicy(Slf4jMockPolicy.class) 33 | public class JaxrsAppRegistrationTest extends PowerMockTestCase { 34 | 35 | private static final String DEFINITION_PROPERTY = "resteasy.jaxrs.app.registration"; 36 | private static final String APP_CLASSES_PROPERTY = "resteasy.jaxrs.app.classes"; 37 | private static final String APP_CLASSES_PROPERTY_LEGACY = "resteasy.jaxrs.app"; 38 | 39 | private static Set allPossibleAppClasses; 40 | 41 | static { 42 | Set _allPossibleAppClasses = new HashSet(); 43 | 44 | _allPossibleAppClasses.add(TestApplication1.class); 45 | _allPossibleAppClasses.add(TestApplication2.class); 46 | _allPossibleAppClasses.add(TestApplication3.class); 47 | _allPossibleAppClasses.add(TestApplication4.class); 48 | _allPossibleAppClasses.add(TestApplication5.class); 49 | _allPossibleAppClasses.add(Application.class); 50 | 51 | allPossibleAppClasses = Collections.unmodifiableSet(_allPossibleAppClasses); 52 | } 53 | 54 | @BeforeMethod 55 | public void beforeTest() { 56 | PowerMockito.mockStatic(AutoConfigurationPackages.class); 57 | List packages = new ArrayList(); 58 | packages.add("com.paypal.springboot.resteasy.sample"); 59 | PowerMockito.when(AutoConfigurationPackages.get(any(BeanFactory.class))).thenReturn(packages); 60 | } 61 | 62 | @Test 63 | public void nullTest() { 64 | ConfigurableEnvironment configurableEnvironmentMock = mock(ConfigurableEnvironment.class); 65 | when(configurableEnvironmentMock.getProperty(DEFINITION_PROPERTY)).thenReturn(null); 66 | 67 | Set expectedRegisteredAppClasses = new HashSet(); 68 | expectedRegisteredAppClasses.add(TestApplication1.class); 69 | expectedRegisteredAppClasses.add(TestApplication2.class); 70 | expectedRegisteredAppClasses.add(TestApplication4.class); 71 | expectedRegisteredAppClasses.add(TestApplication5.class); 72 | 73 | test(configurableEnvironmentMock, expectedRegisteredAppClasses); 74 | } 75 | 76 | @Test 77 | public void autoTest() { 78 | ConfigurableEnvironment configurableEnvironmentMock = mock(ConfigurableEnvironment.class); 79 | when(configurableEnvironmentMock.getProperty(DEFINITION_PROPERTY)).thenReturn("auto"); 80 | 81 | Set expectedRegisteredAppClasses = new HashSet(); 82 | expectedRegisteredAppClasses.add(TestApplication1.class); 83 | expectedRegisteredAppClasses.add(TestApplication2.class); 84 | expectedRegisteredAppClasses.add(TestApplication4.class); 85 | expectedRegisteredAppClasses.add(TestApplication5.class); 86 | 87 | test(configurableEnvironmentMock, expectedRegisteredAppClasses); 88 | } 89 | 90 | @Test 91 | public void beansTest() { 92 | ConfigurableEnvironment configurableEnvironmentMock = mock(ConfigurableEnvironment.class); 93 | when(configurableEnvironmentMock.getProperty(DEFINITION_PROPERTY)).thenReturn("beans"); 94 | 95 | Set expectedRegisteredAppClasses = new HashSet(); 96 | expectedRegisteredAppClasses.add(TestApplication1.class); 97 | expectedRegisteredAppClasses.add(TestApplication4.class); 98 | 99 | test(configurableEnvironmentMock, expectedRegisteredAppClasses); 100 | } 101 | 102 | @Test 103 | public void propertyTest() { 104 | ConfigurableEnvironment configurableEnvironmentMock = mock(ConfigurableEnvironment.class); 105 | when(configurableEnvironmentMock.getProperty(DEFINITION_PROPERTY)).thenReturn("property"); 106 | when(configurableEnvironmentMock.getProperty(APP_CLASSES_PROPERTY)).thenReturn("com.paypal.springboot.resteasy.sample.TestApplication3, com.paypal.springboot.resteasy.sample.TestApplication4,com.paypal.springboot.resteasy.sample.TestApplication2"); 107 | 108 | Set expectedRegisteredAppClasses = new HashSet(); 109 | expectedRegisteredAppClasses.add(TestApplication2.class); 110 | expectedRegisteredAppClasses.add(TestApplication4.class); 111 | 112 | test(configurableEnvironmentMock, expectedRegisteredAppClasses); 113 | } 114 | 115 | @Test 116 | public void legacyPropertyTest() { 117 | ConfigurableEnvironment configurableEnvironmentMock = mock(ConfigurableEnvironment.class); 118 | when(configurableEnvironmentMock.getProperty(DEFINITION_PROPERTY)).thenReturn("property"); 119 | when(configurableEnvironmentMock.getProperty(APP_CLASSES_PROPERTY_LEGACY)).thenReturn("com.paypal.springboot.resteasy.sample.TestApplication3, com.paypal.springboot.resteasy.sample.TestApplication4,com.paypal.springboot.resteasy.sample.TestApplication2"); 120 | 121 | Set expectedRegisteredAppClasses = new HashSet(); 122 | expectedRegisteredAppClasses.add(TestApplication2.class); 123 | expectedRegisteredAppClasses.add(TestApplication4.class); 124 | 125 | test(configurableEnvironmentMock, expectedRegisteredAppClasses); 126 | } 127 | 128 | @Test 129 | public void scanningTest() { 130 | ConfigurableEnvironment configurableEnvironmentMock = mock(ConfigurableEnvironment.class); 131 | when(configurableEnvironmentMock.getProperty(DEFINITION_PROPERTY)).thenReturn("scanning"); 132 | 133 | Set expectedRegisteredAppClasses = new HashSet(); 134 | expectedRegisteredAppClasses.add(TestApplication1.class); 135 | expectedRegisteredAppClasses.add(TestApplication2.class); 136 | expectedRegisteredAppClasses.add(TestApplication4.class); 137 | expectedRegisteredAppClasses.add(TestApplication5.class); 138 | 139 | test(configurableEnvironmentMock, expectedRegisteredAppClasses); 140 | } 141 | 142 | @Test(expectedExceptions = IllegalArgumentException.class, 143 | expectedExceptionsMessageRegExp = "Property " + DEFINITION_PROPERTY + 144 | " has not been properly set, value blah is invalid. JAX-RS Application classes registration is being set to AUTO.") 145 | public void invalidRegistrationTest() { 146 | ConfigurableEnvironment configurableEnvironmentMock = mock(ConfigurableEnvironment.class); 147 | when(configurableEnvironmentMock.getProperty(DEFINITION_PROPERTY)).thenReturn("blah"); 148 | 149 | ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class); 150 | when(beanFactory.getBean(ConfigurableEnvironment.class)).thenReturn(configurableEnvironmentMock); 151 | 152 | ResteasyEmbeddedServletInitializer resteasyEmbeddedServletInitializer = new ResteasyEmbeddedServletInitializer(); 153 | resteasyEmbeddedServletInitializer.postProcessBeanFactory(beanFactory); 154 | } 155 | 156 | @Test(expectedExceptions = BeansException.class) 157 | public void classNotFoundTest() { 158 | ConfigurableEnvironment configurableEnvironmentMock = mock(ConfigurableEnvironment.class); 159 | when(configurableEnvironmentMock.getProperty(DEFINITION_PROPERTY)).thenReturn("property"); 160 | when(configurableEnvironmentMock.getProperty(APP_CLASSES_PROPERTY)).thenReturn("com.paypal.springboot.resteasy.sample.TestApplication3, com.paypal.springboot.resteasy.sample.TestApplication4,com.paypal.springboot.resteasy.sample.TestApplication9"); 161 | 162 | ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class); 163 | when(beanFactory.getBean(ConfigurableEnvironment.class)).thenReturn(configurableEnvironmentMock); 164 | 165 | ResteasyEmbeddedServletInitializer resteasyEmbeddedServletInitializer = new ResteasyEmbeddedServletInitializer(); 166 | resteasyEmbeddedServletInitializer.postProcessBeanFactory(beanFactory); 167 | } 168 | 169 | @Test 170 | public void testPropertyNoApps() { 171 | ConfigurableEnvironment configurableEnvironmentMock = mock(ConfigurableEnvironment.class); 172 | when(configurableEnvironmentMock.getProperty(DEFINITION_PROPERTY)).thenReturn("property"); 173 | 174 | Set expectedRegisteredAppClasses = new HashSet(); 175 | expectedRegisteredAppClasses.add(Application.class); 176 | 177 | test(configurableEnvironmentMock, expectedRegisteredAppClasses); 178 | } 179 | 180 | private void test(ConfigurableEnvironment envMock, Set expectedRegisteredAppClasses) { 181 | ConfigurableListableBeanFactory beanFactory = prepareTest(envMock); 182 | performTest(envMock, beanFactory, expectedRegisteredAppClasses); 183 | } 184 | 185 | private ConfigurableListableBeanFactory prepareTest(ConfigurableEnvironment envMock) { 186 | ConfigurableListableBeanFactory beanFactory = mock( 187 | ConfigurableListableBeanFactory.class, 188 | withSettings().extraInterfaces(BeanDefinitionRegistry.class) 189 | ); 190 | 191 | when(beanFactory.getBean(ConfigurableEnvironment.class)).thenReturn(envMock); 192 | when(beanFactory.getBeanNamesForAnnotation(Path.class)).thenReturn(new String[]{"testResource1", "testResource2"}); 193 | when(beanFactory.getType("testResource1")).thenReturn((Class) TestResource1.class); 194 | when(beanFactory.getType("testResource2")).thenReturn((Class) TestResource2.class); 195 | 196 | String definition = envMock.getProperty(DEFINITION_PROPERTY); 197 | 198 | if((definition != null && definition.equals("beans"))) { 199 | // Although TestApplication1 and TestApplication4 are not really Spring beans, here we are simulating 200 | // they are to see how the JAX-RS Application registration behaves 201 | Map applicationsMap = new HashMap(); 202 | applicationsMap.put("testApplication1", new TestApplication1()); 203 | applicationsMap.put("testApplication4", new TestApplication4()); 204 | when(beanFactory.getBeansOfType(Application.class, true, false)).thenReturn(applicationsMap); 205 | } 206 | 207 | return beanFactory; 208 | } 209 | 210 | private void performTest(ConfigurableEnvironment envMock, ConfigurableListableBeanFactory beanFactory, Set expectedRegisteredAppClasses) { 211 | String definition = envMock.getProperty(DEFINITION_PROPERTY); 212 | boolean findSpringBeans = (definition == null || definition.equals("auto") || definition.equals("beans")); 213 | boolean getAppsProperty = (definition == null || definition.equals("auto") || definition.equals("property")); 214 | 215 | ResteasyEmbeddedServletInitializer resteasyEmbeddedServletInitializer = new ResteasyEmbeddedServletInitializer(); 216 | resteasyEmbeddedServletInitializer.postProcessBeanFactory(beanFactory); 217 | 218 | verify(beanFactory, VerificationModeFactory.times(getAppsProperty ? 2 : 1)).getBean(ConfigurableEnvironment.class); 219 | verify(beanFactory, VerificationModeFactory.times(findSpringBeans ? 1 : 0)).getBeansOfType(Application.class, true, false); 220 | verify(beanFactory, VerificationModeFactory.times(1)).getBeanNamesForAnnotation(Path.class); 221 | verify(beanFactory, VerificationModeFactory.times(1)).getBeanNamesForAnnotation(Provider.class); 222 | verify(beanFactory, VerificationModeFactory.times(2)).getType(anyString()); 223 | 224 | BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; 225 | 226 | Set expectedNotRegisteredAppClassess = new HashSet(allPossibleAppClasses); 227 | for(Class applicationClass : expectedRegisteredAppClasses) { 228 | verify(registry, VerificationModeFactory.times(1)).registerBeanDefinition(eq(applicationClass.getName()), any(GenericBeanDefinition.class)); 229 | expectedNotRegisteredAppClassess.remove(applicationClass); 230 | } 231 | for(Class applicationClass : expectedNotRegisteredAppClassess) { 232 | verify(registry, VerificationModeFactory.times(0)).registerBeanDefinition(eq(applicationClass.getName()), any(GenericBeanDefinition.class)); 233 | } 234 | } 235 | 236 | } 237 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/test/java/com/paypal/springboot/resteasy/ResteasyAutoConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy; 2 | 3 | import org.jboss.resteasy.core.Dispatcher; 4 | import org.jboss.resteasy.core.ResourceMethodRegistry; 5 | import org.jboss.resteasy.plugins.spring.SpringBeanProcessor; 6 | import org.jboss.resteasy.spi.Registry; 7 | import org.jboss.resteasy.spi.ResteasyProviderFactory; 8 | import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 9 | import org.springframework.mock.web.MockServletContext; 10 | import org.testng.Assert; 11 | import org.testng.annotations.Test; 12 | 13 | import javax.servlet.ServletContext; 14 | import javax.servlet.ServletContextEvent; 15 | import javax.servlet.ServletContextListener; 16 | 17 | /** 18 | * Created by facarvalho on 11/24/15. 19 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 20 | */ 21 | public class ResteasyAutoConfigurationTest { 22 | 23 | @Test 24 | public void springBeanProcessor() { 25 | BeanFactoryPostProcessor beanFactoryPostProcessor = new ResteasyAutoConfiguration().springBeanProcessor(); 26 | 27 | Assert.assertNotNull(beanFactoryPostProcessor); 28 | Assert.assertEquals(SpringBeanProcessor.class, beanFactoryPostProcessor.getClass()); 29 | 30 | SpringBeanProcessor springBeanProcessor = (SpringBeanProcessor) beanFactoryPostProcessor; 31 | Registry springBeanProcessorRegistry = springBeanProcessor.getRegistry(); 32 | ResteasyProviderFactory providerFactory = springBeanProcessor.getProviderFactory(); 33 | 34 | Assert.assertNotNull(springBeanProcessorRegistry); 35 | Assert.assertNotNull(providerFactory); 36 | Assert.assertEquals(ResourceMethodRegistry.class, springBeanProcessorRegistry.getClass()); 37 | } 38 | 39 | @Test 40 | public void syncDispatcherServletContextListenerTest() throws Exception { 41 | ServletContext servletContext = new MockServletContext(); 42 | testServletContextListener(servletContext); 43 | } 44 | 45 | @Test 46 | public void asyncDispatcherServletContextListenerTest() throws Exception { 47 | ServletContext servletContext = new MockServletContext(); 48 | servletContext.setInitParameter("resteasy.async.job.service.enabled", "true"); 49 | testServletContextListener(servletContext); 50 | } 51 | 52 | private void testServletContextListener(ServletContext servletContext) throws Exception { 53 | ResteasyAutoConfiguration resteasyAutoConfiguration = new ResteasyAutoConfiguration(); 54 | BeanFactoryPostProcessor beanFactoryPostProcessor = ResteasyAutoConfiguration.springBeanProcessor(); 55 | ServletContextListener servletContextListener = resteasyAutoConfiguration.resteasyBootstrapListener(beanFactoryPostProcessor); 56 | Assert.assertNotNull(servletContextListener); 57 | 58 | ServletContextEvent sce = new ServletContextEvent(servletContext); 59 | servletContextListener.contextInitialized(sce); 60 | 61 | ResteasyProviderFactory servletContextProviderFactory = (ResteasyProviderFactory) servletContext.getAttribute(ResteasyProviderFactory.class.getName()); 62 | Dispatcher servletContextDispatcher = (Dispatcher) servletContext.getAttribute(Dispatcher.class.getName()); 63 | Registry servletContextRegistry = (Registry) servletContext.getAttribute(Registry.class.getName()); 64 | 65 | Assert.assertNotNull(servletContextProviderFactory); 66 | Assert.assertNotNull(servletContextDispatcher); 67 | Assert.assertNotNull(servletContextRegistry); 68 | 69 | // Exercising fully cobertura branch coverage 70 | servletContextListener.contextDestroyed(sce); 71 | ServletContextListener servletContextListener2 = resteasyAutoConfiguration.resteasyBootstrapListener(beanFactoryPostProcessor); 72 | servletContextListener2.contextDestroyed(sce); 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/test/java/com/paypal/springboot/resteasy/ResteasyEmbeddedServletInitializerTest.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 5 | import org.springframework.context.ApplicationContext; 6 | import org.springframework.test.context.ContextConfiguration; 7 | import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; 8 | import org.testng.Assert; 9 | import org.testng.annotations.Test; 10 | 11 | import java.util.Map; 12 | 13 | /** 14 | * Created by facarvalho on 11/25/15. 15 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 16 | */ 17 | @ContextConfiguration("classpath:test-config.xml") 18 | public class ResteasyEmbeddedServletInitializerTest extends AbstractTestNGSpringContextTests { 19 | 20 | @Autowired 21 | private ApplicationContext applicationContext; 22 | 23 | @Test 24 | public void postProcessBeanFactory() { 25 | Map servletRegistrationBeans = applicationContext.getBeansOfType(ServletRegistrationBean.class); 26 | Assert.assertNotNull(servletRegistrationBeans); 27 | 28 | // Although there are 5 sample JAX-RS Application classes, one of them is not annotated with the ApplicationPath annotation! 29 | Assert.assertEquals(servletRegistrationBeans.size(), 4); 30 | 31 | for(String applicationClassName : servletRegistrationBeans.keySet()) { 32 | testApplicaton(applicationClassName, servletRegistrationBeans.get(applicationClassName)); 33 | } 34 | } 35 | 36 | private void testApplicaton(String applicationClassName, ServletRegistrationBean servletRegistrationBean) { 37 | Assert.assertEquals(applicationClassName, servletRegistrationBean.getServletName()); 38 | Assert.assertTrue(servletRegistrationBean.isAsyncSupported()); 39 | Assert.assertEquals(applicationClassName, servletRegistrationBean.getInitParameters().get("javax.ws.rs.Application")); 40 | Assert.assertTrue(servletRegistrationBean.isAsyncSupported()); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/test/java/com/paypal/springboot/resteasy/sample/SampleApp.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy.sample; 2 | 3 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * Sample app for test purposes 8 | * 9 | * Created by facarvalho on 5/17/17. 10 | */ 11 | @EnableAutoConfiguration 12 | @Component 13 | public class SampleApp { 14 | } 15 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/test/java/com/paypal/springboot/resteasy/sample/TestApplication1.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy.sample; 2 | 3 | import javax.ws.rs.ApplicationPath; 4 | import javax.ws.rs.core.Application; 5 | 6 | /** 7 | * Sample test JAX-RS application. 8 | * 9 | * Created by facarvalho on 11/25/15. 10 | */ 11 | @ApplicationPath("/myapp1") 12 | public class TestApplication1 extends Application { 13 | } 14 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/test/java/com/paypal/springboot/resteasy/sample/TestApplication2.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy.sample; 2 | 3 | import javax.ws.rs.ApplicationPath; 4 | import javax.ws.rs.core.Application; 5 | 6 | /** 7 | * Sample test JAX-RS application. 8 | * 9 | * Created by facarvalho on 11/25/15. 10 | */ 11 | @ApplicationPath("myapp2") 12 | public class TestApplication2 extends Application { 13 | } 14 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/test/java/com/paypal/springboot/resteasy/sample/TestApplication3.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy.sample; 2 | 3 | import javax.ws.rs.core.Application; 4 | 5 | /** 6 | * This application, although extending Application class, 7 | * is NOT annotated with ApplicationPath annotation, which 8 | * should prevent its registration 9 | * 10 | * Created by facarvalho on 11/25/15. 11 | */ 12 | public class TestApplication3 extends Application { 13 | } 14 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/test/java/com/paypal/springboot/resteasy/sample/TestApplication4.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy.sample; 2 | 3 | import javax.ws.rs.ApplicationPath; 4 | import javax.ws.rs.core.Application; 5 | 6 | /** 7 | * Sample test JAX-RS application. 8 | * 9 | * Created by facarvalho on 11/25/15. 10 | */ 11 | @ApplicationPath("/") 12 | public class TestApplication4 extends Application { 13 | } 14 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/test/java/com/paypal/springboot/resteasy/sample/TestApplication5.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy.sample; 2 | 3 | import javax.ws.rs.ApplicationPath; 4 | import javax.ws.rs.core.Application; 5 | 6 | /** 7 | * Sample test JAX-RS application. 8 | * 9 | * Created by facarvalho on 11/25/15. 10 | */ 11 | @ApplicationPath("myapp5/") 12 | public class TestApplication5 extends Application { 13 | } 14 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/test/java/com/paypal/springboot/resteasy/sample/TestProvider1.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy.sample; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import javax.ws.rs.core.Response; 6 | import javax.ws.rs.ext.ExceptionMapper; 7 | import javax.ws.rs.ext.Provider; 8 | 9 | /** 10 | * Created by facarvalho on 6/9/16. 11 | */ 12 | @Component 13 | @Provider 14 | public class TestProvider1 implements ExceptionMapper { 15 | 16 | public Response toResponse(Exception exception) { 17 | return null; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/test/java/com/paypal/springboot/resteasy/sample/TestResource1.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy.sample; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import javax.ws.rs.GET; 6 | import javax.ws.rs.Path; 7 | 8 | /** 9 | * Created by facarvalho on 6/9/16. 10 | */ 11 | @Path("resource1") 12 | @Component 13 | public class TestResource1 { 14 | 15 | @GET 16 | public void get() { 17 | // Test get method 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/test/java/com/paypal/springboot/resteasy/sample/TestResource2.java: -------------------------------------------------------------------------------- 1 | package com.paypal.springboot.resteasy.sample; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import javax.ws.rs.GET; 6 | import javax.ws.rs.Path; 7 | 8 | /** 9 | * Created by facarvalho on 6/9/16. 10 | */ 11 | @Path("resource2") 12 | @Component 13 | public class TestResource2 { 14 | 15 | @GET 16 | public void get() { 17 | // Test get method 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %level %logger %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /resteasy-spring-boot-starter/src/test/resources/test-config.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /sample-app/README.md: -------------------------------------------------------------------------------- 1 | # Sample application 2 | 3 | This is a super simple JAX-RS RESTEasy Spring Boot application just to exercise RESTEasy Spring Boot starter.
4 | 5 | ## Starting the application 6 | 7 | You can start the application as you for any other regular Spring Boot application. For example: 8 | 9 | 1. From the command line, under the sample application project, run `mvn spring-boot:run` 10 | 1. From your favorite IDE, run class `com.test.Application` 11 | 12 | ## Testing it 13 | 14 | Send a **POST** request message, containing the payload below, to [http://localhost:8080/sample-app/echo](http://localhost:8080/sample-app/echo). 15 | 16 | ``` 17 | is there anybody out there? 18 | ``` 19 | 20 | You should receive a response message with a payload similar to this as result: 21 | 22 | ``` json 23 | { 24 | "timestamp": "1484775122357", 25 | "echoText": "is there anybody out there?" 26 | } 27 | ``` 28 | 29 | The request message payload can be anything as plain text. 30 | The response message is supposed to echo that, plus a timestamp of the moment the echo response was created on the server side. The response message will be in JSON format. 31 | -------------------------------------------------------------------------------- /sample-app/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.test 7 | sample-app 8 | 2.3.4-RELEASE 9 | jar 10 | 11 | Simple test application for the Resteasy Spring Boot starter 12 | 13 | 14 | org.springframework.boot 15 | spring-boot-starter-parent 16 | 1.5.8.RELEASE 17 | 18 | 19 | 20 | 21 | UTF-8 22 | 1.8 23 | 3.1.4.Final 24 | 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-actuator 30 | runtime 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-web 35 | 36 | 37 | javax.ws.rs 38 | javax.ws.rs-api 39 | 2.0 40 | 41 | 42 | com.paypal.springboot 43 | resteasy-spring-boot-starter 44 | ${project.version} 45 | runtime 46 | 47 | 48 | 49 | 50 | org.jboss.resteasy 51 | resteasy-validator-provider-11 52 | ${resteasy.version} 53 | 54 | 55 | 56 | 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-maven-plugin 61 | 62 | 66 | 67 | 68 | 69 | repackage 70 | 71 | 72 | exec 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/sample/app/Application.java: -------------------------------------------------------------------------------- 1 | package com.sample.app; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.web.support.SpringBootServletInitializer; 6 | 7 | /** 8 | * SpringBoot entry point application 9 | * 10 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 11 | */ 12 | @SpringBootApplication 13 | public class Application extends SpringBootServletInitializer { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(Application.class, args); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/sample/app/CustomContainerResponseFilter.java: -------------------------------------------------------------------------------- 1 | package com.sample.app; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | 6 | import javax.ws.rs.container.ContainerRequestContext; 7 | import javax.ws.rs.container.ContainerResponseContext; 8 | import javax.ws.rs.container.ContainerResponseFilter; 9 | import javax.ws.rs.ext.Provider; 10 | import java.io.IOException; 11 | 12 | /** 13 | * Custom container request filter just to 14 | * exercise Spring beans as providers and, 15 | * specifically, in this case, as a filter. 16 | * 17 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 18 | */ 19 | @Component 20 | @Provider 21 | public class CustomContainerResponseFilter implements ContainerResponseFilter { 22 | 23 | @Autowired 24 | private CustomSingletonBean customSingletonBean; 25 | 26 | @Override 27 | public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { 28 | 29 | // This will cause a NPE if this bean couldn't be injected, 30 | // and that is all we want to check. No need for assertions here 31 | customSingletonBean.amIAlive(); 32 | 33 | // Checks if request has a HTTP header named "ping". 34 | // If it does, adds an HTTP header named "pong" to the response. 35 | // The header value is irrelevant. 36 | if(requestContext.getHeaderString("ping") != null) { 37 | responseContext.getHeaders().add("pong", "pong"); 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/sample/app/CustomExceptionMapper.java: -------------------------------------------------------------------------------- 1 | package com.sample.app; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | 6 | import javax.ws.rs.NotFoundException; 7 | import javax.ws.rs.core.MediaType; 8 | import javax.ws.rs.core.Response; 9 | import javax.ws.rs.ext.ExceptionMapper; 10 | import javax.ws.rs.ext.Provider; 11 | 12 | /** 13 | * Custom exception mapper for 404 cases. 14 | * 15 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 16 | */ 17 | @Component 18 | @Provider 19 | public class CustomExceptionMapper implements ExceptionMapper { 20 | 21 | @Autowired 22 | private CustomSingletonBean customSingletonBean; 23 | 24 | @Override 25 | public Response toResponse(NotFoundException exception) { 26 | 27 | // This will cause a NPE if this bean couldn't be injected, 28 | // and that is all we want to check. No need for assertions here 29 | customSingletonBean.amIAlive(); 30 | 31 | Response.ResponseBuilder responseBuilder = Response.status(Response.Status.NOT_FOUND).entity("The resource you've requested, has not been found!"); 32 | responseBuilder.type(MediaType.TEXT_PLAIN); 33 | return responseBuilder.build(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/sample/app/CustomSingletonBean.java: -------------------------------------------------------------------------------- 1 | package com.sample.app; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | /** 6 | * This singleton Spring bean just exists to 7 | * test that injections work normally in 8 | * JAX-RS provider beans 9 | * 10 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 11 | */ 12 | @Component 13 | public class CustomSingletonBean { 14 | 15 | public boolean amIAlive() { 16 | return true; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/sample/app/Echo.java: -------------------------------------------------------------------------------- 1 | package com.sample.app; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | 6 | import javax.ws.rs.Consumes; 7 | import javax.ws.rs.POST; 8 | import javax.ws.rs.Path; 9 | import javax.ws.rs.Produces; 10 | import javax.ws.rs.core.MediaType; 11 | 12 | import org.hibernate.validator.constraints.NotEmpty; 13 | 14 | /** 15 | * Echo REST endpoint class 16 | * 17 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 18 | */ 19 | @Path("/echo") 20 | @Component 21 | public class Echo { 22 | 23 | @Autowired 24 | private EchoMessageCreator echoer; 25 | 26 | /** 27 | * Receives a simple POST request message containing as payload 28 | * a text, in text plain format, to be echoed by the service. 29 | * It returns as response, in JSON, the text to be echoed plus a timestamp of the 30 | * moment the echo response was created on the server side 31 | * 32 | * @param echoText 33 | * @return 34 | */ 35 | @POST 36 | @Consumes({ MediaType.TEXT_PLAIN }) 37 | @Produces({ MediaType.APPLICATION_JSON }) 38 | public EchoMessage echo(@NotEmpty String echoText) { 39 | return echoer.createEchoMessage(echoText); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/sample/app/EchoMessage.java: -------------------------------------------------------------------------------- 1 | package com.sample.app; 2 | 3 | /** 4 | * A simple echo message, containing the text to be echoed 5 | * and timestamp of the moment the message was created 6 | * 7 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 8 | */ 9 | public class EchoMessage { 10 | 11 | private long timestamp; 12 | private String echoText; 13 | 14 | public EchoMessage(String echoText) { 15 | timestamp = System.currentTimeMillis(); 16 | this.echoText = echoText; 17 | } 18 | 19 | public long getTimestamp() { 20 | return timestamp; 21 | } 22 | 23 | public String getEchoText() { 24 | return echoText; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /sample-app/src/main/java/com/sample/app/EchoMessageCreator.java: -------------------------------------------------------------------------------- 1 | package com.sample.app; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | /** 6 | * This bean creates {@link EchoMessage} objects based on 7 | * echo texts received as input 8 | * 9 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 10 | */ 11 | @Component 12 | public class EchoMessageCreator { 13 | 14 | public EchoMessage createEchoMessage(String echoText) { 15 | return new EchoMessage(echoText); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/sample/app/JaxrsApplication.java: -------------------------------------------------------------------------------- 1 | package com.sample.app; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import javax.ws.rs.ApplicationPath; 6 | import javax.ws.rs.core.Application; 7 | 8 | /** 9 | * JAX-RS application 10 | * 11 | * @author Fabio Carvalho (facarvalho@paypal.com or fabiocarvalho777@gmail.com) 12 | */ 13 | @Component 14 | @ApplicationPath("/sample-app/") 15 | public class JaxrsApplication extends Application { 16 | } 17 | -------------------------------------------------------------------------------- /sample-app/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | resteasy: 2 | jaxrs: 3 | app: 4 | registration: property 5 | classes: com.sample.app.JaxrsApplication -------------------------------------------------------------------------------- /sample-app/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %level %logger %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | --------------------------------------------------------------------------------