├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── etc
├── checkstyle
│ └── rules.xml
├── eclipse
│ └── java-formatter.xml
└── pmd
│ └── ruleset.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── pom.xml
└── src
├── main
├── java
│ └── org
│ │ └── example
│ │ └── ws
│ │ ├── Application.java
│ │ ├── actuator
│ │ └── health
│ │ │ └── GreetingHealthIndicator.java
│ │ ├── batch
│ │ └── GreetingBatchBean.java
│ │ ├── model
│ │ └── Greeting.java
│ │ ├── repository
│ │ └── GreetingRepository.java
│ │ ├── service
│ │ ├── EmailService.java
│ │ ├── EmailServiceBean.java
│ │ ├── GreetingService.java
│ │ ├── GreetingServiceBean.java
│ │ └── quote
│ │ │ └── tss
│ │ │ ├── Quote.java
│ │ │ ├── QuoteResponse.java
│ │ │ ├── QuoteResponseContents.java
│ │ │ ├── QuoteResponseSuccess.java
│ │ │ ├── QuoteService.java
│ │ │ └── QuoteServiceBean.java
│ │ ├── util
│ │ └── AsyncResponse.java
│ │ └── web
│ │ ├── DefaultExceptionAttributes.java
│ │ ├── ExceptionAttributes.java
│ │ └── api
│ │ ├── BaseController.java
│ │ ├── GreetingController.java
│ │ └── QuoteController.java
└── resources
│ ├── config
│ ├── application-batch.properties
│ └── application.properties
│ └── data
│ └── hsqldb
│ ├── data.sql
│ └── schema.sql
└── test
└── java
└── org
└── example
└── ws
├── AbstractControllerTest.java
├── AbstractTest.java
├── service
└── GreetingServiceTest.java
└── web
└── api
├── GreetingControllerMocksTest.java
└── GreetingControllerTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Eclipse Directories and Files
2 | .project
3 | .classpath
4 | .springBeans
5 | /.settings
6 | /.gradle
7 |
8 | # Generated Directories and Files
9 | /target
10 | /build
11 | /bin
12 |
13 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
14 | hs_err_pid*
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spring Boot Fundamentals
2 |
3 | ## Acknowledgements
4 |
5 | This is a [LEAN**STACKS**](http://www.leanstacks.com) solution.
6 |
7 | For more detailed information and instruction about constructing Spring Boot RESTful web services, see the book [Lean Application Engineering Featuring Backbone.Marionette and the Spring Framework](https://leanpub.com/leanstacks-marionette-spring).
8 |
9 | LEAN**STACKS** offers several technology instruction video series, publications, and starter projects. For more information go to [LeanStacks.com](http://www.leanstacks.com/).
10 |
11 | ## Repository
12 |
13 | This repository is a companion for the LEAN**STACKS** YouTube channel playlist entitled [Spring Boot Fundamentals](https://www.youtube.com/playlist?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
14 |
15 | ### Repository Organization
16 |
17 | Each episode of the Spring Boot Fundamentals video series has a corresponding branch in this repository. For example, all of the source code illustrated in the episode entitled [Bootstrapping a Spring Boot Application Project](https://youtu.be/XbknBOmMuPQ?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY) may be found on the repository branch named [bootstrap](https://github.com/mwarman/spring-boot-fundamentals/tree/bootstrap).
18 |
19 | ### Branches
20 |
21 | #### bootstrap
22 |
23 | The branch named `bootstrap` contains the source code illustrated in the episode [Bootstrapping a Spring Boot Application Project](https://youtu.be/XbknBOmMuPQ?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
24 |
25 | #### restws-1
26 |
27 | The branch named `restws-1` contains the source code illustrated in the episode [Creating RESTful Web Services with Spring Boot - Part 1](https://youtu.be/kbisNUfqVLM?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
28 |
29 | #### restws-2
30 |
31 | The branch named `restws-2` contains the source code illustrated in the episode [Creating RESTful Web Services with Spring Boot - Part 2](https://youtu.be/mrrHTJxppi8?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
32 |
33 | #### service
34 |
35 | The branch named `service` contains the source code illustrated in the episode [Creating Service Components with Spring Boot](https://youtu.be/qJnAM_ZZvWA?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
36 |
37 | #### repository
38 |
39 | The branch named `repository` contains the source code illustrated in the episode [Using Spring Data Repositories with Spring Boot](https://youtu.be/4bPT-0f-am4?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
40 |
41 | #### transactional
42 |
43 | The branch named `transactional` contains the source code illustrated in the episode [Declarative Transaction Management with Spring Boot](https://youtu.be/4bPT-0f-am4?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
44 |
45 | #### cache
46 |
47 | The branch named `cache` contains the source code illustrated in the episode [Declarative Cache Management with Spring Boot](https://youtu.be/g4h268Hx0AU?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
48 |
49 | #### scheduled
50 |
51 | The branch named `scheduled` contains the source code illustrated in the episode [Creating Scheduled Processes with Spring Boot](https://youtu.be/TEMsEcdAsbY?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
52 |
53 | #### async
54 |
55 | The branch named `async` contains the source code illustrated in the episode [Creating Asynchronous Processes with Spring Boot](https://youtu.be/106WWFvgNW0?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
56 |
57 | #### configuration
58 |
59 | The branch named `configuration` contains the source code illustrated in the episode [Using Profiles and Properties to Create Environment-Specific Runtime Configurations with Spring Boot](https://youtu.be/0zjQX7WwjrI?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
60 |
61 | #### unit-test
62 |
63 | The branch named `unit-test` contains the source code illustrated in the episode [Creating Unit Tests with Spring Boot](https://youtu.be/WKD9E8KsQME?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
64 |
65 | #### unit-test-controller
66 |
67 | The branch named `unit-test-controller` contains the source code illustrated in the episode [Creating Web Service Controller Unit Tests with Spring Boot](https://youtu.be/zjVobP0sonA?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
68 |
69 | #### unit-test-mockito
70 |
71 | The branch named `unit-test-mockito` contains the source code illustrated in the episode [Creating Web Service Controller Unit Tests with Mockito and Spring Boot](https://youtu.be/7TMuBxTy3GE?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
72 |
73 | #### actuator
74 |
75 | The branch named `actuator` contains the source code illustrated in the episode [Production Monitoring and Management with Spring Boot Actuator](https://youtu.be/7L5rBQUMiPI?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
76 |
77 | #### security
78 |
79 | The branch named `security` contains the source code illustrated in the episode [Protecting Application Assets with Spring Security - Out-of-the-Box Features](https://youtu.be/CQfp2ngwT5U?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
80 |
81 | #### controller-hierarchy
82 |
83 | The branch named `controller-hierarchy` contains the source code illustrated in the episode [Creating Meaningful RESTful Web Service Controller Hierarchies with Spring Boot](https://youtu.be/Dg8hb0HPMWA?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
84 |
85 | #### gradle
86 |
87 | The branch named `gradle` contains the source code illustrated in the episode [Using the Gradle Build System](https://youtu.be/wRHj2hUHQd0?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
88 |
89 | #### gradle-build-dash
90 |
91 | The branch named `gradle-build-dash` contains the source code illustrated in the episode [Introduction to Gradle Project and Build Reports](https://youtu.be/I_jnSDjN2eo?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
92 |
93 | #### gradle-jacoco
94 |
95 | The branch named `gradle-jacoco` contains the source code illustrated in the episode [Using the Gradle JaCoCo Plugin for Unit Test Code Coverage Reporting](https://youtu.be/ieYs0hkogVY?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
96 |
97 | #### gradle-checkstyle
98 |
99 | The branch named `gradle-checkstyle` contains the source code illustrated in the episode [Using the Gradle Checkstyle Plugin for Code Style Reporting](https://youtu.be/zo3zyyo7Vkw?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
100 |
101 | #### gradle-pmd
102 |
103 | The branch named `gradle-pmd` contains the source code illustrated in the episode [Using the Gradle PMD Plugin for Static Code Analysis](https://youtu.be/Eek-5VJV2Xk?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
104 |
105 | #### gradle-defaulttasks
106 |
107 | The branch named `gradle-defaulttasks` contains the source code illustrated in the episode [Configuring Gradle Default Tasks](https://youtu.be/6NrFSDzwcfg?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
108 |
109 | #### upgrade-140
110 |
111 | The branch named `upgrade-140` contains the source code illustrated in the episode [Upgrading to Spring Boot 1.4](https://youtu.be/oxhhYZNKXAM?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
112 |
113 | #### executable
114 |
115 | The branch named `executable` contains the source code illustrated in the episode [Installing a Spring Boot Application on a Server - Part One](https://youtu.be/eCalW4OBpV8?list=PLGDwUiT1wr6-Fn3N2oqJpTdhGjFHnIIKY).
116 |
117 | #### resttemplate
118 |
119 | The branch named `resttemplate` contains the source code illustrated in the episode [Using RestTemplate and Spring Boot to Integrate with REST Services]().
120 |
121 |
122 | ## Languages
123 |
124 | This project is authored in Java.
125 |
126 | ## Installation
127 |
128 | ### Fork the Repository
129 |
130 | Fork the [Spring Boot Fundamentals](https://github.com/mwarman/spring-boot-fundamentals) repository on GitHub. Clone the project to your host machine.
131 |
132 | ### Dependencies
133 |
134 | The project requires the following dependencies be installed on the host machine:
135 |
136 | * Java Development Kit 7 or later
137 |
138 | and choose one of:
139 | * Apache Maven 3 or later
140 | * Gradle 2.12 or later
141 |
142 | ## Running
143 |
144 | The project supports [Maven](http://maven.apache.org/) and [Gradle](http://gradle.org/) for build, package, and test workflow automation.
145 |
146 | ### Maven
147 |
148 | The following Maven goals are the most commonly used.
149 |
150 | #### spring-boot:run
151 |
152 | The `spring-boot:run` Maven goal performs the following workflow steps:
153 |
154 | * compiles Java classes to the /target directory
155 | * copies all resources to the /target directory
156 | * starts an embedded Apache Tomcat server
157 |
158 | To execute the `spring-boot:run` Maven goal, type the following command at a terminal prompt in the project base directory.
159 |
160 | ```
161 | mvn spring-boot:run
162 | ```
163 |
164 | Type `ctrl-C` to halt the web server.
165 |
166 | This goal is used for local machine development and functional testing. Use the `package` goal for server deployment.
167 |
168 | #### test
169 |
170 | The `test` Maven goal performs the following workflow steps:
171 |
172 | * compiles Java classes to the /target directory
173 | * copies all resources to the /target directory
174 | * executes the unit test suites
175 | * produces unit test reports
176 |
177 | The `test` Maven goal is designed to allow engineers the means to run the unit test suites against the main source code. This goal may also be used on continuous integration servers such as Jenkins, etc.
178 |
179 | To execute the `test` Maven goal, type the following command at a terminal prompt in the project base directory.
180 |
181 | ```
182 | mvn clean test
183 | ```
184 |
185 | #### package
186 |
187 | The `package` Maven goal performs the following workflow steps:
188 |
189 | * compiles Java classes to the /target directory
190 | * copies all resources to the /target directory
191 | * executes the unit test suites
192 | * produces unit test reports
193 | * prepares an executable JAR file in the /target directory
194 |
195 | The `package` Maven goal is designed to prepare the application for distribution to server environments. The application and all dependencies are packaged into a single, executable JAR file.
196 |
197 | To execute the `package` goal, type the following command at a terminal prompt in the project base directory.
198 |
199 | ```
200 | mvn clean package
201 | ```
202 |
203 | The application distribution artifact is placed in the /target directory and is named using the `artifactId` and `version` from the pom.xml file. To run the JAR file use the following command:
204 |
205 | ```
206 | java -jar example-1.0.0.jar
207 | ```
208 |
209 | By default, the batch and hsqldb profiles are active. To run the application with a specific set of active profiles, supply the `--spring.profiles.active` command line argument. For example, to start the project using MySQL instad of HSQLDB and enable the batch process:
210 |
211 | ```
212 | java -jar example-1.0.0.jar --spring.profiles.active=mysql,batch
213 | ```
214 |
215 | ### Gradle
216 |
217 | The following Gradle tasks are the most commonly used.
218 |
219 | #### bootRun
220 |
221 | The `bootRun` Gradle task performs the following workflow steps:
222 |
223 | * compiles Java classes to the /build directory
224 | * copies all resources to the /build directory
225 | * starts an embedded Apache Tomcat server
226 |
227 | To execute the `bootRun` Gradle task, type the following command at a terminal prompt in the project base directory.
228 |
229 | ```
230 | gradle bootRun
231 | ```
232 |
233 | Type `ctrl-C` to halt the web server.
234 |
235 | This task is used for local machine development and functional testing. Use the `assemble` or `build` task for server deployment.
236 |
237 | #### assemble
238 |
239 | The `assemble` Gradle task performs the following workflow steps:
240 |
241 | * compiles Java classes to the /build directory
242 | * copies all resources to the /build directory
243 | * prepares an executable JAR file in the /build/libs directory
244 |
245 | The `assemble` Gradle task is designed to allow engineers the means to compile the project and produce an executable JAR file suitable for server environments without executing unit tests or producing other project reports.
246 |
247 | To execute the `assemble` Gradle task, type the following command at a terminal prompt in the project base directory.
248 |
249 | ```
250 | gradle clean assemble
251 | ```
252 |
253 | #### build
254 |
255 | The `build` Gradle task performs the following workflow steps:
256 |
257 | * compiles Java classes to the /build directory
258 | * copies all resources to the /build directory
259 | * executes the unit test suites
260 | * produces unit test reports
261 | * prepares an executable JAR file in the /build/libs directory
262 |
263 | The `build` Gradle task is prepares the application for distribution to server environments. The application and all dependencies are packaged into a single, executable JAR file.
264 |
265 | This task is ideal for use on continuous integration servers such as Jenkins, etc. because it produces unit test, code coverage, and static analysis reports.
266 |
267 | To execute the `build` Gradle task, type the following command at a terminal prompt in the project base directory.
268 |
269 | ```
270 | gradle clean build
271 | ```
272 |
273 | The application distribution artifact is placed in the /build/libs directory and is named using the project name and version from the `build.gradle` file. To run the JAR file use the following command:
274 |
275 | ```
276 | java -jar build/libs/example-1.0.0.jar
277 | ```
278 |
279 | By default, the batch and hsqldb profiles are active. To run the application with a specific set of active profiles, supply the `--spring.profiles.active` command line argument. For example, to start the project using MySQL instad of HSQLDB and enable the batch process:
280 |
281 | ```
282 | java -jar build/libs/example-1.0.0.jar --spring.profiles.active=mysql,batch
283 | ```
284 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | id 'org.springframework.boot' version '1.5.1.RELEASE'
4 | id 'eclipse'
5 | id 'jacoco'
6 | id 'checkstyle'
7 | id 'pmd'
8 | id 'project-report'
9 | id 'build-dashboard'
10 | }
11 |
12 | group = 'com.leanstacks'
13 | version = '1.0.0-SNAPSHOT'
14 | sourceCompatibility = 1.8
15 |
16 | ext {
17 | jacocoVersion = '0.7.7.201606060606'
18 | checkstyleVersion = '7.2'
19 | pmdVersion = '5.5.2'
20 | guavaVersion = '20.0'
21 | jadiraVersion = '5.0.0.GA'
22 | }
23 |
24 | repositories {
25 | jcenter()
26 | }
27 |
28 | dependencies {
29 | compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
30 | compile group: 'org.springframework.boot', name: 'spring-boot-starter-security'
31 | compile group: 'org.springframework.boot', name: 'spring-boot-starter-actuator'
32 | compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa'
33 |
34 | compile group: 'org.springframework', name: 'spring-context-support'
35 |
36 | compile group: 'joda-time', name: 'joda-time'
37 | compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-joda'
38 | compile group: 'com.google.guava', name: 'guava', version: guavaVersion
39 | compile group: 'com.github.ben-manes.caffeine', name: 'caffeine'
40 | compile group: 'org.jadira.usertype', name: 'usertype.extended', version: jadiraVersion
41 |
42 | runtime group: 'org.hsqldb', name: 'hsqldb'
43 | runtime group: 'mysql', name: 'mysql-connector-java'
44 |
45 | testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test'
46 | }
47 |
48 | defaultTasks 'clean', 'build'
49 |
50 | springBoot {
51 | executable = true
52 | buildInfo()
53 | }
54 |
55 | jacoco {
56 | toolVersion = jacocoVersion
57 | }
58 |
59 | jacocoTestReport {
60 | reports {
61 | html.enabled = true
62 | xml.enabled = true
63 | csv.enabled = true
64 | }
65 | }
66 | test.finalizedBy jacocoTestReport
67 |
68 | checkstyle {
69 | toolVersion = checkstyleVersion
70 | config = rootProject.resources.text.fromFile('etc/checkstyle/rules.xml')
71 | }
72 |
73 | pmd {
74 | toolVersion = pmdVersion
75 | ruleSetConfig = rootProject.resources.text.fromFile('etc/pmd/ruleset.xml')
76 | ignoreFailures = true
77 | }
78 |
79 | check.finalizedBy projectReport
80 |
81 | projectReport.finalizedBy buildDashboard
82 |
--------------------------------------------------------------------------------
/etc/checkstyle/rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
68 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
108 |
109 |
110 |
111 |
112 |
113 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
124 |
125 |
126 |
127 |
129 |
130 |
131 |
132 |
134 |
136 |
138 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
161 |
162 |
163 |
164 |
165 |
167 |
168 |
169 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
181 |
182 |
183 |
184 |
185 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
--------------------------------------------------------------------------------
/etc/eclipse/java-formatter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
--------------------------------------------------------------------------------
/etc/pmd/ruleset.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | This is the LeanStacks Official PMD ruleset.
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leanstacks/spring-boot-fundamentals/b98476c9ec9a10ac87757e0b76e1679a1f7f10bb/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Nov 15 07:04:38 EST 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn ( ) {
37 | echo "$*"
38 | }
39 |
40 | die ( ) {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | 4.0.0
5 |
6 | org.example
7 | spring-boot-fundamentals
8 | 1.0.0-SNAPSHOT
9 |
10 |
11 | org.springframework.boot
12 | spring-boot-starter-parent
13 | 1.5.1.RELEASE
14 |
15 |
16 |
17 | UTF-8
18 | 1.8
19 |
20 |
21 |
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-web
26 |
27 |
28 |
29 |
30 | org.springframework.boot
31 | spring-boot-starter-security
32 |
33 |
34 |
35 |
36 | org.springframework.boot
37 | spring-boot-starter-data-jpa
38 |
39 |
40 | org.hsqldb
41 | hsqldb
42 | runtime
43 |
44 |
45 |
46 |
47 | org.springframework
48 | spring-context-support
49 |
50 |
51 | com.github.ben-manes.caffeine
52 | caffeine
53 |
54 |
55 |
56 |
57 | com.google.guava
58 | guava
59 | 20.0
60 |
61 |
62 |
63 |
64 | org.springframework.boot
65 | spring-boot-starter-actuator
66 |
67 |
68 |
69 |
70 | org.springframework.boot
71 | spring-boot-starter-test
72 | test
73 |
74 |
75 |
76 |
77 |
78 |
79 | org.springframework.boot
80 | spring-boot-maven-plugin
81 |
82 | true
83 |
84 |
85 |
86 |
87 | build-info
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/Application.java:
--------------------------------------------------------------------------------
1 | package org.example.ws;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.cache.annotation.EnableCaching;
6 | import org.springframework.scheduling.annotation.EnableAsync;
7 | import org.springframework.scheduling.annotation.EnableScheduling;
8 | import org.springframework.transaction.annotation.EnableTransactionManagement;
9 |
10 | /**
11 | * Spring Boot main application class. Serves as both the runtime application
12 | * entry point and the central Java configuration class.
13 | *
14 | * @author Matt Warman
15 | */
16 | @SpringBootApplication
17 | @EnableTransactionManagement
18 | @EnableCaching
19 | @EnableScheduling
20 | @EnableAsync
21 | public class Application {
22 |
23 | /**
24 | * Entry point for the application.
25 | *
26 | * @param args Command line arguments.
27 | * @throws Exception Thrown when an unexpected Exception is thrown from the
28 | * application.
29 | */
30 | public static void main(String[] args) throws Exception {
31 | SpringApplication.run(Application.class, args);
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/actuator/health/GreetingHealthIndicator.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.actuator.health;
2 |
3 | import java.util.Collection;
4 |
5 | import org.example.ws.model.Greeting;
6 | import org.example.ws.service.GreetingService;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.boot.actuate.health.Health;
9 | import org.springframework.boot.actuate.health.HealthIndicator;
10 | import org.springframework.stereotype.Component;
11 |
12 | /**
13 | * The GreetingHealthIndicator is a custom Spring Boot Actuator HealthIndicator
14 | * implementation. HealthIndicator classes are invoked when the Actuator
15 | * 'health' endpoint is invoked. Each HealthIndicator class assesses some
16 | * portion of the application's health, returing a Health object which indicates
17 | * that status and, optionally, additional health attributes.
18 | *
19 | * @author Matt Warman
20 | */
21 | @Component
22 | public class GreetingHealthIndicator implements HealthIndicator {
23 |
24 | /**
25 | * The GreetingService business service.
26 | */
27 | @Autowired
28 | private GreetingService greetingService;
29 |
30 | @Override
31 | public Health health() {
32 |
33 | // Assess the application's Greeting health. If the application's
34 | // Greeting components have data to service user requests, the Greeting
35 | // component is considered 'healthy', otherwise it is not.
36 |
37 | Collection greetings = greetingService.findAll();
38 |
39 | if (greetings == null || greetings.size() == 0) {
40 | return Health.down().withDetail("count", 0).build();
41 | }
42 |
43 | return Health.up().withDetail("count", greetings.size()).build();
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/batch/GreetingBatchBean.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.batch;
2 |
3 | import java.util.Collection;
4 |
5 | import org.example.ws.model.Greeting;
6 | import org.example.ws.service.GreetingService;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.context.annotation.Profile;
11 | import org.springframework.scheduling.annotation.Scheduled;
12 | import org.springframework.stereotype.Component;
13 |
14 | /**
15 | * The GreetingBatchBean contains @Scheduled
methods operating on
16 | * Greeting entities to perform batch operations.
17 | *
18 | * @author Matt Warman
19 | */
20 | @Profile("batch")
21 | @Component
22 | public class GreetingBatchBean {
23 |
24 | /**
25 | * The Logger for this class.
26 | */
27 | private Logger logger = LoggerFactory.getLogger(this.getClass());
28 |
29 | /**
30 | * The GreetingService business service.
31 | */
32 | @Autowired
33 | private GreetingService greetingService;
34 |
35 | /**
36 | * Use a cron expression to execute logic on a schedule.
37 | *
38 | * Expression: second minute hour day-of-month month weekday
39 | *
40 | * @see http ://docs.spring.io/spring/docs/current/javadoc-api/org/
41 | * springframework /scheduling/support/CronSequenceGenerator.html
42 | */
43 | @Scheduled(
44 | cron = "${batch.greeting.cron}")
45 | public void cronJob() {
46 | logger.info("> cronJob");
47 |
48 | // Add scheduled logic here
49 | Collection greetings = greetingService.findAll();
50 | logger.info("There are {} greetings in the data store.",
51 | greetings.size());
52 |
53 | logger.info("< cronJob");
54 | }
55 |
56 | /**
57 | * Execute logic beginning at fixed intervals with a delay after the
58 | * application starts. Use the fixedRate
element to indicate
59 | * how frequently the method is to be invoked. Use the
60 | * initialDelay
element to indicate how long to wait after
61 | * application startup to schedule the first execution.
62 | */
63 | @Scheduled(
64 | initialDelayString = "${batch.greeting.initialdelay}",
65 | fixedRateString = "${batch.greeting.fixedrate}")
66 | public void fixedRateJobWithInitialDelay() {
67 | logger.info("> fixedRateJobWithInitialDelay");
68 |
69 | // Add scheduled logic here
70 |
71 | // Simulate job processing time
72 | long pause = 5000;
73 | long start = System.currentTimeMillis();
74 | do {
75 | if (start + pause < System.currentTimeMillis()) {
76 | break;
77 | }
78 | } while (true);
79 | logger.info("Processing time was {} seconds.", pause / 1000);
80 |
81 | logger.info("< fixedRateJobWithInitialDelay");
82 | }
83 |
84 | /**
85 | * Execute logic with a delay between the end of the last execution and the
86 | * beginning of the next. Use the fixedDelay
element to
87 | * indicate the time to wait between executions. Use the
88 | * initialDelay
element to indicate how long to wait after
89 | * application startup to schedule the first execution.
90 | */
91 | @Scheduled(
92 | initialDelayString = "${batch.greeting.initialdelay}",
93 | fixedDelayString = "${batch.greeting.fixeddelay}")
94 | public void fixedDelayJobWithInitialDelay() {
95 | logger.info("> fixedDelayJobWithInitialDelay");
96 |
97 | // Add scheduled logic here
98 |
99 | // Simulate job processing time
100 | long pause = 5000;
101 | long start = System.currentTimeMillis();
102 | do {
103 | if (start + pause < System.currentTimeMillis()) {
104 | break;
105 | }
106 | } while (true);
107 | logger.info("Processing time was {} seconds.", pause / 1000);
108 |
109 | logger.info("< fixedDelayJobWithInitialDelay");
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/model/Greeting.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.model;
2 |
3 | import javax.persistence.Entity;
4 | import javax.persistence.GeneratedValue;
5 | import javax.persistence.Id;
6 |
7 | @Entity
8 | public class Greeting {
9 |
10 | @Id
11 | @GeneratedValue
12 | private Long id;
13 |
14 | private String text;
15 |
16 | public Greeting() {
17 |
18 | }
19 |
20 | public Long getId() {
21 | return id;
22 | }
23 |
24 | public void setId(Long id) {
25 | this.id = id;
26 | }
27 |
28 | public String getText() {
29 | return text;
30 | }
31 |
32 | public void setText(String text) {
33 | this.text = text;
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/repository/GreetingRepository.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.repository;
2 |
3 | import org.example.ws.model.Greeting;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 | import org.springframework.stereotype.Repository;
6 |
7 | @Repository
8 | public interface GreetingRepository extends JpaRepository {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/service/EmailService.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.service;
2 |
3 | import java.util.concurrent.Future;
4 |
5 | import org.example.ws.model.Greeting;
6 |
7 | /**
8 | * The EmailService interface defines all public business behaviors for
9 | * composing and transmitting email messages.
10 | *
11 | * This interface should be injected into EmailService clients, not the
12 | * implementation bean.
13 | *
14 | * @author Matt Warman
15 | */
16 | public interface EmailService {
17 |
18 | /**
19 | * Send a Greeting via email synchronously.
20 | * @param greeting A Greeting to send.
21 | * @return A Boolean whose value is TRUE if sent successfully; otherwise
22 | * FALSE.
23 | */
24 | Boolean send(Greeting greeting);
25 |
26 | /**
27 | * Send a Greeting via email asynchronously.
28 | * @param greeting A Greeting to send.
29 | */
30 | void sendAsync(Greeting greeting);
31 |
32 | /**
33 | * Send a Greeting via email asynchronously. Returns a Future<Boolean>
34 | * response allowing the client to obtain the status of the operation once
35 | * it is completed.
36 | * @param greeting A Greeting to send.
37 | * @return A Future<Boolean> whose value is TRUE if sent successfully;
38 | * otherwise, FALSE.
39 | */
40 | Future sendAsyncWithResult(Greeting greeting);
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/service/EmailServiceBean.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.service;
2 |
3 | import java.util.concurrent.Future;
4 |
5 | import org.example.ws.model.Greeting;
6 | import org.example.ws.util.AsyncResponse;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.scheduling.annotation.Async;
10 | import org.springframework.stereotype.Service;
11 |
12 | /**
13 | * The EmailServiceBean implements all business behaviors defined by the
14 | * EmailService interface.
15 | *
16 | * @author Matt Warman
17 | */
18 | @Service
19 | public class EmailServiceBean implements EmailService {
20 |
21 | /**
22 | * The Logger for this class.
23 | */
24 | private Logger logger = LoggerFactory.getLogger(this.getClass());
25 |
26 | @Override
27 | public Boolean send(Greeting greeting) {
28 | logger.info("> send");
29 |
30 | Boolean success = Boolean.FALSE;
31 |
32 | // Simulate method execution time
33 | long pause = 5000;
34 | try {
35 | Thread.sleep(pause);
36 | } catch (Exception e) {
37 | // do nothing
38 | }
39 | logger.info("Processing time was {} seconds.", pause / 1000);
40 |
41 | success = Boolean.TRUE;
42 |
43 | logger.info("< send");
44 | return success;
45 | }
46 |
47 | @Async
48 | @Override
49 | public void sendAsync(Greeting greeting) {
50 | logger.info("> sendAsync");
51 |
52 | try {
53 | send(greeting);
54 | } catch (Exception e) {
55 | logger.warn("Exception caught sending asynchronous mail.", e);
56 | }
57 |
58 | logger.info("< sendAsync");
59 | }
60 |
61 | @Async
62 | @Override
63 | public Future sendAsyncWithResult(Greeting greeting) {
64 | logger.info("> sendAsyncWithResult");
65 |
66 | AsyncResponse response = new AsyncResponse();
67 |
68 | try {
69 | Boolean success = send(greeting);
70 | response.complete(success);
71 | } catch (Exception e) {
72 | logger.warn("Exception caught sending asynchronous mail.", e);
73 | response.completeExceptionally(e);
74 | }
75 |
76 | logger.info("< sendAsyncWithResult");
77 | return response;
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/service/GreetingService.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.service;
2 |
3 | import java.util.Collection;
4 |
5 | import org.example.ws.model.Greeting;
6 |
7 | /**
8 | * The GreetingService interface defines all public business behaviors for
9 | * operations on the Greeting entity model.
10 | *
11 | * This interface should be injected into GreetingService clients, not the
12 | * implementation bean.
13 | *
14 | * @author Matt Warman
15 | */
16 | public interface GreetingService {
17 |
18 | /**
19 | * Find all Greeting entities.
20 | * @return A Collection of Greeting objects.
21 | */
22 | Collection findAll();
23 |
24 | /**
25 | * Find a single Greeting entity by primary key identifier.
26 | * @param id A Long primary key identifier.
27 | * @return A Greeting or null
if none found.
28 | */
29 | Greeting findOne(Long id);
30 |
31 | /**
32 | * Persists a Greeting entity in the data store.
33 | * @param greeting A Greeting object to be persisted.
34 | * @return The persisted Greeting entity.
35 | */
36 | Greeting create(Greeting greeting);
37 |
38 | /**
39 | * Updates a previously persisted Greeting entity in the data store.
40 | * @param greeting A Greeting object to be updated.
41 | * @return The updated Greeting entity.
42 | */
43 | Greeting update(Greeting greeting);
44 |
45 | /**
46 | * Removes a previously persisted Greeting entity from the data store.
47 | * @param id A Long primary key identifier.
48 | */
49 | void delete(Long id);
50 |
51 | /**
52 | * Evicts all members of the "greetings" cache.
53 | */
54 | void evictCache();
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/service/GreetingServiceBean.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.service;
2 |
3 | import java.util.Collection;
4 |
5 | import javax.persistence.EntityExistsException;
6 | import javax.persistence.NoResultException;
7 |
8 | import org.example.ws.model.Greeting;
9 | import org.example.ws.repository.GreetingRepository;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.boot.actuate.metrics.CounterService;
14 | import org.springframework.cache.annotation.CacheEvict;
15 | import org.springframework.cache.annotation.CachePut;
16 | import org.springframework.cache.annotation.Cacheable;
17 | import org.springframework.stereotype.Service;
18 | import org.springframework.transaction.annotation.Propagation;
19 | import org.springframework.transaction.annotation.Transactional;
20 |
21 | /**
22 | * The GreetingServiceBean encapsulates all business behaviors operating on the
23 | * Greeting entity model object.
24 | *
25 | * @author Matt Warman
26 | */
27 | @Service
28 | @Transactional(
29 | propagation = Propagation.SUPPORTS,
30 | readOnly = true)
31 | public class GreetingServiceBean implements GreetingService {
32 |
33 | private Logger logger = LoggerFactory.getLogger(this.getClass());
34 |
35 | /**
36 | * The CounterService
captures metrics for Spring Actuator.
37 | */
38 | @Autowired
39 | private CounterService counterService;
40 |
41 | /**
42 | * The Spring Data repository for Greeting entities.
43 | */
44 | @Autowired
45 | private GreetingRepository greetingRepository;
46 |
47 | @Override
48 | public Collection findAll() {
49 | logger.info("> findAll");
50 |
51 | counterService.increment("method.invoked.greetingServiceBean.findAll");
52 |
53 | Collection greetings = greetingRepository.findAll();
54 |
55 | logger.info("< findAll");
56 | return greetings;
57 | }
58 |
59 | @Override
60 | @Cacheable(
61 | value = "greetings",
62 | key = "#id")
63 | public Greeting findOne(Long id) {
64 | logger.info("> findOne id:{}", id);
65 |
66 | counterService.increment("method.invoked.greetingServiceBean.findOne");
67 |
68 | Greeting greeting = greetingRepository.findOne(id);
69 |
70 | logger.info("< findOne id:{}", id);
71 | return greeting;
72 | }
73 |
74 | @Override
75 | @Transactional(
76 | propagation = Propagation.REQUIRED,
77 | readOnly = false)
78 | @CachePut(
79 | value = "greetings",
80 | key = "#result.id")
81 | public Greeting create(Greeting greeting) {
82 | logger.info("> create");
83 |
84 | counterService.increment("method.invoked.greetingServiceBean.create");
85 |
86 | // Ensure the entity object to be created does NOT exist in the
87 | // repository. Prevent the default behavior of save() which will update
88 | // an existing entity if the entity matching the supplied id exists.
89 | if (greeting.getId() != null) {
90 | // Cannot create Greeting with specified ID value
91 | logger.error(
92 | "Attempted to create a Greeting, but id attribute was not null.");
93 | throw new EntityExistsException(
94 | "The id attribute must be null to persist a new entity.");
95 | }
96 |
97 | Greeting savedGreeting = greetingRepository.save(greeting);
98 |
99 | logger.info("< create");
100 | return savedGreeting;
101 | }
102 |
103 | @Override
104 | @Transactional(
105 | propagation = Propagation.REQUIRED,
106 | readOnly = false)
107 | @CachePut(
108 | value = "greetings",
109 | key = "#greeting.id")
110 | public Greeting update(Greeting greeting) {
111 | logger.info("> update id:{}", greeting.getId());
112 |
113 | counterService.increment("method.invoked.greetingServiceBean.update");
114 |
115 | // Ensure the entity object to be updated exists in the repository to
116 | // prevent the default behavior of save() which will persist a new
117 | // entity if the entity matching the id does not exist
118 | Greeting greetingToUpdate = findOne(greeting.getId());
119 | if (greetingToUpdate == null) {
120 | // Cannot update Greeting that hasn't been persisted
121 | logger.error(
122 | "Attempted to update a Greeting, but the entity does not exist.");
123 | throw new NoResultException("Requested entity not found.");
124 | }
125 |
126 | greetingToUpdate.setText(greeting.getText());
127 | Greeting updatedGreeting = greetingRepository.save(greetingToUpdate);
128 |
129 | logger.info("< update id:{}", greeting.getId());
130 | return updatedGreeting;
131 | }
132 |
133 | @Override
134 | @Transactional(
135 | propagation = Propagation.REQUIRED,
136 | readOnly = false)
137 | @CacheEvict(
138 | value = "greetings",
139 | key = "#id")
140 | public void delete(Long id) {
141 | logger.info("> delete id:{}", id);
142 |
143 | counterService.increment("method.invoked.greetingServiceBean.delete");
144 |
145 | greetingRepository.delete(id);
146 |
147 | logger.info("< delete id:{}", id);
148 | }
149 |
150 | @Override
151 | @CacheEvict(
152 | value = "greetings",
153 | allEntries = true)
154 | public void evictCache() {
155 | logger.info("> evictCache");
156 | logger.info("< evictCache");
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/service/quote/tss/Quote.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.service.quote.tss;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Date;
5 |
6 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
7 |
8 | /**
9 | * The Quote class models the attributes of a Quote, a famous or meaningful
10 | * phrase.
11 | *
12 | * @author Matt Warman
13 | */
14 | @JsonIgnoreProperties(
15 | ignoreUnknown = true)
16 | public class Quote {
17 |
18 | /**
19 | * A uniquie identifier.
20 | */
21 | private String id;
22 | /**
23 | * The quote, or quote category, title.
24 | */
25 | private String title;
26 | /**
27 | * The quote text.
28 | */
29 | private String quote;
30 | /**
31 | * The length of the quote text.
32 | */
33 | private String length;
34 | /**
35 | * The date the quote was provided by the external service.
36 | */
37 | private Date date;
38 | /**
39 | * The person or entity to whom the quote is attributed.
40 | */
41 | private String author;
42 | /**
43 | * An image associated with the quote text.
44 | */
45 | private String background;
46 | /**
47 | * The category or general grouping to which the quote text belongs.
48 | */
49 | private String category;
50 | /**
51 | * An array of tags, often the categories, which are applicable to the quote
52 | * text.
53 | */
54 | private ArrayList tags;
55 |
56 | /**
57 | * Construct a Quote instance.
58 | */
59 | public Quote() {
60 |
61 | }
62 |
63 | public String getId() {
64 | return id;
65 | }
66 |
67 | public void setId(String id) {
68 | this.id = id;
69 | }
70 |
71 | public String getTitle() {
72 | return title;
73 | }
74 |
75 | public void setTitle(String title) {
76 | this.title = title;
77 | }
78 |
79 | public String getQuote() {
80 | return quote;
81 | }
82 |
83 | public void setQuote(String quote) {
84 | this.quote = quote;
85 | }
86 |
87 | public String getLength() {
88 | return length;
89 | }
90 |
91 | public void setLength(String length) {
92 | this.length = length;
93 | }
94 |
95 | public Date getDate() {
96 | return date;
97 | }
98 |
99 | public void setDate(Date date) {
100 | this.date = date;
101 | }
102 |
103 | public String getAuthor() {
104 | return author;
105 | }
106 |
107 | public void setAuthor(String author) {
108 | this.author = author;
109 | }
110 |
111 | public String getBackground() {
112 | return background;
113 | }
114 |
115 | public void setBackground(String background) {
116 | this.background = background;
117 | }
118 |
119 | public String getCategory() {
120 | return category;
121 | }
122 |
123 | public void setCategory(String category) {
124 | this.category = category;
125 | }
126 |
127 | public ArrayList getTags() {
128 | return tags;
129 | }
130 |
131 | public void setTags(ArrayList tags) {
132 | this.tags = tags;
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/service/quote/tss/QuoteResponse.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.service.quote.tss;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4 |
5 | /**
6 | * The QuoteResponse class models the attributes of a response from the "They
7 | * Said So" (TSS) remote Quote API. This class represents the top-level model of
8 | * the API response.
9 | *
10 | * @author Matt Warman
11 | */
12 | @JsonIgnoreProperties(
13 | ignoreUnknown = true)
14 | public class QuoteResponse {
15 |
16 | /**
17 | * The success or failure status of the API call.
18 | */
19 | private QuoteResponseSuccess success;
20 | /**
21 | * The body of the API call response.
22 | */
23 | private QuoteResponseContents contents;
24 |
25 | /**
26 | * Construct a new QuoteResponse.
27 | */
28 | public QuoteResponse() {
29 |
30 | }
31 |
32 | /**
33 | * Returns the QuoteResponseSuccess object.
34 | * @return A QuoteResponseSuccess object.
35 | */
36 | public QuoteResponseSuccess getSuccess() {
37 | return success;
38 | }
39 |
40 | /**
41 | * Sets the QuoteResponseSuccess object.
42 | * @param success A QuoteResponseSuccess object.
43 | */
44 | public void setSuccess(QuoteResponseSuccess success) {
45 | this.success = success;
46 | }
47 |
48 | /**
49 | * A helper method which examines the internal value of the
50 | * QuoteResponseSuccess object and returns a boolean indicating the success
51 | * or failure of the API call.
52 | * @return A boolean whose value is true
if the API call was
53 | * successful, otherwise returns false
.
54 | */
55 | public boolean isSuccess() {
56 | if (success != null && success.getTotal() > 0) {
57 | return true;
58 | }
59 | return false;
60 | }
61 |
62 | /**
63 | * Returns the QuoteResponseContents object.
64 | * @return A QuoteResponseContents object.
65 | */
66 | public QuoteResponseContents getContents() {
67 | return contents;
68 | }
69 |
70 | /**
71 | * Sets the QuoteResponseContents object.
72 | * @param contents A QuoteResponseContents object.
73 | */
74 | public void setContents(QuoteResponseContents contents) {
75 | this.contents = contents;
76 | }
77 |
78 | /**
79 | * A helper method which examines the internal value of the
80 | * QuoteResponseContents object and returns the first Quote object from the
81 | * Collection if the API call was successful and the Collection is not
82 | * empty.
83 | * @return A Quote object or null
.
84 | */
85 | public Quote getQuote() {
86 | if (isSuccess()) {
87 | if (contents != null && contents.getQuotes().size() > 0) {
88 | return contents.getQuotes().get(0);
89 | }
90 | }
91 | return null;
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/service/quote/tss/QuoteResponseContents.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.service.quote.tss;
2 |
3 | import java.util.ArrayList;
4 |
5 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
6 |
7 | /**
8 | * The QuoteResponseContents class is a container for the body of a response
9 | * from the "They Said So" (TSS) remote Quote API. This class holds the
10 | * object(s) returned from the call.
11 | *
12 | * @author Matt Warman
13 | */
14 | @JsonIgnoreProperties(
15 | ignoreUnknown = true)
16 | public class QuoteResponseContents {
17 |
18 | /**
19 | * An array of Quote objects.
20 | */
21 | private ArrayList quotes = new ArrayList(0);
22 |
23 | /**
24 | * Contructs a new QuoteResponseContents object.
25 | */
26 | public QuoteResponseContents() {
27 |
28 | }
29 |
30 | /**
31 | * Returns the array of Quote objects.
32 | * @return An array of Quote objects.
33 | */
34 | public ArrayList getQuotes() {
35 | return quotes;
36 | }
37 |
38 | /**
39 | * Sets the array of Quote objects.
40 | * @param quotes An array of Quote objects.
41 | */
42 | public void setQuotes(ArrayList quotes) {
43 | if (quotes == null) {
44 | this.quotes = new ArrayList(0);
45 | }
46 | this.quotes = quotes;
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/service/quote/tss/QuoteResponseSuccess.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.service.quote.tss;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4 |
5 | /**
6 | * The QuoteResponseSuccess class models the invocation status attributes of a
7 | * response from the "They Said So" (TSS) remote Quote API. This class
8 | * represents the pass-fail indicator model of the API response.
9 | *
10 | * @author Matt Warman
11 | */
12 | @JsonIgnoreProperties(
13 | ignoreUnknown = true)
14 | public class QuoteResponseSuccess {
15 |
16 | /**
17 | * The total number of objects contained in the response.
18 | */
19 | private int total;
20 |
21 | /**
22 | * Contruct a new QuoteResponseSuccess object.
23 | */
24 | public QuoteResponseSuccess() {
25 |
26 | }
27 |
28 | /**
29 | * Returns the value of the total attribute.
30 | * @return An int value.
31 | */
32 | public int getTotal() {
33 | return total;
34 | }
35 |
36 | /**
37 | * Sets the value of the total attribute.
38 | * @param total An int value.
39 | */
40 | public void setTotal(int total) {
41 | this.total = total;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/service/quote/tss/QuoteService.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.service.quote.tss;
2 |
3 | /**
4 | * The QuoteService interface defines all public business behaviors for
5 | * operations Quote objects.
6 | *
7 | * This interface should be injected into QuoteService clients, not the
8 | * implementation bean.
9 | *
10 | * @author Matt Warman
11 | */
12 | public interface QuoteService {
13 |
14 | /**
15 | * The 'inspirational' Quote category value.
16 | */
17 | String CATEGORY_INSPIRATIONAL = "inspire";
18 |
19 | /**
20 | * Retrieves the Quote of the day. A daily rotating Quote object.
21 | * @param category An optional String value of a Quote category to retrieve.
22 | * If not specified, the default category value is used.
23 | * @return A Quote object or null
if none found.
24 | */
25 | Quote getDaily(String category);
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/service/quote/tss/QuoteServiceBean.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.service.quote.tss;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.boot.actuate.metrics.CounterService;
7 | import org.springframework.boot.web.client.RestTemplateBuilder;
8 | import org.springframework.stereotype.Service;
9 | import org.springframework.web.client.RestTemplate;
10 |
11 | /**
12 | * The QuoteServiceBean encapsulates all business behaviors operating on the
13 | * Quote class.
14 | *
15 | * @author Matt Warman
16 | */
17 | @Service
18 | public class QuoteServiceBean implements QuoteService {
19 |
20 | /**
21 | * The Logger for this class.
22 | */
23 | private Logger logger = LoggerFactory.getLogger(this.getClass());
24 |
25 | /**
26 | * The CounterService
captures metrics for Spring Actuator.
27 | */
28 | @Autowired
29 | private CounterService counterService;
30 |
31 | /**
32 | * The RestTemplate used to retrieve data from the remote Quote API.
33 | */
34 | private final RestTemplate restTemplate;
35 |
36 | /**
37 | * Construct a QuoteServiceBean with a RestTemplateBuilder used to
38 | * instantiate the RestTemplate used by this business service.
39 | * @param restTemplateBuilder A RestTemplateBuilder injected from the
40 | * ApplicationContext.
41 | */
42 | public QuoteServiceBean(RestTemplateBuilder restTemplateBuilder) {
43 | this.restTemplate = restTemplateBuilder.build();
44 | }
45 |
46 | @Override
47 | public Quote getDaily(String category) {
48 | logger.info("> getDaily");
49 |
50 | counterService.increment("method.invoked.quoteServiceBean.getDaily");
51 |
52 | String quoteCategory = QuoteService.CATEGORY_INSPIRATIONAL;
53 | if (category != null && category.trim().length() > 0) {
54 | quoteCategory = category.trim();
55 | }
56 |
57 | QuoteResponse quoteResponse = this.restTemplate.getForObject(
58 | "http://quotes.rest/qod.json?category={cat}",
59 | QuoteResponse.class, quoteCategory);
60 |
61 | Quote quote = quoteResponse.getQuote();
62 |
63 | logger.info("< getDaily");
64 | return quote;
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/util/AsyncResponse.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.util;
2 |
3 | import java.util.concurrent.CancellationException;
4 | import java.util.concurrent.ExecutionException;
5 | import java.util.concurrent.Future;
6 | import java.util.concurrent.TimeUnit;
7 | import java.util.concurrent.TimeoutException;
8 |
9 | /**
10 | * The AsyncResponse class implements the Future interface. This class
11 | * facilitates the normal and exceptional completion of asynchronous tasks (or
12 | * methods) and wraps their response.
13 | *
14 | * The AsyncResponse class seeks to mimic some behaviors defined in the
15 | * CompletableFuture class provided in JDK version 8. If using JDK 7 or earlier,
16 | * the AsyncResponse class is a suitable substitute for CompletableFuture.
17 | *
18 | * @author Matt Warman
19 | *
20 | * @param The type of Value object wrapped and returned by the
21 | * AsyncResponse.
22 | */
23 | public class AsyncResponse implements Future {
24 |
25 | /**
26 | * Indicates the block operation should run indefinitely until the
27 | * AsyncResponse state changes.
28 | */
29 | private static final long BLOCK_INDEFINITELY = 0;
30 |
31 | /**
32 | * The value returned from the task.
33 | */
34 | private V value;
35 | /**
36 | * The exception, if any, thrown by the task.
37 | */
38 | private Exception executionException;
39 | /**
40 | * TRUE if the task throws an Exception. Otherwise FALSE.
41 | */
42 | private boolean isCompletedExceptionally = false;
43 | /**
44 | * TRUE when the task is cancelled or interrupted. Otherwise FALSE.
45 | */
46 | private boolean isCancelled = false;
47 | /**
48 | * TRUE when the task is complete. Otherwise FALSE.
49 | */
50 | private boolean isDone = false;
51 | /**
52 | * The interval, in milliseconds, which any get
method checks
53 | * if the task is complete. Default: 100 milliseconds.
54 | */
55 | private long checkCompletedInterval = 100;
56 |
57 | /**
58 | * Create a new AsyncResponse which has no value and is not complete.
59 | */
60 | public AsyncResponse() {
61 |
62 | }
63 |
64 | /**
65 | * Create a new, completed AsyncResponse with the supplied value.
66 | * @param val An object of type V used as the task response value.
67 | */
68 | public AsyncResponse(V val) {
69 | this.value = val;
70 | this.isDone = true;
71 | }
72 |
73 | /**
74 | * Create a new, completed AsyncResponse with the supplied Exception. The
75 | * AsyncResponse is marked as completed exceptionally. When the client
76 | * invokes one of the get
methods, an ExecutionException will
77 | * be thrown using the supplied Exception as the cause of the
78 | * ExecutionException.
79 | *
80 | * @param ex A Throwable.
81 | */
82 | public AsyncResponse(Throwable ex) {
83 | this.executionException = new ExecutionException(ex);
84 | this.isCompletedExceptionally = true;
85 | this.isDone = true;
86 | }
87 |
88 | @Override
89 | public boolean cancel(boolean mayInterruptIfRunning) {
90 | this.isCancelled = true;
91 | this.isDone = true;
92 |
93 | return false;
94 | }
95 |
96 | @Override
97 | public boolean isCancelled() {
98 | return this.isCancelled;
99 | }
100 |
101 | public boolean isCompletedExceptionally() {
102 | return this.isCompletedExceptionally;
103 | }
104 |
105 | @Override
106 | public boolean isDone() {
107 | return this.isDone;
108 | }
109 |
110 | @Override
111 | public V get() throws InterruptedException, ExecutionException {
112 |
113 | block(BLOCK_INDEFINITELY);
114 |
115 | if (isCancelled()) {
116 | throw new CancellationException();
117 | }
118 | if (isCompletedExceptionally()) {
119 | throw new ExecutionException(this.executionException);
120 | }
121 | if (isDone()) {
122 | return this.value;
123 | }
124 |
125 | throw new InterruptedException();
126 | }
127 |
128 | @Override
129 | public V get(long timeout, TimeUnit unit)
130 | throws InterruptedException, ExecutionException, TimeoutException {
131 |
132 | long timeoutInMillis = unit.toMillis(timeout);
133 | block(timeoutInMillis);
134 |
135 | if (isCancelled()) {
136 | throw new CancellationException();
137 | }
138 | if (isCompletedExceptionally()) {
139 | throw new ExecutionException(this.executionException);
140 | }
141 | if (isDone()) {
142 | return this.value;
143 | }
144 |
145 | throw new InterruptedException();
146 | }
147 |
148 | /**
149 | * Mark this AsyncResponse as finished (completed) and set the supplied
150 | * value V as the task return value.
151 | * @param val An object of type V.
152 | * @return A boolean that when TRUE indicates the AsyncResponse state was
153 | * successfully updated. A response of FALSE indicates the
154 | * AsyncResponse state could not be set correctly.
155 | */
156 | public boolean complete(V val) {
157 | this.value = val;
158 | this.isDone = true;
159 |
160 | return true;
161 | }
162 |
163 | /**
164 | * Mark this AsyncResposne as finished (completed) with an exception. The
165 | * AsyncResponse value (V) is set to null. The supplied Throwable will be
166 | * used as the Cause of an ExceptionException thrown when any
167 | * get
method is called.
168 | *
169 | * @param ex A Throwable.
170 | * @return A boolean that when TRUE indicates the AsyncResponse state was
171 | * successfully updated. A response of FALSE indicates the
172 | * AsyncResponse state could not be set correctly.
173 | */
174 | public boolean completeExceptionally(Throwable ex) {
175 | this.value = null;
176 | this.executionException = new ExecutionException(ex);
177 | this.isCompletedExceptionally = true;
178 | this.isDone = true;
179 |
180 | return true;
181 | }
182 |
183 | /**
184 | * Set the interval at which any get
method evaluates if the
185 | * AsyncResponse is complete or cancelled.
186 | * @param millis A long number of milliseconds.
187 | */
188 | public void setCheckCompletedInterval(long millis) {
189 | this.checkCompletedInterval = millis;
190 | }
191 |
192 | /**
193 | * Pauses the current thread until the AsyncResponse is in a completed or
194 | * cancelled status OR the specified timeout (in milliseconds) has elapsed.
195 | * If the timeout value is zero (0), then wait indefinitely for the
196 | * AsyncResponse to be completed or cancelled.
197 | *
198 | * @param timeout A long number of milliseconds after which the process
199 | * ceases to wait for state change.
200 | * @throws InterruptedException Thrown when the blocking operation is
201 | * interrupted.
202 | */
203 | private void block(long timeout) throws InterruptedException {
204 | long start = System.currentTimeMillis();
205 |
206 | // Block until done, cancelled, or the timeout is exceeded
207 | while (!isDone() && !isCancelled()) {
208 | if (timeout > BLOCK_INDEFINITELY) {
209 | long now = System.currentTimeMillis();
210 | if (now > start + timeout) {
211 | break;
212 | }
213 | }
214 | Thread.sleep(checkCompletedInterval);
215 | }
216 | }
217 |
218 | }
219 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/web/DefaultExceptionAttributes.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.web;
2 |
3 | import java.util.Date;
4 | import java.util.LinkedHashMap;
5 | import java.util.Map;
6 |
7 | import javax.servlet.http.HttpServletRequest;
8 |
9 | import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;
10 | import org.springframework.http.HttpStatus;
11 | import org.springframework.web.context.request.RequestAttributes;
12 |
13 | /**
14 | * The default implementation of {@link ExceptionAttributes}. This
15 | * implementation seeks to be similar to the {@link DefaultErrorAttributes}
16 | * class, but differs in the source of the attribute data. The
17 | * DefaultErrorAttributes class requires the exception to be thrown from the
18 | * Controller so that it may gather attribute values from
19 | * {@link RequestAttributes}. This class uses the {@link Exception},
20 | * {@link HttpServletRequest}, and {@link HttpStatus} values.
21 | *
22 | * Provides a Map of the following attributes when they are available:
23 | *
24 | * - timestamp - The time that the exception attributes were processed
25 | *
- status - The HTTP status code in the response
26 | *
- error - The HTTP status reason text
27 | *
- exception - The class name of the Exception
28 | *
- message - The Exception message
29 | *
- path - The HTTP request servlet path when the exception was thrown
30 | *
31 | *
32 | * @author Matt Warman
33 | * @see ExceptionAttributes
34 | *
35 | */
36 | public class DefaultExceptionAttributes implements ExceptionAttributes {
37 |
38 | /**
39 | * The timestamp attribute key.
40 | */
41 | public static final String TIMESTAMP = "timestamp";
42 | /**
43 | * The status attribute key.
44 | */
45 | public static final String STATUS = "status";
46 | /**
47 | * The error attribute key.
48 | */
49 | public static final String ERROR = "error";
50 | /**
51 | * The exception attribute key.
52 | */
53 | public static final String EXCEPTION = "exception";
54 | /**
55 | * The message attribute key.
56 | */
57 | public static final String MESSAGE = "message";
58 | /**
59 | * The path attribute key.
60 | */
61 | public static final String PATH = "path";
62 |
63 | @Override
64 | public Map getExceptionAttributes(Exception exception,
65 | HttpServletRequest httpRequest, HttpStatus httpStatus) {
66 |
67 | Map exceptionAttributes = new LinkedHashMap();
68 |
69 | exceptionAttributes.put(TIMESTAMP, new Date());
70 | addHttpStatus(exceptionAttributes, httpStatus);
71 | addExceptionDetail(exceptionAttributes, exception);
72 | addPath(exceptionAttributes, httpRequest);
73 |
74 | return exceptionAttributes;
75 | }
76 |
77 | /**
78 | * Adds the status and error attribute values from the {@link HttpStatus}
79 | * value.
80 | * @param exceptionAttributes The Map of exception attributes.
81 | * @param httpStatus The HttpStatus enum value.
82 | */
83 | private void addHttpStatus(Map exceptionAttributes,
84 | HttpStatus httpStatus) {
85 | exceptionAttributes.put(STATUS, httpStatus.value());
86 | exceptionAttributes.put(ERROR, httpStatus.getReasonPhrase());
87 | }
88 |
89 | /**
90 | * Adds the exception and message attribute values from the
91 | * {@link Exception}.
92 | * @param exceptionAttributes The Map of exception attributes.
93 | * @param exception The Exception object.
94 | */
95 | private void addExceptionDetail(Map exceptionAttributes,
96 | Exception exception) {
97 | exceptionAttributes.put(EXCEPTION, exception.getClass().getName());
98 | exceptionAttributes.put(MESSAGE, exception.getMessage());
99 | }
100 |
101 | /**
102 | * Adds the path attribute value from the {@link HttpServletRequest}.
103 | * @param exceptionAttributes The Map of exception attributes.
104 | * @param httpRequest The HttpServletRequest object.
105 | */
106 | private void addPath(Map exceptionAttributes,
107 | HttpServletRequest httpRequest) {
108 | exceptionAttributes.put(PATH, httpRequest.getServletPath());
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/web/ExceptionAttributes.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.web;
2 |
3 | import java.util.Map;
4 |
5 | import javax.servlet.http.HttpServletRequest;
6 | import javax.servlet.http.HttpServletResponse;
7 |
8 | import org.springframework.http.HttpStatus;
9 | import org.springframework.web.bind.annotation.ResponseBody;
10 |
11 | /**
12 | * The ExceptionAttributes interface defines the behavioral contract to be
13 | * implemented by concrete ExceptionAttributes classes.
14 | *
15 | * Provides attributes which describe Exceptions and the context in which they
16 | * occurred.
17 | *
18 | * @author Matt Warman
19 | * @see DefaultExceptionAttributes
20 | *
21 | */
22 | public interface ExceptionAttributes {
23 |
24 | /**
25 | * Returns a {@link Map} of exception attributes. The Map may be used to
26 | * display an error page or serialized into a {@link ResponseBody}.
27 | *
28 | * @param exception The Exception reported.
29 | * @param httpRequest The HttpServletRequest in which the Exception
30 | * occurred.
31 | * @param httpStatus The HttpStatus value that will be used in the
32 | * {@link HttpServletResponse}.
33 | * @return A Map of exception attributes.
34 | */
35 | Map getExceptionAttributes(Exception exception,
36 | HttpServletRequest httpRequest, HttpStatus httpStatus);
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/web/api/BaseController.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.web.api;
2 |
3 | import java.util.Map;
4 |
5 | import javax.persistence.NoResultException;
6 | import javax.servlet.http.HttpServletRequest;
7 |
8 | import org.example.ws.web.DefaultExceptionAttributes;
9 | import org.example.ws.web.ExceptionAttributes;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 | import org.springframework.http.HttpStatus;
13 | import org.springframework.http.ResponseEntity;
14 | import org.springframework.web.bind.annotation.ExceptionHandler;
15 |
16 | /**
17 | * The BaseController class implements common functionality for all Controller
18 | * classes. The @ExceptionHandler
methods provide a consistent
19 | * response when Exceptions are thrown from @RequestMapping
20 | * annotated Controller methods.
21 | *
22 | * @author Matt Warman
23 | */
24 | public class BaseController {
25 |
26 | /**
27 | * The Logger for this class.
28 | */
29 | protected Logger logger = LoggerFactory.getLogger(this.getClass());
30 |
31 | /**
32 | * Handles JPA NoResultExceptions thrown from web service controller
33 | * methods. Creates a response with Exception Attributes as JSON and HTTP
34 | * status code 404, not found.
35 | *
36 | * @param noResultException A NoResultException instance.
37 | * @param request The HttpServletRequest in which the NoResultException was
38 | * raised.
39 | * @return A ResponseEntity containing the Exception Attributes in the body
40 | * and HTTP status code 404.
41 | */
42 | @ExceptionHandler(NoResultException.class)
43 | public ResponseEntity