Update a response object of the end-point - java

I generated automatically SpringMVC API using swagger. Now I want to update some end-points manually.
I have the folloiwng end-point:
#ApiOperation(value = "Estimation of ...", notes = "...", response = Similarity.class, responseContainer = "List")
#io.swagger.annotations.ApiResponses(value = {
#io.swagger.annotations.ApiResponse(code = 200, message = "Similarity metrics", response = Similarity.class),
#io.swagger.annotations.ApiResponse(code = 200, message = "Unexpected error", response = Similarity.class) })
#RequestMapping(value = "/estimateSimilarity",
produces = { "application/json" },
method = RequestMethod.GET)
public ResponseEntity<HashMap<String,Double>> estimateSimilarity(
#ApiParam(value = "...", required = true)
#RequestParam(value = "term1", required = true) String term,
#ApiParam(value = "...", required = true)
#RequestParam(value = "terms", required = true) List<String> concepts)
throws NotFoundException {
Similarity similarity = new Similarity();
HashMap<String,Double> result = similarity.getEstimates(term1, terms);
return new ResponseEntity<HashMap<String,Double>>(HttpStatus.OK);
}
Instead of response = Similarity.class, I want to return HashMap<String,Double> result. How should I update the above-given code to be able to return this object?

Try modifying the ApiOperations Response container.
#ApiOperation(value = "Estimation of ...", notes = "...", response = Double.class, responseContainer = "Map")

Related

Document a multipart/form-data endpoint with Springfox

I have a POST endpoint which receives a #ModelAttribute parameter. Everything is working ok, but the swagger documentation fails to have the descriptions, examples, etc.
I am using java 11, springboot 2.5.4 and springfox-boot-starter 3.0.0
Here is my code:
#Api
#RestController
#RequestMapping("/foo")
#Validated
public class MyRest {
#PostMapping(value = "/{id}/bar", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE })
#ApiOperation(value = "Do nothing", notes = "This endpoint does nothing")
public ResponseEntity<String> search(
#ModelAttribute MyModelRequest request,
#ApiParam(value = "Folder ID", required = true)
#PathVariable String id) {
// some business code
return new ResponseEntity<>("lorem ipsum", HttpStatus.OK);
}
}
MyModelRequest
#ApiModel
#Data
public class MyModelRequest {
#ApiParam(name = "fileName", value = "The name of the image to be stored in database")
#ApiModelProperty(value = "name model description", example = "summer picture", required = true)
private String name;
#DecimalMin("0.00")
#DecimalMax("100.00")
#ApiParam(name = "accuracy", value = "The required accuracy")
#ApiModelProperty(value = "Minimum required accuracy", example = "95.15", required = false)
private BigDecimal accuracy;
#ApiParam(name = "marginTop", value = "Top margin of the image")
#ApiModelProperty(value = "Separation between top item and the image", example = "300", required = false)
private Integer marginTop;
#ApiParam(name = "image")
#ApiModelProperty(value = "The image to be stored", example = "vacations.png", required = true)
private MultipartFile image;
}
And this is the generated swagger doc
UPDATE: I noticed that if I change the consumes = { MediaType.MULTIPART_FORM_DATA_VALUE } for consumes = { MediaType.APPLICATION_JSON_VALUE } or remove the whole "consumes" parameter from the endpoint, the documentation shows up correctly, however, doing this will make the fileupload fail.

unable to reach request mapping endpoint so im getting 404 error

I created a new controller with request mapping "/route" but no matter whatever i do I am unable to reach the endpoint localhost:8080/route. I always get 404.
How can I access the endpoint route without getting 404?
#RestController
public interface RouteController {
#ApiOperation(value = "Add a route details to adapter", nickname = "addroute", notes = "Add route details to the table and Send it to ATLAS", tags = {
"route", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Successful Insertion into DB and Proceded for Transformation"),
#ApiResponse(code = 405, message = "Invalid Input") })
#RequestMapping(value = "/route", produces = { "application/json" }, consumes = {
"application/json" }, method = RequestMethod.POST)
public ResponseEntity<Void> createData(
#ApiParam(value = "Route Array", required = true) #Valid #RequestBody RouteArray routeArray) ;
}
Implementation class
public class RouteControllerImpl implements RouteController {
#Autowired
private RouteService routeService;
#Override
public ResponseEntity<Void> createData(#ApiParam(value = "Route Details", required = true) #Valid #RequestBody RouteArray routeArray) {
return new ResponseEntity<Void>(routeService.transformRoute(routeArray));
}
}
You need to change a few things in your implementation. You should not use #RestController with your OpenAPI api, but rather use #Api annotation. You should use #RestController on the class implementing this API.
#Validated
#Api(
value = "route",
description = The Controller API"
)
public interface RouteController {
#ApiOperation(value = "Add a route details to adapter", nickname = "addroute", notes = "Add route details to the table and Send it to ATLAS", tags = {
"route", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Successful Insertion into DB and Proceded for Transformation"),
#ApiResponse(code = 405, message = "Invalid Input") })
#RequestMapping(value = "/route", produces = { "application/json" }, consumes = {
"application/json" }, method = RequestMethod.POST)
public ResponseEntity<Void> createData(
#ApiParam(value = "Route Array", required = true) #Valid #RequestBody RouteArray routeArray) ;
}
Implementation:
#RestController
public class RouteControllerImpl implements RouteController {
#Autowired
private RouteService routeService;
#Override
public ResponseEntity<Void> createData(#Valid RouteArray routeArray) {
return new ResponseEntity<Void>(routeService.transformRoute(routeArray));
}

Have controller methods to getAll, getById and getByEmail in the same controller while following RESTful API standards

I have an entity Users, which contains id and emailAddress. I want to write a controller which has 3 get methods simultaneously:
getAll() - http://localhost:8080/api/users?pageNumber=0&pageSize=10
get(UUID id) - http://localhost:8080/api/users/209347293875928375928
get(String email) - http://localhost:8080/api/users/fireball%40email.com or http://localhost:8080/api/users?emailAddress="fireball%40email.com"
NOTE: The difference in the two pieces of code is the arguments of the last function in both(The first is #PathVariable and the second is a #RequestParam.
I have tried two ways to accomplish this(both run into a separate issue), the first being:
#GetMapping
#ApiOperation(value = "List all Users")
#ApiResponses({
#ApiResponse(code = 200, message = "OK", response = User.class, responseContainer = "List"),
#ApiResponse(code = 401, message = "Unauthorized", response = Error.class),
#ApiResponse(code = 404, message = "Not Found", response = Error.class),
#ApiResponse(code = 500, message = "Internal Server Error", response = Error.class)
})
#Transactional(readOnly = true)
public ResponseEntity<Page<User>> getAll(
#RequestParam(required = false, defaultValue = "0") int pageNumber,
#RequestParam(required = false, defaultValue = "10") int pageSize) {
return ResponseEntity.ok(UserService.getUsers(pageNumber, pageSize));
}
#GetMapping(path = "/{id}")
#ApiOperation(value = "Get User by ID")
#Transactional(readOnly = true)
#ApiResponses({
#ApiResponse(code = 200, message = "OK", response = User.class),
#ApiResponse(code = 401, message = "Unauthorized", response = Error.class),
#ApiResponse(code = 404, message = "Not Found", response = Error.class),
#ApiResponse(code = 500, message = "Internal Server Error", response = Error.class)
})
public ResponseEntity<?> get(#PathVariable("id") final UUID id) {
return UserService.get(id)
.map(ResponseEntity::ok)
.map(ResponseEntity.class::cast)
.orElse(
ResponseEntity.status(NOT_FOUND).body(new Error(format(USER_NOT_FOUND_MESSAGE, id))));
}
#GetMapping
#ApiOperation(value = "Get User by Email Address")
#Transactional(readOnly = true)
#ApiResponses({
#ApiResponse(code = 200, message = "OK", response = User.class),
#ApiResponse(code = 401, message = "Unauthorized", response = Error.class),
#ApiResponse(code = 404, message = "Not Found", response = Error.class),
#ApiResponse(code = 500, message = "Internal Server Error", response = Error.class)
})
public ResponseEntity<?> get(#RequestParam("email") final String email) {
return UserService.get(email)
.map(ResponseEntity::ok)
.map(ResponseEntity.class::cast)
.orElse(
ResponseEntity.status(NOT_FOUND).body(new Error(format(USER_NOT_FOUND_MESSAGE, email))));
}
The above fails at compilation with Ambiguous mapping. Cannot map 'userController' method as error. Essentially getAll() and get(#RequestParam("email") final String email) have the same URL path - /users.
The second being:
#GetMapping
#ApiOperation(value = "List all Users")
#ApiResponses({
#ApiResponse(code = 200, message = "OK", response = User.class, responseContainer = "List"),
#ApiResponse(code = 401, message = "Unauthorized", response = Error.class),
#ApiResponse(code = 404, message = "Not Found", response = Error.class),
#ApiResponse(code = 500, message = "Internal Server Error", response = Error.class)
})
#Transactional(readOnly = true)
public ResponseEntity<Page<User>> getAll(
#RequestParam(required = false, defaultValue = "0") int pageNumber,
#RequestParam(required = false, defaultValue = "10") int pageSize) {
return ResponseEntity.ok(UserService.getUsers(pageNumber, pageSize));
}
#GetMapping(path = "/{id}")
#ApiOperation(value = "Get User by ID")
#Transactional(readOnly = true)
#ApiResponses({
#ApiResponse(code = 200, message = "OK", response = User.class),
#ApiResponse(code = 401, message = "Unauthorized", response = Error.class),
#ApiResponse(code = 404, message = "Not Found", response = Error.class),
#ApiResponse(code = 500, message = "Internal Server Error", response = Error.class)
})
public ResponseEntity<?> get(#PathVariable("id") final UUID id) {
return UserService.get(id)
.map(ResponseEntity::ok)
.map(ResponseEntity.class::cast)
.orElse(
ResponseEntity.status(NOT_FOUND).body(new Error(format(USER_NOT_FOUND_MESSAGE, id))));
}
#GetMapping(path = "/{email}")
#ApiOperation(value = "Get User by Email Address")
#Transactional(readOnly = true)
#ApiResponses({
#ApiResponse(code = 200, message = "OK", response = User.class),
#ApiResponse(code = 401, message = "Unauthorized", response = Error.class),
#ApiResponse(code = 404, message = "Not Found", response = Error.class),
#ApiResponse(code = 500, message = "Internal Server Error", response = Error.class)
})
public ResponseEntity<?> get(#PathVariable("email") final String email) {
return UserService.get(email)
.map(ResponseEntity::ok)
.map(ResponseEntity.class::cast)
.orElse(
ResponseEntity.status(NOT_FOUND).body(new Error(format(USER_NOT_FOUND_MESSAGE, email))));
}
Here I run into the issue of the controller not being able to resolve between get(#PathVariable("email") final String email and get(#PathVariable("id") final UUID id) with the following error:
Ambiguous handler methods mapped for '/api/gems/users/fireball%40email.com': {public org.springframework.http.ResponseEntity com.personal.project.controllers.UserController.get(java.util.UUID), public org.springframework.http.ResponseEntity com.personal.project.controllers.UserController.get(java.lang.String)}
You can solve it by merging these two Ambiguous path into one.
For the first case:
#GetMapping
#ApiOperation(value = "List all Users")
#ApiResponses({
#ApiResponse(code = 200, message = "OK", response = User.class, responseContainer = "List"),
#ApiResponse(code = 401, message = "Unauthorized", response = Error.class),
#ApiResponse(code = 404, message = "Not Found", response = Error.class),
#ApiResponse(code = 500, message = "Internal Server Error", response = Error.class)
})
#Transactional(readOnly = true)
public ResponseEntity<?> getUsers(
#RequestParam(required = false, defaultValue = "0") int pageNumber,
#RequestParam(required = false, defaultValue = "10") int pageSize,
// add email as param.
#RequestParam(required = false) String email,
) {
if(email ==null || StringUtils.isEmpty(email)){
return ResponseEntity.ok(UserService.getUsers(pageNumber, pageSize));
}else return UserService.get(email)
.map(ResponseEntity::ok)
.map(ResponseEntity.class::cast)
.orElse(
ResponseEntity.status(NOT_FOUND).body(new Error(format(USER_NOT_FOUND_MESSAGE, email))));
}
For the second one:
// change the type UUID id to string.
#GetMapping(path = "/{id}")
...
public ResponseEntity<?> get(#PathVariable("id") final String id) {
// check if id as an uuid or email, and based on that take action
}
In line with my previous answer, you only need two controller methods. Then, in the method that handles GET /users requests, the presence or absence of the name query parameter will determine whether the server will fetch all users or filter the user by email.
See below the example below. For simplification, I have not added the OpenAPI annotations and also removed pagination, but you can easily add them as you need:
#RestController
#RequestMapping(path = "/users",
produces = MediaType.APPLICATION_JSON_VALUE)
public class UserController {
#GetMapping
public ResponseEntity<List<User>> findUsers(#RequestParam("email") final String email) {
// If email is null/empty/blank, then fetch all users
// Otherwise filter users by email
}
#GetMapping(path = "/{id}")
public ResponseEntity<User> findUserById(#PathVariable("id") final UUID id) {
// Find user with the given ID
}
}
In case you plan to support multiple filters (or simply want to avoid if-'else's or custom repository methods), it may be a good idea to use Query by Example (QBE), which is supported by Spring Data:
Query by Example (QBE) is a user-friendly querying technique with a simple interface. It allows dynamic query creation and does not require you to write queries that contain field names. In fact, Query by Example does not require you to write queries by using store-specific query languages at all.
The documentation also states the following:
The Query by Example API consists of three parts:
Probe: The actual example of a domain object with populated fields.
ExampleMatcher: The ExampleMatcher carries details on how to match particular fields. It can be reused across multiple Examples.
Example: An Example consists of the probe and the ExampleMatcher. It is used to create the query.
Query by Example is well suited for several use cases:
Querying your data store with a set of static or dynamic constraints.
Frequent refactoring of the domain objects without worrying about breaking existing queries.
Working independently from the underlying data store API.
Query by Example also has several limitations:
No support for nested or grouped property constraints, such as firstname = ?0 or (firstname = ?1 and lastname = ?2).
Only supports starts/contains/ends/regex matching for strings and exact matching for other property types.
See below how to use Query by Example in your case:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class UserFilter {
private String email;
}
#RestController
#RequiredArgsConstructor
#RequestMapping(path = "/users",
produces = MediaType.APPLICATION_JSON_VALUE)
public class UserController {
private final UserService userService;
#GetMapping
public ResponseEntity<List<User>> findUsers(#RequestParam("email") final String email) {
UserFilter filter = UserFilter.builder().name(email).build();
List<User> users = userService.findUsers(filter);
return ResponseEntity.ok(users);
}
#GetMapping(path = "/{id}")
public ResponseEntity<User> findUserById(#PathVariable("id") final UUID id) {
User user = userService.findUserById(filter).orElseThrow(SomeSortOfException::new);
return ResponseEntity.ok(user);
}
}
#Service
#RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public List<User> findUsers(UserFilter filter) {
User user = new User();
user.setEmail(filter.getEmail());
Example<User> example = Example.of(user);
return userRepository.findAll(example);
}
public Optional<User> findUserById(UUID id) {
return userRepository.findById(id);
}
}

