From 26a7576c1cdd036be1ea5008d0193be3e66b0523 Mon Sep 17 00:00:00 2001
From: Matt Warman
Date: Wed, 5 Dec 2018 14:22:31 -0500
Subject: [PATCH 01/10] See #59. Updated project metadata to version 2.3.0
---
build.gradle | 2 +-
pom.xml | 2 +-
src/main/java/com/leanstacks/ws/ApiDocsConfiguration.java | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/build.gradle b/build.gradle
index 081a996..df30363 100644
--- a/build.gradle
+++ b/build.gradle
@@ -19,7 +19,7 @@ ext {
}
group = 'com.leanstacks'
-version = '2.2.0'
+version = '2.3.0'
sourceCompatibility = 11
targetCompatibility = 11
diff --git a/pom.xml b/pom.xml
index 0eab6b9..b617fcb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.leanstacksskeleton-ws-spring-boot
- 2.2.0
+ 2.3.0Spring Boot Starter ProjectStarter application stack for RESTful web services using Spring Boot.
diff --git a/src/main/java/com/leanstacks/ws/ApiDocsConfiguration.java b/src/main/java/com/leanstacks/ws/ApiDocsConfiguration.java
index 096241f..1d54a54 100644
--- a/src/main/java/com/leanstacks/ws/ApiDocsConfiguration.java
+++ b/src/main/java/com/leanstacks/ws/ApiDocsConfiguration.java
@@ -27,7 +27,7 @@ public class ApiDocsConfiguration {
/**
* The project version.
*/
- public static final String PROJECT_VERSION = "2.2.0";
+ public static final String PROJECT_VERSION = "2.3.0";
/**
* The project contact name.
*/
From bece3c8558450d729064ea9ed2bb7c3d7e504a06 Mon Sep 17 00:00:00 2001
From: Matt Warman
Date: Sat, 8 Dec 2018 06:31:07 -0500
Subject: [PATCH 02/10] See #57. Update PMD ruleset to comply with v7 PMD
standards.
---
build.gradle | 3 +-
etc/pmd/ruleset.xml | 119 +++++++++---------
.../ws/batch/GreetingBatchBean.java | 21 +++-
.../java/com/leanstacks/ws/model/Account.java | 24 ++++
.../com/leanstacks/ws/model/Greeting.java | 11 ++
.../ws/service/GreetingServiceBean.java | 19 ++-
.../RestResponseEntityExceptionHandler.java | 3 +
.../java/com/leanstacks/ws/AbstractTest.java | 12 +-
.../ws/service/GreetingServiceTest.java | 46 +++++--
.../ws/util/BCryptPasswordEncoderUtil.java | 42 +++----
.../ws/web/api/GreetingControllerTest.java | 35 ++++++
11 files changed, 234 insertions(+), 101 deletions(-)
diff --git a/build.gradle b/build.gradle
index df30363..65df97e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -90,7 +90,8 @@ checkstyle {
pmd {
toolVersion = pmdVersion
- ruleSetConfig = rootProject.resources.text.fromFile('etc/pmd/ruleset.xml')
+ ruleSets = []
+ ruleSetFiles = files('etc/pmd/ruleset.xml')
ignoreFailures = true
}
diff --git a/etc/pmd/ruleset.xml b/etc/pmd/ruleset.xml
index 3af8d85..01b1a71 100644
--- a/etc/pmd/ruleset.xml
+++ b/etc/pmd/ruleset.xml
@@ -1,78 +1,83 @@
-
+ xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.io/ruleset_2_0_0.xsd">
- This is the LeanStacks Official PMD ruleset.
-
+ This is the LeanStacks Official PMD ruleset.
+
-
-
+
-
-
-
-
-
-
-
-
-
+
+
-
+
+
-
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
+
+
+
+
+
-
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/leanstacks/ws/batch/GreetingBatchBean.java b/src/main/java/com/leanstacks/ws/batch/GreetingBatchBean.java
index a7de569..9c07c1c 100644
--- a/src/main/java/com/leanstacks/ws/batch/GreetingBatchBean.java
+++ b/src/main/java/com/leanstacks/ws/batch/GreetingBatchBean.java
@@ -30,19 +30,36 @@ public class GreetingBatchBean {
*/
private static final Logger logger = LoggerFactory.getLogger(GreetingBatchBean.class);
+ /**
+ * Format for printed messages.
+ */
private static final String MESSAGE_FORMAT = "There are {} greetings in the data store.";
- // Metric Counters
+ /**
+ * Metric Counter for cron method invocations.
+ */
private final transient Counter cronMethodCounter;
+ /**
+ * Metric Counter for fixed rate method invocations.
+ */
private final transient Counter fixedRateMethodCounter;
+ /**
+ * Metric Counter for fixed rate initial delay method invocations.
+ */
private final transient Counter fixedRateInitialDelayMethodCounter;
+ /**
+ * Metric Counter for fixed delay method invocations.
+ */
private final transient Counter fixedDelayMethodCounter;
+ /**
+ * Metric Counter for fixed delay initial delay method invocations.
+ */
private final transient Counter fixedDelayInitialDelayMethodCounter;
/**
* The GreetingService business service.
*/
- private transient GreetingService greetingService;
+ private final transient GreetingService greetingService;
/**
* Construct a GreetingBatchBean with supplied dependencies.
diff --git a/src/main/java/com/leanstacks/ws/model/Account.java b/src/main/java/com/leanstacks/ws/model/Account.java
index f988612..7aeb2bd 100644
--- a/src/main/java/com/leanstacks/ws/model/Account.java
+++ b/src/main/java/com/leanstacks/ws/model/Account.java
@@ -21,24 +21,45 @@ public class Account extends TransactionalEntity {
private static final long serialVersionUID = 1L;
+ /**
+ * Login username.
+ */
@NotNull
private String username;
+ /**
+ * Login password.
+ */
@NotNull
private String password;
+ /**
+ * Account enabled status indicator.
+ */
@NotNull
private boolean enabled = true;
+ /**
+ * Credential status indicator.
+ */
@NotNull
private boolean credentialsexpired;
+ /**
+ * Account expired status indicator.
+ */
@NotNull
private boolean expired;
+ /**
+ * Account locked indicator.
+ */
@NotNull
private boolean locked;
+ /**
+ * Authorization information.
+ */
@ManyToMany(fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
@JoinTable(name = "AccountRole",
@@ -48,6 +69,9 @@ public class Account extends TransactionalEntity {
referencedColumnName = "id"))
private Set roles;
+ /**
+ * Create a new Account object.
+ */
public Account() {
super();
}
diff --git a/src/main/java/com/leanstacks/ws/model/Greeting.java b/src/main/java/com/leanstacks/ws/model/Greeting.java
index 4670409..f63b01d 100644
--- a/src/main/java/com/leanstacks/ws/model/Greeting.java
+++ b/src/main/java/com/leanstacks/ws/model/Greeting.java
@@ -15,6 +15,9 @@ public class Greeting extends TransactionalEntity {
private static final long serialVersionUID = 1L;
+ /**
+ * The text value.
+ */
@ApiModelProperty(value = "The actual text of the Greeting.",
required = true,
position = 100,
@@ -22,10 +25,18 @@ public class Greeting extends TransactionalEntity {
@NotNull
private String text;
+ /**
+ * Create a new Greeting object.
+ */
public Greeting() {
super();
}
+ /**
+ * Create a new Greeting object with the supplied text value.
+ *
+ * @param text A String text value.
+ */
public Greeting(final String text) {
super();
this.text = text;
diff --git a/src/main/java/com/leanstacks/ws/service/GreetingServiceBean.java b/src/main/java/com/leanstacks/ws/service/GreetingServiceBean.java
index f240ea6..12646a7 100644
--- a/src/main/java/com/leanstacks/ws/service/GreetingServiceBean.java
+++ b/src/main/java/com/leanstacks/ws/service/GreetingServiceBean.java
@@ -35,12 +35,29 @@ public class GreetingServiceBean implements GreetingService {
*/
private static final Logger logger = LoggerFactory.getLogger(GreetingServiceBean.class);
- // Metric Counters
+ /**
+ * Metric Counter for findAll method invocations.
+ */
private final transient Counter findAllMethodInvocationCounter;
+ /**
+ * Metric Counter for findOne method invocations.
+ */
private final transient Counter findOneMethodInvocationCounter;
+ /**
+ * Metric Counter for create method invocations.
+ */
private final transient Counter createMethodInvocationCounter;
+ /**
+ * Metric Counter for update method invocations.
+ */
private final transient Counter updateMethodInvocationCounter;
+ /**
+ * Metric Counter for delete method invocations.
+ */
private final transient Counter deleteMethodInvocationCounter;
+ /**
+ * Metric Counter for evictCache method invocations.
+ */
private final transient Counter evictCacheMethodInvocationCounter;
/**
diff --git a/src/main/java/com/leanstacks/ws/web/api/RestResponseEntityExceptionHandler.java b/src/main/java/com/leanstacks/ws/web/api/RestResponseEntityExceptionHandler.java
index 600ddc4..f7403db 100644
--- a/src/main/java/com/leanstacks/ws/web/api/RestResponseEntityExceptionHandler.java
+++ b/src/main/java/com/leanstacks/ws/web/api/RestResponseEntityExceptionHandler.java
@@ -23,6 +23,9 @@
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
+ /**
+ * The Logger for this Class.
+ */
private static final Logger logger = LoggerFactory.getLogger(RestResponseEntityExceptionHandler.class);
/**
diff --git a/src/test/java/com/leanstacks/ws/AbstractTest.java b/src/test/java/com/leanstacks/ws/AbstractTest.java
index 77c0fa2..edcb804 100644
--- a/src/test/java/com/leanstacks/ws/AbstractTest.java
+++ b/src/test/java/com/leanstacks/ws/AbstractTest.java
@@ -18,6 +18,9 @@ public abstract class AbstractTest {
*/
public static final String USERNAME = "unittest";
+ /**
+ * Tasks performed before each test method.
+ */
@Before
public void before() {
RequestContext.setUsername(AbstractTest.USERNAME);
@@ -25,17 +28,22 @@ public void before() {
}
/**
- * Perform initialization tasks before the execution of each test method.
+ * Perform initialization tasks before the execution of each test method. Concrete test classes may override this
+ * method to implement class-specific tasks.
*/
public abstract void doBeforeEachTest();
+ /**
+ * Tasks performed after each test method.
+ */
@After
public void after() {
doAfterEachTest();
}
/**
- * Perform clean up tasks after the execution of each test method.
+ * Perform clean up tasks after the execution of each test method. Concrete test classes may override this method to
+ * implement class-specific tasks.
*/
public abstract void doAfterEachTest();
diff --git a/src/test/java/com/leanstacks/ws/service/GreetingServiceTest.java b/src/test/java/com/leanstacks/ws/service/GreetingServiceTest.java
index 793e506..2e0a119 100644
--- a/src/test/java/com/leanstacks/ws/service/GreetingServiceTest.java
+++ b/src/test/java/com/leanstacks/ws/service/GreetingServiceTest.java
@@ -24,8 +24,14 @@
@BasicTransactionalTest
public class GreetingServiceTest extends AbstractTest {
+ /**
+ * Constant 'test'.
+ */
private static final String VALUE_TEXT = "test";
+ /**
+ * The GreetingService business service.
+ */
@Autowired
private transient GreetingService greetingService;
@@ -39,6 +45,9 @@ public void doAfterEachTest() {
// perform test clean up
}
+ /**
+ * Test fetch a collection of Greetings.
+ */
@Test
public void testGetGreetings() {
@@ -49,6 +58,9 @@ public void testGetGreetings() {
}
+ /**
+ * Test fetch a single Greeting.
+ */
@Test
public void testGetGreeting() {
@@ -61,6 +73,9 @@ public void testGetGreeting() {
}
+ /**
+ * Test fetch a single greeting with invalid identifier.
+ */
@Test
public void testGetGreetingNotFound() {
@@ -72,6 +87,9 @@ public void testGetGreetingNotFound() {
}
+ /**
+ * Test create a Greeting.
+ */
@Test
public void testCreateGreeting() {
@@ -90,26 +108,28 @@ public void testCreateGreeting() {
}
+ /**
+ * Test create a Greeting with invalid data.
+ */
@Test
public void testCreateGreetingWithId() {
- Exception exception = null;
-
final Greeting greeting = new Greeting();
greeting.setId(Long.MAX_VALUE);
greeting.setText(VALUE_TEXT);
try {
greetingService.create(greeting);
+ Assert.fail("failure - expected exception");
} catch (EntityExistsException eee) {
- exception = eee;
+ Assert.assertNotNull("failure - expected exception not null", eee);
}
- Assert.assertNotNull("failure - expected exception", exception);
- Assert.assertTrue("failure - expected EntityExistsException", exception instanceof EntityExistsException);
-
}
+ /**
+ * Test update a Greeting.
+ */
@Test
public void testUpdateGreeting() {
@@ -129,26 +149,28 @@ public void testUpdateGreeting() {
}
+ /**
+ * Test update a Greeting which does not exist.
+ */
@Test
public void testUpdateGreetingNotFound() {
- Exception exception = null;
-
final Greeting greeting = new Greeting();
greeting.setId(Long.MAX_VALUE);
greeting.setText("test");
try {
greetingService.update(greeting);
+ Assert.fail("failure - expected exception");
} catch (NoResultException nre) {
- exception = nre;
+ Assert.assertNotNull("failure - expected exception not null", nre);
}
- Assert.assertNotNull("failure - expected exception", exception);
- Assert.assertTrue("failure - expected NoResultException", exception instanceof NoResultException);
-
}
+ /**
+ * Test delete a Greeting.
+ */
@Test
public void testDeleteGreeting() {
diff --git a/src/test/java/com/leanstacks/ws/util/BCryptPasswordEncoderUtil.java b/src/test/java/com/leanstacks/ws/util/BCryptPasswordEncoderUtil.java
index f934556..65f9dd7 100644
--- a/src/test/java/com/leanstacks/ws/util/BCryptPasswordEncoderUtil.java
+++ b/src/test/java/com/leanstacks/ws/util/BCryptPasswordEncoderUtil.java
@@ -4,8 +4,6 @@
import java.io.OutputStreamWriter;
import java.io.Writer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
@@ -24,11 +22,6 @@
*/
public class BCryptPasswordEncoderUtil {
- /**
- * The Logger for this Class.
- */
- private static final Logger logger = LoggerFactory.getLogger(BCryptPasswordEncoderUtil.class);
-
/**
* The format for encoder messages.
*/
@@ -54,8 +47,9 @@ public String encode(final String clearText) {
* Facilitates gathering user input and invoking the class behavior.
*
* @param args An array of command line input values. (not used)
+ * @throws IOException Thrown if performing IO operations fails.
*/
- public static void main(final String... args) {
+ public static void main(final String... args) throws IOException {
final BCryptPasswordEncoderUtil encoderUtil = new BCryptPasswordEncoderUtil();
@@ -73,32 +67,28 @@ public static void main(final String... args) {
* Writes a message to the console.
*
* @param str A String message value.
+ * @throws IOException Thrown if writing output fails.
*/
- private void write(final String str) {
-
- try {
- if (writer == null) {
- writer = new OutputStreamWriter(System.out);
- }
- writer.write(str);
- } catch (IOException ioe) {
- logger.error("Writer cannot write.", ioe);
- System.exit(1);
+ private void write(final String str) throws IOException {
+
+ if (writer == null) {
+ writer = new OutputStreamWriter(System.out);
}
+ writer.write(str);
+
}
/**
* Closes all system resources and prepares for application termination.
+ *
+ * @throws IOException Thrown if closing the output stream fails.
*/
- private void close() {
- try {
- if (writer != null) {
- writer.close();
- }
- } catch (IOException ioe) {
- logger.error("Problem closing resources.", ioe);
- System.exit(1);
+ private void close() throws IOException {
+
+ if (writer != null) {
+ writer.close();
}
+
}
}
diff --git a/src/test/java/com/leanstacks/ws/web/api/GreetingControllerTest.java b/src/test/java/com/leanstacks/ws/web/api/GreetingControllerTest.java
index 9923d72..f240c3f 100644
--- a/src/test/java/com/leanstacks/ws/web/api/GreetingControllerTest.java
+++ b/src/test/java/com/leanstacks/ws/web/api/GreetingControllerTest.java
@@ -92,6 +92,11 @@ public void doAfterEachTest() {
// perform test clean up
}
+ /**
+ * Test fetch collection of Greetings.
+ *
+ * @throws Exception Thrown if mocking failure occurs.
+ */
@Test
public void testGetGreetings() throws Exception {
@@ -118,6 +123,11 @@ public void testGetGreetings() throws Exception {
}
+ /**
+ * Test fetch a Greeting by identifier.
+ *
+ * @throws Exception Thrown if mocking failure occurs.
+ */
@Test
public void testGetGreeting() throws Exception {
@@ -145,6 +155,11 @@ public void testGetGreeting() throws Exception {
Assert.assertTrue("failure - expected HTTP response body to have a value", !Strings.isNullOrEmpty(content));
}
+ /**
+ * Test fetch a Greeting with unknown identifier.
+ *
+ * @throws Exception Thrown if mocking failure occurs.
+ */
@Test
public void testGetGreetingNotFound() throws Exception {
@@ -172,6 +187,11 @@ public void testGetGreetingNotFound() throws Exception {
}
+ /**
+ * Test create a Greeting.
+ *
+ * @throws Exception Thrown if mocking failure occurs.
+ */
@Test
public void testCreateGreeting() throws Exception {
@@ -208,6 +228,11 @@ public void testCreateGreeting() throws Exception {
Assert.assertEquals("failure - expected text attribute match", entity.getText(), createdEntity.getText());
}
+ /**
+ * Test update a Greeting.
+ *
+ * @throws Exception Thrown if mocking failure occurs.
+ */
@Test
public void testUpdateGreeting() throws Exception {
@@ -245,6 +270,11 @@ public void testUpdateGreeting() throws Exception {
}
+ /**
+ * Test delete a Greeting.
+ *
+ * @throws Exception Thrown if mocking failure occurs.
+ */
@Test
public void testDeleteGreeting() throws Exception {
@@ -267,6 +297,11 @@ public void testDeleteGreeting() throws Exception {
}
+ /**
+ * Test sending email asynchronously.
+ *
+ * @throws Exception Thrown if mocking failure occurs.
+ */
@Test
public void testSendGreetingAsync() throws Exception {
From b9274e56eacc12aef1846b42c99e100239754e8f Mon Sep 17 00:00:00 2001
From: Matt Warman
Date: Sun, 9 Dec 2018 12:24:52 -0500
Subject: [PATCH 03/10] See #60. Using Spring method-specific shortcuts for
RequestMapping.
---
.../ws/web/api/GreetingController.java | 68 ++++++++-----------
.../ws/web/api/GreetingControllerTest.java | 2 +-
2 files changed, 31 insertions(+), 39 deletions(-)
diff --git a/src/main/java/com/leanstacks/ws/web/api/GreetingController.java b/src/main/java/com/leanstacks/ws/web/api/GreetingController.java
index fdd5ada..7e779d9 100644
--- a/src/main/java/com/leanstacks/ws/web/api/GreetingController.java
+++ b/src/main/java/com/leanstacks/ws/web/api/GreetingController.java
@@ -4,17 +4,21 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import javax.persistence.NoResultException;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.leanstacks.ws.model.Greeting;
@@ -33,6 +37,7 @@
* @author Matt Warman
*/
@RestController
+@RequestMapping("/api/greetings")
public class GreetingController {
/**
@@ -67,16 +72,14 @@ public class GreetingController {
required = true,
dataType = "string",
paramType = "header"))
- @RequestMapping(value = "/api/greetings",
- method = RequestMethod.GET,
- produces = MediaType.APPLICATION_JSON_VALUE)
- public ResponseEntity> getGreetings() {
+ @GetMapping
+ public Collection getGreetings() {
logger.info("> getGreetings");
final Collection greetings = greetingService.findAll();
logger.info("< getGreetings");
- return new ResponseEntity>(greetings, HttpStatus.OK);
+ return greetings;
}
/**
@@ -100,20 +103,18 @@ public ResponseEntity> getGreetings() {
required = true,
dataType = "string",
paramType = "header"))
- @RequestMapping(value = "/api/greetings/{id}",
- method = RequestMethod.GET,
- produces = MediaType.APPLICATION_JSON_VALUE)
- public ResponseEntity getGreeting(@ApiParam("Greeting ID") @PathVariable final Long id) {
+ @GetMapping("/{id}")
+ public Greeting getGreeting(@ApiParam("Greeting ID") @PathVariable final Long id) {
logger.info("> getGreeting");
final Greeting greeting = greetingService.findOne(id);
if (greeting == null) {
logger.info("< getGreeting");
- return new ResponseEntity(HttpStatus.NOT_FOUND);
+ throw new NoResultException("Greeting not found.");
}
logger.info("< getGreeting");
- return new ResponseEntity(greeting, HttpStatus.OK);
+ return greeting;
}
/**
@@ -139,17 +140,15 @@ public ResponseEntity getGreeting(@ApiParam("Greeting ID") @PathVariab
required = true,
dataType = "string",
paramType = "header"))
- @RequestMapping(value = "/api/greetings",
- method = RequestMethod.POST,
- consumes = MediaType.APPLICATION_JSON_VALUE,
- produces = MediaType.APPLICATION_JSON_VALUE)
- public ResponseEntity createGreeting(@RequestBody final Greeting greeting) {
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED)
+ public Greeting createGreeting(@RequestBody final Greeting greeting) {
logger.info("> createGreeting");
final Greeting savedGreeting = greetingService.create(greeting);
logger.info("< createGreeting");
- return new ResponseEntity(savedGreeting, HttpStatus.CREATED);
+ return savedGreeting;
}
/**
@@ -175,11 +174,8 @@ public ResponseEntity createGreeting(@RequestBody final Greeting greet
required = true,
dataType = "string",
paramType = "header"))
- @RequestMapping(value = "/api/greetings/{id}",
- method = RequestMethod.PUT,
- consumes = MediaType.APPLICATION_JSON_VALUE,
- produces = MediaType.APPLICATION_JSON_VALUE)
- public ResponseEntity updateGreeting(@ApiParam("Greeting ID") @PathVariable("id") final Long id,
+ @PutMapping("/{id}")
+ public Greeting updateGreeting(@ApiParam("Greeting ID") @PathVariable("id") final Long id,
@RequestBody final Greeting greeting) {
logger.info("> updateGreeting");
@@ -188,7 +184,7 @@ public ResponseEntity updateGreeting(@ApiParam("Greeting ID") @PathVar
final Greeting updatedGreeting = greetingService.update(greeting);
logger.info("< updateGreeting");
- return new ResponseEntity(updatedGreeting, HttpStatus.OK);
+ return updatedGreeting;
}
/**
@@ -202,7 +198,6 @@ public ResponseEntity updateGreeting(@ApiParam("Greeting ID") @PathVar
*
*
* @param id A Long URL path variable containing the Greeting primary key identifier.
- * @return A ResponseEntity with an empty response body and a HTTP status code as described in the method comment.
*/
@ApiOperation(value = "${GreetingController.deleteGreeting.title}",
notes = "${GreetingController.deleteGreeting.notes}",
@@ -212,15 +207,14 @@ public ResponseEntity updateGreeting(@ApiParam("Greeting ID") @PathVar
required = true,
dataType = "string",
paramType = "header"))
- @RequestMapping(value = "/api/greetings/{id}",
- method = RequestMethod.DELETE)
- public ResponseEntity deleteGreeting(@ApiParam("Greeting ID") @PathVariable("id") final Long id) {
+ @DeleteMapping("/{id}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void deleteGreeting(@ApiParam("Greeting ID") @PathVariable("id") final Long id) {
logger.info("> deleteGreeting");
greetingService.delete(id);
logger.info("< deleteGreeting");
- return new ResponseEntity(HttpStatus.NO_CONTENT);
}
/**
@@ -246,10 +240,8 @@ public ResponseEntity deleteGreeting(@ApiParam("Greeting ID") @PathVariabl
required = true,
dataType = "string",
paramType = "header"))
- @RequestMapping(value = "/api/greetings/{id}/send",
- method = RequestMethod.POST,
- produces = MediaType.APPLICATION_JSON_VALUE)
- public ResponseEntity sendGreeting(@ApiParam("Greeting ID") @PathVariable("id") final Long id,
+ @PostMapping("/{id}/send")
+ public Greeting sendGreeting(@ApiParam("Greeting ID") @PathVariable("id") final Long id,
@ApiParam("Wait for Response") @RequestParam(value = "wait",
defaultValue = "false") final boolean waitForAsyncResult) {
@@ -261,7 +253,7 @@ public ResponseEntity sendGreeting(@ApiParam("Greeting ID") @PathVaria
greeting = greetingService.findOne(id);
if (greeting == null) {
logger.info("< sendGreeting");
- return new ResponseEntity(HttpStatus.NOT_FOUND);
+ throw new NoResultException("Greeting not found.");
}
if (waitForAsyncResult) {
@@ -273,11 +265,11 @@ public ResponseEntity sendGreeting(@ApiParam("Greeting ID") @PathVaria
}
} catch (ExecutionException | InterruptedException ex) {
logger.error("A problem occurred sending the Greeting.", ex);
- return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR);
+ throw new IllegalStateException(ex);
}
logger.info("< sendGreeting");
- return new ResponseEntity(greeting, HttpStatus.OK);
+ return greeting;
}
diff --git a/src/test/java/com/leanstacks/ws/web/api/GreetingControllerTest.java b/src/test/java/com/leanstacks/ws/web/api/GreetingControllerTest.java
index f240c3f..2c79659 100644
--- a/src/test/java/com/leanstacks/ws/web/api/GreetingControllerTest.java
+++ b/src/test/java/com/leanstacks/ws/web/api/GreetingControllerTest.java
@@ -183,7 +183,7 @@ public void testGetGreetingNotFound() throws Exception {
// Perform standard JUnit assertions on the test results
Assert.assertEquals("failure - expected HTTP status 404", 404, status);
- Assert.assertTrue("failure - expected HTTP response body to be empty", Strings.isNullOrEmpty(content));
+ Assert.assertTrue("failure - expected HTTP response body to have a value", !Strings.isNullOrEmpty(content));
}
From b38aaa67c59c758389e475d23c57e4e587f07155 Mon Sep 17 00:00:00 2001
From: Matt Warman
Date: Mon, 10 Dec 2018 09:48:18 -0500
Subject: [PATCH 04/10] See #62. Use Optional wrappers for GreetingService
---
.../ws/service/GreetingService.java | 17 +++----
.../ws/service/GreetingServiceBean.java | 30 +++++-------
.../ws/web/api/GreetingController.java | 47 +++++++------------
.../RestResponseEntityExceptionHandler.java | 38 +++++++++++----
.../ws/service/GreetingServiceTest.java | 32 ++++++-------
.../ws/web/api/GreetingControllerTest.java | 19 ++++----
6 files changed, 91 insertions(+), 92 deletions(-)
diff --git a/src/main/java/com/leanstacks/ws/service/GreetingService.java b/src/main/java/com/leanstacks/ws/service/GreetingService.java
index 21c337c..a0c3f16 100644
--- a/src/main/java/com/leanstacks/ws/service/GreetingService.java
+++ b/src/main/java/com/leanstacks/ws/service/GreetingService.java
@@ -1,6 +1,7 @@
package com.leanstacks.ws.service;
-import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
import com.leanstacks.ws.model.Greeting;
@@ -19,17 +20,17 @@ public interface GreetingService {
/**
* Find all Greeting entities.
*
- * @return A Collection of Greeting objects.
+ * @return A List of Greeting objects.
*/
- Collection findAll();
+ List findAll();
/**
- * Find a single Greeting entity by primary key identifier.
+ * Find a single Greeting entity by primary key identifier. Returns an Optional wrapped Greeting.
*
- * @param id A BigInteger primary key identifier.
- * @return A Greeting or null if none found.
+ * @param id A Long primary key identifier.
+ * @return A Optional Greeting
*/
- Greeting findOne(Long id);
+ Optional findOne(Long id);
/**
* Persists a Greeting entity in the data store.
@@ -50,7 +51,7 @@ public interface GreetingService {
/**
* Removes a previously persisted Greeting entity from the data store.
*
- * @param id A BigInteger primary key identifier.
+ * @param id A Long primary key identifier.
*/
void delete(Long id);
diff --git a/src/main/java/com/leanstacks/ws/service/GreetingServiceBean.java b/src/main/java/com/leanstacks/ws/service/GreetingServiceBean.java
index 12646a7..64032e7 100644
--- a/src/main/java/com/leanstacks/ws/service/GreetingServiceBean.java
+++ b/src/main/java/com/leanstacks/ws/service/GreetingServiceBean.java
@@ -1,11 +1,8 @@
package com.leanstacks.ws.service;
-import java.util.Collection;
+import java.util.List;
import java.util.Optional;
-import javax.persistence.EntityExistsException;
-import javax.persistence.NoResultException;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -83,12 +80,12 @@ public GreetingServiceBean(final GreetingRepository greetingRepository, final Me
}
@Override
- public Collection findAll() {
+ public List findAll() {
logger.info("> findAll");
findAllMethodInvocationCounter.increment();
- final Collection greetings = greetingRepository.findAll();
+ final List greetings = greetingRepository.findAll();
logger.info("< findAll");
return greetings;
@@ -97,19 +94,19 @@ public Collection findAll() {
@Cacheable(value = Application.CACHE_GREETINGS,
key = "#id")
@Override
- public Greeting findOne(final Long id) {
+ public Optional findOne(final Long id) {
logger.info("> findOne {}", id);
findOneMethodInvocationCounter.increment();
- final Optional result = greetingRepository.findById(id);
+ final Optional greetingOptional = greetingRepository.findById(id);
logger.info("< findOne {}", id);
- return result.isPresent() ? result.get() : null;
+ return greetingOptional;
}
@CachePut(value = Application.CACHE_GREETINGS,
- key = "#result.id")
+ key = "#result?.id")
@Transactional
@Override
public Greeting create(final Greeting greeting) {
@@ -123,7 +120,7 @@ public Greeting create(final Greeting greeting) {
if (greeting.getId() != null) {
logger.error("Attempted to create a Greeting, but id attribute was not null.");
logger.info("< create");
- throw new EntityExistsException(
+ throw new IllegalArgumentException(
"Cannot create new Greeting with supplied id. The id attribute must be null to create an entity.");
}
@@ -142,15 +139,10 @@ public Greeting update(final Greeting greeting) {
updateMethodInvocationCounter.increment();
- // Ensure the entity object to be updated exists in the repository to
- // prevent the default behavior of save() which will persist a new
+ // findOne returns an Optional which will throw NoSuchElementException when null.
+ // This will prevent the default behavior of save() which will persist a new
// entity if the entity matching the id does not exist
- final Greeting greetingToUpdate = findOne(greeting.getId());
- if (greetingToUpdate == null) {
- logger.error("Attempted to update a Greeting, but the entity does not exist.");
- logger.info("< update {}", greeting.getId());
- throw new NoResultException("Requested Greeting not found.");
- }
+ final Greeting greetingToUpdate = findOne(greeting.getId()).get();
greetingToUpdate.setText(greeting.getText());
final Greeting updatedGreeting = greetingRepository.save(greetingToUpdate);
diff --git a/src/main/java/com/leanstacks/ws/web/api/GreetingController.java b/src/main/java/com/leanstacks/ws/web/api/GreetingController.java
index 7e779d9..a06b3ef 100644
--- a/src/main/java/com/leanstacks/ws/web/api/GreetingController.java
+++ b/src/main/java/com/leanstacks/ws/web/api/GreetingController.java
@@ -1,11 +1,10 @@
package com.leanstacks.ws.web.api;
-import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
-import javax.persistence.NoResultException;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -61,7 +60,7 @@ public class GreetingController {
* Web service endpoint to fetch all Greeting entities. The service returns the collection of Greeting entities as
* JSON.
*
- * @return A ResponseEntity containing a Collection of Greeting objects.
+ * @return A List of Greeting objects.
*/
@ApiOperation(value = "${GreetingController.getGreetings.title}",
notes = "${GreetingController.getGreetings.notes}",
@@ -73,10 +72,10 @@ public class GreetingController {
dataType = "string",
paramType = "header"))
@GetMapping
- public Collection getGreetings() {
+ public List getGreetings() {
logger.info("> getGreetings");
- final Collection greetings = greetingService.findAll();
+ final List greetings = greetingService.findAll();
logger.info("< getGreetings");
return greetings;
@@ -92,8 +91,7 @@ public Collection getGreetings() {
*
*
* @param id A Long URL path variable containing the Greeting primary key identifier.
- * @return A ResponseEntity containing a single Greeting object, if found, and a HTTP status code as described in
- * the method comment.
+ * @return A Greeting object, if found, and a HTTP status code as described in the method comment.
*/
@ApiOperation(value = "${GreetingController.getGreeting.title}",
notes = "${GreetingController.getGreeting.notes}",
@@ -107,14 +105,10 @@ public Collection getGreetings() {
public Greeting getGreeting(@ApiParam("Greeting ID") @PathVariable final Long id) {
logger.info("> getGreeting");
- final Greeting greeting = greetingService.findOne(id);
- if (greeting == null) {
- logger.info("< getGreeting");
- throw new NoResultException("Greeting not found.");
- }
+ final Optional greetingOptional = greetingService.findOne(id);
logger.info("< getGreeting");
- return greeting;
+ return greetingOptional.get();
}
/**
@@ -124,12 +118,11 @@ public Greeting getGreeting(@ApiParam("Greeting ID") @PathVariable final Long id
*
*
* If created successfully, the persisted Greeting is returned as JSON with HTTP status 201. If not created
- * successfully, the service returns an empty response body with HTTP status 500.
+ * successfully, the service returns an ExceptionDetail response body with HTTP status 400 or 500.
*
*
* @param greeting The Greeting object to be created.
- * @return A ResponseEntity containing a single Greeting object, if created successfully, and a HTTP status code as
- * described in the method comment.
+ * @return A Greeting object, if created successfully, and a HTTP status code as described in the method comment.
*/
@ApiOperation(value = "${GreetingController.createGreeting.title}",
notes = "${GreetingController.createGreeting.notes}",
@@ -158,13 +151,12 @@ public Greeting createGreeting(@RequestBody final Greeting greeting) {
*
*
* If updated successfully, the persisted Greeting is returned as JSON with HTTP status 200. If not found, the
- * service returns an empty response body and HTTP status 404. If not updated successfully, the service returns an
- * empty response body with HTTP status 500.
+ * service returns an ExceptionDetail response body and HTTP status 404. If not updated successfully, the service
+ * returns an empty response body with HTTP status 400 or 500.
*
*
* @param greeting The Greeting object to be updated.
- * @return A ResponseEntity containing a single Greeting object, if updated successfully, and a HTTP status code as
- * described in the method comment.
+ * @return A Greeting object, if updated successfully, and a HTTP status code as described in the method comment.
*/
@ApiOperation(value = "${GreetingController.updateGreeting.title}",
notes = "${GreetingController.updateGreeting.notes}",
@@ -194,7 +186,7 @@ public Greeting updateGreeting(@ApiParam("Greeting ID") @PathVariable("id") fina
*
*
* If deleted successfully, the service returns an empty response body with HTTP status 204. If not deleted
- * successfully, the service returns an empty response body with HTTP status 500.
+ * successfully, the service returns an ExceptionDetail response body with HTTP status 500.
*
*
* @param id A Long URL path variable containing the Greeting primary key identifier.
@@ -223,14 +215,13 @@ public void deleteGreeting(@ApiParam("Greeting ID") @PathVariable("id") final Lo
*
*
* If found, the Greeting is returned as JSON with HTTP status 200 and sent via Email. If not found, the service
- * returns an empty response body with HTTP status 404.
+ * returns an Exception response body with HTTP status 404.
*
*
* @param id A Long URL path variable containing the Greeting primary key identifier.
* @param waitForAsyncResult A boolean indicating if the web service should wait for the asynchronous email
* transmission.
- * @return A ResponseEntity containing a single Greeting object, if found, and a HTTP status code as described in
- * the method comment.
+ * @return A Greeting object, if found, and a HTTP status code as described in the method comment.
*/
@ApiOperation(value = "${GreetingController.sendGreeting.title}",
notes = "${GreetingController.sendGreeting.notes}",
@@ -250,11 +241,7 @@ public Greeting sendGreeting(@ApiParam("Greeting ID") @PathVariable("id") final
Greeting greeting;
try {
- greeting = greetingService.findOne(id);
- if (greeting == null) {
- logger.info("< sendGreeting");
- throw new NoResultException("Greeting not found.");
- }
+ greeting = greetingService.findOne(id).get();
if (waitForAsyncResult) {
final Future asyncResponse = emailService.sendAsyncWithResult(greeting);
diff --git a/src/main/java/com/leanstacks/ws/web/api/RestResponseEntityExceptionHandler.java b/src/main/java/com/leanstacks/ws/web/api/RestResponseEntityExceptionHandler.java
index f7403db..281d77d 100644
--- a/src/main/java/com/leanstacks/ws/web/api/RestResponseEntityExceptionHandler.java
+++ b/src/main/java/com/leanstacks/ws/web/api/RestResponseEntityExceptionHandler.java
@@ -29,11 +29,11 @@ public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionH
private static final Logger logger = LoggerFactory.getLogger(RestResponseEntityExceptionHandler.class);
/**
- * Handles JPA NoResultExceptions thrown from web service controller methods. Creates a response with an empty body
- * and HTTP status code 404, not found.
+ * Handles JPA NoResultExceptions thrown from web service controller methods. Creates a response with an
+ * ExceptionDetail body and HTTP status code 404, not found.
*
* @param ex A NoResultException instance.
- * @return A ResponseEntity with an empty response body and HTTP status code 404.
+ * @return A ResponseEntity with an ExceptionDetail response body and HTTP status code 404.
*/
@ExceptionHandler(NoResultException.class)
public ResponseEntity