|
@@ -310,9 +310,6 @@ public interface VoteRepository extends CrudRepository<Vote, Long> {
|
310
|
310
|
|
311
|
311
|
|
312
|
312
|
|
313
|
|
-
|
314
|
|
-
|
315
|
|
-
|
316
|
313
|
-
|
317
|
314
|
# Part 3.2.3 - Modify `VoteController`
|
318
|
315
|
* Create a `getAllVotes` method in the `VoteController`
|
|
@@ -325,10 +322,6 @@ public Iterable<Vote> getAllVotes(@PathVariable Long pollId) {
|
325
|
322
|
}
|
326
|
323
|
```
|
327
|
324
|
|
328
|
|
-
|
329
|
|
-
|
330
|
|
-
|
331
|
|
-
|
332
|
325
|
-
|
333
|
326
|
-
|
334
|
327
|
# Part 4 - Data Transfer Object (DTO) Implementation
|
|
@@ -337,7 +330,7 @@ public Iterable<Vote> getAllVotes(@PathVariable Long pollId) {
|
337
|
330
|
* Create a sub package of `java` named `dtos`
|
338
|
331
|
|
339
|
332
|
-
|
340
|
|
-# Part 4.1 - Create class `OptionCount`
|
|
333
|
+## Part 4.1 - Create class `OptionCount`
|
341
|
334
|
* The `OptionCount` DTO contains the `ID` of the option and a count of votes casted for that option.
|
342
|
335
|
|
343
|
336
|
```java
|
|
@@ -363,7 +356,7 @@ public class OptionCount {
|
363
|
356
|
}
|
364
|
357
|
```
|
365
|
358
|
|
366
|
|
-# Part 4.2 - Create class `VoteResult`
|
|
359
|
+## Part 4.2 - Create class `VoteResult`
|
367
|
360
|
* The `VoteResult` DTO contains the total votes cast and a collection of `OptionCount` instances.
|
368
|
361
|
|
369
|
362
|
```java
|
|
@@ -391,7 +384,7 @@ public class VoteResult {
|
391
|
384
|
```
|
392
|
385
|
|
393
|
386
|
|
394
|
|
-# Part 4.3 - Create class `ComputeResultController`
|
|
387
|
+## Part 4.3 - Create class `ComputeResultController`
|
395
|
388
|
* Following the principles used in creating the `PollController` and `VoteController`, we create a new `ComputeResultController` class
|
396
|
389
|
|
397
|
390
|
```java
|
|
@@ -417,7 +410,126 @@ public class ComputeResultController {
|
417
|
410
|
* The computed results are sent to the client using a newly created instance of `ResponseEntity`.
|
418
|
411
|
|
419
|
412
|
|
420
|
|
-# Part 4.4 - Test via Postman
|
|
413
|
+## Part 4.4 - Test via Postman
|
421
|
414
|
* Start/restart the `QuickPoll` application.
|
422
|
415
|
* Using the earlier Postman requests, create a poll and cast votes on its options.
|
423
|
416
|
* Ensure a JSON file with a `status` of `200` is returned by executing a `GET` request of `http://localhost:8080/computeresults?pollId=1` via Postman
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+# Part 5 - Error Handling
|
|
420
|
+
|
|
421
|
+## Part 5.1 - Create `ResourceNotFoundException`
|
|
422
|
+
|
|
423
|
+- Create a `exception` package inside of `io.zipcoder.springdemo.QuickPollApplication`
|
|
424
|
+- Create a `ResourceNotFoundException` class that extends `RuntimeException`. We'll use this to signal when a requested resource is not found.
|
|
425
|
+- Annotate the `ResourceNotFoundException` class with `@ResponseStatus(HttpStatus.NOT_FOUND)`. This informs Spring that any request mapping that throws a `ResourceNotFoundException` should result in a `404 NOT FOUND` http status.
|
|
426
|
+- Implement three constructors
|
|
427
|
+ - A no-arg constructor
|
|
428
|
+ - A constructor that takes a `String message` and passes it to the superclass constructor
|
|
429
|
+ - A constructor that takes `String message` and `Throwable cause` and passes both to the superclass constructor
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+## Part 5.2 - Verify polls
|
|
433
|
+
|
|
434
|
+Create a void method in `PollController` called `verifyPoll` that checks if a specific poll id exists and throws a `ResourceNotFoundException` if not. Use this in any method that searches for or updates an existing poll (eg: Get, Put, and Delete methods).
|
|
435
|
+
|
|
436
|
+**Note**: This means that trying to submit a PUT request for a resource that doesn't exist will not implicitly create it; it should throw a 404 instead.
|
|
437
|
+
|
|
438
|
+## Part 5.3 - Create custom Error Responses
|
|
439
|
+
|
|
440
|
+Spring provides some built-in exception handling and error response, but we'll customize it a bit here. Create an `ErrorDetail` class in a new `io.zipcoder.tc_spring_poll_application.dto.error` package to hold relevant information any time an error occurs.
|
|
441
|
+
|
|
442
|
+Fields (Don't forget to provide getters and setters):
|
|
443
|
+
|
|
444
|
+- `String title`: a brief title of the error condition, eg: "Validation Failure" or "Internal Server Error"
|
|
445
|
+- `int status`: the HTTP status code for the current request; redundant but useful for client-side error handling
|
|
446
|
+- `String detail`: A short, human-readable description of the error that may be presented to a user
|
|
447
|
+- `long timeStamp`: the time in milliseconds when the error occurred
|
|
448
|
+- `String developerMessage`: detailed information such as exception class name or a stack trace useful for developers to debug
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+## Part 5.4 - Create a `@ControllerAdvice`
|
|
452
|
+
|
|
453
|
+In this section we add custom handling for the exceptions we created before. A `@ControllerAdvice` is an AOP feature that wraps a controller and adds some functionality when needed. In this case we are adding functionality only when an exception is thrown.
|
|
454
|
+
|
|
455
|
+- Create RestExceptionHandler class annotated with `@ControllerAdvice`
|
|
456
|
+- Create a handler method with the header shown below
|
|
457
|
+- Populate an ErrorDetail object in the method, and return a ResponseEntity containing the ErrorDetail and an HTTP `NOT_FOUND` status
|
|
458
|
+ - Use java.util's `new Date().getTime()` for the timestamp
|
|
459
|
+ - Provide the detail and developer messages from the `ResourceNotFoundException`
|
|
460
|
+
|
|
461
|
+```
|
|
462
|
+@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> handleResourceNotFoundException(ResourceNotFoundException rnfe, HttpServletRequest request) {...}
|
|
463
|
+```
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+## Part 5.4 - Validating domain entities
|
|
468
|
+
|
|
469
|
+Now it's time to make sure that all objects persisted to the database actually contain valid values. Use the `org.hibernate.validator.constraints.NotEmpty` and `javax.validation.constraints.Size` and `javax.validation.Valid` annotations for validation.
|
|
470
|
+
|
|
471
|
+- In the `Poll` class:
|
|
472
|
+ - `options` should be `@Size(min=2, max = 6)`
|
|
473
|
+ - `question` should be `@NotEmpty`
|
|
474
|
+- To enforce these validations, add `@Valid` annotations to Poll objects in `RequestMapping`-annotated controller methods (there should be 2)
|
|
475
|
+
|
|
476
|
+## Part 5.5 - Customizing validation errors
|
|
477
|
+
|
|
478
|
+In order to customize validation errors we'll need a class for error information. Create a `ValidationError` class in `io.zipcoder.tc_spring_poll_application.dto.error` with the following fields and appropriate getters and setters:
|
|
479
|
+
|
|
480
|
+- `String code`
|
|
481
|
+- `String message`
|
|
482
|
+
|
|
483
|
+We also need a new field in the `ErrorDetail` class to hold errors. There may be multiple validation errors associated with a request, sometimes more than one of the same type, so this field will be a collection, specifically a `Map<String, List<ValidationError>> errors` field.
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+## Part 5.6 - Create a validation error handler
|
|
487
|
+
|
|
488
|
+- add below handler to `RestExceptionHandler`
|
|
489
|
+
|
|
490
|
+```
|
|
491
|
+@ExceptionHandler(MethodArgumentNotValidException.class)
|
|
492
|
+public ResponseEntity<?>
|
|
493
|
+handleValidationError( MethodArgumentNotValidException manve,
|
|
494
|
+ HttpServletRequest request){...}
|
|
495
|
+```
|
|
496
|
+
|
|
497
|
+In this handler we need to do the following:
|
|
498
|
+
|
|
499
|
+- Create the ErrorDetail object (similar to before)
|
|
500
|
+- Get the list of field validation errors
|
|
501
|
+- For each field error, add it to the appropriate list in the ErrorDetail (see below)
|
|
502
|
+- Return a `ResponseEntity` containing the error detail and the appropriate HTTP status code (`400 Bad Request`)
|
|
503
|
+
|
|
504
|
+```
|
|
505
|
+List<FieldError> fieldErrors = manve.getBindingResult().getFieldErrors();
|
|
506
|
+ for(FieldError fe : fieldErrors) {
|
|
507
|
+
|
|
508
|
+ List<ValidationError> validationErrorList = errorDetail.getErrors().get(fe.getField());
|
|
509
|
+ if(validationErrorList == null) {
|
|
510
|
+ validationErrorList = new ArrayList<>();
|
|
511
|
+ errorDetail.getErrors().put(fe.getField(), validationErrorList);
|
|
512
|
+ }
|
|
513
|
+ ValidationError validationError = new ValidationError();
|
|
514
|
+ validationError.setCode(fe.getCode());
|
|
515
|
+ validationError.setMessage(messageSource.getMessage(fe, null));
|
|
516
|
+ validationErrorList.add(validationError);
|
|
517
|
+ }
|
|
518
|
+```
|
|
519
|
+
|
|
520
|
+## Part 5.7 - Externalize strings in a messages.properties file
|
|
521
|
+
|
|
522
|
+Commonly used strings in your Java program can be removed from the source code and placed in a separate file. This is called externalizing, and is useful for allowing changes to text displayed without impacting actual program logic. One example of where this is done is in internationalization, the practice of providing multilingual support in an application, allowing users to use an application in their native language.
|
|
523
|
+
|
|
524
|
+There are two steps needed here to externalize and standardize the validation error messages:
|
|
525
|
+
|
|
526
|
+- Create a `messages.properties` file in the `src/main/resources` directory with the given properties below
|
|
527
|
+- Use an autowired `MessageSource` in the `RestExceptionHandler` to set the message on ValidationError objects (ie: `setMessage(messageSource.getMessage(fe,null));` )
|
|
528
|
+
|
|
529
|
+**`messages.properties` content**:
|
|
530
|
+
|
|
531
|
+```
|
|
532
|
+NotEmpty.poll.question=Question is a required field
|
|
533
|
+Size.poll.options=Options must be greater than {2} and less than {1}
|
|
534
|
+```
|
|
535
|
+
|