springdoc-openapi different examples

I use springdoc-openapi to document my REST API. An error is returned by an error object, that has an errorCode and message. I use #Schema annotation to document an example. However I need different examples per different errors. Is there any way, how to do that?
Example from my code:
#PostMapping(consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
#Operation(summary = "Get new license or retrieve previously issued one for this userId.", tags = "License Endpoint", description = "Licensing operations.",
responses = {
#ApiResponse(
responseCode = "200",
description = "New license or previously issued license for this user, if request was called multiple times.",
content = {#Content(schema = #Schema(implementation = LicenseResponse.class))}
),
#ApiResponse(responseCode = "400",
description = "License can not be retrieved because of either expired bundle or requested bundleId does not exist.",
//I need different example for this error
content = {#Content(schema = #Schema(implementation = LicenseErrorResponse.class))
}
),
#ApiResponse(responseCode = "500",
description = "Internal Error",
//And different example for this error
content = {#Content(schema = #Schema(implementation = LicenseErrorResponse.class))
}
)
}
)
#LoggedIO(input = INFO, result = INFO)
public ResponseEntity<Object> newLicense(#Valid #RequestBody LicenseRequest licenseRequest) {
//content not interesting
}
import javax.validation.constraints.NotBlank;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
#Data
public class LicenseErrorResponse {
// I need different examples for different error in controller.
#Schema(example = "UNKNOWN_BUNDLE_ID", required = true)
private final LicenseErrorCode licenseErrorCode;
#Schema(example = "Bundle doesn't exist, bundleId=com.unknown.id")
private final String message;
#JsonCreator
public LicenseErrorResponse(
#NotBlank #JsonProperty(value = "errorCode") final LicenseErrorCode licenseErrorCode,
#NotBlank #JsonProperty(value = "message") final String message) {
this.licenseErrorCode = licenseErrorCode;
this.message = message;
}
public enum LicenseErrorCode {
EXPIRED_BUNDLE, UNKNOWN_BUNDLE_ID, OTHER
}
}
One way to do that is you can define a string as an example
public static final String exampleInternalError = "{\r\n"
+ " \"licenseErrorCode\": 500,\r\n"
+ " \"message\": \"Internal Error\"\r\n" + "}";
same is used to show the example as
#ApiResponse(responseCode = "500",
description = "Internal Error",
//And different example for this error
content = #Content(schema = #Schema(implementation = LicenseErrorResponse.class),
examples = #ExampleObject(description = "Internal Error", value = exampleInternalError)))

