├── .gitignore
├── LICENSE
├── README.md
├── create-pdfs-with-wkhtmltopdf
├── README.md
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── net
│ │ │ └── petrikainulainen
│ │ │ └── spring
│ │ │ └── trenches
│ │ │ ├── GooglePdfController.java
│ │ │ ├── PdfController.java
│ │ │ ├── PdfFileCreator.java
│ │ │ ├── PdfFileRequest.java
│ │ │ └── SpringBootExampleApplication.java
│ └── resources
│ │ └── application.properties
│ └── test
│ └── java
│ └── net
│ └── petrikainulainen
│ └── spring
│ └── trenches
│ └── PdfControllerTest.java
├── datetimeformat-annotation
├── README.md
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── net
│ │ └── petrikainulainen
│ │ └── spring
│ │ └── trenches
│ │ ├── DateTimeController.java
│ │ ├── DateTimeService.java
│ │ └── SpringBootExampleApplication.java
│ └── test
│ └── java
│ └── net
│ └── petrikainulainen
│ └── spring
│ └── trenches
│ └── DateTimeControllerTest.java
├── handler-method-argument-resolver
├── README.md
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── net
│ │ └── petrikainulainen
│ │ └── spring
│ │ └── trenches
│ │ ├── FooBar.java
│ │ ├── FooBarController.java
│ │ ├── FooBarHandlerMethodArgumentResolver.java
│ │ ├── FooBarService.java
│ │ └── SpringBootExampleApplication.java
│ └── test
│ └── java
│ └── net
│ └── petrikainulainen
│ └── spring
│ └── trenches
│ ├── FooBarControllerTest.java
│ └── FooBarHandlerMethodArgumentResolverTest.java
├── property-value-beans
├── README.md
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── net
│ │ │ └── petrikainulainen
│ │ │ └── spring
│ │ │ └── trenches
│ │ │ ├── config
│ │ │ ├── ApplicationProperties.java
│ │ │ ├── BuildProperties.java
│ │ │ ├── CommitProperties.java
│ │ │ ├── GitProperties.java
│ │ │ ├── WebAppConfig.java
│ │ │ ├── WebAppContext.java
│ │ │ └── WebProperties.java
│ │ │ └── web
│ │ │ └── PropertiesController.java
│ └── resources
│ │ ├── application.properties
│ │ └── log4j.properties
│ └── test
│ └── java
│ └── net
│ └── petrikainulainen
│ └── spring
│ └── trenches
│ └── config
│ ├── ApplicationPropertiesTest.java
│ ├── BuildPropertiesTest.java
│ ├── CommitPropertiesTest.java
│ ├── GitPropertiesTest.java
│ └── WebPropertiesTest.java
├── protected-method-scheduled-job
├── spring-sec-31
│ ├── README
│ ├── pom.xml
│ ├── profiles
│ │ ├── dev
│ │ │ └── config.properties
│ │ └── prod
│ │ │ └── config.properties
│ └── src
│ │ ├── main
│ │ ├── java
│ │ │ └── net
│ │ │ │ └── petrikainulainen
│ │ │ │ └── spring
│ │ │ │ └── trenches
│ │ │ │ ├── config
│ │ │ │ ├── ExampleApplicationConfig.java
│ │ │ │ └── ExampleApplicationContext.java
│ │ │ │ └── scheduling
│ │ │ │ ├── job
│ │ │ │ ├── AuthenticationUtil.java
│ │ │ │ ├── ScheduledJobOne.java
│ │ │ │ └── ScheduledJobTwo.java
│ │ │ │ └── service
│ │ │ │ ├── HelloMessageService.java
│ │ │ │ └── MessageService.java
│ │ └── resources
│ │ │ ├── application.properties
│ │ │ ├── applicationContext-security.xml
│ │ │ ├── applicationContext.xml
│ │ │ └── log4j.properties
│ │ └── test
│ │ ├── java
│ │ └── net
│ │ │ └── petrikainulainen
│ │ │ └── spring
│ │ │ └── trenches
│ │ │ └── scheduling
│ │ │ └── job
│ │ │ └── AuthenticationUtilTest.java
│ │ └── resources
│ │ └── log4j.properties
└── spring-sec-32
│ ├── README
│ ├── pom.xml
│ ├── profiles
│ ├── dev
│ │ └── config.properties
│ └── prod
│ │ └── config.properties
│ └── src
│ └── main
│ ├── java
│ └── net
│ │ └── petrikainulainen
│ │ └── spring
│ │ └── trenches
│ │ ├── config
│ │ ├── ExampleApplicationConfig.java
│ │ ├── ExampleApplicationContext.java
│ │ └── ExampleSecurityContext.java
│ │ └── scheduling
│ │ ├── job
│ │ ├── ScheduledJobOne.java
│ │ └── ScheduledJobTwo.java
│ │ └── service
│ │ ├── HelloMessageService.java
│ │ └── MessageService.java
│ └── resources
│ ├── application.properties
│ ├── applicationContext-security.xml
│ ├── applicationContext.xml
│ └── log4j.properties
├── rest-validation-3.1
├── README
├── pom.xml
├── profiles
│ └── dev
│ │ ├── config.properties
│ │ └── system.properties
└── src
│ ├── main
│ ├── java
│ │ └── net
│ │ │ └── petrikainulainen
│ │ │ └── spring
│ │ │ └── trenches
│ │ │ ├── comment
│ │ │ ├── controller
│ │ │ │ └── CommentController.java
│ │ │ └── dto
│ │ │ │ ├── CommentDTO.java
│ │ │ │ ├── FieldErrorDTO.java
│ │ │ │ └── ValidationErrorDTO.java
│ │ │ └── config
│ │ │ ├── ExampleApplicationConfig.java
│ │ │ ├── ExampleApplicationContext.java
│ │ │ ├── MessageContext.java
│ │ │ └── TestMessageContext.java
│ └── resources
│ │ ├── application.properties
│ │ ├── exampleApplicationContext.xml
│ │ ├── i18n
│ │ └── messages.properties
│ │ └── log4j.properties
│ └── test
│ ├── java
│ ├── net
│ │ └── petrikainulainen
│ │ │ └── spring
│ │ │ └── trenches
│ │ │ ├── UnitTestUtil.java
│ │ │ └── comment
│ │ │ ├── controller
│ │ │ ├── CommentControllerTest.java
│ │ │ └── MessagesNotFoundCommentControllerTest.java
│ │ │ └── dto
│ │ │ └── ValidationErrorDTOTest.java
│ └── org
│ │ └── springframework
│ │ └── test
│ │ └── web
│ │ └── server
│ │ └── samples
│ │ └── context
│ │ ├── GenericWebContextLoader.java
│ │ └── WebContextLoader.java
│ └── resources
│ ├── i18n
│ └── messages-test.properties
│ └── log4j.properties
├── rest-validation-3.2
├── README
├── pom.xml
├── profiles
│ └── dev
│ │ ├── config.properties
│ │ └── system.properties
└── src
│ ├── main
│ ├── java
│ │ └── net
│ │ │ └── petrikainulainen
│ │ │ └── spring
│ │ │ └── trenches
│ │ │ ├── comment
│ │ │ ├── controller
│ │ │ │ ├── CommentController.java
│ │ │ │ └── RestErrorHandler.java
│ │ │ └── dto
│ │ │ │ ├── CommentDTO.java
│ │ │ │ ├── FieldErrorDTO.java
│ │ │ │ └── ValidationErrorDTO.java
│ │ │ └── config
│ │ │ ├── ExampleApplicationConfig.java
│ │ │ ├── ExampleApplicationContext.java
│ │ │ ├── MessageContext.java
│ │ │ └── TestMessageContext.java
│ └── resources
│ │ ├── application.properties
│ │ ├── exampleApplicationContext.xml
│ │ ├── i18n
│ │ └── messages.properties
│ │ └── log4j.properties
│ └── test
│ ├── java
│ ├── net
│ │ └── petrikainulainen
│ │ │ └── spring
│ │ │ └── trenches
│ │ │ ├── UnitTestUtil.java
│ │ │ └── comment
│ │ │ ├── controller
│ │ │ ├── CommentControllerTest.java
│ │ │ └── MessagesNotFoundCommentControllerTest.java
│ │ │ └── dto
│ │ │ └── ValidationErrorDTOTest.java
│ └── org
│ │ └── springframework
│ │ └── test
│ │ └── web
│ │ └── server
│ │ └── samples
│ │ └── context
│ │ ├── GenericWebContextLoader.java
│ │ └── WebContextLoader.java
│ └── resources
│ ├── i18n
│ └── messages-test.properties
│ └── log4j.properties
├── scheduled-with-properties-file
├── README
├── pom.xml
├── profiles
│ ├── dev
│ │ └── config.properties
│ └── prod
│ │ └── config.properties
└── src
│ └── main
│ ├── java
│ └── net
│ │ └── petrikainulainen
│ │ └── spring
│ │ └── trenches
│ │ ├── config
│ │ ├── ExampleApplicationConfig.java
│ │ └── ExampleApplicationContext.java
│ │ └── scheduling
│ │ └── ScheduledJob.java
│ └── resources
│ ├── application.properties
│ ├── exampleApplicationContext.xml
│ └── log4j.properties
├── spring-test-dbunit-tips
├── README.md
├── pom.xml
├── profiles
│ ├── dev
│ │ └── config.properties
│ └── integration-test
│ │ └── config.properties
└── src
│ ├── integration-test
│ ├── java
│ │ └── net
│ │ │ └── petrikainulainen
│ │ │ └── spring
│ │ │ └── trenches
│ │ │ └── todo
│ │ │ └── repository
│ │ │ ├── ColumnSensingReplacementDataSetLoader.java
│ │ │ ├── DbTestUtil.java
│ │ │ └── ITTodoRepositoryTest.java
│ └── resources
│ │ └── net
│ │ └── petrikainulainen
│ │ └── spring
│ │ └── trenches
│ │ └── todo
│ │ └── repository
│ │ ├── no-todo-entries.xml
│ │ ├── save-todo-entry-with-title-and-description-expected.xml
│ │ ├── save-todo-entry-without-description-expected.xml
│ │ └── todo-entries.xml
│ ├── main
│ ├── java
│ │ └── net
│ │ │ └── petrikainulainen
│ │ │ └── spring
│ │ │ └── trenches
│ │ │ ├── config
│ │ │ └── PersistenceContext.java
│ │ │ ├── todo
│ │ │ ├── model
│ │ │ │ └── Todo.java
│ │ │ └── repository
│ │ │ │ └── TodoRepository.java
│ │ │ └── util
│ │ │ └── PreCondition.java
│ └── resources
│ │ ├── application.properties
│ │ └── exampleApplicationContext-persistence.xml
│ └── test
│ ├── java
│ └── net
│ │ └── petrikainulainen
│ │ └── spring
│ │ └── trenches
│ │ └── todo
│ │ └── model
│ │ └── TodoTest.java
│ └── resources
│ └── log4j.properties
└── type-converters
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── net
│ └── petrikainulainen
│ └── spring
│ └── trenches
│ ├── DateTimeController.java
│ ├── DateTimeService.java
│ ├── LocalDateConverter.java
│ ├── LocalDateTimeConverter.java
│ └── SpringBootExampleApplication.java
└── test
└── java
└── net
└── petrikainulainen
└── spring
└── trenches
├── DateTimeControllerTest.java
├── LocalDateConverterTest.java
└── LocalDateTimeConverterTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | overlays
3 | target
4 | .idea
5 | .DS_Store
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2012 - 2015 Petri Kainulainen
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Test With Spring Course
2 |
3 | If you are struggling to write good automated tests for Spring web applications, you are not alone! [I have launched a video course](https://www.testwithspring.com/?utm_source=github&utm_medium=social&utm_content=spring-from-the-trenches&utm_campaign=test-with-spring-course-presales) that describes how you can write automated tests which embrace change and help you to save your time (and nerves).
4 |
5 | # Spring From the Trenches
6 |
7 | This repository contains the example application of my [Spring From the Trenches](http://www.petrikainulainen.net/spring-from-the-trenches/) tutorial
8 |
--------------------------------------------------------------------------------
/create-pdfs-with-wkhtmltopdf/README.md:
--------------------------------------------------------------------------------
1 | This blog post is the example application of the blog post:
2 |
3 | * [Spring From the Trenches: Creating PDF Documents With Wkhtmltopdf](http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-creating-pdf-documents-with-wkhtmltopdf/)
4 |
5 | *Note:* This example application (and the blog post) is based on the ideas of [Kyösti Herrala](https://www.linkedin.com/in/kherrala)
6 |
7 | Prerequisites
8 | =============
9 |
10 | You need to install the following tools if you want to run this application:
11 |
12 | * [wkhtmltopdf](http://wkhtmltopdf.org/)
13 | * [JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
14 | * [Maven](http://maven.apache.org/) (the application is tested with Maven 3.3.3)
15 |
16 | Running the Tests
17 | =================
18 |
19 | You can run the unit tests by using the following command:
20 |
21 | mvn clean test
22 |
23 | Running the Application
24 | =======================
25 |
26 | You can run the application by using the following command:
27 |
28 | mvn clean spring-boot:run
29 |
--------------------------------------------------------------------------------
/create-pdfs-with-wkhtmltopdf/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | net.petrikainulainen.spring.trenches
5 | pdf-with-wkhtmltopdf
6 | 0.1
7 | Spring From the Trenches: Creating PDFs With Wkhtmltopdf
8 |
9 |
10 |
11 |
12 | org.springframework.boot
13 | spring-boot-starter-parent
14 | 1.3.1.RELEASE
15 |
16 |
17 |
18 | 1.8
19 | UTF-8
20 |
21 |
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-web
26 |
27 |
28 | commons-io
29 | commons-io
30 | 2.4
31 |
32 |
33 | org.apache.commons
34 | commons-lang3
35 | 3.4
36 |
37 |
38 |
39 |
40 | org.springframework.boot
41 | spring-boot-starter-test
42 | test
43 |
44 |
45 | org.assertj
46 | assertj-core
47 | 3.2.0
48 |
49 |
50 | com.nitorcreations
51 | junit-runners
52 | 1.3
53 | test
54 |
55 |
56 | info.solidsoft.mockito
57 | mockito-java8
58 | 0.3.0
59 | test
60 |
61 |
62 |
63 | ROOT
64 |
65 |
66 | org.apache.maven.plugins
67 | maven-compiler-plugin
68 | 3.2
69 |
70 | ${jdk.version}
71 | ${jdk.version}
72 | ${project.build.sourceEncoding}
73 |
74 |
75 |
76 | org.apache.maven.plugins
77 | maven-surefire-plugin
78 | 2.18
79 |
80 |
81 | org.springframework.boot
82 | spring-boot-maven-plugin
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/create-pdfs-with-wkhtmltopdf/src/main/java/net/petrikainulainen/spring/trenches/GooglePdfController.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.apache.tomcat.util.http.fileupload.IOUtils;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.beans.factory.annotation.Value;
8 | import org.springframework.stereotype.Controller;
9 | import org.springframework.web.bind.annotation.RequestMapping;
10 | import org.springframework.web.bind.annotation.RequestMethod;
11 | import org.springframework.web.client.RestTemplate;
12 |
13 | import javax.servlet.http.HttpServletResponse;
14 | import java.io.ByteArrayInputStream;
15 | import java.io.IOException;
16 | import java.io.InputStream;
17 | import java.io.OutputStream;
18 |
19 | /**
20 | * @author Petri Kainulainen
21 | */
22 | @Controller
23 | class GooglePdfController {
24 |
25 | private static final Logger LOGGER = LoggerFactory.getLogger(GooglePdfController.class);
26 |
27 | private final String pdfServiceUrl;
28 | private final RestTemplate restTemplate;
29 |
30 | @Autowired
31 | GooglePdfController(@Value("${pdf.service.url}") String pdfServiceUrl, RestTemplate restTemplate) {
32 | this.pdfServiceUrl = pdfServiceUrl;
33 | this.restTemplate = restTemplate;
34 | }
35 |
36 | @RequestMapping(value = "/pdf/google", method = RequestMethod.GET)
37 | void createPdfFromGoogle(HttpServletResponse response) {
38 | LOGGER.info("Creating PDF file from www.google.com");
39 |
40 | PdfFileRequest fileRequest = new PdfFileRequest();
41 | fileRequest.setFileName("google.pdf");
42 | fileRequest.setSourceHtmlUrl("http://www.google.com");
43 |
44 | byte[] pdfFile = restTemplate.postForObject(pdfServiceUrl,
45 | fileRequest, byte[].class);
46 | writePdfFileToResponse(pdfFile, "google.pdf", response);
47 |
48 | LOGGER.info("Created PDF file from www.google.com");
49 | }
50 |
51 | private void writePdfFileToResponse(byte[] pdfFile, String fileName, HttpServletResponse response) {
52 | try (InputStream in = new ByteArrayInputStream(pdfFile)) {
53 | OutputStream out = response.getOutputStream();
54 | IOUtils.copy(in, out);
55 | out.flush();
56 |
57 | response.setContentType("application/pdf");
58 | response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
59 | }
60 | catch (IOException ex) {
61 | LOGGER.error("Could not write PDF File to the response because an exception was thrown: ", ex);
62 | throw new RuntimeException("Error occurred when creating PDF file", ex);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/create-pdfs-with-wkhtmltopdf/src/main/java/net/petrikainulainen/spring/trenches/PdfController.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.web.bind.annotation.RequestBody;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RequestMethod;
9 | import org.springframework.web.bind.annotation.RestController;
10 |
11 | import javax.servlet.http.HttpServletResponse;
12 |
13 | /**
14 | * Provides public API that is used to create PDF documents from HTML documents.
15 | * @author Petri Kainulainen
16 | */
17 | @RestController
18 | class PdfController {
19 |
20 | private static final Logger LOGGER = LoggerFactory.getLogger(PdfController.class);
21 |
22 | private final PdfFileCreator pdfFileCreator;
23 |
24 | @Autowired
25 | PdfController(PdfFileCreator pdfFileCreator) {
26 | this.pdfFileCreator = pdfFileCreator;
27 | }
28 |
29 | /**
30 | * Creates a PDF file from an HTML document and writes the created file to the response.
31 | * @param fileRequest Configures the PDF creation process.
32 | * @param response The HTTP response in which the created PDF file is written.
33 | */
34 | @RequestMapping(value = "/api/pdf", method = RequestMethod.POST)
35 | void createPdf(@RequestBody PdfFileRequest fileRequest, HttpServletResponse response) {
36 | LOGGER.info("Creating PDF file from request: {}", fileRequest);
37 |
38 | pdfFileCreator.writePdfToResponse(fileRequest, response);
39 |
40 | LOGGER.info("Created PDF file from request: {}", fileRequest);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/create-pdfs-with-wkhtmltopdf/src/main/java/net/petrikainulainen/spring/trenches/PdfFileRequest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 |
5 | /**
6 | * This class is used to configure the PDF creation process.
7 | *
8 | * @author Petri Kainulainen
9 | */
10 | public class PdfFileRequest {
11 |
12 | private String fileName;
13 | private String sourceHtmlUrl;
14 |
15 | PdfFileRequest() {}
16 |
17 | public String getFileName() {
18 | return fileName;
19 | }
20 |
21 | public String getSourceHtmlUrl() {
22 | return sourceHtmlUrl;
23 | }
24 |
25 | /**
26 | * Sets the file name of the returned PDF file.
27 | * @param fileName
28 | */
29 | public void setFileName(String fileName) {
30 | this.fileName = fileName;
31 | }
32 |
33 | /**
34 | * Sets the URL of the HTML document that is transformed into PDF.
35 | * @param sourceHtmlUrl
36 | */
37 | public void setSourceHtmlUrl(String sourceHtmlUrl) {
38 | this.sourceHtmlUrl = sourceHtmlUrl;
39 | }
40 |
41 | @Override
42 | public String toString() {
43 | return new ToStringBuilder(this)
44 | .append("fileName", this.fileName)
45 | .append("sourceHtmlUrl", this.sourceHtmlUrl)
46 | .toString();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/create-pdfs-with-wkhtmltopdf/src/main/java/net/petrikainulainen/spring/trenches/SpringBootExampleApplication.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.ComponentScan;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.web.client.RestTemplate;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | @Configuration
14 | @EnableAutoConfiguration
15 | @ComponentScan
16 | public class SpringBootExampleApplication {
17 | @Bean
18 | public RestTemplate restTemplate() {
19 | return new RestTemplate();
20 | }
21 |
22 | public static void main(String[] args) {
23 | SpringApplication.run(SpringBootExampleApplication.class, args);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/create-pdfs-with-wkhtmltopdf/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | pdf.service.url=http://localhost:8080/api/pdf
--------------------------------------------------------------------------------
/create-pdfs-with-wkhtmltopdf/src/test/java/net/petrikainulainen/spring/trenches/PdfControllerTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import com.nitorcreations.junit.runners.NestedRunner;
6 | import org.junit.Before;
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 | import org.springframework.http.MediaType;
10 | import org.springframework.test.web.servlet.MockMvc;
11 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
12 |
13 | import javax.servlet.http.HttpServletResponse;
14 | import java.io.IOException;
15 |
16 | import static info.solidsoft.mockito.java8.AssertionMatcher.assertArg;
17 | import static org.assertj.core.api.Assertions.assertThat;
18 | import static org.mockito.Matchers.isA;
19 | import static org.mockito.Mockito.mock;
20 | import static org.mockito.Mockito.times;
21 | import static org.mockito.Mockito.verify;
22 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
24 |
25 | /**
26 | * @author Petri Kainulainen
27 | */
28 | @RunWith(NestedRunner.class)
29 | public class PdfControllerTest {
30 |
31 | private MockMvc mockMvc;
32 | private PdfFileCreator pdfFileCreator;
33 |
34 | @Before
35 | public void initSystemUnderTest() {
36 | pdfFileCreator = mock(PdfFileCreator.class);
37 | mockMvc = MockMvcBuilders.standaloneSetup(new PdfController(pdfFileCreator))
38 | .build();
39 | }
40 |
41 | public class CreatePdf {
42 |
43 | private final String PDF_FILE_NAME = "file.pdf";
44 | private final String SOURCE_HTML_URL = "http://www.google.com";
45 |
46 | private PdfFileRequest fileRequest;
47 |
48 | @Before
49 | public void createFileRequest() {
50 | fileRequest = new PdfFileRequest();
51 | fileRequest.setFileName(PDF_FILE_NAME);
52 | fileRequest.setSourceHtmlUrl(SOURCE_HTML_URL);
53 | }
54 |
55 | @Test
56 | public void shouldReturnResponseStatusOk() throws Exception {
57 | mockMvc.perform(post("/api/pdf")
58 | .contentType(MediaType.APPLICATION_JSON_UTF8)
59 | .content(convertObjectToJsonBytes(fileRequest))
60 | )
61 | .andExpect(status().isOk());
62 | }
63 |
64 | @Test
65 | public void shouldPassFileNameForwardToPdfCreator() throws Exception {
66 | mockMvc.perform(post("/api/pdf")
67 | .contentType(MediaType.APPLICATION_JSON_UTF8)
68 | .content(convertObjectToJsonBytes(fileRequest))
69 | );
70 |
71 | verify(pdfFileCreator, times(1)).writePdfToResponse(
72 | assertArg(fileRequest -> assertThat(fileRequest.getFileName()).isEqualTo(PDF_FILE_NAME)),
73 | isA(HttpServletResponse.class)
74 | );
75 | }
76 |
77 | @Test
78 | public void shouldPassSourceHtmlUrlForwardToPdfCreator() throws Exception {
79 | mockMvc.perform(post("/api/pdf")
80 | .contentType(MediaType.APPLICATION_JSON_UTF8)
81 | .content(convertObjectToJsonBytes(fileRequest))
82 | );
83 |
84 | verify(pdfFileCreator, times(1)).writePdfToResponse(
85 | assertArg(fileRequest -> assertThat(fileRequest.getSourceHtmlUrl()).isEqualTo(SOURCE_HTML_URL)),
86 | isA(HttpServletResponse.class)
87 | );
88 | }
89 |
90 | private byte[] convertObjectToJsonBytes(Object object) throws IOException {
91 | ObjectMapper mapper = new ObjectMapper();
92 | mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
93 | return mapper.writeValueAsBytes(object);
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/datetimeformat-annotation/README.md:
--------------------------------------------------------------------------------
1 | This blog post is the example application of the blog post:
2 |
3 | * [Spring From the Trenches: Parsing Date and Time Information From a Request Parameter](http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-parsing-date-and-time-information-from-a-request-parameter/)
4 |
5 | Prerequisites
6 | =============
7 |
8 | You need to install the following tools if you want to run this application:
9 |
10 | * [JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
11 | * [Maven](http://maven.apache.org/) (the application is tested with Maven 3.3.3)
12 |
13 | Running the Tests
14 | =================
15 |
16 | You can run the unit tests by using the following command:
17 |
18 | mvn clean test
19 |
20 | Running the Application
21 | =======================
22 |
23 | You can run the application by using the following command:
24 |
25 | mvn clean spring-boot:run
26 |
--------------------------------------------------------------------------------
/datetimeformat-annotation/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | net.petrikainulainen.spring.trenches
5 | datetimeformat-annotation
6 | 0.1
7 | Spring From the Trenches: The @DateTimeFormat Annotation
8 |
9 |
10 |
11 |
12 | org.springframework.boot
13 | spring-boot-starter-parent
14 | 1.2.6.RELEASE
15 |
16 |
17 |
18 | 1.8
19 | UTF-8
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-test
32 | test
33 |
34 |
35 | com.nitorcreations
36 | junit-runners
37 | 1.3
38 | test
39 |
40 |
41 | info.solidsoft.mockito
42 | mockito-java8
43 | 0.3.0
44 | test
45 |
46 |
47 |
48 | ROOT
49 |
50 |
51 | org.apache.maven.plugins
52 | maven-compiler-plugin
53 | 3.2
54 |
55 | ${jdk.version}
56 | ${jdk.version}
57 | ${project.build.sourceEncoding}
58 |
59 |
60 |
61 | org.apache.maven.plugins
62 | maven-surefire-plugin
63 | 2.18
64 |
65 |
66 | org.springframework.boot
67 | spring-boot-maven-plugin
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/datetimeformat-annotation/src/main/java/net/petrikainulainen/spring/trenches/DateTimeController.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.format.annotation.DateTimeFormat;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RequestMethod;
9 | import org.springframework.web.bind.annotation.RequestParam;
10 | import org.springframework.web.bind.annotation.RestController;
11 |
12 | import java.time.LocalDate;
13 | import java.time.LocalDateTime;
14 |
15 | /**
16 | * @author Petri Kainulainen
17 | */
18 | @RestController
19 | @RequestMapping("/api/datetime/")
20 | final class DateTimeController {
21 |
22 | private static final Logger LOGGER = LoggerFactory.getLogger(DateTimeController.class);
23 |
24 | private final DateTimeService dateTimeService;
25 |
26 | @Autowired
27 | DateTimeController(DateTimeService dateTimeService) {
28 | this.dateTimeService = dateTimeService;
29 | }
30 |
31 | @RequestMapping(value = "date", method = RequestMethod.POST)
32 | public void processDate(@RequestParam("date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) {
33 | LOGGER.info("Processing date: {}", date);
34 | dateTimeService.processDate(date);
35 | }
36 |
37 | @RequestMapping(value = "datetime", method = RequestMethod.POST)
38 | public void processDateTime(@RequestParam("datetime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateAndTime) {
39 | LOGGER.info("Processing date and time: {}", dateAndTime);
40 | dateTimeService.processDateAndTime(dateAndTime);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/datetimeformat-annotation/src/main/java/net/petrikainulainen/spring/trenches/DateTimeService.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.stereotype.Service;
6 |
7 | import java.time.LocalDate;
8 | import java.time.LocalDateTime;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | @Service
14 | class DateTimeService {
15 |
16 | private static final Logger LOGGER = LoggerFactory.getLogger(DateTimeService.class);
17 |
18 | void processDate(LocalDate date) {
19 | LOGGER.info("Processing date: {}", date);
20 | }
21 |
22 | void processDateAndTime(LocalDateTime dateAndTime) {
23 | LOGGER.info("Processing datetime: {}", dateAndTime);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/datetimeformat-annotation/src/main/java/net/petrikainulainen/spring/trenches/SpringBootExampleApplication.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
5 | import org.springframework.context.annotation.ComponentScan;
6 | import org.springframework.context.annotation.Configuration;
7 |
8 | /**
9 | * @author Petri Kainulainen
10 | */
11 | @Configuration
12 | @EnableAutoConfiguration
13 | @ComponentScan
14 | public class SpringBootExampleApplication {
15 |
16 | public static void main(String[] args) {
17 | SpringApplication.run(SpringBootExampleApplication.class, args);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/datetimeformat-annotation/src/test/java/net/petrikainulainen/spring/trenches/DateTimeControllerTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import com.nitorcreations.junit.runners.NestedRunner;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.springframework.test.web.servlet.MockMvc;
8 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
9 |
10 | import java.time.LocalDate;
11 | import java.time.LocalDateTime;
12 |
13 | import static info.solidsoft.mockito.java8.AssertionMatcher.assertArg;
14 | import static org.junit.Assert.assertTrue;
15 | import static org.mockito.Mockito.mock;
16 | import static org.mockito.Mockito.times;
17 | import static org.mockito.Mockito.verify;
18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
20 |
21 | /**
22 | * @author Petri Kainulainen
23 | */
24 | @RunWith(NestedRunner.class)
25 | public class DateTimeControllerTest {
26 |
27 | private DateTimeService dateTimeService;
28 |
29 | private MockMvc mockMvc;
30 |
31 | @Before
32 | public void setUp() {
33 | dateTimeService = mock(DateTimeService.class);
34 | mockMvc = MockMvcBuilders.standaloneSetup(new DateTimeController(dateTimeService))
35 | .build();
36 | }
37 |
38 | public class ProcessDate {
39 |
40 | private final String DATE = "2015-09-26";
41 | private final String REQUEST_PARAM_DATE = "date";
42 |
43 | @Test
44 | public void shouldReturnResponseStatusOk() throws Exception {
45 | mockMvc.perform(post("/api/datetime/date")
46 | .param(REQUEST_PARAM_DATE, DATE)
47 |
48 | )
49 | .andExpect(status().isOk());
50 | }
51 |
52 | @Test
53 | public void shouldProcessDate() throws Exception {
54 | mockMvc.perform(post("/api/datetime/date")
55 | .param(REQUEST_PARAM_DATE, DATE)
56 |
57 | );
58 |
59 | verify(dateTimeService, times(1)).processDate(assertArg(
60 | date -> assertTrue(date.equals(LocalDate.of(2015, 9, 26)))
61 | ));
62 | }
63 | }
64 |
65 | public class ProcessDateTime {
66 |
67 | private final String DATE_TIME = "2015-09-26T01:30:00.000";
68 | private final String REQUEST_PARAM_DATE_TIME = "datetime";
69 |
70 | @Test
71 | public void shouldReturnResponseStatusOk() throws Exception {
72 | mockMvc.perform(post("/api/datetime/datetime")
73 | .param(REQUEST_PARAM_DATE_TIME, DATE_TIME)
74 |
75 | )
76 | .andExpect(status().isOk());
77 | }
78 |
79 | @Test
80 | public void shouldProcessDateTime() throws Exception {
81 | mockMvc.perform(post("/api/datetime/datetime")
82 | .param(REQUEST_PARAM_DATE_TIME, DATE_TIME)
83 |
84 | );
85 |
86 | verify(dateTimeService, times(1)).processDateAndTime(assertArg(
87 | dateAndTime -> assertTrue(dateAndTime.equals(LocalDateTime.of(2015, 9, 26, 1, 30)))
88 | ));
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/handler-method-argument-resolver/README.md:
--------------------------------------------------------------------------------
1 | This blog post is the example application of the blog post:
2 |
3 | * [Spring From the Trenches: Creating a Custom HandlerMethodArgumentResolver]() - not published yet
4 |
5 | Prerequisites
6 | =============
7 |
8 | You need to install the following tools if you want to run this application:
9 |
10 | * [JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
11 | * [Maven](http://maven.apache.org/) (the application is tested with Maven 3.3.3)
12 |
13 | Running the Tests
14 | =================
15 |
16 | You can run the unit tests by using the following command:
17 |
18 | mvn clean test
19 |
20 | Running the Application
21 | =======================
22 |
23 | You can run the application by using the following command:
24 |
25 | mvn clean spring-boot:run
26 |
--------------------------------------------------------------------------------
/handler-method-argument-resolver/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | net.petrikainulainen.spring.trenches
5 | handler-method-argument-resolver
6 | 0.1
7 | Spring From the Trenches: Creating a Custom HandlerMethodArgumentResolver
8 |
9 |
10 |
11 |
12 | org.springframework.boot
13 | spring-boot-starter-parent
14 | 1.2.7.RELEASE
15 |
16 |
17 |
18 | 1.8
19 | UTF-8
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-test
32 | test
33 |
34 |
35 | com.nitorcreations
36 | junit-runners
37 | 1.3
38 | test
39 |
40 |
41 | info.solidsoft.mockito
42 | mockito-java8
43 | 0.3.0
44 | test
45 |
46 |
47 | org.assertj
48 | assertj-core
49 | 3.2.0
50 | test
51 |
52 |
53 |
54 | ROOT
55 |
56 |
57 | org.apache.maven.plugins
58 | maven-compiler-plugin
59 | 3.2
60 |
61 | ${jdk.version}
62 | ${jdk.version}
63 | ${project.build.sourceEncoding}
64 |
65 |
66 |
67 | org.apache.maven.plugins
68 | maven-surefire-plugin
69 | 2.18
70 |
71 |
72 | org.springframework.boot
73 | spring-boot-maven-plugin
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/handler-method-argument-resolver/src/main/java/net/petrikainulainen/spring/trenches/FooBar.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | /**
4 | * @author Petri Kainulainen
5 | */
6 | public class FooBar {
7 |
8 | private final String bar;
9 | private final String foo;
10 |
11 | FooBar(String bar, String foo) {
12 | this.bar = bar;
13 | this.foo = foo;
14 | }
15 |
16 | public String getBar() {
17 | return bar;
18 | }
19 |
20 | public String getFoo() {
21 | return foo;
22 | }
23 |
24 | @Override
25 | public String toString() {
26 | return "FooBar{" +
27 | "bar='" + bar + '\'' +
28 | ", foo='" + foo + '\'' +
29 | '}';
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/handler-method-argument-resolver/src/main/java/net/petrikainulainen/spring/trenches/FooBarController.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.web.bind.annotation.RequestMapping;
7 | import org.springframework.web.bind.annotation.RequestMethod;
8 | import org.springframework.web.bind.annotation.RestController;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | @RestController
14 | final class FooBarController {
15 |
16 | private static final Logger LOGGER = LoggerFactory.getLogger(FooBarController.class);
17 |
18 | private final FooBarService fooBarService;
19 |
20 | @Autowired
21 | FooBarController(FooBarService fooBarService) {
22 | this.fooBarService = fooBarService;
23 | }
24 |
25 | @RequestMapping(value = "/test", method = RequestMethod.GET)
26 | public void processFooBar(FooBar fooBar) {
27 | LOGGER.info("Processing a FooBar object: {}", fooBar);
28 | fooBarService.processFooBar(fooBar);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/handler-method-argument-resolver/src/main/java/net/petrikainulainen/spring/trenches/FooBarHandlerMethodArgumentResolver.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.core.MethodParameter;
6 | import org.springframework.web.bind.support.WebDataBinderFactory;
7 | import org.springframework.web.context.request.NativeWebRequest;
8 | import org.springframework.web.method.support.HandlerMethodArgumentResolver;
9 | import org.springframework.web.method.support.ModelAndViewContainer;
10 |
11 | /**
12 | * This component creates new {@link FooBar} objects. The values of the {@code bar} and {@code foo} properties
13 | * from the following request parameters:
14 | *
15 | *
16 | * The value of the {@code bar} request parameter is set as the value of the {@code bar} property. If the value
17 | * of the request parameter is set, the default value is used instead.
18 | *
19 | *
20 | * The value of the {@code foo} request parameter is set as the value of the {@code foo} property. If the value
21 | * of the request parameter is set, the default value is used instead.
22 | *
23 | *
24 | * @author Petri Kainulainen
25 | */
26 | public final class FooBarHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
27 |
28 | private static final Logger LOGGER = LoggerFactory.getLogger(FooBarHandlerMethodArgumentResolver.class);
29 |
30 | private static final String DEFAULT_BAR = "defaultBar";
31 | private static final String DEFAULT_FOO = "defaultFoo";
32 |
33 | private static final String REQUEST_PARAM_NAME_BAR = "bar";
34 | private static final String REQUEST_PARAM_NAME_FOO = "foo";
35 |
36 | @Override
37 | public boolean supportsParameter(MethodParameter methodParameter) {
38 | return methodParameter.getParameterType().equals(FooBar.class);
39 | }
40 |
41 | @Override
42 | public Object resolveArgument(MethodParameter methodParameter,
43 | ModelAndViewContainer modelAndViewContainer,
44 | NativeWebRequest nativeWebRequest,
45 | WebDataBinderFactory webDataBinderFactory) throws Exception {
46 | LOGGER.debug("Creating a FooBar object from the incoming request.");
47 |
48 | String bar = nativeWebRequest.getParameter(REQUEST_PARAM_NAME_BAR);
49 | String foo = nativeWebRequest.getParameter(REQUEST_PARAM_NAME_FOO);
50 |
51 | if (isNotSet(bar)) {
52 | bar = DEFAULT_BAR;
53 | }
54 |
55 | if (isNotSet(foo)) {
56 | foo = DEFAULT_FOO;
57 | }
58 |
59 | LOGGER.debug("Returning a new FooBar object with bar: {} and foo: {}", bar, foo);
60 |
61 | return new FooBar(bar, foo);
62 | }
63 |
64 | private boolean isNotSet(String value) {
65 | return value == null;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/handler-method-argument-resolver/src/main/java/net/petrikainulainen/spring/trenches/FooBarService.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.stereotype.Service;
6 |
7 | /**
8 | * @author Petri Kainulainen
9 | */
10 | @Service
11 | class FooBarService {
12 |
13 | private static final Logger LOGGER = LoggerFactory.getLogger(FooBarService.class);
14 |
15 | public void processFooBar(FooBar fooBar) {
16 | LOGGER.info("Processing a FooBar object: {}", fooBar);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/handler-method-argument-resolver/src/main/java/net/petrikainulainen/spring/trenches/SpringBootExampleApplication.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
5 | import org.springframework.context.annotation.ComponentScan;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.web.method.support.HandlerMethodArgumentResolver;
8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
9 |
10 | import java.util.List;
11 |
12 | /**
13 | * @author Petri Kainulainen
14 | */
15 | @Configuration
16 | @EnableAutoConfiguration
17 | @ComponentScan
18 | public class SpringBootExampleApplication extends WebMvcConfigurerAdapter {
19 |
20 | @Override
21 | public void addArgumentResolvers(List argumentResolvers) {
22 | argumentResolvers.add(new FooBarHandlerMethodArgumentResolver());
23 | }
24 |
25 | public static void main(String[] args) {
26 | SpringApplication.run(SpringBootExampleApplication.class, args);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/handler-method-argument-resolver/src/test/java/net/petrikainulainen/spring/trenches/FooBarControllerTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import com.nitorcreations.junit.runners.NestedRunner;
4 | import org.junit.Before;
5 | import org.junit.runner.RunWith;
6 | import org.junit.Test;
7 | import org.springframework.test.web.servlet.MockMvc;
8 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
9 |
10 | import static info.solidsoft.mockito.java8.AssertionMatcher.assertArg;
11 | import static org.assertj.core.api.Assertions.assertThat;
12 | import static org.mockito.Mockito.mock;
13 | import static org.mockito.Mockito.times;
14 | import static org.mockito.Mockito.verify;
15 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
16 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
17 |
18 | /**
19 | * @author Petri Kainulainen
20 | */
21 | @RunWith(NestedRunner.class)
22 | public class FooBarControllerTest {
23 |
24 | public class ProcessFooBar {
25 |
26 | private FooBarService fooBarService;
27 |
28 | private MockMvc mockMvc;
29 |
30 | @Before
31 | public void setUp() {
32 | fooBarService = mock(FooBarService.class);
33 | mockMvc = MockMvcBuilders.standaloneSetup(new FooBarController(fooBarService))
34 | .setCustomArgumentResolvers(new FooBarHandlerMethodArgumentResolver())
35 | .build();
36 | }
37 |
38 | @Test
39 | public void shouldReturnResponseStatusOk() throws Exception {
40 | mockMvc.perform(get("/test"))
41 | .andExpect(status().isOk());
42 | }
43 |
44 | public class WhenRequestParametersAreNotSet {
45 |
46 | @Test
47 | public void shouldProcessFooBarWithDefaultBarValue() throws Exception {
48 | mockMvc.perform(get("/test"));
49 |
50 | verify(fooBarService, times(1)).processFooBar(assertArg(
51 | fooBar -> assertThat(fooBar.getBar()).isEqualTo("defaultBar")
52 | ));
53 | }
54 |
55 | @Test
56 | public void shouldProcessFooBarWithDefaultFooValue() throws Exception {
57 | mockMvc.perform(get("/test"));
58 |
59 | verify(fooBarService, times(1)).processFooBar(assertArg(
60 | fooBar -> assertThat(fooBar.getFoo()).isEqualTo("defaultFoo")
61 | ));
62 | }
63 | }
64 |
65 | public class WhenRequestParametersAreSet {
66 |
67 | private final String BAR = "bar";
68 | private final String FOO = "foo";
69 |
70 | private final String REQUEST_PARAM_BAR = "bar";
71 | private final String REQUEST_PARAM_FOO = "foo";
72 |
73 | @Test
74 | public void shouldProcessFooBarWithResolvedBarValue() throws Exception {
75 | mockMvc.perform(get("/test")
76 | .param(REQUEST_PARAM_BAR, BAR)
77 | .param(REQUEST_PARAM_FOO, FOO)
78 | );
79 |
80 | verify(fooBarService, times(1)).processFooBar(assertArg(
81 | fooBar -> assertThat(fooBar.getBar()).isEqualTo(BAR)
82 | ));
83 | }
84 |
85 | @Test
86 | public void shouldProcessFooBarWithResolvedFooValue() throws Exception {
87 | mockMvc.perform(get("/test")
88 | .param(REQUEST_PARAM_BAR, BAR)
89 | .param(REQUEST_PARAM_FOO, FOO)
90 | );
91 |
92 | verify(fooBarService, times(1)).processFooBar(assertArg(
93 | fooBar -> assertThat(fooBar.getFoo()).isEqualTo(FOO)
94 | ));
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/property-value-beans/README.md:
--------------------------------------------------------------------------------
1 | This blog post is the example application of the blog post:
2 |
3 | * [Spring From the Trenches: Injecting Property Values Into Configuration Beans](http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-injecting-property-values-into-configuration-beans/)
4 | * [Spring From the Trenches: Returning Runtime Configuration as JSON](http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-returning-runtime-configuration-as-json/)
5 | * [Spring From the Trenches: Returning Git Commit Information as JSON](http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-returning-git-commit-information-as-json/)
6 |
7 | Prerequisites
8 | =============
9 |
10 | You need to install the following tools if you want to run this application:
11 |
12 | * [JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
13 | * [Maven](http://maven.apache.org/) (the application is tested with Maven 3.2.1)
14 |
15 | Running the Tests
16 | =================
17 |
18 | You can run the unit tests by using the following command:
19 |
20 | mvn clean test
21 |
22 | Running the Application
23 | =======================
24 |
25 | You can run the application by using the following command:
26 |
27 | mvn clean jetty:run -P dev
28 |
29 | You can get the runtime configuration of the application by sending a GET request to the url:
30 |
31 | http://localhost:8080/config
32 |
33 | You can get the Git commit information by sending a GET request to the url:
34 |
35 | http://localhost:8080/version
36 |
--------------------------------------------------------------------------------
/property-value-beans/src/main/java/net/petrikainulainen/spring/trenches/config/ApplicationProperties.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.beans.factory.annotation.Value;
8 | import org.springframework.stereotype.Component;
9 |
10 | import javax.annotation.PostConstruct;
11 |
12 | /**
13 | * This class is a configuration bean that contain the configuration of our application.
14 | *
15 | * @author Petri Kainulainen
16 | */
17 | @Component
18 | public final class ApplicationProperties {
19 |
20 | private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationProperties.class);
21 |
22 | private final String name;
23 |
24 | private final boolean productionModeEnabled;
25 |
26 | private final WebProperties webProperties;
27 |
28 | @Autowired
29 | public ApplicationProperties(@Value("${app.name}") String name,
30 | @Value("${app.production.mode.enabled:false}") boolean productionModeEnabled,
31 | WebProperties webProperties) {
32 | this.name = name;
33 | this.productionModeEnabled = productionModeEnabled;
34 | this.webProperties = webProperties;
35 | }
36 |
37 | /**
38 | * Returns the name of the application.
39 | * @return
40 | */
41 | public String getName() {
42 | return name;
43 | }
44 |
45 | /**
46 | * Returns true if the production mode is enabled and false otherwise.
47 | * @return
48 | */
49 | public boolean isProductionModeEnabled() {
50 | return productionModeEnabled;
51 | }
52 |
53 | /**
54 | * Returns the configuration properties of the web layer.
55 | * @return
56 | */
57 | public WebProperties getWebProperties() {
58 | return webProperties;
59 | }
60 |
61 | @Override
62 | public String toString() {
63 | return new ToStringBuilder(this)
64 | .append("name", this.name)
65 | .append("productionModeEnabled", this.productionModeEnabled)
66 | .append("webProperties", this.webProperties)
67 | .toString();
68 | }
69 |
70 | @PostConstruct
71 | public void writeConfigurationToLog() {
72 | LOGGER.info("Starting application by using configuration: {}", this);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/property-value-beans/src/main/java/net/petrikainulainen/spring/trenches/config/BuildProperties.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * This bean contains information about the person who packaged this application
10 | * into a binary.
11 | *
12 | * @author Petri Kainulainen
13 | */
14 | @Component
15 | public class BuildProperties {
16 |
17 | private final String time;
18 |
19 | private final String userEmail;
20 |
21 | private final String userName;
22 |
23 | @Autowired
24 | public BuildProperties(@Value("${git.build.time}") String time,
25 | @Value("${git.build.user.email}") String userEmail,
26 | @Value("${git.build.user.name}") String userName) {
27 | this.time = time;
28 | this.userEmail = userEmail;
29 | this.userName = userName;
30 | }
31 |
32 | /**
33 | * Returns the build time.
34 | * @return
35 | */
36 | public String getTime() {
37 | return time;
38 | }
39 |
40 | /**
41 | * Returns the email address of the user who started the build.
42 | * @return
43 | */
44 | public String getUserEmail() {
45 | return userEmail;
46 | }
47 |
48 | /**
49 | * Returns the name of the user who started the build.
50 | * @return
51 | */
52 | public String getUserName() {
53 | return userName;
54 | }
55 |
56 | @Override
57 | public String toString() {
58 | return new ToStringBuilder(this)
59 | .append("time", this.time)
60 | .append("userEmail", this.userEmail)
61 | .append("userName", this.userName)
62 | .toString();
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/property-value-beans/src/main/java/net/petrikainulainen/spring/trenches/config/CommitProperties.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * This class contains the information of the latest commit that is included
10 | * in the build.
11 | *
12 | * @author Petri Kainulainen
13 | */
14 | @Component
15 | public class CommitProperties {
16 |
17 | private final String describe;
18 |
19 | private final String describeShort;
20 |
21 | private final String fullMessage;
22 |
23 | private final String id;
24 |
25 | private final String idAbbrev;
26 |
27 | private final String shortMessage;
28 |
29 | private final String time;
30 |
31 | private final String userEmail;
32 |
33 | private final String userName;
34 |
35 | @Autowired
36 | public CommitProperties(@Value("${git.commit.id.describe}") String describe,
37 | @Value("${git.commit.id.describe-short}") String describeShort,
38 | @Value("${git.commit.message.full}") String fullMessage,
39 | @Value("${git.commit.id}") String id,
40 | @Value("${git.commit.id.abbrev}") String idAbbrev,
41 | @Value("${git.commit.message.short}") String shortMessage,
42 | @Value("${git.commit.time}") String time,
43 | @Value("${git.commit.user.email}") String userEmail,
44 | @Value("${git.commit.user.name}") String userName) {
45 | this.describe = describe;
46 | this.describeShort = describeShort;
47 | this.fullMessage = fullMessage;
48 | this.id = id;
49 | this.idAbbrev = idAbbrev;
50 | this.shortMessage = shortMessage;
51 | this.time = time;
52 | this.userEmail = userEmail;
53 | this.userName = userName;
54 | }
55 |
56 | public String getDescribe() {
57 | return describe;
58 | }
59 |
60 | public String getDescribeShort() {
61 | return describeShort;
62 | }
63 |
64 | /**
65 | * Returns the full commit message.
66 | * @return
67 | */
68 | public String getFullMessage() {
69 | return fullMessage;
70 | }
71 |
72 | /**
73 | * Returns the full commit id.
74 | * @return
75 | */
76 | public String getId() {
77 | return id;
78 | }
79 |
80 | /**
81 | * Returns the commit id abbrev.
82 | * @return
83 | */
84 | public String getIdAbbrev() {
85 | return idAbbrev;
86 | }
87 |
88 | /**
89 | * Returns the short commit message.
90 | * @return
91 | */
92 | public String getShortMessage() {
93 | return shortMessage;
94 | }
95 |
96 | /**
97 | * Returns the time when the commit was made.
98 | * @return
99 | */
100 | public String getTime() {
101 | return time;
102 | }
103 |
104 | /**
105 | * Returns the email address of the user who made the commit.
106 | * @return
107 | */
108 | public String getUserEmail() {
109 | return userEmail;
110 | }
111 |
112 | /**
113 | * Returns the name of the user who made the commit.
114 | * @return
115 | */
116 | public String getUserName() {
117 | return userName;
118 | }
119 |
120 | @Override
121 | public String toString() {
122 | return new ToStringBuilder(this)
123 | .append("describe", this.describe)
124 | .append("describeShort", this.describeShort)
125 | .append("fullMessage", this.fullMessage)
126 | .append("id", this.id)
127 | .append("idAbbrev", this.idAbbrev)
128 | .append("shortMessage", this.shortMessage)
129 | .append("time", this.time)
130 | .append("userEmail", this.userEmail)
131 | .append("userName", this.userName)
132 | .toString();
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/property-value-beans/src/main/java/net/petrikainulainen/spring/trenches/config/GitProperties.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.beans.factory.annotation.Value;
8 | import org.springframework.stereotype.Component;
9 |
10 | import javax.annotation.PostConstruct;
11 |
12 | /**
13 | * This class is the "root" class of the class hierarchy that contains information about
14 | * the state of the Git repository. The property values inserted into this bean and into the
15 | * other beans of the hierarchy are resolved at build time.
16 | *
17 | * @author Petri Kainulainen
18 | */
19 | @Component
20 | public class GitProperties {
21 |
22 | private static final Logger LOGGER = LoggerFactory.getLogger(GitProperties.class);
23 |
24 | private String branch;
25 |
26 | private final BuildProperties build;
27 |
28 | private final CommitProperties commit;
29 |
30 | private final boolean dirty;
31 |
32 | private final String remoteOriginUrl;
33 |
34 | private final String tags;
35 |
36 | @Autowired
37 | public GitProperties(@Value("${git.branch}") String branch,
38 | BuildProperties build,
39 | CommitProperties commit,
40 | @Value("${git.dirty}") boolean dirty,
41 | @Value("${git.remote.origin.url}") String remoteOriginUrl,
42 | @Value("${git.tags}") String tags) {
43 | this.branch = branch;
44 | this.build = build;
45 | this.commit = commit;
46 | this.dirty = dirty;
47 | this.remoteOriginUrl = remoteOriginUrl;
48 | this.tags = tags;
49 | }
50 |
51 | /**
52 | * Returns the branch name.
53 | * @return
54 | */
55 | public String getBranch() {
56 | return branch;
57 | }
58 |
59 | /**
60 | * Returns the build information.
61 | * @return
62 | */
63 | public BuildProperties getBuild() {
64 | return build;
65 | }
66 |
67 | /**
68 | * Returns the commit information.
69 | * @return
70 | */
71 | public CommitProperties getCommit() {
72 | return commit;
73 | }
74 |
75 | /**
76 | * Returns true if the repository is dirty (i.e. it contains uncommitted changes)
77 | * and false otherwise.
78 | * @return
79 | */
80 | public boolean isDirty() {
81 | return dirty;
82 | }
83 |
84 | /**
85 | * Returns the url of the remote origin.
86 | * @return
87 | */
88 | public String getRemoteOriginUrl() {
89 | return remoteOriginUrl;
90 | }
91 |
92 | /**
93 | * Returns the tag names. If no tags is found, this returns an empty string.
94 | * @return
95 | */
96 | public String getTags() {
97 | return tags;
98 | }
99 |
100 | @Override
101 | public String toString() {
102 | return new ToStringBuilder(this)
103 | .append("branch", this.branch)
104 | .append("build", this.build)
105 | .append("commit", this.commit)
106 | .append("dirty", this.dirty)
107 | .append("remoteOriginUrl", this.remoteOriginUrl)
108 | .append("tags", this.tags)
109 | .toString();
110 | }
111 |
112 | @PostConstruct
113 | public void writeGitCommitInformationToLog() {
114 | LOGGER.info("Application was built by using the Git commit: {}", this);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/property-value-beans/src/main/java/net/petrikainulainen/spring/trenches/config/WebAppConfig.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.web.WebApplicationInitializer;
4 | import org.springframework.web.context.ContextLoaderListener;
5 | import org.springframework.web.context.WebApplicationContext;
6 | import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
7 | import org.springframework.web.filter.CharacterEncodingFilter;
8 | import org.springframework.web.servlet.DispatcherServlet;
9 |
10 | import javax.servlet.DispatcherType;
11 | import javax.servlet.FilterRegistration;
12 | import javax.servlet.ServletContext;
13 | import javax.servlet.ServletException;
14 | import javax.servlet.ServletRegistration;
15 | import java.util.EnumSet;
16 |
17 | /**
18 | * @author Petri Kainulainen
19 | */
20 | public class WebAppConfig implements WebApplicationInitializer {
21 | private static final String CHARACTER_ENCODING_FILTER_ENCODING = "UTF-8";
22 | private static final String CHARACTER_ENCODING_FILTER_NAME = "characterEncoding";
23 | private static final String CHARACTER_ENCODING_FILTER_URL_PATTERN = "/*";
24 |
25 | private static final String DISPATCHER_SERVLET_NAME = "dispatcher";
26 | private static final String DISPATCHER_SERVLET_MAPPING = "/";
27 |
28 | @Override
29 | public void onStartup(ServletContext servletContext) throws ServletException {
30 | AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
31 | rootContext.register(WebAppContext.class);
32 |
33 | //XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
34 | //rootContext.setConfigLocation("classpath:applicationContext.xml");
35 |
36 | configureDispatcherServlet(servletContext, rootContext);
37 | EnumSet dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD);
38 | configureCharacterEncodingFilter(servletContext, dispatcherTypes);
39 | servletContext.addListener(new ContextLoaderListener(rootContext));
40 | }
41 |
42 | private void configureDispatcherServlet(ServletContext servletContext, WebApplicationContext rootContext) {
43 | ServletRegistration.Dynamic dispatcher = servletContext.addServlet(
44 | DISPATCHER_SERVLET_NAME,
45 | new DispatcherServlet(rootContext)
46 | );
47 | dispatcher.setLoadOnStartup(1);
48 | dispatcher.addMapping(DISPATCHER_SERVLET_MAPPING);
49 | }
50 |
51 | private void configureCharacterEncodingFilter(ServletContext servletContext, EnumSet dispatcherTypes) {
52 | CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
53 | characterEncodingFilter.setEncoding(CHARACTER_ENCODING_FILTER_ENCODING);
54 | characterEncodingFilter.setForceEncoding(true);
55 | FilterRegistration.Dynamic characterEncoding = servletContext.addFilter(CHARACTER_ENCODING_FILTER_NAME, characterEncodingFilter);
56 | characterEncoding.addMappingForUrlPatterns(dispatcherTypes, true, CHARACTER_ENCODING_FILTER_URL_PATTERN);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/property-value-beans/src/main/java/net/petrikainulainen/spring/trenches/config/WebAppContext.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.ComponentScan;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.context.annotation.PropertySource;
7 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
8 | import org.springframework.web.servlet.config.annotation.EnableWebMvc;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | @Configuration
14 | @ComponentScan({
15 | "net.petrikainulainen.spring.trenches.config",
16 | "net.petrikainulainen.spring.trenches.web"
17 | })
18 | @EnableWebMvc
19 | @PropertySource("classpath:application.properties")
20 | public class WebAppContext {
21 |
22 | @Bean
23 | static PropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() {
24 | return new PropertySourcesPlaceholderConfigurer();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/property-value-beans/src/main/java/net/petrikainulainen/spring/trenches/config/WebProperties.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * This class is a configuration bean that contains the configuration of our web layer.
10 | * @author Petri Kainulainen
11 | */
12 | @Component
13 | public final class WebProperties {
14 |
15 | private final String protocol;
16 |
17 | private final String serverHost;
18 |
19 | private final int serverPort;
20 |
21 | @Autowired
22 | public WebProperties(@Value("${app.server.protocol}") String protocol,
23 | @Value("${app.server.host}") String serverHost,
24 | @Value("${app.server.port}") int serverPort) {
25 | checkThatProtocolIsValid(protocol);
26 |
27 | this.protocol = protocol.toLowerCase();
28 | this.serverHost = serverHost;
29 | this.serverPort = serverPort;
30 | }
31 |
32 | private void checkThatProtocolIsValid(String protocol) {
33 | if (!protocol.equalsIgnoreCase("http") && !protocol.equalsIgnoreCase("https")) {
34 | throw new IllegalArgumentException(String.format(
35 | "Protocol: %s is not allowed. Allowed protocols are: http and https.",
36 | protocol
37 | ));
38 | }
39 | }
40 |
41 | /**
42 | * Returns the protocol that is used to access our application. Legal values are: http and https.
43 | * @return
44 | */
45 | public String getProtocol() {
46 | return protocol;
47 | }
48 |
49 | /**
50 | * Returns the host of the server that runs our application.
51 | * @return
52 | */
53 | public String getServerHost() {
54 | return serverHost;
55 | }
56 |
57 | /**
58 | * Returns the port that listened by the server that runs our application.
59 | * @return
60 | */
61 | public int getServerPort() {
62 | return serverPort;
63 | }
64 |
65 | @Override
66 | public String toString() {
67 | return new ToStringBuilder(this)
68 | .append("protocol", this.protocol)
69 | .append("serverHost", this.serverHost)
70 | .append("serverPort", this.serverPort)
71 | .toString();
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/property-value-beans/src/main/java/net/petrikainulainen/spring/trenches/web/PropertiesController.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.web;
2 |
3 | import net.petrikainulainen.spring.trenches.config.ApplicationProperties;
4 | import net.petrikainulainen.spring.trenches.config.GitProperties;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.web.bind.annotation.RequestMapping;
9 | import org.springframework.web.bind.annotation.RequestMethod;
10 | import org.springframework.web.bind.annotation.RestController;
11 |
12 | /**
13 | * This controller provides a method that is used to get information
14 | * about the runtime configuration of our application.
15 | *
16 | * @author Petri Kainulainen
17 | */
18 | @RestController
19 | final class PropertiesController {
20 |
21 | private final static Logger LOGGER = LoggerFactory.getLogger(PropertiesController.class);
22 |
23 | private final ApplicationProperties applicationProperties;
24 |
25 | private final GitProperties gitProperties;
26 |
27 | @Autowired
28 | PropertiesController(ApplicationProperties applicationProperties, GitProperties gitProperties) {
29 | this.applicationProperties = applicationProperties;
30 | this.gitProperties = gitProperties;
31 | }
32 |
33 | /**
34 | * Returns the configuration of our application as JSON.
35 | * @return
36 | */
37 | @RequestMapping(value = "/config", method = RequestMethod.GET)
38 | ApplicationProperties getAppConfiguration() {
39 | LOGGER.info("Returning application configuration.");
40 | return applicationProperties;
41 | }
42 |
43 | @RequestMapping(value = "/version", method = RequestMethod.GET)
44 | GitProperties getVersion() {
45 | LOGGER.info("Returning version information.");
46 | return gitProperties;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/property-value-beans/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | app.name=Configuration Properties example
2 | app.production.mode.enabled=false
3 |
4 | app.server.port=8080
5 | app.server.protocol=http
6 | app.server.host=localhost
7 |
8 | git.tags=${git.tags}
9 | git.branch=${git.branch}
10 | git.dirty=${git.dirty}
11 | git.remote.origin.url=${git.remote.origin.url}
12 |
13 | git.commit.id=${git.commit.id}
14 | git.commit.id.abbrev=${git.commit.id.abbrev}
15 | git.commit.id.describe=${git.commit.id.describe}
16 | git.commit.id.describe-short=${git.commit.id.describe-short}
17 | git.commit.user.name=${git.commit.user.name}
18 | git.commit.user.email=${git.commit.user.email}
19 | git.commit.message.full=${git.commit.message.full}
20 | git.commit.message.short=${git.commit.message.short}
21 | git.commit.time=${git.commit.time}
22 |
23 | git.build.user.name=${git.build.user.name}
24 | git.build.user.email=${git.build.user.email}
25 | git.build.time=${git.build.time}
26 |
--------------------------------------------------------------------------------
/property-value-beans/src/main/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.appender.Stdout=org.apache.log4j.ConsoleAppender
2 | log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout
3 | log4j.appender.Stdout.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n
4 |
5 | log4j.rootLogger=DEBUG,Stdout
6 |
7 | log4j.logger.org.springframework=DEBUG
--------------------------------------------------------------------------------
/property-value-beans/src/test/java/net/petrikainulainen/spring/trenches/config/ApplicationPropertiesTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import com.nitorcreations.junit.runners.NestedRunner;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 |
8 | import static org.assertj.core.api.Assertions.assertThat;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | @RunWith(NestedRunner.class)
14 | public class ApplicationPropertiesTest {
15 |
16 | public class CreateNew {
17 |
18 | private final String NAME = "application";
19 | private final String PROTOCOL = "http";
20 | private final String SERVER_HOST = "localhost";
21 | private final int SERVER_PORT = 80;
22 |
23 | private ApplicationProperties properties;
24 | private WebProperties webProperties;
25 |
26 |
27 | @Before
28 | public void createApplicationProperties() {
29 | webProperties = new WebProperties(PROTOCOL, SERVER_HOST, SERVER_PORT);
30 | properties = new ApplicationProperties(NAME, true, webProperties);
31 | }
32 |
33 | @Test
34 | public void shouldSetName() {
35 | assertThat(properties.getName()).isEqualTo(NAME);
36 | }
37 |
38 | @Test
39 | public void shouldSetProductionModeEnabled() {
40 | assertThat(properties.isProductionModeEnabled()).isTrue();
41 | }
42 |
43 | @Test
44 | public void shouldSetWebProperties() {
45 | assertThat(properties.getWebProperties()).isEqualTo(webProperties);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/property-value-beans/src/test/java/net/petrikainulainen/spring/trenches/config/BuildPropertiesTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import com.nitorcreations.junit.runners.NestedRunner;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 |
8 | import static org.assertj.core.api.Assertions.assertThat;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | @RunWith(NestedRunner.class)
14 | public class BuildPropertiesTest {
15 |
16 | public class CreateNew {
17 |
18 | private final String TIME = "17.04.2015 @ 23:53:52 EEST";
19 | private final String USER_EMAIL = "foo@bar.com";
20 | private final String USER_NAME = "Foo Bar";
21 |
22 | private BuildProperties properties;
23 |
24 | @Before
25 | public void createBuildProperties() {
26 | properties = new BuildProperties(TIME, USER_EMAIL, USER_NAME);
27 | }
28 |
29 | @Test
30 | public void shouldSetTime() {
31 | assertThat(properties.getTime()).isEqualTo(TIME);
32 | }
33 |
34 | @Test
35 | public void shouldSetUserEmail() {
36 | assertThat(properties.getUserEmail()).isEqualTo(USER_EMAIL);
37 | }
38 |
39 | @Test
40 | public void shouldSetUserName() {
41 | assertThat(properties.getUserName()).isEqualTo(USER_NAME);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/property-value-beans/src/test/java/net/petrikainulainen/spring/trenches/config/CommitPropertiesTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import com.nitorcreations.junit.runners.NestedRunner;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 |
8 | import static org.assertj.core.api.Assertions.assertThat;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | @RunWith(NestedRunner.class)
14 | public class CommitPropertiesTest {
15 |
16 | public class CreateNew {
17 |
18 | private final String DESCRIBE = "1bdfe9c-dirty";
19 | private final String DESCRIBE_SHORT = "1bdfe9c-dirty";
20 | private final String ID = "1bdfe9cf22b550a3ebe170f60df165e5c26448f9";
21 | private final String ID_ABBREV = "1bdfe9c";
22 | private final String MESSAGE_FULL = "Declare PropertySourcesPlaceholderConfigurer in a static @Bean method";
23 | private final String MESSAGE_SHORT = "Declare PropertySourcesPlaceholderConfigurer in a static @Bean method";
24 | private final String TIME = "16.04.2015 @ 23:35:23 EEST";
25 | private final String USER_EMAIL = "foo@bar.com";
26 | private final String USER_NAME = "Foo Bar";
27 |
28 | private CommitProperties properties;
29 |
30 | @Before
31 | public void createCommitProperties() {
32 | properties = new CommitProperties(DESCRIBE,
33 | DESCRIBE_SHORT,
34 | MESSAGE_FULL,
35 | ID,
36 | ID_ABBREV,
37 | MESSAGE_SHORT,
38 | TIME,
39 | USER_EMAIL,
40 | USER_NAME);
41 | }
42 |
43 | @Test
44 | public void shouldSetDescribe() {
45 | assertThat(properties.getDescribe()).isEqualTo(DESCRIBE);
46 | }
47 |
48 | @Test
49 | public void shouldSetDescribeShort() {
50 | assertThat(properties.getDescribeShort()).isEqualTo(DESCRIBE_SHORT);
51 | }
52 |
53 | @Test
54 | public void shouldSetId() {
55 | assertThat(properties.getId()).isEqualTo(ID);
56 | }
57 |
58 | @Test
59 | public void shouldSetIdAbbrev() {
60 | assertThat(properties.getIdAbbrev()).isEqualTo(ID_ABBREV);
61 | }
62 |
63 | @Test
64 | public void shouldSetFullMessage() {
65 | assertThat(properties.getFullMessage()).isEqualTo(MESSAGE_FULL);
66 | }
67 |
68 | @Test
69 | public void shouldSetShortMessage() {
70 | assertThat(properties.getShortMessage()).isEqualTo(MESSAGE_SHORT);
71 | }
72 |
73 | @Test
74 | public void shouldSetTime() {
75 | assertThat(properties.getTime()).isEqualTo(TIME);
76 | }
77 |
78 | @Test
79 | public void shouldSetUserEmail() {
80 | assertThat(properties.getUserEmail()).isEqualTo(USER_EMAIL);
81 | }
82 |
83 | @Test
84 | public void shouldSetUserName() {
85 | assertThat(properties.getUserName()).isEqualTo(USER_NAME);
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/property-value-beans/src/test/java/net/petrikainulainen/spring/trenches/config/GitPropertiesTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import com.nitorcreations.junit.runners.NestedRunner;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 |
8 | import static org.assertj.core.api.Assertions.assertThat;
9 | import static org.mockito.Mockito.mock;
10 |
11 | /**
12 | * @author Petri Kainulainen
13 | */
14 | @RunWith(NestedRunner.class)
15 | public class GitPropertiesTest {
16 |
17 | public class CreateNew {
18 |
19 | private final String BRANCH = "master";
20 | private final BuildProperties BUILD_PROPERTIES = mock(BuildProperties.class);
21 | private final CommitProperties COMMIT_PROPERTIES = mock(CommitProperties.class);
22 | private final boolean DIRTY = true;
23 | private final String REMOTE_ORIGIN_URL = "git@github.com:pkainulainen/spring-from-the-trenches.git";
24 | private final String TAGS = "tag";
25 |
26 | private GitProperties properties;
27 |
28 | @Before
29 | public void createGitProperties() {
30 | properties = new GitProperties(BRANCH,
31 | BUILD_PROPERTIES,
32 | COMMIT_PROPERTIES,
33 | DIRTY,
34 | REMOTE_ORIGIN_URL,
35 | TAGS);
36 | }
37 |
38 | @Test
39 | public void shouldSetBranch() {
40 | assertThat(properties.getBranch()).isEqualTo(BRANCH);
41 | }
42 |
43 | @Test
44 | public void shouldSetBuildProperties() {
45 | assertThat(properties.getBuild()).isEqualTo(BUILD_PROPERTIES);
46 | }
47 |
48 | @Test
49 | public void shouldSetCommitProperties() {
50 | assertThat(properties.getCommit()).isEqualTo(COMMIT_PROPERTIES);
51 | }
52 |
53 | @Test
54 | public void shouldSetDirtyToTrue() {
55 | assertThat(properties.isDirty()).isTrue();
56 | }
57 |
58 | @Test
59 | public void shouldSetRemoteOriginUrl() {
60 | assertThat(properties.getRemoteOriginUrl()).isEqualTo(REMOTE_ORIGIN_URL);
61 | }
62 |
63 | @Test
64 | public void shouldSetTags() {
65 | assertThat(properties.getTags()).isEqualTo(TAGS);
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/property-value-beans/src/test/java/net/petrikainulainen/spring/trenches/config/WebPropertiesTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import com.nitorcreations.junit.runners.NestedRunner;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 |
8 | import static org.assertj.core.api.Assertions.assertThat;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | @RunWith(NestedRunner.class)
14 | public class WebPropertiesTest {
15 |
16 | public class CreateNew {
17 |
18 | private final String SERVER_HOST = "localhost";
19 | private final int SERVER_PORT = 80;
20 |
21 | public class WhenValidProtocolIsUsed {
22 |
23 | private final String PROTOCOL_HTTP_IN_LOWERCASE = "http";
24 | private final String PROTOCOL_HTTPS_IN_LOWERCASE = "https";
25 |
26 | private WebProperties properties;
27 |
28 | @Before
29 | public void createWebProperties() {
30 | properties = new WebProperties(PROTOCOL_HTTP_IN_LOWERCASE, SERVER_HOST, SERVER_PORT);
31 | }
32 |
33 | @Test
34 | public void shouldSetServerHost() {
35 | assertThat(properties.getServerHost()).isEqualTo(SERVER_HOST);
36 | }
37 |
38 | @Test
39 | public void shouldSetServerPort() {
40 | assertThat(properties.getServerPort()).isEqualTo(SERVER_PORT);
41 | }
42 |
43 | public class WhenProtocolIsHttp {
44 |
45 | public class WhenProtocolIsInLowercase {
46 |
47 | @Test
48 | public void shouldSetProtocolToHttp() {
49 | WebProperties properties = new WebProperties(PROTOCOL_HTTP_IN_LOWERCASE, SERVER_HOST, SERVER_PORT);
50 | assertThat(properties.getProtocol()).isEqualTo(PROTOCOL_HTTP_IN_LOWERCASE);
51 | }
52 | }
53 |
54 | public class WhenProtocolNameIsInUppercase {
55 |
56 | @Test
57 | public void shouldSetProtocolToHttp() {
58 | WebProperties properties = new WebProperties(PROTOCOL_HTTP_IN_LOWERCASE.toUpperCase(), SERVER_HOST, SERVER_PORT);
59 | assertThat(properties.getProtocol()).isEqualTo(PROTOCOL_HTTP_IN_LOWERCASE);
60 | }
61 | }
62 | }
63 |
64 | public class WhenProtocolIsHttps {
65 |
66 |
67 | public class WhenProtocolIsInLowercase {
68 |
69 | @Test
70 | public void shouldSetProtocolToHttps() {
71 | WebProperties properties = new WebProperties(PROTOCOL_HTTPS_IN_LOWERCASE, SERVER_HOST, SERVER_PORT);
72 | assertThat(properties.getProtocol()).isEqualTo(PROTOCOL_HTTPS_IN_LOWERCASE);
73 | }
74 | }
75 |
76 | public class WhenProtocolNameIsInUppercase {
77 |
78 | @Test
79 | public void shouldSetProtocolToHttps() {
80 | WebProperties properties = new WebProperties(PROTOCOL_HTTPS_IN_LOWERCASE.toUpperCase(), SERVER_HOST, SERVER_PORT);
81 | assertThat(properties.getProtocol()).isEqualTo(PROTOCOL_HTTPS_IN_LOWERCASE);
82 | }
83 | }
84 | }
85 | }
86 |
87 | public class WhenProtocolIsNotValid {
88 |
89 | private final String PROTOCOL_FTP = "ftp";
90 |
91 | @Test(expected = IllegalArgumentException.class)
92 | public void shouldThrowException() {
93 | new WebProperties(PROTOCOL_FTP, SERVER_HOST, SERVER_PORT);
94 | }
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/README:
--------------------------------------------------------------------------------
1 | This is an example application of my blog post:
2 |
3 | Spring From the Trenches: Invoking a Secured Method From a Scheduled Job
4 |
5 | http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-invoking-a-secured-method-from-a-scheduled-job/
6 |
7 | RUNNING THE APPLICATION:
8 |
9 | You can run the application by running the following command at command prompt:
10 |
11 | mvn clean jetty:run -P dev (development profile)
12 | mvn clean jetty:run -P prod (production profile)
13 |
14 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/profiles/dev/config.properties:
--------------------------------------------------------------------------------
1 | scheduling.job.cron=0-59 * * * * *
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/profiles/prod/config.properties:
--------------------------------------------------------------------------------
1 | scheduling.job.cron=0 0-59 * * * *
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/src/main/java/net/petrikainulainen/spring/trenches/config/ExampleApplicationConfig.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.web.WebApplicationInitializer;
4 | import org.springframework.web.context.ContextLoaderListener;
5 | import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
6 |
7 | import javax.servlet.ServletContext;
8 | import javax.servlet.ServletException;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | public class ExampleApplicationConfig implements WebApplicationInitializer {
14 |
15 | @Override
16 | public void onStartup(ServletContext servletContext) throws ServletException {
17 | AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
18 | rootContext.register(ExampleApplicationContext.class);
19 |
20 | //XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
21 | //rootContext.setConfigLocation("classpath:applicationContext.xml");
22 |
23 | servletContext.addListener(new ContextLoaderListener(rootContext));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/src/main/java/net/petrikainulainen/spring/trenches/config/ExampleApplicationContext.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.context.annotation.*;
4 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
5 | import org.springframework.core.io.ClassPathResource;
6 | import org.springframework.scheduling.annotation.EnableScheduling;
7 |
8 | /**
9 | * @author Petri Kainulainen
10 | */
11 | @Configuration
12 | @EnableScheduling
13 | @ComponentScan(basePackages = {
14 | "net.petrikainulainen.spring.trenches.scheduling"
15 | })
16 | @ImportResource("classpath:applicationContext-security.xml")
17 | @PropertySource("classpath:application.properties")
18 | public class ExampleApplicationContext {
19 |
20 | @Bean
21 | public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
22 | PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
23 |
24 | properties.setLocation(new ClassPathResource( "application.properties" ));
25 | properties.setIgnoreResourceNotFound(false);
26 |
27 | return properties;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/src/main/java/net/petrikainulainen/spring/trenches/scheduling/job/AuthenticationUtil.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.scheduling.job;
2 |
3 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
4 | import org.springframework.security.core.Authentication;
5 | import org.springframework.security.core.GrantedAuthority;
6 | import org.springframework.security.core.authority.AuthorityUtils;
7 | import org.springframework.security.core.context.SecurityContextHolder;
8 |
9 | import java.util.Collection;
10 |
11 | /**
12 | * This class provides static methods which are used to configure
13 | * the Authentication object which is used when a scheduled job
14 | * is invoked.
15 | *
16 | * The methods of this class must be called from every job which invokes protected
17 | * methods. There is one reason for this:
18 | *
19 | * If you haven't configured otherwise, the security context is stored to ThreadLocal.
20 | * In other words, Each thread has its own security context. This means that all
21 | * scheduled jobs which are executed in the same thread share the same security context.
22 | *
23 | * If you use the default thread pool which has only one thread, the other jobs have the same
24 | * privileges than the job which added the Authentication object to the the security context.
25 | *
26 | * If you use a thread pool, the other jobs have the same privileges than the job which
27 | * added the Authentication object to the security context IF these jobs are executed in
28 | * the same thread than the "original" job.
29 | *
30 | * This might not a problem because often we want that all jobs are run
31 | * by using the same privileges. The problem is that we want to avoid dependencies
32 | * between jobs. Because we want that each job is can act is an independent unit
33 | * of work, we have to configure the used Authentication object in every job
34 | * which requires access to protected methods.
35 | * @author Petri Kainulainen
36 | */
37 | public final class AuthenticationUtil {
38 |
39 | private static final String USERNAME = "user";
40 |
41 | //Ensures that this class cannot be instantiated
42 | private AuthenticationUtil() {
43 | }
44 |
45 | /**
46 | * Removes the authentication from the security context. This method must
47 | * be called after the job has been run.
48 | */
49 | public static void clearAuthentication() {
50 | SecurityContextHolder.getContext().setAuthentication(null);
51 | }
52 |
53 | /**
54 | * Sets the authentication to the security context. This method must
55 | * be called before the job is run.
56 | * @param role The role which is granted to the created Authentication object.
57 | */
58 | public static void configureAuthentication(String role) {
59 | Collection authorities = AuthorityUtils.createAuthorityList(role);
60 | Authentication authentication = new UsernamePasswordAuthenticationToken(
61 | USERNAME,
62 | role,
63 | authorities
64 | );
65 | SecurityContextHolder.getContext().setAuthentication(authentication);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/src/main/java/net/petrikainulainen/spring/trenches/scheduling/job/ScheduledJobOne.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.scheduling.job;
2 |
3 | import net.petrikainulainen.spring.trenches.scheduling.service.MessageService;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.scheduling.annotation.Scheduled;
8 | import org.springframework.stereotype.Component;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | @Component
14 | public class ScheduledJobOne {
15 |
16 | private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJobOne.class);
17 |
18 | private final MessageService messageService;
19 |
20 | @Autowired
21 | public ScheduledJobOne(MessageService messageService) {
22 | this.messageService = messageService;
23 | }
24 |
25 | @Scheduled(cron = "${scheduling.job.cron}")
26 | public void run() {
27 | LOGGER.debug("Starting scheduled job 1.");
28 |
29 | AuthenticationUtil.configureAuthentication("ROLE_USER");
30 |
31 | String message = messageService.getMessage();
32 | LOGGER.debug("Received message 1: {}", message);
33 |
34 | AuthenticationUtil.clearAuthentication();
35 |
36 | LOGGER.debug("Scheduled job 1 is finished.");
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/src/main/java/net/petrikainulainen/spring/trenches/scheduling/job/ScheduledJobTwo.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.scheduling.job;
2 |
3 | import net.petrikainulainen.spring.trenches.scheduling.service.MessageService;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.scheduling.annotation.Scheduled;
8 | import org.springframework.stereotype.Component;
9 |
10 | @Component
11 | public class ScheduledJobTwo {
12 |
13 | private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJobOne.class);
14 |
15 | private final MessageService messageService;
16 |
17 | @Autowired
18 | public ScheduledJobTwo(MessageService messageService) {
19 | this.messageService = messageService;
20 | }
21 |
22 | @Scheduled(cron = "${scheduling.job.cron}")
23 | public void run() {
24 | LOGGER.debug("Starting scheduled job 2.");
25 |
26 | String message = messageService.getMessage();
27 | LOGGER.debug("Received message 2: {}", message);
28 |
29 | LOGGER.debug("Scheduled job 2 is finished.");
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/src/main/java/net/petrikainulainen/spring/trenches/scheduling/service/HelloMessageService.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.scheduling.service;
2 |
3 | import org.springframework.stereotype.Service;
4 |
5 | /**
6 | * @author Petri Kainulainen
7 | */
8 | @Service
9 | public class HelloMessageService implements MessageService {
10 |
11 | @Override
12 | public String getMessage() {
13 | return "Hello World!";
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/src/main/java/net/petrikainulainen/spring/trenches/scheduling/service/MessageService.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.scheduling.service;
2 |
3 | import org.springframework.security.access.prepost.PreAuthorize;
4 |
5 | /**
6 | * @author Petri Kainulainen
7 | */
8 | public interface MessageService {
9 |
10 | /**
11 | * @return The message.
12 | */
13 | @PreAuthorize("hasRole('ROLE_USER')")
14 | public String getMessage();
15 | }
16 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #Scheduled job
2 | scheduling.job.cron=${scheduling.job.cron}
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/src/main/resources/applicationContext-security.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/src/main/resources/applicationContext.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/src/main/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.appender.Stdout=org.apache.log4j.ConsoleAppender
2 | log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout
3 | log4j.appender.Stdout.layout.conversionPattern=%d %-5p - %-26.26c{1} - %m\n
4 |
5 | log4j.rootLogger=DEBUG,Stdout
6 | log4j.logger.org.springframework=INFO
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/src/test/java/net/petrikainulainen/spring/trenches/scheduling/job/AuthenticationUtilTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.scheduling.job;
2 |
3 | import org.junit.Test;
4 | import org.springframework.security.authentication.TestingAuthenticationToken;
5 | import org.springframework.security.core.Authentication;
6 | import org.springframework.security.core.GrantedAuthority;
7 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
8 | import org.springframework.security.core.context.SecurityContextHolder;
9 | import org.springframework.security.core.userdetails.User;
10 |
11 | import java.util.Arrays;
12 | import java.util.Collection;
13 |
14 | import static org.assertj.core.api.Assertions.assertThat;
15 |
16 | /**
17 | * @author Petri Kainulainen
18 | */
19 | public class AuthenticationUtilTest {
20 |
21 | private static final String USERNAME = "username";
22 | private static final String PASSWORD = "password";
23 | private static final String ROLE = "ROLE_USER";
24 |
25 | @Test
26 | public void clearAuthentication_ShouldRemoveAuthenticationFromSecurityContext() {
27 | Authentication authentication = createAuthentication();
28 | SecurityContextHolder.getContext().setAuthentication(authentication);
29 |
30 | AuthenticationUtil.clearAuthentication();
31 |
32 | Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication();
33 | assertThat(currentAuthentication).isNull();
34 | }
35 |
36 | private Authentication createAuthentication() {
37 | Collection grantedAuthorities = createGrantedAuthorities(ROLE);
38 | User principal = new User(USERNAME, PASSWORD, grantedAuthorities);
39 | return new TestingAuthenticationToken(principal, grantedAuthorities);
40 |
41 | }
42 |
43 | private Collection createGrantedAuthorities(String role) {
44 | GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role);
45 | return Arrays.asList(grantedAuthority);
46 | }
47 |
48 | @Test
49 | public void configurationAuthentication_ShouldSetAuthenticationToSecurityContext() {
50 | AuthenticationUtil.configureAuthentication(ROLE);
51 |
52 | Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication();
53 | assertThat(currentAuthentication.getAuthorities()).hasSize(1);
54 | assertThat(currentAuthentication.getAuthorities().iterator().next().getAuthority()).isEqualTo(ROLE);
55 |
56 | User principal = (User) currentAuthentication.getPrincipal();
57 | assertThat(principal.getAuthorities()).hasSize(1);
58 | assertThat(principal.getAuthorities().iterator().next().getAuthority()).isEqualTo(ROLE);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-31/src/test/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.appender.Stdout=org.apache.log4j.ConsoleAppender
2 | log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout
3 | log4j.appender.Stdout.layout.conversionPattern=%d %-5p - %-26.26c{1} - %m\n
4 |
5 | log4j.rootLogger=OFF,Stdout
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-32/README:
--------------------------------------------------------------------------------
1 | This is an example application of my blog post:
2 |
3 | Spring From the Trenches: Invoking a Secured Method From a Scheduled Job
4 |
5 | http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-invoking-a-secured-method-from-a-scheduled-job/
6 |
7 | NOTE: The XML configuration is not working at the moment.
8 |
9 | RUNNING THE APPLICATION:
10 |
11 | You can run the application by running the following command at command prompt:
12 |
13 | mvn clean jetty:run -P dev (development profile)
14 | mvn clean jetty:run -P prod (production profile)
15 |
16 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-32/profiles/dev/config.properties:
--------------------------------------------------------------------------------
1 | scheduling.job.cron=0-59 * * * * *
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-32/profiles/prod/config.properties:
--------------------------------------------------------------------------------
1 | scheduling.job.cron=0 0-59 * * * *
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-32/src/main/java/net/petrikainulainen/spring/trenches/config/ExampleApplicationConfig.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.web.WebApplicationInitializer;
4 | import org.springframework.web.context.ContextLoaderListener;
5 | import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
6 |
7 | import javax.servlet.ServletContext;
8 | import javax.servlet.ServletException;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | public class ExampleApplicationConfig implements WebApplicationInitializer {
14 |
15 | @Override
16 | public void onStartup(ServletContext servletContext) throws ServletException {
17 | AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
18 | rootContext.register(ExampleApplicationContext.class);
19 |
20 | //The XML configuration is not working at the moment!
21 | //XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
22 | //rootContext.setConfigLocation("classpath:applicationContext.xml");
23 |
24 | servletContext.addListener(new ContextLoaderListener(rootContext));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-32/src/main/java/net/petrikainulainen/spring/trenches/config/ExampleApplicationContext.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.context.annotation.*;
4 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
5 | import org.springframework.core.io.ClassPathResource;
6 | import org.springframework.scheduling.annotation.EnableScheduling;
7 | import org.springframework.scheduling.annotation.SchedulingConfigurer;
8 | import org.springframework.scheduling.config.ScheduledTaskRegistrar;
9 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
10 | import org.springframework.security.concurrent.DelegatingSecurityContextScheduledExecutorService;
11 | import org.springframework.security.core.Authentication;
12 | import org.springframework.security.core.GrantedAuthority;
13 | import org.springframework.security.core.authority.AuthorityUtils;
14 | import org.springframework.security.core.context.SecurityContext;
15 | import org.springframework.security.core.context.SecurityContextHolder;
16 |
17 | import java.util.Collection;
18 | import java.util.concurrent.Executor;
19 | import java.util.concurrent.Executors;
20 | import java.util.concurrent.ScheduledExecutorService;
21 |
22 | /**
23 | * @author Petri Kainulainen
24 | */
25 | @Configuration
26 | @EnableScheduling
27 | @ComponentScan(basePackages = {
28 | "net.petrikainulainen.spring.trenches.scheduling"
29 | })
30 | @Import(ExampleSecurityContext.class)
31 | @PropertySource("classpath:application.properties")
32 | public class ExampleApplicationContext implements SchedulingConfigurer {
33 |
34 | private static final String ROLE = "ROLE_USER";
35 | private static final String USERNAME = "user";
36 |
37 | @Override
38 | public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
39 | taskRegistrar.setScheduler(taskExecutor());
40 | }
41 |
42 | @Bean
43 | public Executor taskExecutor() {
44 | ScheduledExecutorService delegateExecutor = Executors.newSingleThreadScheduledExecutor();
45 | SecurityContext schedulerContext = createSchedulerSecurityContext();
46 | return new DelegatingSecurityContextScheduledExecutorService(delegateExecutor, schedulerContext);
47 | }
48 |
49 | private SecurityContext createSchedulerSecurityContext() {
50 | SecurityContext context = SecurityContextHolder.createEmptyContext();
51 |
52 | Collection authorities = AuthorityUtils.createAuthorityList(ROLE);
53 | Authentication authentication = new UsernamePasswordAuthenticationToken(
54 | USERNAME,
55 | ROLE,
56 | authorities
57 | );
58 | context.setAuthentication(authentication);
59 |
60 | return context;
61 | }
62 |
63 | @Bean
64 | public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
65 | PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
66 |
67 | properties.setLocation(new ClassPathResource( "application.properties" ));
68 | properties.setIgnoreResourceNotFound(false);
69 |
70 | return properties;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-32/src/main/java/net/petrikainulainen/spring/trenches/config/ExampleSecurityContext.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.context.annotation.Configuration;
4 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
5 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
6 | import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
7 |
8 | @Configuration
9 | @EnableGlobalMethodSecurity(prePostEnabled = true)
10 | public class ExampleSecurityContext extends GlobalMethodSecurityConfiguration {
11 |
12 | @Override
13 | protected void configure(AuthenticationManagerBuilder auth) throws Exception {
14 | auth.inMemoryAuthentication()
15 | .withUser("user").password("password").roles("USER");
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-32/src/main/java/net/petrikainulainen/spring/trenches/scheduling/job/ScheduledJobOne.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.scheduling.job;
2 |
3 | import net.petrikainulainen.spring.trenches.scheduling.service.MessageService;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.scheduling.annotation.Scheduled;
8 | import org.springframework.stereotype.Component;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | @Component
14 | public class ScheduledJobOne {
15 |
16 | private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJobOne.class);
17 |
18 | private final MessageService messageService;
19 |
20 | @Autowired
21 | public ScheduledJobOne(MessageService messageService) {
22 | this.messageService = messageService;
23 | }
24 |
25 | @Scheduled(cron = "${scheduling.job.cron}")
26 | public void run() {
27 | LOGGER.debug("Starting scheduled job 1.");
28 |
29 | String message = messageService.getMessage();
30 | LOGGER.debug("Received message 1: {}", message);
31 |
32 | LOGGER.debug("Scheduled job 1 is finished.");
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-32/src/main/java/net/petrikainulainen/spring/trenches/scheduling/job/ScheduledJobTwo.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.scheduling.job;
2 |
3 | import net.petrikainulainen.spring.trenches.scheduling.service.MessageService;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.scheduling.annotation.Scheduled;
8 | import org.springframework.stereotype.Component;
9 |
10 | @Component
11 | public class ScheduledJobTwo {
12 |
13 | private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJobOne.class);
14 |
15 | private final MessageService messageService;
16 |
17 | @Autowired
18 | public ScheduledJobTwo(MessageService messageService) {
19 | this.messageService = messageService;
20 | }
21 |
22 | @Scheduled(cron = "${scheduling.job.cron}")
23 | public void run() {
24 | LOGGER.debug("Starting scheduled job 2.");
25 |
26 | String message = messageService.getMessage();
27 | LOGGER.debug("Received message 2: {}", message);
28 |
29 | LOGGER.debug("Scheduled job 2 is finished.");
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-32/src/main/java/net/petrikainulainen/spring/trenches/scheduling/service/HelloMessageService.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.scheduling.service;
2 |
3 | import org.springframework.stereotype.Service;
4 |
5 | /**
6 | * @author Petri Kainulainen
7 | */
8 | @Service
9 | public class HelloMessageService implements MessageService {
10 |
11 | @Override
12 | public String getMessage() {
13 | return "Hello World!";
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-32/src/main/java/net/petrikainulainen/spring/trenches/scheduling/service/MessageService.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.scheduling.service;
2 |
3 | import org.springframework.security.access.prepost.PreAuthorize;
4 |
5 | /**
6 | * @author Petri Kainulainen
7 | */
8 | public interface MessageService {
9 |
10 | /**
11 | * @return The message.
12 | */
13 | @PreAuthorize("hasRole('ROLE_USER')")
14 | public String getMessage();
15 | }
16 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-32/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #Scheduled job
2 | scheduling.job.cron=${scheduling.job.cron}
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-32/src/main/resources/applicationContext-security.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-32/src/main/resources/applicationContext.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/protected-method-scheduled-job/spring-sec-32/src/main/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.appender.Stdout=org.apache.log4j.ConsoleAppender
2 | log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout
3 | log4j.appender.Stdout.layout.conversionPattern=%d %-5p - %-26.26c{1} - %m\n
4 |
5 | log4j.rootLogger=DEBUG,Stdout
6 | log4j.logger.org.springframework=INFO
--------------------------------------------------------------------------------
/rest-validation-3.1/README:
--------------------------------------------------------------------------------
1 | This is an example application of my blog post:
2 |
3 | Spring From the Trenches: Adding Validation to a REST API
4 | http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/
5 |
6 | RUNNING THE APPLICATION:
7 |
8 | - Run command mvn clean jetty:run
9 |
10 |
--------------------------------------------------------------------------------
/rest-validation-3.1/profiles/dev/config.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pkainulainen/spring-from-the-trenches/fb15fe255af3ff5327fe3b993f74eb1a40fb486d/rest-validation-3.1/profiles/dev/config.properties
--------------------------------------------------------------------------------
/rest-validation-3.1/profiles/dev/system.properties:
--------------------------------------------------------------------------------
1 | spring.profiles.active=webapp
--------------------------------------------------------------------------------
/rest-validation-3.1/src/main/java/net/petrikainulainen/spring/trenches/comment/controller/CommentController.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.comment.controller;
2 |
3 | import net.petrikainulainen.spring.trenches.comment.dto.CommentDTO;
4 | import net.petrikainulainen.spring.trenches.comment.dto.ValidationErrorDTO;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.context.MessageSource;
9 | import org.springframework.context.i18n.LocaleContextHolder;
10 | import org.springframework.http.HttpStatus;
11 | import org.springframework.stereotype.Controller;
12 | import org.springframework.validation.BindingResult;
13 | import org.springframework.validation.FieldError;
14 | import org.springframework.web.bind.MethodArgumentNotValidException;
15 | import org.springframework.web.bind.annotation.*;
16 |
17 | import javax.validation.Valid;
18 | import java.util.List;
19 | import java.util.Locale;
20 |
21 | /**
22 | * @author Petri Kainulainen
23 | */
24 | @Controller
25 | public class CommentController {
26 |
27 | private static final Logger LOGGER = LoggerFactory.getLogger(CommentController.class);
28 |
29 | private MessageSource messageSource;
30 |
31 | @Autowired
32 | public CommentController(MessageSource messageSource) {
33 | this.messageSource = messageSource;
34 | }
35 |
36 | @RequestMapping(value = "/api/comment", method = RequestMethod.POST)
37 | @ResponseBody
38 | public CommentDTO add(@Valid @RequestBody CommentDTO comment) {
39 | LOGGER.debug("Received comment: {}", comment);
40 | return comment;
41 | }
42 |
43 |
44 | @ExceptionHandler(MethodArgumentNotValidException.class)
45 | @ResponseStatus(HttpStatus.BAD_REQUEST)
46 | @ResponseBody
47 | public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
48 | LOGGER.debug("Handling form validation error");
49 |
50 | BindingResult result = ex.getBindingResult();
51 | List fieldErrors = result.getFieldErrors();
52 |
53 | return processFieldErrors(fieldErrors);
54 | }
55 |
56 | private ValidationErrorDTO processFieldErrors(List fieldErrors) {
57 | ValidationErrorDTO dto = new ValidationErrorDTO();
58 |
59 | for (FieldError fieldError: fieldErrors) {
60 | String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
61 | LOGGER.debug("Adding error message: {} to field: {}", localizedErrorMessage, fieldError.getField());
62 | dto.addFieldError(fieldError.getField(), localizedErrorMessage);
63 | }
64 |
65 | return dto;
66 | }
67 |
68 | private String resolveLocalizedErrorMessage(FieldError fieldError) {
69 | Locale currentLocale = LocaleContextHolder.getLocale();
70 | String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale);
71 |
72 | //If a message was not found, return the most accurate field error code instead.
73 | //You can remove this check if you prefer to get the default error message.
74 | if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) {
75 | String[] fieldErrorCodes = fieldError.getCodes();
76 | localizedErrorMessage = fieldErrorCodes[0];
77 | }
78 |
79 | return localizedErrorMessage;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/rest-validation-3.1/src/main/java/net/petrikainulainen/spring/trenches/comment/dto/CommentDTO.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.comment.dto;
2 |
3 | import org.hibernate.validator.constraints.Length;
4 | import org.hibernate.validator.constraints.NotEmpty;
5 |
6 | /**
7 | * @author Petri Kainulainen
8 | */
9 | public class CommentDTO {
10 |
11 | @NotEmpty
12 | @Length(max = 140)
13 | private String text;
14 |
15 | public CommentDTO() {
16 |
17 | }
18 |
19 | public String getText() {
20 | return text;
21 | }
22 |
23 | public void setText(String text) {
24 | this.text = text;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/rest-validation-3.1/src/main/java/net/petrikainulainen/spring/trenches/comment/dto/FieldErrorDTO.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.comment.dto;
2 |
3 | /**
4 | * @author Petri Kainulainen
5 | */
6 | public class FieldErrorDTO {
7 |
8 | private String field;
9 |
10 | private String message;
11 |
12 | public FieldErrorDTO(String field, String message) {
13 | this.field = field;
14 | this.message = message;
15 | }
16 |
17 | public String getField() {
18 | return field;
19 | }
20 |
21 | public String getMessage() {
22 | return message;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/rest-validation-3.1/src/main/java/net/petrikainulainen/spring/trenches/comment/dto/ValidationErrorDTO.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.comment.dto;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | /**
7 | * @author Petri Kainulainen
8 | */
9 | public class ValidationErrorDTO {
10 |
11 | private List fieldErrors = new ArrayList<>();
12 |
13 | public ValidationErrorDTO() {
14 |
15 | }
16 |
17 | public void addFieldError(String path, String message) {
18 | FieldErrorDTO error = new FieldErrorDTO(path, message);
19 | fieldErrors.add(error);
20 | }
21 |
22 | public List getFieldErrors() {
23 | return fieldErrors;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/rest-validation-3.1/src/main/java/net/petrikainulainen/spring/trenches/config/ExampleApplicationConfig.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.web.WebApplicationInitializer;
4 | import org.springframework.web.context.ContextLoaderListener;
5 | import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
6 | import org.springframework.web.filter.DelegatingFilterProxy;
7 | import org.springframework.web.servlet.DispatcherServlet;
8 |
9 | import javax.servlet.*;
10 | import java.util.EnumSet;
11 |
12 | /**
13 | * @author Petri Kainulainen
14 | */
15 | public class ExampleApplicationConfig implements WebApplicationInitializer {
16 | private static final String DISPATCHER_SERVLET_NAME = "dispatcher";
17 | private static final String DISPATCHER_SERVLET_MAPPING = "/";
18 |
19 | @Override
20 | public void onStartup(ServletContext servletContext) throws ServletException {
21 | AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
22 | rootContext.register(ExampleApplicationContext.class);
23 |
24 | //XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
25 | //rootContext.setConfigLocation("classpath:exampleApplicationContext.xml");
26 |
27 | ServletRegistration.Dynamic dispatcher = servletContext.addServlet(DISPATCHER_SERVLET_NAME, new DispatcherServlet(rootContext));
28 | dispatcher.setLoadOnStartup(1);
29 | dispatcher.addMapping(DISPATCHER_SERVLET_MAPPING);
30 |
31 | servletContext.addListener(new ContextLoaderListener(rootContext));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/rest-validation-3.1/src/main/java/net/petrikainulainen/spring/trenches/config/ExampleApplicationContext.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.context.MessageSource;
4 | import org.springframework.context.annotation.*;
5 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
6 | import org.springframework.context.support.ResourceBundleMessageSource;
7 | import org.springframework.core.io.ClassPathResource;
8 | import org.springframework.web.servlet.ViewResolver;
9 | import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
10 | import org.springframework.web.servlet.config.annotation.EnableWebMvc;
11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
12 | import org.springframework.web.servlet.view.InternalResourceViewResolver;
13 | import org.springframework.web.servlet.view.JstlView;
14 |
15 | /**
16 | * @author Petri Kainulainen
17 | */
18 | @Configuration
19 | @EnableWebMvc
20 | @ComponentScan(basePackages = {
21 | "net.petrikainulainen.spring.trenches.comment.controller"
22 | })
23 | @Import({MessageContext.class, TestMessageContext.class})
24 | @PropertySource("classpath:application.properties")
25 | public class ExampleApplicationContext extends WebMvcConfigurerAdapter {
26 |
27 | @Override
28 | public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
29 | configurer.enable();
30 | }
31 |
32 | @Bean
33 | public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
34 | PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
35 |
36 | properties.setLocation(new ClassPathResource( "application.properties" ));
37 | properties.setIgnoreResourceNotFound(false);
38 |
39 | return properties;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/rest-validation-3.1/src/main/java/net/petrikainulainen/spring/trenches/config/MessageContext.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.context.MessageSource;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.context.annotation.Profile;
7 | import org.springframework.context.support.ResourceBundleMessageSource;
8 |
9 | /**
10 | * @author Petri Kainulainen
11 | */
12 | @Configuration
13 | @Profile("webapp")
14 | public class MessageContext {
15 |
16 | private static final String MESSAGE_SOURCE_BASE_NAME = "i18n/messages";
17 |
18 | @Bean
19 | public MessageSource messageSource() {
20 | ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
21 |
22 | messageSource.setBasename(MESSAGE_SOURCE_BASE_NAME);
23 | messageSource.setUseCodeAsDefaultMessage(true);
24 |
25 | return messageSource;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/rest-validation-3.1/src/main/java/net/petrikainulainen/spring/trenches/config/TestMessageContext.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.context.MessageSource;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.context.annotation.Profile;
7 | import org.springframework.context.support.ResourceBundleMessageSource;
8 |
9 | /**
10 | * @author Petri Kainulainen
11 | */
12 | @Configuration
13 | @Profile("test")
14 | public class TestMessageContext {
15 |
16 | private static final String MESSAGE_SOURCE_BASE_NAME = "i18n/messages-test";
17 |
18 | @Bean
19 | public MessageSource messageSource() {
20 | ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
21 |
22 | messageSource.setBasename(MESSAGE_SOURCE_BASE_NAME);
23 | messageSource.setUseCodeAsDefaultMessage(true);
24 |
25 | return messageSource;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/rest-validation-3.1/src/main/resources/application.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pkainulainen/spring-from-the-trenches/fb15fe255af3ff5327fe3b993f74eb1a40fb486d/rest-validation-3.1/src/main/resources/application.properties
--------------------------------------------------------------------------------
/rest-validation-3.1/src/main/resources/exampleApplicationContext.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/rest-validation-3.1/src/main/resources/i18n/messages.properties:
--------------------------------------------------------------------------------
1 | NotEmpty.commentDTO.text=Text cannot be empty.
2 | Length.commentDTO.text=The maximum length of text is {1} characters.
--------------------------------------------------------------------------------
/rest-validation-3.1/src/main/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.appender.Stdout=org.apache.log4j.ConsoleAppender
2 | log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout
3 | log4j.appender.Stdout.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n
4 |
5 | log4j.rootLogger=DEBUG,Stdout
6 | log4j.logger.org.springframework=DEBUG
7 | log4j.logger.org.dbunit=INFO
--------------------------------------------------------------------------------
/rest-validation-3.1/src/test/java/net/petrikainulainen/spring/trenches/UnitTestUtil.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize;
6 | import org.springframework.http.MediaType;
7 |
8 | import java.io.IOException;
9 | import java.nio.charset.Charset;
10 |
11 | /**
12 | * @author Petri Kainulainen
13 | */
14 | public class UnitTestUtil {
15 |
16 | private static final String CHARACTER = "C";
17 |
18 | public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
19 |
20 | public static byte[] convertObjectToJsonBytes(Object object) throws IOException {
21 | ObjectMapper mapper = new ObjectMapper();
22 | mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
23 | return mapper.writeValueAsBytes(object);
24 | }
25 |
26 | public static String createStringWithLength(int length) {
27 | StringBuilder builder = new StringBuilder();
28 | for (int index = 0; index <= length; index++) {
29 | builder.append(CHARACTER);
30 | }
31 | return builder.toString();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/rest-validation-3.1/src/test/java/net/petrikainulainen/spring/trenches/comment/controller/MessagesNotFoundCommentControllerTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.comment.controller;
2 |
3 | import net.petrikainulainen.spring.trenches.UnitTestUtil;
4 | import net.petrikainulainen.spring.trenches.comment.dto.CommentDTO;
5 | import net.petrikainulainen.spring.trenches.config.ExampleApplicationContext;
6 | import org.junit.Before;
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 | import org.springframework.test.context.ActiveProfiles;
10 | import org.springframework.test.context.ContextConfiguration;
11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12 | import org.springframework.test.web.server.MockMvc;
13 | import org.springframework.test.web.server.samples.context.WebContextLoader;
14 | import org.springframework.test.web.server.setup.MockMvcBuilders;
15 | import org.springframework.web.context.WebApplicationContext;
16 |
17 | import javax.annotation.Resource;
18 |
19 | import static org.hamcrest.Matchers.*;
20 | import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
21 | import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;
22 |
23 | /**
24 | * @author Petri Kainulainen
25 | */
26 | @RunWith(SpringJUnit4ClassRunner.class)
27 | @ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
28 | //@ContextConfiguration(loader = WebContextLoader.class, locations = {"classpath:exampleApplicationContext.xml"})
29 | @ActiveProfiles("test")
30 | public class MessagesNotFoundCommentControllerTest {
31 |
32 | private static final String COMMENT_TEXT = "comment";
33 |
34 | @Resource
35 | private WebApplicationContext webApplicationContext;
36 |
37 | private MockMvc mockMvc;
38 |
39 | @Before
40 | public void setUp() {
41 | mockMvc = MockMvcBuilders.webApplicationContextSetup(webApplicationContext).build();
42 | }
43 |
44 | @Test
45 | public void add_CommentTextIsEmpty_ShouldReturnValidationError() throws Exception {
46 | CommentDTO added = new CommentDTO();
47 | mockMvc.perform(post("/api/comment")
48 | .contentType(UnitTestUtil.APPLICATION_JSON_UTF8)
49 | .body(UnitTestUtil.convertObjectToJsonBytes(added))
50 | )
51 | .andExpect(status().isBadRequest())
52 | .andExpect(content().mimeType(UnitTestUtil.APPLICATION_JSON_UTF8))
53 | .andExpect(jsonPath("$.fieldErrors", hasSize(1)))
54 | .andExpect(jsonPath("$.fieldErrors[0].field", is("text")))
55 | .andExpect(jsonPath("$.fieldErrors[0].message", is("NotEmpty.commentDTO.text")));
56 | }
57 |
58 | @Test
59 | public void add_CommentTextIsTooLong_ShouldReturnValidationError() throws Exception {
60 | String added = UnitTestUtil.createStringWithLength(141);
61 | CommentDTO expected = createComment(added);
62 | mockMvc.perform(post("/api/comment")
63 | .contentType(UnitTestUtil.APPLICATION_JSON_UTF8)
64 | .body(UnitTestUtil.convertObjectToJsonBytes(expected))
65 | )
66 | .andExpect(status().isBadRequest())
67 | .andExpect(content().mimeType(UnitTestUtil.APPLICATION_JSON_UTF8))
68 | .andExpect(jsonPath("$.fieldErrors", hasSize(1)))
69 | .andExpect(jsonPath("$.fieldErrors[0].field", is("text")))
70 | .andExpect(jsonPath("$.fieldErrors[0].message", is("Length.commentDTO.text")));
71 | }
72 |
73 | private CommentDTO createComment(String text) {
74 | CommentDTO comment = new CommentDTO();
75 | comment.setText(text);
76 | return comment;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/rest-validation-3.1/src/test/java/net/petrikainulainen/spring/trenches/comment/dto/ValidationErrorDTOTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.comment.dto;
2 |
3 | import org.junit.Test;
4 |
5 | import java.util.List;
6 |
7 | import static org.hamcrest.Matchers.is;
8 | import static org.junit.Assert.assertThat;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | public class ValidationErrorDTOTest {
14 |
15 | private static final String MESSAGE = "Foo is not valid";
16 | private static final String FIELD = "foo";
17 |
18 | @Test
19 | public void addValidationError_ShouldAddNewValidationError() {
20 | ValidationErrorDTO validationError = new ValidationErrorDTO();
21 | validationError.addFieldError(FIELD, MESSAGE);
22 |
23 | List fieldErrors = validationError.getFieldErrors();
24 | assertThat(fieldErrors.size(), is(1));
25 |
26 | FieldErrorDTO fieldError = fieldErrors.get(0);
27 | assertThat(fieldError.getField(), is(FIELD));
28 | assertThat(fieldError.getMessage(), is(MESSAGE));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/rest-validation-3.1/src/test/java/org/springframework/test/web/server/samples/context/WebContextLoader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.springframework.test.web.server.samples.context;
17 |
18 | public class WebContextLoader extends GenericWebContextLoader {
19 |
20 | public WebContextLoader() {
21 | super("src/test/resources/META-INF/web-resources", false);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/rest-validation-3.1/src/test/resources/i18n/messages-test.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pkainulainen/spring-from-the-trenches/fb15fe255af3ff5327fe3b993f74eb1a40fb486d/rest-validation-3.1/src/test/resources/i18n/messages-test.properties
--------------------------------------------------------------------------------
/rest-validation-3.1/src/test/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.appender.Stdout=org.apache.log4j.ConsoleAppender
2 | log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout
3 | log4j.appender.Stdout.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n
4 |
5 | log4j.rootLogger=OFF,Stdout
6 | log4j.logger.org.springframework=OFF
7 | log4j.logger.org.dbunit=OFF
--------------------------------------------------------------------------------
/rest-validation-3.2/README:
--------------------------------------------------------------------------------
1 | This is an example application of my blog post:
2 |
3 | Spring From the Trenches: Adding Validation to a REST API:
4 | http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/
5 |
6 |
7 | RUNNING THE APPLICATION:
8 |
9 | - Run command mvn clean jetty:run
10 |
--------------------------------------------------------------------------------
/rest-validation-3.2/profiles/dev/config.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pkainulainen/spring-from-the-trenches/fb15fe255af3ff5327fe3b993f74eb1a40fb486d/rest-validation-3.2/profiles/dev/config.properties
--------------------------------------------------------------------------------
/rest-validation-3.2/profiles/dev/system.properties:
--------------------------------------------------------------------------------
1 | spring.profiles.active=webapp
--------------------------------------------------------------------------------
/rest-validation-3.2/src/main/java/net/petrikainulainen/spring/trenches/comment/controller/CommentController.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.comment.controller;
2 |
3 | import net.petrikainulainen.spring.trenches.comment.dto.CommentDTO;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.stereotype.Controller;
7 | import org.springframework.web.bind.annotation.*;
8 |
9 | import javax.validation.Valid;
10 |
11 | /**
12 | * @author Petri Kainulainen
13 | */
14 | @Controller
15 | public class CommentController {
16 |
17 | private static final Logger LOGGER = LoggerFactory.getLogger(CommentController.class);
18 |
19 | @RequestMapping(value = "/api/comment", method = RequestMethod.POST)
20 | @ResponseBody
21 | public CommentDTO add(@Valid @RequestBody CommentDTO comment) {
22 | LOGGER.debug("Received comment: {}", comment);
23 | return comment;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/rest-validation-3.2/src/main/java/net/petrikainulainen/spring/trenches/comment/controller/RestErrorHandler.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.comment.controller;
2 |
3 | import net.petrikainulainen.spring.trenches.comment.dto.ValidationErrorDTO;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.context.MessageSource;
8 | import org.springframework.context.i18n.LocaleContextHolder;
9 | import org.springframework.http.HttpStatus;
10 | import org.springframework.validation.BindingResult;
11 | import org.springframework.validation.FieldError;
12 | import org.springframework.web.bind.MethodArgumentNotValidException;
13 | import org.springframework.web.bind.annotation.ControllerAdvice;
14 | import org.springframework.web.bind.annotation.ExceptionHandler;
15 | import org.springframework.web.bind.annotation.ResponseBody;
16 | import org.springframework.web.bind.annotation.ResponseStatus;
17 |
18 | import java.util.List;
19 | import java.util.Locale;
20 |
21 | /**
22 | * @author Petri Kainulainen
23 | */
24 | @ControllerAdvice
25 | public class RestErrorHandler {
26 |
27 | private static final Logger LOGGER = LoggerFactory.getLogger(RestErrorHandler.class);
28 |
29 | private MessageSource messageSource;
30 |
31 | @Autowired
32 | public RestErrorHandler(MessageSource messageSource) {
33 | this.messageSource = messageSource;
34 | }
35 |
36 | @ExceptionHandler(MethodArgumentNotValidException.class)
37 | @ResponseStatus(HttpStatus.BAD_REQUEST)
38 | @ResponseBody
39 | public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
40 | LOGGER.debug("Handling form validation error");
41 |
42 | BindingResult result = ex.getBindingResult();
43 | List fieldErrors = result.getFieldErrors();
44 |
45 | return processFieldErrors(fieldErrors);
46 | }
47 |
48 | private ValidationErrorDTO processFieldErrors(List fieldErrors) {
49 | ValidationErrorDTO dto = new ValidationErrorDTO();
50 |
51 | for (FieldError fieldError: fieldErrors) {
52 | String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
53 | LOGGER.debug("Adding error message: {} to field: {}", localizedErrorMessage, fieldError.getField());
54 | dto.addFieldError(fieldError.getField(), localizedErrorMessage);
55 | }
56 |
57 | return dto;
58 | }
59 |
60 | private String resolveLocalizedErrorMessage(FieldError fieldError) {
61 | Locale currentLocale = LocaleContextHolder.getLocale();
62 | String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale);
63 |
64 | //If a message was not found, return the most accurate field error code instead.
65 | //You can remove this check if you prefer to get the default error message.
66 | if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) {
67 | String[] fieldErrorCodes = fieldError.getCodes();
68 | localizedErrorMessage = fieldErrorCodes[0];
69 | }
70 |
71 | return localizedErrorMessage;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/rest-validation-3.2/src/main/java/net/petrikainulainen/spring/trenches/comment/dto/CommentDTO.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.comment.dto;
2 |
3 | import org.hibernate.validator.constraints.Length;
4 | import org.hibernate.validator.constraints.NotEmpty;
5 |
6 | /**
7 | * @author Petri Kainulainen
8 | */
9 | public class CommentDTO {
10 |
11 | @NotEmpty
12 | @Length(max = 140)
13 | private String text;
14 |
15 | public CommentDTO() {
16 |
17 | }
18 |
19 | public String getText() {
20 | return text;
21 | }
22 |
23 | public void setText(String text) {
24 | this.text = text;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/rest-validation-3.2/src/main/java/net/petrikainulainen/spring/trenches/comment/dto/FieldErrorDTO.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.comment.dto;
2 |
3 | /**
4 | * @author Petri Kainulainen
5 | */
6 | public class FieldErrorDTO {
7 |
8 | private String field;
9 |
10 | private String message;
11 |
12 | public FieldErrorDTO(String field, String message) {
13 | this.field = field;
14 | this.message = message;
15 | }
16 |
17 | public String getField() {
18 | return field;
19 | }
20 |
21 | public String getMessage() {
22 | return message;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/rest-validation-3.2/src/main/java/net/petrikainulainen/spring/trenches/comment/dto/ValidationErrorDTO.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.comment.dto;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | /**
7 | * @author Petri Kainulainen
8 | */
9 | public class ValidationErrorDTO {
10 |
11 | private List fieldErrors = new ArrayList<>();
12 |
13 | public ValidationErrorDTO() {
14 |
15 | }
16 |
17 | public void addFieldError(String path, String message) {
18 | FieldErrorDTO error = new FieldErrorDTO(path, message);
19 | fieldErrors.add(error);
20 | }
21 |
22 | public List getFieldErrors() {
23 | return fieldErrors;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/rest-validation-3.2/src/main/java/net/petrikainulainen/spring/trenches/config/ExampleApplicationConfig.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.web.WebApplicationInitializer;
4 | import org.springframework.web.context.ContextLoaderListener;
5 | import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
6 | import org.springframework.web.filter.DelegatingFilterProxy;
7 | import org.springframework.web.servlet.DispatcherServlet;
8 |
9 | import javax.servlet.*;
10 | import java.util.EnumSet;
11 |
12 | /**
13 | * @author Petri Kainulainen
14 | */
15 | public class ExampleApplicationConfig implements WebApplicationInitializer {
16 | private static final String DISPATCHER_SERVLET_NAME = "dispatcher";
17 | private static final String DISPATCHER_SERVLET_MAPPING = "/";
18 |
19 | @Override
20 | public void onStartup(ServletContext servletContext) throws ServletException {
21 | AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
22 | rootContext.register(ExampleApplicationContext.class);
23 |
24 | //XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
25 | //rootContext.setConfigLocation("classpath:exampleApplicationContext.xml");
26 |
27 | ServletRegistration.Dynamic dispatcher = servletContext.addServlet(DISPATCHER_SERVLET_NAME, new DispatcherServlet(rootContext));
28 | dispatcher.setLoadOnStartup(1);
29 | dispatcher.addMapping(DISPATCHER_SERVLET_MAPPING);
30 |
31 | servletContext.addListener(new ContextLoaderListener(rootContext));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/rest-validation-3.2/src/main/java/net/petrikainulainen/spring/trenches/config/ExampleApplicationContext.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.context.MessageSource;
4 | import org.springframework.context.annotation.*;
5 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
6 | import org.springframework.context.support.ResourceBundleMessageSource;
7 | import org.springframework.core.io.ClassPathResource;
8 | import org.springframework.web.servlet.ViewResolver;
9 | import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
10 | import org.springframework.web.servlet.config.annotation.EnableWebMvc;
11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
12 | import org.springframework.web.servlet.view.InternalResourceViewResolver;
13 | import org.springframework.web.servlet.view.JstlView;
14 |
15 | /**
16 | * @author Petri Kainulainen
17 | */
18 | @Configuration
19 | @EnableWebMvc
20 | @ComponentScan(basePackages = {
21 | "net.petrikainulainen.spring.trenches.comment.controller"
22 | })
23 | @Import({MessageContext.class, TestMessageContext.class})
24 | @PropertySource("classpath:application.properties")
25 | public class ExampleApplicationContext extends WebMvcConfigurerAdapter {
26 |
27 | @Override
28 | public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
29 | configurer.enable();
30 | }
31 |
32 | @Bean
33 | public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
34 | PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
35 |
36 | properties.setLocation(new ClassPathResource( "application.properties" ));
37 | properties.setIgnoreResourceNotFound(false);
38 |
39 | return properties;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/rest-validation-3.2/src/main/java/net/petrikainulainen/spring/trenches/config/MessageContext.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.context.MessageSource;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.context.annotation.Profile;
7 | import org.springframework.context.support.ResourceBundleMessageSource;
8 |
9 | /**
10 | * @author Petri Kainulainen
11 | */
12 | @Configuration
13 | @Profile("webapp")
14 | public class MessageContext {
15 |
16 | private static final String MESSAGE_SOURCE_BASE_NAME = "i18n/messages";
17 |
18 | @Bean
19 | public MessageSource messageSource() {
20 | ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
21 |
22 | messageSource.setBasename(MESSAGE_SOURCE_BASE_NAME);
23 | messageSource.setUseCodeAsDefaultMessage(true);
24 |
25 | return messageSource;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/rest-validation-3.2/src/main/java/net/petrikainulainen/spring/trenches/config/TestMessageContext.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.context.MessageSource;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.context.annotation.Profile;
7 | import org.springframework.context.support.ResourceBundleMessageSource;
8 |
9 | /**
10 | * @author Petri Kainulainen
11 | */
12 | @Configuration
13 | @Profile("test")
14 | public class TestMessageContext {
15 |
16 | private static final String MESSAGE_SOURCE_BASE_NAME = "i18n/messages-test";
17 |
18 | @Bean
19 | public MessageSource messageSource() {
20 | ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
21 |
22 | messageSource.setBasename(MESSAGE_SOURCE_BASE_NAME);
23 | messageSource.setUseCodeAsDefaultMessage(true);
24 |
25 | return messageSource;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/rest-validation-3.2/src/main/resources/application.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pkainulainen/spring-from-the-trenches/fb15fe255af3ff5327fe3b993f74eb1a40fb486d/rest-validation-3.2/src/main/resources/application.properties
--------------------------------------------------------------------------------
/rest-validation-3.2/src/main/resources/exampleApplicationContext.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/rest-validation-3.2/src/main/resources/i18n/messages.properties:
--------------------------------------------------------------------------------
1 | NotEmpty.commentDTO.text=Text cannot be empty.
2 | Length.commentDTO.text=The maximum length of text is {1} characters.
--------------------------------------------------------------------------------
/rest-validation-3.2/src/main/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.appender.Stdout=org.apache.log4j.ConsoleAppender
2 | log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout
3 | log4j.appender.Stdout.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n
4 |
5 | log4j.rootLogger=DEBUG,Stdout
6 | log4j.logger.org.springframework=DEBUG
7 | log4j.logger.org.dbunit=INFO
--------------------------------------------------------------------------------
/rest-validation-3.2/src/test/java/net/petrikainulainen/spring/trenches/UnitTestUtil.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize;
6 | import org.springframework.http.MediaType;
7 |
8 | import java.io.IOException;
9 | import java.nio.charset.Charset;
10 |
11 | /**
12 | * @author Petri Kainulainen
13 | */
14 | public class UnitTestUtil {
15 |
16 | private static final String CHARACTER = "C";
17 |
18 | public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
19 |
20 | public static byte[] convertObjectToJsonBytes(Object object) throws IOException {
21 | ObjectMapper mapper = new ObjectMapper();
22 | mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
23 | return mapper.writeValueAsBytes(object);
24 | }
25 |
26 | public static String createStringWithLength(int length) {
27 | StringBuilder builder = new StringBuilder();
28 | for (int index = 0; index <= length; index++) {
29 | builder.append(CHARACTER);
30 | }
31 | return builder.toString();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/rest-validation-3.2/src/test/java/net/petrikainulainen/spring/trenches/comment/controller/MessagesNotFoundCommentControllerTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.comment.controller;
2 |
3 | import net.petrikainulainen.spring.trenches.UnitTestUtil;
4 | import net.petrikainulainen.spring.trenches.comment.dto.CommentDTO;
5 | import net.petrikainulainen.spring.trenches.config.ExampleApplicationContext;
6 | import org.hamcrest.Matcher;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.springframework.test.context.ActiveProfiles;
11 | import org.springframework.test.context.ContextConfiguration;
12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
13 | import org.springframework.test.context.web.WebAppConfiguration;
14 | import org.springframework.test.web.servlet.MockMvc;
15 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
16 | import org.springframework.web.context.WebApplicationContext;
17 |
18 | import javax.annotation.Resource;
19 |
20 | import java.util.Collection;
21 |
22 | import static org.hamcrest.Matchers.*;
23 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
24 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
25 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
26 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
27 |
28 | /**
29 | * @author Petri Kainulainen
30 | */
31 | @RunWith(SpringJUnit4ClassRunner.class)
32 | @ContextConfiguration(classes = {ExampleApplicationContext.class})
33 | //@ContextConfiguration(locations = {"classpath:exampleApplicationContext.xml"})
34 | @WebAppConfiguration
35 | @ActiveProfiles("test")
36 | public class MessagesNotFoundCommentControllerTest {
37 |
38 | private static final String COMMENT_TEXT = "comment";
39 |
40 | @Resource
41 | private WebApplicationContext webApplicationContext;
42 |
43 | private MockMvc mockMvc;
44 |
45 | @Before
46 | public void setUp() {
47 | mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
48 | }
49 |
50 | @Test
51 | public void add_CommentTextIsEmpty_ShouldReturnValidationError() throws Exception {
52 | CommentDTO added = new CommentDTO();
53 | mockMvc.perform(post("/api/comment")
54 | .contentType(UnitTestUtil.APPLICATION_JSON_UTF8)
55 | .content(UnitTestUtil.convertObjectToJsonBytes(added))
56 | )
57 | .andExpect(status().isBadRequest())
58 | .andExpect(content().contentType(UnitTestUtil.APPLICATION_JSON_UTF8))
59 | .andExpect(jsonPath("$.fieldErrors", hasSize(1)))
60 | .andExpect(jsonPath("$.fieldErrors[0].field", is("text")))
61 | .andExpect(jsonPath("$.fieldErrors[0].message", is("NotEmpty.commentDTO.text")));
62 | }
63 |
64 |
65 |
66 | @Test
67 | public void add_CommentTextIsTooLong_ShouldReturnValidationError() throws Exception {
68 | String added = UnitTestUtil.createStringWithLength(141);
69 | CommentDTO expected = createComment(added);
70 | mockMvc.perform(post("/api/comment")
71 | .contentType(UnitTestUtil.APPLICATION_JSON_UTF8)
72 | .content(UnitTestUtil.convertObjectToJsonBytes(expected))
73 | )
74 | .andExpect(status().isBadRequest())
75 | .andExpect(content().contentType(UnitTestUtil.APPLICATION_JSON_UTF8))
76 | .andExpect(jsonPath("$.fieldErrors", hasSize(1)))
77 | .andExpect(jsonPath("$.fieldErrors[0].field", is("text")))
78 | .andExpect(jsonPath("$.fieldErrors[0].message", is("Length.commentDTO.text")));
79 | }
80 |
81 | private CommentDTO createComment(String text) {
82 | CommentDTO comment = new CommentDTO();
83 | comment.setText(text);
84 | return comment;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/rest-validation-3.2/src/test/java/net/petrikainulainen/spring/trenches/comment/dto/ValidationErrorDTOTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.comment.dto;
2 |
3 | import org.junit.Test;
4 |
5 | import java.util.List;
6 |
7 | import static org.hamcrest.Matchers.is;
8 | import static org.junit.Assert.assertThat;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | public class ValidationErrorDTOTest {
14 |
15 | private static final String MESSAGE = "Foo is not valid";
16 | private static final String FIELD = "foo";
17 |
18 | @Test
19 | public void addValidationError_ShouldAddNewValidationError() {
20 | ValidationErrorDTO validationError = new ValidationErrorDTO();
21 | validationError.addFieldError(FIELD, MESSAGE);
22 |
23 | List fieldErrors = validationError.getFieldErrors();
24 | assertThat(fieldErrors.size(), is(1));
25 |
26 | FieldErrorDTO fieldError = fieldErrors.get(0);
27 | assertThat(fieldError.getField(), is(FIELD));
28 | assertThat(fieldError.getMessage(), is(MESSAGE));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/rest-validation-3.2/src/test/java/org/springframework/test/web/server/samples/context/WebContextLoader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.springframework.test.web.server.samples.context;
17 |
18 | public class WebContextLoader extends GenericWebContextLoader {
19 |
20 | public WebContextLoader() {
21 | super("src/test/resources/META-INF/web-resources", false);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/rest-validation-3.2/src/test/resources/i18n/messages-test.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pkainulainen/spring-from-the-trenches/fb15fe255af3ff5327fe3b993f74eb1a40fb486d/rest-validation-3.2/src/test/resources/i18n/messages-test.properties
--------------------------------------------------------------------------------
/rest-validation-3.2/src/test/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.appender.Stdout=org.apache.log4j.ConsoleAppender
2 | log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout
3 | log4j.appender.Stdout.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n
4 |
5 | log4j.rootLogger=OFF,Stdout
6 | log4j.logger.org.springframework=OFF
7 | log4j.logger.org.dbunit=OFF
--------------------------------------------------------------------------------
/scheduled-with-properties-file/README:
--------------------------------------------------------------------------------
1 | This is an example application of my blog post:
2 |
3 | Spring from the Trenches: Using Environment Specific Cron Expressions with the @Scheduled Annotation
4 | http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-using-environment-specific-cron-expressions-with-the-scheduled-annotation/
5 |
6 | RUNNING THE APPLICATION:
7 |
8 | You can run the application by running the following command at command prompt:
9 |
10 | mvn clean jetty:run -P dev (development profile)
11 | mvn clean jetty:run -P prod (production profile)
12 |
13 |
--------------------------------------------------------------------------------
/scheduled-with-properties-file/profiles/dev/config.properties:
--------------------------------------------------------------------------------
1 | scheduling.job.cron=0-59 * * * * *
--------------------------------------------------------------------------------
/scheduled-with-properties-file/profiles/prod/config.properties:
--------------------------------------------------------------------------------
1 | scheduling.job.cron=0 0-59 * * * *
--------------------------------------------------------------------------------
/scheduled-with-properties-file/src/main/java/net/petrikainulainen/spring/trenches/config/ExampleApplicationConfig.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.web.WebApplicationInitializer;
4 | import org.springframework.web.context.ContextLoaderListener;
5 | import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
6 | import org.springframework.web.context.support.XmlWebApplicationContext;
7 |
8 | import javax.servlet.*;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | public class ExampleApplicationConfig implements WebApplicationInitializer {
14 |
15 | @Override
16 | public void onStartup(ServletContext servletContext) throws ServletException {
17 | AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
18 | rootContext.register(ExampleApplicationContext.class);
19 |
20 | //XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
21 | //rootContext.setConfigLocation("classpath:exampleApplicationContext.xml");
22 |
23 | servletContext.addListener(new ContextLoaderListener(rootContext));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/scheduled-with-properties-file/src/main/java/net/petrikainulainen/spring/trenches/config/ExampleApplicationContext.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import org.springframework.context.annotation.*;
4 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
5 | import org.springframework.core.io.ClassPathResource;
6 | import org.springframework.scheduling.annotation.EnableScheduling;
7 |
8 | /**
9 | * @author Petri Kainulainen
10 | */
11 | @Configuration
12 | @EnableScheduling
13 | @ComponentScan(basePackages = {
14 | "net.petrikainulainen.spring.trenches.scheduling"
15 | })
16 | @PropertySource("classpath:application.properties")
17 | public class ExampleApplicationContext {
18 |
19 | @Bean
20 | public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
21 | PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
22 |
23 | properties.setLocation(new ClassPathResource( "application.properties" ));
24 | properties.setIgnoreResourceNotFound(false);
25 |
26 | return properties;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/scheduled-with-properties-file/src/main/java/net/petrikainulainen/spring/trenches/scheduling/ScheduledJob.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.scheduling;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.scheduling.annotation.Scheduled;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * @author Petri Kainulainen
10 | */
11 | @Component
12 | public class ScheduledJob {
13 |
14 | private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJob.class);
15 |
16 | @Scheduled(cron = "${scheduling.job.cron}")
17 | public void run() {
18 | LOGGER.debug("run()");
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/scheduled-with-properties-file/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #Scheduled job
2 | scheduling.job.cron=${scheduling.job.cron}
--------------------------------------------------------------------------------
/scheduled-with-properties-file/src/main/resources/exampleApplicationContext.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/scheduled-with-properties-file/src/main/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.appender.Stdout=org.apache.log4j.ConsoleAppender
2 | log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout
3 | log4j.appender.Stdout.layout.conversionPattern=%d %-5p - %-26.26c{1} - %m\n
4 |
5 | log4j.rootLogger=DEBUG,Stdout
6 | log4j.logger.org.springframework=DEBUG
7 | log4j.logger.org.dbunit=INFO
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/README.md:
--------------------------------------------------------------------------------
1 | This is the example application of my blog posts:
2 |
3 | * [Spring From the Trenches: Using Null Values in DbUnit Data Sets](http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-using-null-values-in-dbunit-datasets/)
4 | * [Spring From the Trenches: Resetting Autoincrement Columns Before Each Test Method](http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-resetting-auto-increment-columns-before-each-test-method/)
5 |
6 | If you don't know how you can write integration tests for Spring Data JPA repositories,
7 | You should read a blog post titled: [Spring Data JPA Tutorial: Integration Testing](http://www.petrikainulainen.net/programming/spring-framework/spring-data-jpa-tutorial-integration-testing/).
8 |
9 | Running the Tests
10 | =================
11 |
12 | If you want to run the unit tests, you should invoke the following command at command prompt:
13 |
14 | mvn clean test -P dev
15 |
16 | If you want to run the integration tests, you should invoke the command at command prompt:
17 |
18 | mvn clean verify -P integration-test
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/profiles/dev/config.properties:
--------------------------------------------------------------------------------
1 | #Database Configuration
2 | db.driver=org.h2.Driver
3 | db.url=jdbc:h2:mem:datajpa
4 | db.username=sa
5 | db.password=
6 |
7 | #Hibernate Configuration
8 | hibernate.dialect=org.hibernate.dialect.H2Dialect
9 | hibernate.format_sql=true
10 | hibernate.hbm2ddl.auto=create-drop
11 | hibernate.ejb.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy
12 | hibernate.show_sql=true
13 |
14 | #Test Configuration
15 | test.reset.sql.template=ALTER TABLE %s ALTER COLUMN id RESTART WITH 1
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/profiles/integration-test/config.properties:
--------------------------------------------------------------------------------
1 | #Database Configuration
2 | db.driver=org.h2.Driver
3 | db.url=jdbc:h2:mem:datajpa
4 | db.username=sa
5 | db.password=
6 |
7 | #Hibernate Configuration
8 | hibernate.dialect=org.hibernate.dialect.H2Dialect
9 | hibernate.format_sql=false
10 | hibernate.hbm2ddl.auto=create-drop
11 | hibernate.ejb.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy
12 | hibernate.show_sql=false
13 |
14 | #Test Configuration
15 | test.reset.sql.template=ALTER TABLE %s ALTER COLUMN id RESTART WITH 1
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/integration-test/java/net/petrikainulainen/spring/trenches/todo/repository/ColumnSensingReplacementDataSetLoader.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.todo.repository;
2 |
3 |
4 | import com.github.springtestdbunit.dataset.AbstractDataSetLoader;
5 | import org.dbunit.dataset.IDataSet;
6 | import org.dbunit.dataset.ReplacementDataSet;
7 | import org.dbunit.dataset.xml.FlatXmlDataSet;
8 | import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
9 | import org.springframework.core.io.Resource;
10 |
11 | import java.io.InputStream;
12 |
13 | /**
14 | * This class is a custom DbUnit data set loader that support flat XML data sets. This data set loader
15 | * adds support for the extra features:
16 | *
17 | *
You can use the column sensing feature of DbUnit.
18 | *
You can specify that a column's value is null by using the string [null].
19 | *
20 | * @author Petri Kainulainen
21 | */
22 | public class ColumnSensingReplacementDataSetLoader extends AbstractDataSetLoader {
23 |
24 | @Override
25 | protected IDataSet createDataSet(Resource resource) throws Exception {
26 | FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
27 | builder.setColumnSensing(true);
28 | try (InputStream inputStream = resource.getInputStream()) {
29 | return createReplacementDataSet(builder.build(inputStream));
30 | }
31 | }
32 |
33 | private ReplacementDataSet createReplacementDataSet(FlatXmlDataSet dataSet) {
34 | ReplacementDataSet replacementDataSet = new ReplacementDataSet(dataSet);
35 | replacementDataSet.addReplacementObject("[null]", null);
36 | return replacementDataSet;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/integration-test/java/net/petrikainulainen/spring/trenches/todo/repository/DbTestUtil.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.todo.repository;
2 |
3 | import org.springframework.context.ApplicationContext;
4 | import org.springframework.core.env.Environment;
5 |
6 | import javax.sql.DataSource;
7 | import java.sql.Connection;
8 | import java.sql.SQLException;
9 | import java.sql.Statement;
10 |
11 | /**
12 | * This class is used to invoke SQL statements to the database before our test cases are run. You
13 | * can use this class as long as:
14 | *
15 | *
16 | *
17 | * You run your integration tests by using the {@link org.springframework.test.context.junit4.SpringJUnit4ClassRunner}
18 | * class and configure the used application context configuration class or file by using the
19 | * {@link org.springframework.test.context.ContextConfiguration} annotation.
20 | *
21 | *
22 | * Your application context configuration configures both {@link javax.sql.DataSource}
23 | * and {@link org.springframework.core.env.Environment} beans. The {@link javax.sql.DataSource} bean
24 | * is used to open a database connection. The {@link org.springframework.core.env.Environment} bean
25 | * is used to obtain the used SQL statement template from a properties file.
26 | *
27 | *
28 | * You have configured the SQL statement template in the properties file of your application
29 | * by using the key 'test.reset.sql.template'. This template must be configured by using the
30 | * format that is supported by the {@link String#format(String, Object...)} method.
31 | *
32 | *
33 | *
34 | * @author Petri Kainulainen
35 | */
36 | public final class DbTestUtil {
37 |
38 | private static final String PROPERTY_KEY_RESET_SQL_TEMPLATE = "test.reset.sql.template";
39 |
40 | /**
41 | * Prevents instantiation.
42 | */
43 | private DbTestUtil() {}
44 |
45 | /**
46 | * This method reads the invoked SQL statement template from a properties file, creates
47 | * the invoked SQL statements, and invokes them.
48 | *
49 | * @param applicationContext The application context that is used by our tests.
50 | * @param tableNames The names of the database tables which auto-increment column will be reseted.
51 | * @throws SQLException If a SQL statement cannot be invoked for some reason.
52 | */
53 | public static void resetAutoIncrementColumns(ApplicationContext applicationContext,
54 | String... tableNames) throws SQLException {
55 | DataSource dataSource = applicationContext.getBean(DataSource.class);
56 | String resetSqlTemplate = getResetSqlTemplate(applicationContext);
57 | try (Connection dbConnection = dataSource.getConnection()) {
58 | //Create SQL statements that reset the auto-increment columns and invoke
59 | //the created SQL statements.
60 | for (String resetSqlArgument: tableNames) {
61 | try (Statement statement = dbConnection.createStatement()) {
62 | String resetSql = String.format(resetSqlTemplate, resetSqlArgument);
63 | statement.execute(resetSql);
64 | }
65 | }
66 | }
67 | }
68 |
69 | private static String getResetSqlTemplate(ApplicationContext applicationContext) {
70 | //Read the SQL template from the properties file
71 | Environment environment = applicationContext.getBean(Environment.class);
72 | return environment.getRequiredProperty(PROPERTY_KEY_RESET_SQL_TEMPLATE);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/integration-test/java/net/petrikainulainen/spring/trenches/todo/repository/ITTodoRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.todo.repository;
2 |
3 | import com.github.springtestdbunit.DbUnitTestExecutionListener;
4 | import com.github.springtestdbunit.annotation.DatabaseSetup;
5 | import com.github.springtestdbunit.annotation.DbUnitConfiguration;
6 | import com.github.springtestdbunit.annotation.ExpectedDatabase;
7 | import net.petrikainulainen.spring.trenches.config.PersistenceContext;
8 | import net.petrikainulainen.spring.trenches.todo.model.Todo;
9 | import org.junit.Before;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.context.ApplicationContext;
14 | import org.springframework.test.context.ContextConfiguration;
15 | import org.springframework.test.context.TestExecutionListeners;
16 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
17 | import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
18 | import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
19 | import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
20 |
21 | import java.sql.SQLException;
22 | import java.util.List;
23 |
24 | import static org.assertj.core.api.Assertions.assertThat;
25 |
26 | /**
27 | * @author Petri Kainulainen
28 | */
29 | @RunWith(SpringJUnit4ClassRunner.class)
30 | @ContextConfiguration(classes = {PersistenceContext.class})
31 | //@ContextConfiguration(locations = {"classpath:exampleApplicationContext-persistence.xml"})
32 | @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
33 | DirtiesContextTestExecutionListener.class,
34 | TransactionalTestExecutionListener.class,
35 | DbUnitTestExecutionListener.class })
36 | @DbUnitConfiguration(dataSetLoader = ColumnSensingReplacementDataSetLoader.class)
37 | public class ITTodoRepositoryTest {
38 |
39 | private static final Long ID = 2L;
40 | private static final String DESCRIPTION = "description";
41 | private static final String TITLE = "title";
42 | private static final long VERSION = 0L;
43 |
44 | @Autowired
45 | private ApplicationContext applicationContext;
46 |
47 | @Autowired
48 | private TodoRepository repository;
49 |
50 | @Before
51 | public void setUp() throws SQLException {
52 | DbTestUtil.resetAutoIncrementColumns(applicationContext, "todos");
53 | }
54 |
55 | @Test
56 | @DatabaseSetup("todo-entries.xml")
57 | public void findByDescription_ShouldReturnOneTodoEntry() {
58 | List todoEntries = repository.findByDescription(DESCRIPTION);
59 | assertThat(todoEntries).hasSize(1);
60 |
61 | Todo found = todoEntries.get(0);
62 | assertThat(found.getId()).isEqualTo(ID);
63 | assertThat(found.getTitle()).isEqualTo(TITLE);
64 | assertThat(found.getDescription()).isEqualTo(DESCRIPTION);
65 | assertThat(found.getVersion()).isEqualTo(VERSION);
66 | }
67 |
68 | @Test
69 | @DatabaseSetup("no-todo-entries.xml")
70 | @ExpectedDatabase("save-todo-entry-with-title-and-description-expected.xml")
71 | public void save_WithTitleAndDescription_ShouldSaveTodoEntryToDatabase() {
72 | Todo todoEntry = Todo.getBuilder()
73 | .title(TITLE)
74 | .description(DESCRIPTION)
75 | .build();
76 |
77 | repository.save(todoEntry);
78 | }
79 |
80 | @Test
81 | @DatabaseSetup("no-todo-entries.xml")
82 | @ExpectedDatabase("save-todo-entry-without-description-expected.xml")
83 | public void save_WithoutDescription_ShouldSaveTodoEntryToDatabase() {
84 | Todo todoEntry = Todo.getBuilder()
85 | .title(TITLE)
86 | .description(null)
87 | .build();
88 |
89 | repository.save(todoEntry);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/integration-test/resources/net/petrikainulainen/spring/trenches/todo/repository/no-todo-entries.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/integration-test/resources/net/petrikainulainen/spring/trenches/todo/repository/save-todo-entry-with-title-and-description-expected.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/integration-test/resources/net/petrikainulainen/spring/trenches/todo/repository/save-todo-entry-without-description-expected.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/integration-test/resources/net/petrikainulainen/spring/trenches/todo/repository/todo-entries.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/main/java/net/petrikainulainen/spring/trenches/config/PersistenceContext.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.config;
2 |
3 | import com.jolbox.bonecp.BoneCPDataSource;
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.context.annotation.PropertySource;
8 | import org.springframework.core.env.Environment;
9 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
10 | import org.springframework.orm.jpa.JpaTransactionManager;
11 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
12 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
13 | import org.springframework.transaction.annotation.EnableTransactionManagement;
14 |
15 | import javax.sql.DataSource;
16 | import java.util.Properties;
17 |
18 | /**
19 | * @author Petri Kainulainen
20 | */
21 | @Configuration
22 | @EnableTransactionManagement
23 | @EnableJpaRepositories(basePackages = "net.petrikainulainen.spring.trenches.todo.repository")
24 | @PropertySource("classpath:application.properties")
25 | public class PersistenceContext {
26 |
27 | protected static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
28 | protected static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
29 | protected static final String PROPERTY_NAME_DATABASE_URL = "db.url";
30 | protected static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";
31 |
32 | private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
33 | private static final String PROPERTY_NAME_HIBERNATE_FORMAT_SQL = "hibernate.format_sql";
34 | private static final String PROPERTY_NAME_HIBERNATE_HBM2DDL_AUTO = "hibernate.hbm2ddl.auto";
35 | private static final String PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY = "hibernate.ejb.naming_strategy";
36 | private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
37 |
38 | private static final String PROPERTY_PACKAGES_TO_SCAN = "net.petrikainulainen.spring.trenches.todo.model";
39 |
40 | @Autowired
41 | private Environment environment;
42 |
43 | @Bean
44 | public DataSource dataSource() {
45 | BoneCPDataSource dataSource = new BoneCPDataSource();
46 |
47 | dataSource.setDriverClass(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
48 | dataSource.setJdbcUrl(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
49 | dataSource.setUsername(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
50 | dataSource.setPassword(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));
51 |
52 | return dataSource;
53 | }
54 |
55 | @Bean
56 | public JpaTransactionManager transactionManager() {
57 | JpaTransactionManager transactionManager = new JpaTransactionManager();
58 |
59 | transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
60 |
61 | return transactionManager;
62 | }
63 |
64 | @Bean
65 | public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
66 | LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
67 |
68 | entityManagerFactoryBean.setDataSource(dataSource());
69 | entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
70 | entityManagerFactoryBean.setPackagesToScan(PROPERTY_PACKAGES_TO_SCAN);
71 |
72 | Properties jpaProperties = new Properties();
73 | jpaProperties.put(PROPERTY_NAME_HIBERNATE_DIALECT, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
74 | jpaProperties.put(PROPERTY_NAME_HIBERNATE_FORMAT_SQL, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_FORMAT_SQL));
75 | jpaProperties.put(PROPERTY_NAME_HIBERNATE_HBM2DDL_AUTO, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_HBM2DDL_AUTO));
76 | jpaProperties.put(PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY));
77 | jpaProperties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
78 |
79 | entityManagerFactoryBean.setJpaProperties(jpaProperties);
80 |
81 | return entityManagerFactoryBean;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/main/java/net/petrikainulainen/spring/trenches/todo/model/Todo.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.todo.model;
2 |
3 | import javax.persistence.*;
4 |
5 | import static net.petrikainulainen.spring.trenches.util.PreCondition.isTrue;
6 | import static net.petrikainulainen.spring.trenches.util.PreCondition.notEmpty;
7 | import static net.petrikainulainen.spring.trenches.util.PreCondition.notNull;
8 |
9 | /**
10 | * This entity contains the information of a single todo entry.
11 | * @author Petri Kainulainen
12 | */
13 | @Entity
14 | @Table(name="todos")
15 | public class Todo {
16 |
17 | private static final int MAX_LENGTH_DESCRIPTION = 500;
18 | private static final int MAX_LENGTH_TITLE = 100;
19 |
20 | @Id
21 | @GeneratedValue(strategy = GenerationType.AUTO)
22 | private Long id;
23 |
24 | @Column(name = "description", nullable = true, length = MAX_LENGTH_DESCRIPTION)
25 | private String description;
26 |
27 | @Column(name = "title", nullable = false, length = MAX_LENGTH_TITLE)
28 | private String title;
29 |
30 | @Version
31 | private long version;
32 |
33 | public Todo() {
34 |
35 | }
36 |
37 | private Todo(Builder builder) {
38 | this.description = builder.description;
39 | this.title = builder.title;
40 | }
41 |
42 | public static Builder getBuilder() {
43 | return new Builder();
44 | }
45 |
46 | public Long getId() {
47 | return id;
48 | }
49 |
50 | public String getDescription() {
51 | return description;
52 | }
53 |
54 | public String getTitle() {
55 | return title;
56 | }
57 |
58 | public long getVersion() {
59 | return version;
60 | }
61 |
62 | /**
63 | * This entity is so simple that you don't really need to use the builder pattern (use a constructor instead).
64 | * I use the builder pattern here because it makes the code a bit more easier to read.
65 | */
66 | public static class Builder {
67 |
68 | private String description;
69 | private String title;
70 |
71 | private Builder() {}
72 |
73 | public Builder description(String description) {
74 | this.description = description;
75 | return this;
76 | }
77 |
78 | public Builder title(String title) {
79 | this.title = title;
80 | return this;
81 | }
82 |
83 | public Todo build() {
84 | Todo build = new Todo(this);
85 |
86 | notNull(build.getTitle(), "Title cannot be null.");
87 | notEmpty(build.getTitle(), "Title cannot be empty.");
88 | isTrue(build.getTitle().length() <= MAX_LENGTH_TITLE,
89 | "The maximum length of the title is <%d> characters.",
90 | MAX_LENGTH_TITLE
91 | );
92 |
93 | String description = build.getDescription();
94 | isTrue((description == null) || (description.length() <= MAX_LENGTH_DESCRIPTION),
95 | "The maximum length of the description is <%d> characters.",
96 | MAX_LENGTH_DESCRIPTION
97 | );
98 |
99 | return build;
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/main/java/net/petrikainulainen/spring/trenches/todo/repository/TodoRepository.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.todo.repository;
2 |
3 | import net.petrikainulainen.spring.trenches.todo.model.Todo;
4 | import org.springframework.data.repository.CrudRepository;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * This repository provides CRUD operations for {@link net.petrikainulainen.spring.trenches.todo.model.Todo} objects.
10 | * @author Petri Kainulainen
11 | */
12 | public interface TodoRepository extends CrudRepository {
13 |
14 | List findByDescription(String description);
15 | }
16 |
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/main/java/net/petrikainulainen/spring/trenches/util/PreCondition.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.util;
2 |
3 | /**
4 | * This class provides static utility methods that are used to ensure that a constructor or a method was invoked properly.
5 | * These methods throw an exception if the specified precondition is violated.
6 | *
7 | * This class selects the thrown exception by using the guideline given in Effective Java by Joshua Bloch (Item 60).
8 | *
9 | * @author Petri Kainulainen
10 | */
11 | public final class PreCondition {
12 |
13 | /**
14 | * Ensures that the expression given as a method parameter is true.
15 | * @param expression The inspected expression.
16 | * @param errorMessageTemplate The template that is used to construct the message of the exception thrown if the
17 | * inspected exception is false. The template must use the syntax that is supported
18 | * by the {@link java.lang.String#format(String, Object...)} method.
19 | * @param errorMessageArguments The arguments that are used when the message of the thrown exception is constructed.
20 | * @throws java.lang.IllegalArgumentException if the inspected exception is false.
21 | */
22 | public static void isTrue(boolean expression, String errorMessageTemplate, Object... errorMessageArguments) {
23 | isTrue(expression, String.format(errorMessageTemplate, errorMessageArguments));
24 | }
25 |
26 | /**
27 | * Ensures that the expression given as a method parameter is true.
28 | * @param expression The inspected expression.
29 | * @param errorMessage The error message that is passed forward to the exception that is thrown
30 | * if the expression is false.
31 | * @throws java.lang.IllegalArgumentException if the inspected expression is false.
32 | */
33 | public static void isTrue(boolean expression, String errorMessage) {
34 | if (!expression) {
35 | throw new IllegalArgumentException(errorMessage);
36 | }
37 | }
38 |
39 | /**
40 | * Ensures that the string given as a method parameter is not empty.
41 | * @param string The inspected string.
42 | * @param errorMessage The error message that is passed forward to the exception that is thrown if
43 | * the string is empty.
44 | * @throws java.lang.IllegalArgumentException if the inspected string is empty.
45 | */
46 | public static void notEmpty(String string, String errorMessage) {
47 | if (string.isEmpty()) {
48 | throw new IllegalArgumentException(errorMessage);
49 | }
50 | }
51 |
52 | /**
53 | * Ensures that the object given as a method parameter is not null.
54 | * @param reference A reference to the inspected object.
55 | * @param errorMessage The error message that is passed forward to the exception that is thrown if
56 | * the object given as a method parameter is null.
57 | * @throws java.lang.NullPointerException If the object given as a method parameter is null.
58 | */
59 | public static void notNull(Object reference, String errorMessage) {
60 | if (reference == null) {
61 | throw new NullPointerException(errorMessage);
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #Database Configuration
2 | db.driver=${db.driver}
3 | db.url=${db.url}
4 | db.username=${db.username}
5 | db.password=${db.password}
6 |
7 | #Hibernate Configuration
8 | hibernate.dialect=${hibernate.dialect}
9 | hibernate.format_sql=${hibernate.format_sql}
10 | hibernate.hbm2ddl.auto=${hibernate.hbm2ddl.auto}
11 | hibernate.ejb.naming_strategy=${hibernate.ejb.naming_strategy}
12 | hibernate.show_sql=${hibernate.show_sql}
13 |
14 | #Test Configuration
15 | test.reset.sql.template=${test.reset.sql.template}
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/main/resources/exampleApplicationContext-persistence.xml:
--------------------------------------------------------------------------------
1 |
2 |
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 | ${hibernate.dialect}
36 | ${hibernate.ejb.naming_strategy}
37 | ${hibernate.format_sql}
38 | ${hibernate.hbm2ddl.auto}
39 | ${hibernate.show_sql}
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/test/java/net/petrikainulainen/spring/trenches/todo/model/TodoTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches.todo.model;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | /**
8 | * @author Petri Kainulainen
9 | */
10 | public class TodoTest {
11 |
12 | private static final int MAX_LENGTH_DESCRIPTION = 500;
13 | private static final int MAX_LENGTH_TITLE = 100;
14 |
15 | private String DESCRIPTION = "description";
16 | private String TITLE = "title";
17 |
18 | @Test(expected = NullPointerException.class)
19 | public void build_TitleIsNull_ShouldThrowException() {
20 | Todo.getBuilder()
21 | .title(null)
22 | .description(DESCRIPTION)
23 | .build();
24 | }
25 |
26 | @Test(expected = IllegalArgumentException.class)
27 | public void build_TitleIsEmpty_ShouldThrowException() {
28 | Todo.getBuilder()
29 | .title("")
30 | .description(DESCRIPTION)
31 | .build();
32 | }
33 |
34 | @Test(expected = IllegalArgumentException.class)
35 | public void build_TitleIsTooLong_ShouldThrowException() {
36 | String tooLongTitle = createStringWithLength(MAX_LENGTH_TITLE + 1);
37 |
38 | Todo.getBuilder()
39 | .title(tooLongTitle)
40 | .description(DESCRIPTION)
41 | .build();
42 | }
43 |
44 | @Test
45 | public void build_MaxLengthTitle_ShouldCreateNewObject() {
46 | String maxLengthTitle = createStringWithLength(MAX_LENGTH_TITLE);
47 |
48 | Todo todo = Todo.getBuilder()
49 | .title(maxLengthTitle)
50 | .description(DESCRIPTION)
51 | .build();
52 |
53 | assertThat(todo.getTitle()).isEqualTo(maxLengthTitle);
54 | assertThat(todo.getDescription()).isEqualTo(DESCRIPTION);
55 | }
56 |
57 | @Test
58 | public void build_TitleAndDescriptionGiven_ShouldCreateNewObject() {
59 | Todo todo = Todo.getBuilder()
60 | .title(TITLE)
61 | .description(DESCRIPTION)
62 | .build();
63 |
64 | assertThat(todo.getTitle()).isEqualTo(TITLE);
65 | assertThat(todo.getDescription()).isEqualTo(DESCRIPTION);
66 | }
67 |
68 | @Test
69 | public void build_DescriptionIsNull_ShouldCreateNewObject() {
70 | Todo todo = Todo.getBuilder()
71 | .title(TITLE)
72 | .description(null)
73 | .build();
74 |
75 | assertThat(todo.getTitle()).isEqualTo(TITLE);
76 | assertThat(todo.getDescription()).isNull();
77 | }
78 |
79 | @Test
80 | public void build_DescriptionIsEmpty_ShouldCreateNewObject() {
81 | Todo todo = Todo.getBuilder()
82 | .title(TITLE)
83 | .description("")
84 | .build();
85 |
86 | assertThat(todo.getTitle()).isEqualTo(TITLE);
87 | assertThat(todo.getDescription()).isEmpty();
88 | }
89 |
90 | @Test(expected = IllegalArgumentException.class)
91 | public void build_DescriptionIsTooLong_ShouldThrowException() {
92 | String tooLongDescription = createStringWithLength(MAX_LENGTH_DESCRIPTION + 1);
93 |
94 | Todo.getBuilder()
95 | .title(TITLE)
96 | .description(tooLongDescription)
97 | .build();
98 | }
99 |
100 | @Test
101 | public void build_MaxLengthDescription_ShouldCreateNewObject() {
102 | String maxLengthDescription = createStringWithLength(MAX_LENGTH_DESCRIPTION);
103 |
104 | Todo todo = Todo.getBuilder()
105 | .title(TITLE)
106 | .description(maxLengthDescription)
107 | .build();
108 |
109 | assertThat(todo.getTitle()).isEqualTo(TITLE);
110 | assertThat(todo.getDescription()).isEqualTo(maxLengthDescription);
111 | }
112 |
113 | private String createStringWithLength(int lenght) {
114 | StringBuilder string = new StringBuilder();
115 |
116 | for (int index = 0; index < lenght; index++) {
117 | string.append("a");
118 | }
119 |
120 | return string.toString();
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/spring-test-dbunit-tips/src/test/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.appender.Stdout=org.apache.log4j.ConsoleAppender
2 | log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout
3 | log4j.appender.Stdout.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n
4 |
5 | log4j.rootLogger=OFF,Stdout
6 | log4j.logger.org.springframework=OFF
7 | log4j.logger.org.dbunit=OFF
--------------------------------------------------------------------------------
/type-converters/README.md:
--------------------------------------------------------------------------------
1 | This blog post is the example application of the blog post:
2 |
3 | * [Spring From the Trenches: Using Type Converters With Spring MVC](http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-using-type-converters-with-spring-mvc/)
4 |
5 | Prerequisites
6 | =============
7 |
8 | You need to install the following tools if you want to run this application:
9 |
10 | * [JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
11 | * [Maven](http://maven.apache.org/) (the application is tested with Maven 3.3.3)
12 |
13 | Running the Tests
14 | =================
15 |
16 | You can run the unit tests by using the following command:
17 |
18 | mvn clean test
19 |
20 | Running the Application
21 | =======================
22 |
23 | You can run the application by using the following command:
24 |
25 | mvn clean spring-boot:run
26 |
--------------------------------------------------------------------------------
/type-converters/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | net.petrikainulainen.spring.trenches
5 | type-converters
6 | 0.1
7 | Spring From the Trenches: Using Spring Type Converters
8 |
9 |
10 |
11 |
12 | org.springframework.boot
13 | spring-boot-starter-parent
14 | 1.2.6.RELEASE
15 |
16 |
17 |
18 | 1.8
19 | UTF-8
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-test
32 | test
33 |
34 |
35 | com.nitorcreations
36 | junit-runners
37 | 1.3
38 | test
39 |
40 |
41 | info.solidsoft.mockito
42 | mockito-java8
43 | 0.3.0
44 | test
45 |
46 |
47 | org.assertj
48 | assertj-core
49 | 3.2.0
50 | test
51 |
52 |
53 |
54 | ROOT
55 |
56 |
57 | org.apache.maven.plugins
58 | maven-compiler-plugin
59 | 3.2
60 |
61 | ${jdk.version}
62 | ${jdk.version}
63 | ${project.build.sourceEncoding}
64 |
65 |
66 |
67 | org.apache.maven.plugins
68 | maven-surefire-plugin
69 | 2.18
70 |
71 |
72 | org.springframework.boot
73 | spring-boot-maven-plugin
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/type-converters/src/main/java/net/petrikainulainen/spring/trenches/DateTimeController.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.web.bind.annotation.RequestMapping;
7 | import org.springframework.web.bind.annotation.RequestMethod;
8 | import org.springframework.web.bind.annotation.RequestParam;
9 | import org.springframework.web.bind.annotation.RestController;
10 |
11 | import java.time.LocalDate;
12 | import java.time.LocalDateTime;
13 |
14 | /**
15 | * @author Petri Kainulainen
16 | */
17 | @RestController
18 | @RequestMapping("/api/datetime/")
19 | final class DateTimeController {
20 |
21 | private static final Logger LOGGER = LoggerFactory.getLogger(DateTimeController.class);
22 |
23 | private final DateTimeService dateTimeService;
24 |
25 | @Autowired
26 | DateTimeController(DateTimeService dateTimeService) {
27 | this.dateTimeService = dateTimeService;
28 | }
29 |
30 | @RequestMapping(value = "date", method = RequestMethod.POST)
31 | public void processDate(@RequestParam("date") LocalDate date) {
32 | LOGGER.info("Processing date: {}", date);
33 | dateTimeService.processDate(date);
34 | }
35 |
36 | @RequestMapping(value = "datetime", method = RequestMethod.POST)
37 | public void processDateTime(@RequestParam("datetime") LocalDateTime dateAndTime) {
38 | LOGGER.info("Processing date and time: {}", dateAndTime);
39 | dateTimeService.processDateAndTime(dateAndTime);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/type-converters/src/main/java/net/petrikainulainen/spring/trenches/DateTimeService.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.stereotype.Service;
6 |
7 | import java.time.LocalDate;
8 | import java.time.LocalDateTime;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | @Service
14 | class DateTimeService {
15 |
16 | private static final Logger LOGGER = LoggerFactory.getLogger(DateTimeService.class);
17 |
18 | void processDate(LocalDate date) {
19 | LOGGER.info("Processing date: {}", date);
20 | }
21 |
22 | void processDateAndTime(LocalDateTime dateAndTime) {
23 | LOGGER.info("Processing datetime: {}", dateAndTime);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/type-converters/src/main/java/net/petrikainulainen/spring/trenches/LocalDateConverter.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.core.convert.converter.Converter;
6 |
7 | import java.time.LocalDate;
8 | import java.time.format.DateTimeFormatter;
9 |
10 | /**
11 | * This type converter class converts {@code String} objects into Java 8 {@code LocalDate} objects.
12 | *
13 | * @author Petri Kainulainen
14 | */
15 | public final class LocalDateConverter implements Converter {
16 |
17 | private static final Logger LOGGER = LoggerFactory.getLogger(LocalDateConverter.class);
18 |
19 | private final String dateFormat;
20 | private final DateTimeFormatter formatter;
21 |
22 | /**
23 | * Creates a new LocalDateConverter that can convert {@code String} objects into Java 8 {@code LocalDate} objects.
24 | *
25 | * @param dateFormat The used date format. This format must use the syntax supported by the
26 | *
27 | * {@code DateTimeFormat}
28 | * class.
29 | */
30 | public LocalDateConverter(String dateFormat) {
31 | this.dateFormat = dateFormat;
32 | this.formatter = DateTimeFormatter.ofPattern(dateFormat);
33 | }
34 |
35 | @Override
36 | public LocalDate convert(String source) {
37 | LOGGER.trace("Converting the string: {} into a LocalDate object by using date format: {}.",
38 | source,
39 | dateFormat
40 | );
41 |
42 | if (source == null || source.isEmpty()) {
43 | LOGGER.trace("The source string is null. Returning null.");
44 | return null;
45 | }
46 |
47 | LocalDate date = LocalDate.parse(source, formatter);
48 | LOGGER.trace("Converted the string: {} into a LocalDate: {}",
49 | source,
50 | date
51 | );
52 |
53 | return date;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/type-converters/src/main/java/net/petrikainulainen/spring/trenches/LocalDateTimeConverter.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.core.convert.converter.Converter;
6 |
7 | import java.time.LocalDateTime;
8 | import java.time.format.DateTimeFormatter;
9 |
10 | /**
11 | * This type converter class converts {@code String} objects into Java 8 {@code LocalDateTime} objects.
12 | *
13 | * @author Petri Kainulainen
14 | */
15 | public final class LocalDateTimeConverter implements Converter {
16 |
17 | private static final Logger LOGGER = LoggerFactory.getLogger(LocalDateTimeConverter.class);
18 |
19 | private final String dateFormat;
20 | private final DateTimeFormatter formatter;
21 |
22 | /**
23 | * Creates a new LocalDateConverter that can convert {@code String} objects into Java 8 {@code LocalDateTime} objects.
24 | *
25 | * @param dateFormat The used date format. This format must use the syntax supported by the
26 | *
27 | * {@code DateTimeFormat}
28 | * class.
29 | */
30 | public LocalDateTimeConverter(String dateFormat) {
31 | this.dateFormat = dateFormat;
32 | this.formatter = DateTimeFormatter.ofPattern(dateFormat);
33 | }
34 |
35 | @Override
36 | public LocalDateTime convert(String source) {
37 | LOGGER.trace("Converting the string: {} into a LocalDateTime object by using date format: {}.",
38 | source,
39 | dateFormat
40 | );
41 |
42 | if (source == null || source.isEmpty()) {
43 | LOGGER.trace("The source string is null. Returning null.");
44 | return null;
45 | }
46 |
47 | LocalDateTime dateAndTime = LocalDateTime.parse(source, formatter);
48 | LOGGER.trace("Converted the string: {} into a LocalDateTime: {}",
49 | source,
50 | dateAndTime
51 | );
52 |
53 | return dateAndTime;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/type-converters/src/main/java/net/petrikainulainen/spring/trenches/SpringBootExampleApplication.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
5 | import org.springframework.context.annotation.ComponentScan;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.format.FormatterRegistry;
8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
9 |
10 | /**
11 | * @author Petri Kainulainen
12 | */
13 | @Configuration
14 | @EnableAutoConfiguration
15 | @ComponentScan
16 | public class SpringBootExampleApplication extends WebMvcConfigurerAdapter {
17 |
18 | private final String ISO_DATE_FORMAT = "yyyy-MM-dd";
19 | private final String ISO_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS";
20 |
21 | @Override
22 | public void addFormatters(FormatterRegistry registry) {
23 | registry.addConverter(new LocalDateConverter(ISO_DATE_FORMAT));
24 | registry.addConverter(new LocalDateTimeConverter(ISO_DATE_TIME_FORMAT));
25 | }
26 |
27 | public static void main(String[] args) {
28 | SpringApplication.run(SpringBootExampleApplication.class, args);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/type-converters/src/test/java/net/petrikainulainen/spring/trenches/DateTimeControllerTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import com.nitorcreations.junit.runners.NestedRunner;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.springframework.format.support.FormattingConversionService;
8 | import org.springframework.test.web.servlet.MockMvc;
9 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
10 |
11 | import java.time.LocalDate;
12 | import java.time.LocalDateTime;
13 |
14 | import static info.solidsoft.mockito.java8.AssertionMatcher.assertArg;
15 | import static org.mockito.Mockito.mock;
16 | import static org.mockito.Mockito.times;
17 | import static org.mockito.Mockito.verify;
18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
20 |
21 | /**
22 | * @author Petri Kainulainen
23 | */
24 | @RunWith(NestedRunner.class)
25 | public class DateTimeControllerTest {
26 |
27 | private final String ISO_DATE_FORMAT = "yyyy-MM-dd";
28 | private final String ISO_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS";
29 |
30 | private DateTimeService dateTimeService;
31 |
32 | private MockMvc mockMvc;
33 |
34 | @Before
35 | public void setUp() {
36 | dateTimeService = mock(DateTimeService.class);
37 | mockMvc = MockMvcBuilders.standaloneSetup(new DateTimeController(dateTimeService))
38 | .setConversionService(dateTimeConversionService())
39 | .build();
40 | }
41 |
42 | private FormattingConversionService dateTimeConversionService() {
43 | FormattingConversionService dateTimeConversionService = new FormattingConversionService();
44 |
45 | dateTimeConversionService.addConverter(new LocalDateConverter(ISO_DATE_FORMAT));
46 | dateTimeConversionService.addConverter(new LocalDateTimeConverter(ISO_DATE_TIME_FORMAT));
47 |
48 | return dateTimeConversionService;
49 | }
50 |
51 | public class ProcessDate {
52 |
53 | private final String DATE = "2015-09-26";
54 | private final String REQUEST_PARAM_DATE = "date";
55 |
56 | @Test
57 | public void shouldReturnResponseStatusOk() throws Exception {
58 | mockMvc.perform(post("/api/datetime/date")
59 | .param(REQUEST_PARAM_DATE, DATE)
60 |
61 | )
62 | .andExpect(status().isOk());
63 | }
64 |
65 | @Test
66 | public void shouldProcessDate() throws Exception {
67 | mockMvc.perform(post("/api/datetime/date")
68 | .param(REQUEST_PARAM_DATE, DATE)
69 |
70 | );
71 |
72 | verify(dateTimeService, times(1)).processDate(assertArg(
73 | date -> date.equals(LocalDate.of(2015, 9, 26))
74 | ));
75 | }
76 | }
77 |
78 | public class ProcessDateTime {
79 |
80 | private final String DATE_TIME = "2015-09-26T01:30:00.000";
81 | private final String REQUEST_PARAM_DATE_TIME = "datetime";
82 |
83 | @Test
84 | public void shouldReturnResponseStatusOk() throws Exception {
85 | mockMvc.perform(post("/api/datetime/datetime")
86 | .param(REQUEST_PARAM_DATE_TIME, DATE_TIME)
87 |
88 | )
89 | .andExpect(status().isOk());
90 | }
91 |
92 | @Test
93 | public void shouldProcessDateTime() throws Exception {
94 | mockMvc.perform(post("/api/datetime/datetime")
95 | .param(REQUEST_PARAM_DATE_TIME, DATE_TIME)
96 |
97 | );
98 |
99 | verify(dateTimeService, times(1)).processDateAndTime(assertArg(
100 | dateAndTime -> dateAndTime.equals(LocalDateTime.of(2015, 9, 26, 1, 30))
101 | ));
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/type-converters/src/test/java/net/petrikainulainen/spring/trenches/LocalDateConverterTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import com.nitorcreations.junit.runners.NestedRunner;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 |
8 | import java.time.LocalDate;
9 | import java.time.format.DateTimeParseException;
10 |
11 | import static org.assertj.core.api.Assertions.assertThat;
12 |
13 | /**
14 | * @author Petri Kainulainen
15 | */
16 | @RunWith(NestedRunner.class)
17 | public class LocalDateConverterTest {
18 |
19 | public class Convert {
20 |
21 | private final String ISO_DATE_FORMAT = "yyyy-MM-dd";
22 |
23 | private LocalDateConverter converter;
24 |
25 | @Before
26 | public void createConverterThatUsesIsoDateFormat() {
27 | converter = new LocalDateConverter(ISO_DATE_FORMAT);
28 | }
29 |
30 | public class WhenSourceStringIsEmpty {
31 |
32 | @Test
33 | public void shouldReturnNull() {
34 | LocalDate date = converter.convert("");
35 | assertThat(date).isNull();
36 | }
37 | }
38 |
39 | public class WhenSourceStringIsNull {
40 |
41 | @Test
42 | public void shouldReturnNull() {
43 | LocalDate date = converter.convert(null);
44 | assertThat(date).isNull();
45 | }
46 | }
47 |
48 | public class WhenSourceStringUsesIsoDateFormat {
49 |
50 | private final String DATE_STRING = "2015-10-13";
51 | private final LocalDate EXPECTED_DATE = LocalDate.of(2015, 10, 13);
52 |
53 | @Test
54 | public void shouldReturnExpectedDate() {
55 | LocalDate date = converter.convert(DATE_STRING);
56 | assertThat(date).isEqualTo(EXPECTED_DATE);
57 | }
58 | }
59 |
60 | public class WhenSourceStringDoesNotUseIsoDateFormat {
61 |
62 | private final String DATE_STRING = "13.10.2015";
63 |
64 | @Test(expected = DateTimeParseException.class)
65 | public void shouldThrowException() {
66 | converter.convert(DATE_STRING);
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/type-converters/src/test/java/net/petrikainulainen/spring/trenches/LocalDateTimeConverterTest.java:
--------------------------------------------------------------------------------
1 | package net.petrikainulainen.spring.trenches;
2 |
3 | import com.nitorcreations.junit.runners.NestedRunner;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 |
8 | import java.time.LocalDateTime;
9 | import java.time.format.DateTimeParseException;
10 |
11 | import static org.assertj.core.api.Assertions.assertThat;
12 |
13 | /**
14 | * @author Petri Kainulainen
15 | */
16 | @RunWith(NestedRunner.class)
17 | public class LocalDateTimeConverterTest {
18 |
19 | public class Convert {
20 |
21 | private final String ISO_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS";
22 |
23 | private LocalDateTimeConverter converter;
24 |
25 | @Before
26 | public void createConverterThatUsesIsoDateTimeFormat() {
27 | converter = new LocalDateTimeConverter(ISO_DATE_TIME_FORMAT);
28 | }
29 |
30 | public class WhenSourceStringIsEmpty {
31 |
32 | @Test
33 | public void shouldReturnNull() {
34 | LocalDateTime dateAndTime = converter.convert("");
35 | assertThat(dateAndTime).isNull();
36 | }
37 | }
38 |
39 | public class WhenSourceStringIsNull {
40 |
41 | @Test
42 | public void shouldReturnNull() {
43 | LocalDateTime dateAndTime = converter.convert(null);
44 | assertThat(dateAndTime).isNull();
45 | }
46 | }
47 |
48 | public class WhenSourceStringUsesIsoDateFormat {
49 |
50 | private final String DATE_TIME_STRING = "2015-10-13T01:30:00.000";
51 | private final LocalDateTime EXPECTED_DATE_TIME = LocalDateTime.of(2015, 10, 13, 1, 30);
52 |
53 | @Test
54 | public void shouldReturnExpectedDate() {
55 | LocalDateTime dateAndTime = converter.convert(DATE_TIME_STRING);
56 | assertThat(dateAndTime).isEqualTo(EXPECTED_DATE_TIME);
57 | }
58 | }
59 |
60 | public class WhenSourceStringDoesNotUseIsoDateFormat {
61 |
62 | private final String DATE_TIME_STRING = "13.10.2015 01:30:30.000";
63 |
64 | @Test(expected = DateTimeParseException.class)
65 | public void shouldThrowException() {
66 | converter.convert(DATE_TIME_STRING);
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------