├── .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 | * 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 | --------------------------------------------------------------------------------