How to process twilio SMS status callback in java

I have following service method for SMS callback processing
#RequestMapping(value = "/", method = RequestMethod.POST)
#ResponseBody
public void processSms(#RequestParam(value = "MessageStatus", required = false) String messageStatus,
#RequestParam(value = "ApiVersion", required = false) String apiVersion,
#RequestParam(value = "SmsSid", required = false) String smsSid,
#RequestParam(value = "SmsStatus", required = false) String smsStatus,
#RequestParam(value = "To", required = false) String to,
#RequestParam(value = "From", required = false) String from,
#RequestParam(value = "MessageSid", required = false) String messageSid,
#RequestParam(value = "AccountSid", required = false) String accountSid){
TwilioCallBackResponse response = new TwilioCallBackResponse();
response.messageStatus = messageStatus;
response.apiVersion = apiVersion;
response.smsSid = smsSid;
response.smsStatus = smsStatus;
response.to = to;
response.from = from;
response.messageSid = messageSid;
response.accountSid = accountSid;
LOG.info("Incomming twilio callback: " + response.messageStatus);
smsService.processSmsCallback(response);
}
I can get and log response from twilio successfully.
Problem is that in twilio end response error is logged. Should I specify content type or respond with some response body? Any ideas?
This is from twilio log
and error 11200 HTTP retrieval failure is also logged
Status callbacks do not control application flow, so TwiML does not need to be returned in this instance; however, it's recommended that you respond to status callbacks with either a 204 No Content or a 200 OK with Content-Type: text/xml and an empty <Response/> in the body. Failure to respond properly will result in warnings in Debugger.
Ok, based on Jim answer this is what works for me and no more warnings on twilio side
#RequestMapping(value = "/", method = RequestMethod.POST, produces = "text/xml")
#ResponseBody
#ResponseStatus(value = HttpStatus.OK)
public String processSms(#RequestParam(value = "MessageStatus", required = false) String messageStatus,
#RequestParam(value = "ApiVersion", required = false) String apiVersion,
#RequestParam(value = "SmsSid", required = false) String smsSid,
#RequestParam(value = "SmsStatus", required = false) String smsStatus,
#RequestParam(value = "To", required = false) String to,
#RequestParam(value = "From", required = false) String from,
#RequestParam(value = "MessageSid", required = false) String messageSid,
#RequestParam(value = "AccountSid", required = false) String accountSid){
TwilioCallBackResponse response = new TwilioCallBackResponse();
response.messageStatus = messageStatus;
response.apiVersion = apiVersion;
response.smsSid = smsSid;
response.smsStatus = smsStatus;
response.to = to;
response.from = from;
response.messageSid = messageSid;
response.accountSid = accountSid;
LOG.info("Incomming twilio callback: " + JsonUtils.printJson(response));
smsService.processSmsCallback(response);
return "<Response/>";
}

Categories