package org.zalando.problem.spring.web.advice.example; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.gag.annotation.remark.Facepalm; import com.google.gag.annotation.remark.Hack; import com.google.gag.annotation.remark.OhNoYouDidnt; import lombok.Data; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import javax.validation.Constraint; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.validation.Payload; import javax.validation.ReportAsSingleViolation; import javax.validation.Valid; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.constraints.Min; import javax.validation.constraints.Size; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.math.BigDecimal; import java.time.OffsetDateTime; import java.util.Map; import java.util.Set; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.APPLICATION_XML_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; import static org.springframework.web.bind.annotation.RequestMethod.PUT; @Validated @RestController @RequestMapping("/api") public class ExampleRestController { @RequestMapping(path = "/handler-ok", produces = APPLICATION_JSON_VALUE) public ResponseEntity<String> ok() { return ResponseEntity.ok("ok"); } @RequestMapping(path = "/handler-throwable", method = GET) public ResponseEntity<String> throwable() { throw new RuntimeException("expected", new IllegalStateException()); } @RequestMapping(path = "/nested-throwable", method = GET) public ResponseEntity<String> nestedThrowable() { try { try { throw newNullPointer(); } catch (final NullPointerException e) { throw newIllegalArgument(e); } } catch (final IllegalArgumentException e) { throw newIllegalState(e); } } private IllegalStateException newIllegalState(final IllegalArgumentException e) { throw new IllegalStateException("Illegal State", e); } private IllegalArgumentException newIllegalArgument(final NullPointerException e) { throw new IllegalArgumentException("Illegal Argument", e); } private NullPointerException newNullPointer() { throw new NullPointerException("Null Pointer"); } @RequestMapping(path = "/handler-problem", method = GET) public ResponseEntity<String> problem() { throw new ExpectedProblem("Nothing out of the ordinary"); } @RequestMapping(path = "/handler-put", method = PUT, consumes = {APPLICATION_JSON_VALUE, APPLICATION_XML_VALUE}) public ResponseEntity<String> put(@RequestBody final String body) { return ResponseEntity.ok(body); } @RequestMapping(path = "/json-object") public void jsonObject(@RequestBody final Map<String, Object> body) { } @RequestMapping(path = "/json-decimal") public void bigDecimal(@RequestBody final BigDecimal bigDecimal) { } @RequestMapping(path = "/json-user") public void user(@RequestBody final User user) { } @RequestMapping(path = "/handler-params", method = GET) public ResponseEntity<Void> params( @RequestParam("params1") final String[] params1, @RequestParam("params2") final String[] params2) { return ResponseEntity.ok().build(); } @RequestMapping(path = "/handler-headers", method = GET) public ResponseEntity<Void> headers( @RequestHeader("X-Custom-Header") final String header1) { return ResponseEntity.ok().build(); } @RequestMapping(path = "/handler-conversion", method = GET) public ResponseEntity<Void> conversion(@RequestParam("dateTime") final OffsetDateTime dateTime) { return ResponseEntity.ok().build(); } @RequestMapping(path = "/handler-multipart", method = POST) public ResponseEntity<Void> conversion( @RequestPart("payload1") final MultipartFile payload1, @RequestPart("payload2") final MultipartFile payload2) { return ResponseEntity.ok().build(); } @RequestMapping(path = "/handler-invalid-param", method = POST) public void validRequestParam(@RequestBody final UserRequest user) { @Hack("I couldn't make Spring throw this implicitly using annotations...") @Facepalm @OhNoYouDidnt final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); final Set<ConstraintViolation<UserRequest>> violations = validator.validate(user); throw new ConstraintViolationException(violations); } @RequestMapping("/handler-secured") public void secured() { } @RequestMapping(path = "/handler-invalid-body", method = POST) public ResponseEntity<String> validRequestBody(@Valid @RequestBody final UserRequest user) { // TODO find a way to change the "object name" of the body, by default it's the lower-camel-cased class name return ResponseEntity.ok("done"); } @RequestMapping(path = "/handler-invalid-query-strings", method = GET) public ResponseEntity<String> validRequestQueryStrings(@Valid final PageRequest pageRequest) { return ResponseEntity.ok("done"); } @RequestMapping("/not-implemented") public void notImplemented() { throw new UnsupportedOperationException("Not yet implemented"); } @Documented @Constraint(validatedBy = NotBobConstraintValidator.class) @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @ReportAsSingleViolation public @interface NotBob { String message() default "must not be called Bob"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } public static final class NotBobConstraintValidator implements ConstraintValidator<NotBob, UserRequest> { @Override public void initialize(final NotBob constraintAnnotation) { } @Override public boolean isValid(final UserRequest user, final ConstraintValidatorContext context) { return !"Bob".equals(user.getName()); } } @NotBob public static final class UserRequest { private final String name; @JsonCreator public UserRequest(@JsonProperty("name") final String name) { this.name = name; } @Size(min = 3, max = 10) public String getName() { return name; } } @Data static final class PageRequest { @Min(value = 0) private int page = 0; @Min(value = 1) private int size = 50; } }