├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── org
│ │ └── example
│ │ └── ws
│ │ ├── Application.java
│ │ ├── SecurityConfiguration.java
│ │ ├── actuator
│ │ └── health
│ │ │ └── GreetingHealthIndicator.java
│ │ ├── batch
│ │ └── GreetingBatchBean.java
│ │ ├── model
│ │ ├── Account.java
│ │ ├── Greeting.java
│ │ ├── ReferenceEntity.java
│ │ ├── Role.java
│ │ └── TransactionalEntity.java
│ │ ├── repository
│ │ ├── AccountRepository.java
│ │ ├── GreetingRepository.java
│ │ └── RoleRepository.java
│ │ ├── security
│ │ ├── AccountAuthenticationProvider.java
│ │ └── AccountUserDetailsService.java
│ │ ├── service
│ │ ├── AccountService.java
│ │ ├── AccountServiceBean.java
│ │ ├── EmailService.java
│ │ ├── EmailServiceBean.java
│ │ ├── GreetingService.java
│ │ └── GreetingServiceBean.java
│ │ ├── util
│ │ ├── AsyncResponse.java
│ │ └── RequestContext.java
│ │ └── web
│ │ ├── DefaultExceptionAttributes.java
│ │ ├── ExceptionAttributes.java
│ │ ├── api
│ │ ├── BaseController.java
│ │ ├── GreetingController.java
│ │ └── RoleController.java
│ │ └── filter
│ │ └── RequestContextInitializationFilter.java
└── resources
│ ├── config
│ ├── application-batch.properties
│ ├── application-hsqldb.properties
│ ├── application-mysql.properties
│ └── application.properties
│ └── data
│ ├── changelog
│ ├── db.changelog-0.0.1.xml
│ ├── db.changelog-0.1.0.xml
│ └── db.changelog-master.xml
│ ├── hsqldb
│ └── migrations
│ │ ├── V0_0_1__initialize.sql
│ │ └── V0_1_0__migration.sql
│ └── mysql
│ └── migrations
│ ├── V0_0_1__initialize.sql
│ └── V0_1_0__migration.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 |
7 | # Generated Directories and Files
8 | /target
9 |
10 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
11 | hs_err_pid*
12 |
--------------------------------------------------------------------------------
/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 Data 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 Data Fundamentals](https://www.youtube.com/playlist?list=PLGDwUiT1wr693flGbjtm0WoB_722X6lNc).
14 |
15 | ### Repository Organization
16 |
17 | Each episode of the Spring Data Fundamentals video series has a corresponding branch in this repository. For example, all of the source code illustrated in the episode entitled [Abstracting Common Transactional Attributes into a Mapped Superclass with Spring Data JPA](https://youtu.be/_s6THdyyfN8?list=PLGDwUiT1wr693flGbjtm0WoB_722X6lNc) may be found on the repository branch named [transactional-entity](https://github.com/mwarman/spring-data-fundamentals/tree/transactional-entity).
18 |
19 | ### Branches
20 |
21 | #### transactional-entity
22 |
23 | The branch named `transactional-entity` contains the source code illustrated in the episode [Abstracting Common Transactional Attributes into a Mapped Superclass with Spring Data JPA](https://youtu.be/_s6THdyyfN8?list=PLGDwUiT1wr693flGbjtm0WoB_722X6lNc).
24 |
25 | #### reference-entity
26 |
27 | The branch named `reference-entity` contains the source code illustrated in the episode [Abstracting Common Reference Data Attributes into a Mapped Superclass with Spring Data JPA](https://youtu.be/xfgwrJmF8nY?list=PLGDwUiT1wr693flGbjtm0WoB_722X6lNc).
28 |
29 | #### joda
30 |
31 | The branch named `joda` contains the source code illustrated in the episode [Using JODA Framework Classes as JPA Entity Model Attributes with Spring Data JPA](https://youtu.be/OcKtf_-K5cc?list=PLGDwUiT1wr693flGbjtm0WoB_722X6lNc).
32 |
33 | #### jpa-query-definition
34 |
35 | The branch named `jpa-query-definition` contains the source code illustrated in the episode [Exploring Spring Data JPA Query Definition Strategies](https://youtu.be/S5vZP_03ENY?list=PLGDwUiT1wr693flGbjtm0WoB_722X6lNc).
36 |
37 | #### jpa-mysql
38 |
39 | The branch named `jpa-mysql` contains the source code illustrated in the episode [Using a MySQL Database with Spring Data JPA](https://youtu.be/wjpeKiTiuRE?list=PLGDwUiT1wr693flGbjtm0WoB_722X6lNc).
40 |
41 | #### flyway
42 |
43 | The branch named `flyway` contains the source code illustrated in the episode [Using Flyway with Spring Boot for Database Migrations](https://youtu.be/5JUJHHc4KZc?list=PLGDwUiT1wr693flGbjtm0WoB_722X6lNc).
44 |
45 | #### liquibase
46 |
47 | The branch named `liquibase` contains the source code illustrated in the episode [Using Liquibase with Spring Boot for Database Migrations](https://youtu.be/7VeODrRkHXg?list=PLGDwUiT1wr693flGbjtm0WoB_722X6lNc).
48 |
49 |
50 | ## Languages
51 |
52 | This project is authored in Java.
53 |
54 | ## Installation
55 |
56 | ### Fork the Repository
57 |
58 | Fork the [Spring Data Fundamentals](https://github.com/mwarman/spring-data-fundamentals) repository on GitHub. Clone the project to your host machine.
59 |
60 | ### Dependencies
61 |
62 | The project requires the following dependencies be installed on the host machine:
63 |
64 | * Java Development Kit 8 or later
65 | * Apache Maven 3 or later
66 |
67 | ## Running
68 |
69 | The project uses [Maven](http://maven.apache.org/) for build, package, and test workflow automation. The following Maven goals are the most commonly used.
70 |
71 | ### spring-boot:run
72 |
73 | The `spring-boot:run` Maven goal performs the following workflow steps:
74 |
75 | * compiles Java classes to the /target directory
76 | * copies all resources to the /target directory
77 | * starts an embedded Apache Tomcat server
78 |
79 | To execute the `spring-boot:run` Maven goal, type the following command at a terminal prompt in the project base directory.
80 |
81 | ```
82 | mvn spring-boot:run
83 | ```
84 |
85 | Type `ctrl-C` to halt the web server.
86 |
87 | This goal is used for local machine development and functional testing. Use the `package` goal for server deployment.
88 |
89 | ### test
90 |
91 | The `test` Maven goal performs the following workflow steps:
92 |
93 | * compiles Java classes to the /target directory
94 | * copies all resources to the /target directory
95 | * executes the unit test suites
96 | * produces unit test reports
97 |
98 | 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.
99 |
100 | To execute the `test` Maven goal, type the following command at a terminal prompt in the project base directory.
101 |
102 | ```
103 | mvn clean test
104 | ```
105 |
106 | ### package
107 |
108 | The `package` Maven goal performs the following workflow steps:
109 |
110 | * compiles Java classes to the /target directory
111 | * copies all resources to the /target directory
112 | * executes the unit test suites
113 | * produces unit test reports
114 | * prepares an executable JAR file in the /target directory
115 |
116 | 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.
117 |
118 | To execute the `package` goal, type the following command at a terminal prompt in the project base directory.
119 |
120 | ```
121 | mvn clean package
122 | ```
123 |
124 | 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:
125 |
126 | ```
127 | java -jar example-1.0.0.jar
128 | ```
129 |
130 | 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:
131 |
132 | ```
133 | java -jar example-1.0.0.jar --spring.profiles.active=mysql,batch
134 | ```
135 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | 4.0.0
5 |
6 | org.example
7 | spring-data-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.jadira.usertype
41 | usertype.extended
42 | 5.0.0.GA
43 |
44 |
45 | org.flywaydb
46 | flyway-core
47 |
48 |
49 | org.liquibase
50 | liquibase-core
51 |
52 |
53 | org.hsqldb
54 | hsqldb
55 | runtime
56 |
57 |
58 | mysql
59 | mysql-connector-java
60 | runtime
61 |
62 |
63 |
64 |
65 | org.springframework
66 | spring-context-support
67 |
68 |
69 | com.github.ben-manes.caffeine
70 | caffeine
71 |
72 |
73 |
74 |
75 | org.springframework.boot
76 | spring-boot-starter-actuator
77 |
78 |
79 |
80 |
81 | com.google.guava
82 | guava
83 | 19.0
84 |
85 |
86 | joda-time
87 | joda-time
88 |
89 |
90 | com.fasterxml.jackson.datatype
91 | jackson-datatype-joda
92 |
93 |
94 |
95 |
96 | org.springframework.boot
97 | spring-boot-starter-test
98 | test
99 |
100 |
101 |
102 |
103 |
104 |
105 | org.springframework.boot
106 | spring-boot-maven-plugin
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/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/SecurityConfiguration.java:
--------------------------------------------------------------------------------
1 | package org.example.ws;
2 |
3 | import org.example.ws.security.AccountAuthenticationProvider;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.core.annotation.Order;
8 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
12 | import org.springframework.security.config.http.SessionCreationPolicy;
13 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
14 | import org.springframework.security.crypto.password.PasswordEncoder;
15 |
16 | /**
17 | * The SecurityConfiguration class provides a centralized location for
18 | * application security configuration. This class bootstraps the Spring Security
19 | * components during application startup.
20 | *
21 | * @author Matt Warman
22 | */
23 | @Configuration
24 | @EnableWebSecurity
25 | public class SecurityConfiguration {
26 |
27 | /**
28 | * The AccountAuthenticationProvider security component.
29 | */
30 | @Autowired
31 | private AccountAuthenticationProvider accountAuthenticationProvider;
32 |
33 | /**
34 | * Supplies a PasswordEncoder instance to the Spring ApplicationContext. The
35 | * PasswordEncoder is used by the AuthenticationProvider to perform one-way
36 | * hash operations on passwords for credential comparison.
37 | *
38 | * @return A PasswordEncoder.
39 | */
40 | @Bean
41 | public PasswordEncoder passwordEncoder() {
42 | return new BCryptPasswordEncoder();
43 | }
44 |
45 | /**
46 | * This method builds the AuthenticationProvider used by the system to
47 | * process authentication requests.
48 | *
49 | * @param auth An AuthenticationManagerBuilder instance used to construct
50 | * the AuthenticationProvider.
51 | */
52 | @Autowired
53 | public void configureGlobal(AuthenticationManagerBuilder auth) {
54 |
55 | auth.authenticationProvider(accountAuthenticationProvider);
56 |
57 | }
58 |
59 | /**
60 | * This inner class configures a WebSecurityConfigurerAdapter instance for
61 | * the web service API context paths.
62 | *
63 | * @author Matt Warman
64 | */
65 | @Configuration
66 | @Order(1)
67 | public static class ApiWebSecurityConfigurerAdapter
68 | extends WebSecurityConfigurerAdapter {
69 |
70 | @Override
71 | protected void configure(HttpSecurity http) throws Exception {
72 |
73 | // @formatter:off
74 |
75 | http
76 | .antMatcher("/api/**")
77 | .authorizeRequests()
78 | .anyRequest().hasRole("USER")
79 | .and()
80 | .httpBasic()
81 | .and()
82 | .sessionManagement()
83 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
84 | .and()
85 | .csrf()
86 | .disable();
87 |
88 | // @formatter:on
89 |
90 | }
91 |
92 | }
93 |
94 | /**
95 | * This inner class configures a WebSecurityConfigurerAdapter instance for
96 | * the Spring Actuator web service context paths.
97 | *
98 | * @author Matt Warman
99 | */
100 | @Configuration
101 | @Order(2)
102 | public static class ActuatorWebSecurityConfigurerAdapter
103 | extends WebSecurityConfigurerAdapter {
104 |
105 | @Override
106 | protected void configure(HttpSecurity http) throws Exception {
107 |
108 | // @formatter:off
109 |
110 | http
111 | .antMatcher("/actuators/**")
112 | .authorizeRequests()
113 | .anyRequest().hasRole("SYSADMIN")
114 | .and()
115 | .httpBasic()
116 | .and()
117 | .sessionManagement()
118 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
119 | .and()
120 | .csrf()
121 | .disable();
122 |
123 | // @formatter:on
124 |
125 | }
126 |
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/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/Account.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.model;
2 |
3 | import java.util.Set;
4 |
5 | import javax.persistence.CascadeType;
6 | import javax.persistence.Entity;
7 | import javax.persistence.FetchType;
8 | import javax.persistence.JoinColumn;
9 | import javax.persistence.JoinTable;
10 | import javax.persistence.ManyToMany;
11 | import javax.validation.constraints.NotNull;
12 |
13 | /**
14 | * The Account class is an entity model object. An Account describes the
15 | * security credentials and authentication flags that permit access to
16 | * application functionality.
17 | *
18 | * @author Matt Warman
19 | */
20 | @Entity
21 | public class Account extends TransactionalEntity {
22 |
23 | private static final long serialVersionUID = 1L;
24 |
25 | @NotNull
26 | private String username;
27 |
28 | @NotNull
29 | private String password;
30 |
31 | @NotNull
32 | private boolean enabled = true;
33 |
34 | @NotNull
35 | private boolean credentialsexpired = false;
36 |
37 | @NotNull
38 | private boolean expired = false;
39 |
40 | @NotNull
41 | private boolean locked = false;
42 |
43 | @ManyToMany(
44 | fetch = FetchType.EAGER,
45 | cascade = CascadeType.ALL)
46 | @JoinTable(
47 | name = "AccountRole",
48 | joinColumns = @JoinColumn(
49 | name = "accountId",
50 | referencedColumnName = "id") ,
51 | inverseJoinColumns = @JoinColumn(
52 | name = "roleId",
53 | referencedColumnName = "id") )
54 | private Set roles;
55 |
56 | public Account() {
57 |
58 | }
59 |
60 | public String getUsername() {
61 | return username;
62 | }
63 |
64 | public void setUsername(String username) {
65 | this.username = username;
66 | }
67 |
68 | public String getPassword() {
69 | return password;
70 | }
71 |
72 | public void setPassword(String password) {
73 | this.password = password;
74 | }
75 |
76 | public boolean isEnabled() {
77 | return enabled;
78 | }
79 |
80 | public void setEnabled(boolean enabled) {
81 | this.enabled = enabled;
82 | }
83 |
84 | public boolean isCredentialsexpired() {
85 | return credentialsexpired;
86 | }
87 |
88 | public void setCredentialsexpired(boolean credentialsexpired) {
89 | this.credentialsexpired = credentialsexpired;
90 | }
91 |
92 | public boolean isExpired() {
93 | return expired;
94 | }
95 |
96 | public void setExpired(boolean expired) {
97 | this.expired = expired;
98 | }
99 |
100 | public boolean isLocked() {
101 | return locked;
102 | }
103 |
104 | public void setLocked(boolean locked) {
105 | this.locked = locked;
106 | }
107 |
108 | public Set getRoles() {
109 | return roles;
110 | }
111 |
112 | public void setRoles(Set roles) {
113 | this.roles = roles;
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/model/Greeting.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.model;
2 |
3 | import javax.persistence.Entity;
4 |
5 | /**
6 | * The Greeting class is an entity model object.
7 | *
8 | * @author Matt Warman
9 | */
10 | @Entity
11 | public class Greeting extends TransactionalEntity {
12 |
13 | private static final long serialVersionUID = 1L;
14 |
15 | private String text;
16 |
17 | public Greeting() {
18 |
19 | }
20 |
21 | public String getText() {
22 | return text;
23 | }
24 |
25 | public void setText(String text) {
26 | this.text = text;
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/model/ReferenceEntity.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.model;
2 |
3 | import java.io.Serializable;
4 |
5 | import javax.persistence.Id;
6 | import javax.persistence.MappedSuperclass;
7 | import javax.validation.constraints.NotNull;
8 |
9 | import org.joda.time.DateTime;
10 |
11 | /**
12 | * The parent class for all reference entities (i.e. reference data as opposed
13 | * to transactional data).
14 | *
15 | * @see TransactionalEntity
16 | *
17 | * @author Matt Warman
18 | */
19 | @MappedSuperclass
20 | public class ReferenceEntity implements Serializable {
21 |
22 | /**
23 | * The default serial version UID.
24 | */
25 | private static final long serialVersionUID = 1L;
26 |
27 | /**
28 | * The primary key identifier.
29 | */
30 | @Id
31 | private Long id;
32 |
33 | /**
34 | * The unique code value, sometimes used for external reference.
35 | */
36 | @NotNull
37 | private String code;
38 |
39 | /**
40 | * A brief description of the entity.
41 | */
42 | @NotNull
43 | private String label;
44 |
45 | /**
46 | * The ordinal value facilitates sorting the entities.
47 | */
48 | @NotNull
49 | private Integer ordinal;
50 |
51 | /**
52 | * The timestamp at which the entity's values may be applied or used by the
53 | * system.
54 | */
55 | @NotNull
56 | private DateTime effectiveAt;
57 |
58 | /**
59 | * The timestamp at which the entity's values cease to be used by the
60 | * system. If null
the entity is not expired.
61 | */
62 | private DateTime expiresAt;
63 |
64 | /**
65 | * The timestamp when this entity instance was created.
66 | */
67 | @NotNull
68 | private DateTime createdAt;
69 |
70 | public Long getId() {
71 | return id;
72 | }
73 |
74 | public void setId(Long id) {
75 | this.id = id;
76 | }
77 |
78 | public String getCode() {
79 | return code;
80 | }
81 |
82 | public void setCode(String code) {
83 | this.code = code;
84 | }
85 |
86 | public String getLabel() {
87 | return label;
88 | }
89 |
90 | public void setLabel(String label) {
91 | this.label = label;
92 | }
93 |
94 | public Integer getOrdinal() {
95 | return ordinal;
96 | }
97 |
98 | public void setOrdinal(Integer ordinal) {
99 | this.ordinal = ordinal;
100 | }
101 |
102 | public DateTime getEffectiveAt() {
103 | return effectiveAt;
104 | }
105 |
106 | public void setEffectiveAt(DateTime effectiveAt) {
107 | this.effectiveAt = effectiveAt;
108 | }
109 |
110 | public DateTime getExpiresAt() {
111 | return expiresAt;
112 | }
113 |
114 | public void setExpiresAt(DateTime expiresAt) {
115 | this.expiresAt = expiresAt;
116 | }
117 |
118 | public DateTime getCreatedAt() {
119 | return createdAt;
120 | }
121 |
122 | public void setCreatedAt(DateTime createdAt) {
123 | this.createdAt = createdAt;
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/model/Role.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.model;
2 |
3 | import javax.persistence.Entity;
4 |
5 | /**
6 | * The Role class is an entity model object. A Role describes a privilege level
7 | * within the application. A Role is used to authorize an Account to access a
8 | * set of application resources.
9 | *
10 | * @author Matt Warman
11 | */
12 | @Entity
13 | public class Role extends ReferenceEntity {
14 |
15 | private static final long serialVersionUID = 1L;
16 |
17 | public Role() {
18 |
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/model/TransactionalEntity.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.model;
2 |
3 | import java.io.Serializable;
4 | import java.util.UUID;
5 |
6 | import javax.persistence.GeneratedValue;
7 | import javax.persistence.Id;
8 | import javax.persistence.MappedSuperclass;
9 | import javax.persistence.PrePersist;
10 | import javax.persistence.PreUpdate;
11 | import javax.persistence.Version;
12 | import javax.validation.constraints.NotNull;
13 |
14 | import org.example.ws.util.RequestContext;
15 | import org.joda.time.DateTime;
16 |
17 | /**
18 | * The parent class for all transactional persistent entities.
19 | *
20 | * @see ReferenceEntity
21 | *
22 | * @author Matt Warman
23 | */
24 | @MappedSuperclass
25 | public class TransactionalEntity implements Serializable {
26 |
27 | /**
28 | * The default serial version UID.
29 | */
30 | private static final long serialVersionUID = 1L;
31 |
32 | /**
33 | * The primary key identifier.
34 | */
35 | @Id
36 | @GeneratedValue
37 | private Long id;
38 |
39 | /**
40 | * A secondary unique identifier which may be used as a reference to this
41 | * entity by external systems.
42 | */
43 | @NotNull
44 | private String referenceId = UUID.randomUUID().toString();
45 |
46 | /**
47 | * The entity instance version used for optimistic locking.
48 | */
49 | @Version
50 | private Integer version;
51 |
52 | /**
53 | * A reference to the entity or process which created this entity instance.
54 | */
55 | @NotNull
56 | private String createdBy;
57 |
58 | /**
59 | * The timestamp when this entity instance was created.
60 | */
61 | @NotNull
62 | private DateTime createdAt;
63 |
64 | /**
65 | * A reference to the entity or process which most recently updated this
66 | * entity instance.
67 | */
68 | private String updatedBy;
69 |
70 | /**
71 | * The timestamp when this entity instance was most recently updated.
72 | */
73 | private DateTime updatedAt;
74 |
75 | public Long getId() {
76 | return id;
77 | }
78 |
79 | public void setId(Long id) {
80 | this.id = id;
81 | }
82 |
83 | public String getReferenceId() {
84 | return referenceId;
85 | }
86 |
87 | public void setReferenceId(String referenceId) {
88 | this.referenceId = referenceId;
89 | }
90 |
91 | public Integer getVersion() {
92 | return version;
93 | }
94 |
95 | public void setVersion(Integer version) {
96 | this.version = version;
97 | }
98 |
99 | public String getCreatedBy() {
100 | return createdBy;
101 | }
102 |
103 | public void setCreatedBy(String createdBy) {
104 | this.createdBy = createdBy;
105 | }
106 |
107 | public DateTime getCreatedAt() {
108 | return createdAt;
109 | }
110 |
111 | public void setCreatedAt(DateTime createdAt) {
112 | this.createdAt = createdAt;
113 | }
114 |
115 | public String getUpdatedBy() {
116 | return updatedBy;
117 | }
118 |
119 | public void setUpdatedBy(String updatedBy) {
120 | this.updatedBy = updatedBy;
121 | }
122 |
123 | public DateTime getUpdatedAt() {
124 | return updatedAt;
125 | }
126 |
127 | public void setUpdatedAt(DateTime updatedAt) {
128 | this.updatedAt = updatedAt;
129 | }
130 |
131 | /**
132 | * A listener method which is invoked on instances of TransactionalEntity
133 | * (or their subclasses) prior to initial persistence. Sets the
134 | * created
audit values for the entity. Attempts to obtain this
135 | * thread's instance of a username from the RequestContext. If none exists,
136 | * throws an IllegalArgumentException. The username is used to set the
137 | * createdBy
value. The createdAt
value is set to
138 | * the current timestamp.
139 | */
140 | @PrePersist
141 | public void beforePersist() {
142 | String username = RequestContext.getUsername();
143 | if (username == null) {
144 | throw new IllegalArgumentException(
145 | "Cannot persist a TransactionalEntity without a username "
146 | + "in the RequestContext for this thread.");
147 | }
148 | setCreatedBy(username);
149 |
150 | setCreatedAt(new DateTime());
151 | }
152 |
153 | /**
154 | * A listener method which is invoked on instances of TransactionalEntity
155 | * (or their subclasses) prior to being updated. Sets the
156 | * updated
audit values for the entity. Attempts to obtain this
157 | * thread's instance of username from the RequestContext. If none exists,
158 | * throws an IllegalArgumentException. The username is used to set the
159 | * updatedBy
value. The updatedAt
value is set to
160 | * the current timestamp.
161 | */
162 | @PreUpdate
163 | public void beforeUpdate() {
164 | String username = RequestContext.getUsername();
165 | if (username == null) {
166 | throw new IllegalArgumentException(
167 | "Cannot update a TransactionalEntity without a username "
168 | + "in the RequestContext for this thread.");
169 | }
170 | setUpdatedBy(username);
171 |
172 | setUpdatedAt(new DateTime());
173 | }
174 |
175 | /**
176 | * Determines the equality of two TransactionalEntity objects. If the
177 | * supplied object is null, returns false. If both objects are of the same
178 | * class, and their id
values are populated and equal, return
179 | * true
. Otherwise, return false
.
180 | *
181 | * @param that An Object
182 | * @return A boolean
183 | * @see java.lang.Object#equals(java.lang.Object)
184 | */
185 | public boolean equals(Object that) {
186 | if (that == null) {
187 | return false;
188 | }
189 | if (this.getClass().equals(that.getClass())) {
190 | TransactionalEntity thatTE = (TransactionalEntity) that;
191 | if (this.getId() == null || thatTE.getId() == null) {
192 | return false;
193 | }
194 | if (this.getId().equals(thatTE.getId())) {
195 | return true;
196 | }
197 | }
198 | return false;
199 | }
200 |
201 | /**
202 | * Returns the hash value of this object.
203 | *
204 | * @return An int
205 | * @see java.lang.Object#hashCode()
206 | */
207 | public int hashCode() {
208 | if (getId() == null) {
209 | return -1;
210 | }
211 | return getId().hashCode();
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/repository/AccountRepository.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.repository;
2 |
3 | import org.example.ws.model.Account;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 | import org.springframework.stereotype.Repository;
6 |
7 | /**
8 | * The AccountRepository interface is a Spring Data JPA data repository for
9 | * Account entities. The AccountRepository provides all the data access
10 | * behaviors exposed by JpaRepository
and additional custom
11 | * behaviors may be defined in this interface.
12 | *
13 | * @author Matt Warman
14 | */
15 | @Repository
16 | public interface AccountRepository extends JpaRepository {
17 |
18 | /**
19 | * Query for a single Account entity by username.
20 | *
21 | * @param username A String username value to query the repository.
22 | * @return An Account or null
if none found.
23 | */
24 | Account findByUsername(String username);
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/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/repository/RoleRepository.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.repository;
2 |
3 | import java.util.Collection;
4 | import java.util.Date;
5 |
6 | import org.example.ws.model.Role;
7 | import org.springframework.data.jpa.repository.JpaRepository;
8 | import org.springframework.data.jpa.repository.Query;
9 | import org.springframework.data.repository.query.Param;
10 | import org.springframework.stereotype.Repository;
11 |
12 | /**
13 | * The RoleRepository interface is a Spring Data JPA data repository for Role
14 | * entities. The RoleRepository provides all the data access behaviors exposed
15 | * by JpaRepository
and additional custom behaviors may be defined
16 | * in this interface.
17 | *
18 | * @author Matt Warman
19 | */
20 | @Repository
21 | public interface RoleRepository extends JpaRepository {
22 |
23 | /**
24 | * Query for a collection of Role entities by the effectiveAt and expiresAt
25 | * attribute values. Order the collection by the value of the ordinal
26 | * attribute.
27 | *
28 | * Uses the Query Method approach to search the database.
29 | *
30 | * @param effectiveAt A Date effectiveAt attribute value.
31 | * @param expiresAt A Date expiresAt attribute value.
32 | * @return A Collection of Role entity model classes.
33 | */
34 | Collection findByEffectiveAtBeforeAndExpiresAtAfterOrExpiresAtNullOrderByOrdinalAsc(
35 | Date effectiveAt, Date expiresAt);
36 |
37 | /**
38 | * Query for a collection of Role entities by the effectiveAt and expiresAt
39 | * attribute values. Order the collection by the value of the ordinal
40 | * attribute.
41 | *
42 | * Uses a Query annotated JPQL statement to search the database.
43 | *
44 | * @param effectiveAt A Date effectiveAt attribute value.
45 | * @return A Collection of Role entity model classes.
46 | */
47 | @Query("SELECT r FROM Role r WHERE r.effectiveAt <= :effectiveAt AND (r.expiresAt IS NULL OR r.expiresAt > :effectiveAt) ORDER BY r.ordinal ASC")
48 | Collection findAllEffective(@Param("effectiveAt") Date effectiveAt);
49 |
50 | /**
51 | * Query for a single Role entity by the code, effectiveAt, and expiresAt
52 | * attribute values.
53 | *
54 | * Uses the Query Method approach to search the database.
55 | *
56 | * @param code A String code attribute value.
57 | * @param effectiveAt A Date effectiveAt attribute value.
58 | * @param expiresAt A Date expiresAt attribute value.
59 | * @return A Role object or null
if not found.
60 | */
61 | Role findByCodeAndEffectiveAtBeforeAndExpiresAtAfterOrExpiresAtNull(
62 | String code, Date effectiveAt, Date expiresAt);
63 |
64 | /**
65 | * Query for a single Role entity by the code, effectiveAt, and expiresAt
66 | * attribute values.
67 | *
68 | * Uses a Query annotated JPQL statement to search the database.
69 | *
70 | * @param code A String code attribute value.
71 | * @param effectiveAt A Date effectiveAt attribute value.
72 | * @return A Role object or null
if not found.
73 | */
74 | @Query("SELECT r FROM Role r WHERE r.code = :code AND r.effectiveAt <= :effectiveAt AND (r.expiresAt IS NULL OR r.expiresAt > :effectiveAt)")
75 | Role findByCodeAndEffective(@Param("code") String code,
76 | @Param("effectiveAt") Date effectiveAt);
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/security/AccountAuthenticationProvider.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.security;
2 |
3 | import org.example.ws.util.RequestContext;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.security.authentication.BadCredentialsException;
8 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
9 | import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
10 | import org.springframework.security.core.AuthenticationException;
11 | import org.springframework.security.core.userdetails.UserDetails;
12 | import org.springframework.security.crypto.password.PasswordEncoder;
13 | import org.springframework.stereotype.Component;
14 |
15 | /**
16 | * A Spring Security AuthenticationProvider which extends
17 | * AbstractUserDetailsAuthenticationProvider
. This classes uses the
18 | * AccountUserDetailsService
to retrieve a UserDetails instance.
19 | *
20 | * A PasswordEncoder compares the supplied authentication credentials to those
21 | * in the UserDetails.
22 | *
23 | * @author Matt Warman
24 | */
25 | @Component
26 | public class AccountAuthenticationProvider
27 | extends AbstractUserDetailsAuthenticationProvider {
28 |
29 | /**
30 | * The Logger for this class.
31 | */
32 | private Logger logger = LoggerFactory.getLogger(this.getClass());
33 |
34 | /**
35 | * A Spring Security UserDetailsService implementation based upon the
36 | * Account entity model.
37 | */
38 | @Autowired
39 | private AccountUserDetailsService userDetailsService;
40 |
41 | /**
42 | * A PasswordEncoder instance to hash clear test password values.
43 | */
44 | @Autowired
45 | private PasswordEncoder passwordEncoder;
46 |
47 | @Override
48 | protected void additionalAuthenticationChecks(UserDetails userDetails,
49 | UsernamePasswordAuthenticationToken token)
50 | throws AuthenticationException {
51 | logger.debug("> additionalAuthenticationChecks");
52 |
53 | if (token.getCredentials() == null
54 | || userDetails.getPassword() == null) {
55 | throw new BadCredentialsException("Credentials may not be null.");
56 | }
57 |
58 | if (!passwordEncoder.matches((String) token.getCredentials(),
59 | userDetails.getPassword())) {
60 | throw new BadCredentialsException("Invalid credentials.");
61 | }
62 |
63 | RequestContext.setUsername(userDetails.getUsername());
64 |
65 | logger.debug("< additionalAuthenticationChecks");
66 | }
67 |
68 | @Override
69 | protected UserDetails retrieveUser(String username,
70 | UsernamePasswordAuthenticationToken token)
71 | throws AuthenticationException {
72 | logger.debug("> retrieveUser");
73 |
74 | UserDetails userDetails = userDetailsService
75 | .loadUserByUsername(username);
76 |
77 | logger.debug("< retrieveUser");
78 | return userDetails;
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/security/AccountUserDetailsService.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.security;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 |
6 | import org.example.ws.model.Account;
7 | import org.example.ws.model.Role;
8 | import org.example.ws.service.AccountService;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.security.core.GrantedAuthority;
13 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
14 | import org.springframework.security.core.userdetails.User;
15 | import org.springframework.security.core.userdetails.UserDetails;
16 | import org.springframework.security.core.userdetails.UserDetailsService;
17 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
18 | import org.springframework.stereotype.Service;
19 |
20 | /**
21 | * A Spring Security UserDetailsService implementation which creates UserDetails
22 | * objects from the Account and Role entities.
23 | *
24 | * @author Matt Warman
25 | */
26 | @Service
27 | public class AccountUserDetailsService implements UserDetailsService {
28 |
29 | private Logger logger = LoggerFactory.getLogger(this.getClass());
30 |
31 | /**
32 | * The AccountService business service.
33 | */
34 | @Autowired
35 | private AccountService accountService;
36 |
37 | @Override
38 | public UserDetails loadUserByUsername(String username)
39 | throws UsernameNotFoundException {
40 | logger.debug("> loadUserByUsername {}", username);
41 |
42 | Account account = accountService.findByUsername(username);
43 | if (account == null) {
44 | // Not found...
45 | throw new UsernameNotFoundException(
46 | "User " + username + " not found.");
47 | }
48 |
49 | if (account.getRoles() == null || account.getRoles().isEmpty()) {
50 | // No Roles assigned to user...
51 | throw new UsernameNotFoundException("User not authorized.");
52 | }
53 |
54 | Collection grantedAuthorities = new ArrayList();
55 | for (Role role : account.getRoles()) {
56 | grantedAuthorities.add(new SimpleGrantedAuthority(role.getCode()));
57 | }
58 |
59 | User userDetails = new User(account.getUsername(),
60 | account.getPassword(), account.isEnabled(),
61 | !account.isExpired(), !account.isCredentialsexpired(),
62 | !account.isLocked(), grantedAuthorities);
63 |
64 | logger.debug("< loadUserByUsername {}", username);
65 | return userDetails;
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/service/AccountService.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.service;
2 |
3 | import org.example.ws.model.Account;
4 |
5 | /**
6 | * The AccountService interface defines all public business behaviors for
7 | * operations on the Account entity model and some related entities such as
8 | * Role.
9 | *
10 | * This interface should be injected into AccountService clients, not the
11 | * implementation bean.
12 | *
13 | * @author Matt Warman
14 | */
15 | public interface AccountService {
16 |
17 | /**
18 | * Find an Account by the username attribute value.
19 | *
20 | * @param username A String username to query the repository.
21 | * @return An Account instance or null
if none found.
22 | */
23 | Account findByUsername(String username);
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/org/example/ws/service/AccountServiceBean.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.service;
2 |
3 | import org.example.ws.model.Account;
4 | import org.example.ws.repository.AccountRepository;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.stereotype.Service;
9 |
10 | /**
11 | * The AccountServiceBean encapsulates all business behaviors for operations on
12 | * the Account entity model and some related entities such as Role.
13 | *
14 | * @author Matt Warman
15 | */
16 | @Service
17 | public class AccountServiceBean implements AccountService {
18 |
19 | /**
20 | * The Logger for this class.
21 | */
22 | private Logger logger = LoggerFactory.getLogger(this.getClass());
23 |
24 | /**
25 | * The Spring Data repository for Account entities.
26 | */
27 | @Autowired
28 | private AccountRepository accountRepository;
29 |
30 | @Override
31 | public Account findByUsername(String username) {
32 | logger.info("> findByUsername");
33 | Account account = accountRepository.findByUsername(username);
34 |
35 | logger.info("< findByUsername");
36 | return account;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/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/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/util/RequestContext.java:
--------------------------------------------------------------------------------
1 | package org.example.ws.util;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | /**
7 | * The RequestContext facilitates the storage of information for the duration of
8 | * a single request (or web service transaction).
9 | *
10 | * RequestContext attributes are stored in ThreadLocal objects.
11 | *
12 | * @author Matt Warman
13 | *
14 | */
15 | public class RequestContext {
16 |
17 | /**
18 | * The Logger for this class.
19 | */
20 | private static Logger logger = LoggerFactory
21 | .getLogger(RequestContext.class);
22 |
23 | /**
24 | * ThreadLocal storage of username Strings.
25 | */
26 | private static ThreadLocal usernames = new ThreadLocal();
27 |
28 | private RequestContext() {
29 |
30 | }
31 |
32 | /**
33 | * Get the username for the current thread.
34 | *
35 | * @return A String username.
36 | */
37 | public static String getUsername() {
38 | return usernames.get();
39 | }
40 |
41 | /**
42 | * Set the username for the current thread.
43 | *
44 | * @param username A String username.
45 | */
46 | public static void setUsername(String username) {
47 | usernames.set(username);
48 | logger.debug("RequestContext added username {} to current thread",
49 | username);
50 | }
51 |
52 | /**
53 | * Initialize the ThreadLocal attributes for the current thread.
54 | */
55 | public static void init() {
56 | usernames.set(null);
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/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