How can i specify path value from property file in jersey? - java

I want the path should not be hard coded rather be picked up from property such that we can change it according to our need.
Below Code works :---
#Path("ws/{version}")
public class DesignationResource {
#PathParam("version") String version =
Constants.API_VERSION; //(read from property file in class Constants)
#PathParam("servicename_designationList") String servicename_designationList=
Constants.API_POST_CITYLIST_NAME ; //(read from property file in class Constants)
#Path("{servicename_designationList}")
#Produces(MediaType.APPLICATION_JSON)
public Response getDesignations()
{
/**
...CODES...
*/
}
}
But if the class has two methods then its not working and throwing exception
Code: ---
#Path("ws/{version}")
public class DesignationResource {
#PathParam("version") String version =
Constants.API_VERSION; //(read from property file in class Constants)
#PathParam("servicename_designationList") String servicename_designationList=
Constants.API_POST_CITYLIST_NAME ; //(read from property file in class Constants)
#PathParam("servicename_designationListId") String servicename_designationListId=
Constants.API_POST_CITYLISTID_NAME ; //(read from property file in class Constants)
#Path("{servicename_designationList}")
#Produces(MediaType.APPLICATION_JSON)
public Response getDesignations()
{
/**
...CODES...
*/
}
#Path("{servicename_designationListId}")
#Produces(MediaType.APPLICATION_JSON)
public Response getDesignationsId()
{
/**
...CODES...
*/
}
}
Exception recorded as :-----
org.glassfish.jersey.server.model.ModelValidationException: Validation of the application resource model has failed during application initialization.
[[FATAL] A resource model has ambiguous (sub-)resource method for HTTP method GET and input mime-types as defined by #Consumes and #Produces annotations at Java methods public javax.ws.rs.core.Response DesignationResource.getDesignations() and public javax.ws.rs.core.Response DesignationResource.getDesignationsId() at matching regular expression /([^/]+?). These two methods produces and consumes exactly the same mime-types and therefore their invocation as a resource methods will always fail.; source='org.glassfish.jersey.server.model.RuntimeResource#7e5ba613', [FATAL] A resource model has ambiguous (sub-)resource method for HTTP method GET and input mime-types as defined by #Consumes and #Produces annotations at Java methods public javax.ws.rs.core.Response source='org.glassfish.jersey.server.model.RuntimeResource#7e5ba613']
at org.glassfish.jersey.server.ApplicationHandler.initialize(ApplicationHandler.java:465)
...

You are using same path url (servicename_designationListId) in your methods. Give different paths to your methods, like below.
#Path("{servicename_designations}")
#Produces(MediaType.APPLICATION_JSON)
public Response getDesignations()
{
/**
...CODES...
*/
}
#Path("{servicename_designationListId}")
#Produces(MediaType.APPLICATION_JSON)
public Response getDesignationsId()
{
/**
...CODES...
*/
}

As the stacktrace says, paths must be unique (or you use different media-types). I think, you want to do the following:
#Path( Constants.API_POST_CITYLIST_NAME )
#Produces( MediaType.APPLICATION_JSON )
public Response getDesignations()
{
/**
...CODES...
*/
}
#Path( Constants.API_POST_CITYLISTID_NAME )
#Produces( MediaType.APPLICATION_JSON )
public Response getDesignationsId()
{
/**
...CODES...
*/
}

Use the programmatic API to register resources. It will allow you to register things dynamically at runtime in ways that you can't manage via Annotations.

Related

Decoding body parameters with Spring

I'm developing a REST API backend with Spring for a Slack App. I was able to receive messages from Slack (the slash commands) but I'm not able to properly receive component interactions (button clicks).
The official documentation says:
Your Action URL will receive a HTTP POST request, including a payload body parameter, itself containing an application/x-www-form-urlencoded JSON string.
therefore I have written the following #RestController:
#RequestMapping(method = RequestMethod.POST, value = "/actions", headers = {"content-type=application/x-www-form-urlencoded"})
public ResponseEntity action(#RequestParam("payload") ActionController.Action action) {
return ResponseEntity.status(HttpStatus.OK).build();
}
#JsonIgnoreProperties(ignoreUnknown = true)
class Action {
#JsonProperty("type")
private String type;
public Action() {}
public String getType() {
return type;
}
}
however I get the following error:
Failed to convert request element: org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'controllers.ActionController$Action'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'controllers.ActionController$Action': no matching editors or conversion strategy found
What does it mean, and how to resolve?
You receive a string that contains a JSON content. You don't receive a JSON input as application/x-www-form-urlencoded is used as content type and not application/json as stated :
Your Action URL will receive a HTTP POST request, including a payload
body parameter, itself containing an application/x-www-form-urlencoded
JSON string.
So change the parameter type to String and use Jackson or any JSON library to map the String to your Action class :
#RequestMapping(method = RequestMethod.POST, value = "/actions", headers = {"content-type=application/x-www-form-urlencoded"})
public ResponseEntity action(#RequestParam("payload") String actionJSON) {
Action action = objectMapper.readValue(actionJSON, Action.class); 
return ResponseEntity.status(HttpStatus.OK).build();
}
As pvpkiran suggests, you could have replaced #RequestParam by #RequestBody if you could pass the JSON string directly in the body of the POST request, and not as a value of a parameter but it seems that is not the case there.
Indeed by using #RequestBody, the body of the request is passed through an HttpMessageConverter to resolve the method argument.
To answer to your comment, Spring MVC doesn't provide a very simple way to achieve your requirement : mapping the String JSON to your Action class.
But if you really need to automatize this conversion you have a lengthy alternative as stated in the Spring MVC documentation such as Formatters (emphasis is mine) :
Some annotated controller method arguments that represent String-based
request input — e.g. #RequestParam, #RequestHeader, #PathVariable,
#MatrixVariable, and #CookieValue, may require type conversion if the
argument is declared as something other than String.
For such cases type conversion is automatically applied based on the
configured converters. By default simple types such as int, long,
Date, etc. are supported. Type conversion can be customized through a
WebDataBinder, see DataBinder, or by registering Formatters with the
FormattingConversionService, see Spring Field Formatting.
By creating a formatter (FormatterRegistry subclass) for your Action class you could add that in the Spring web config as documented :
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
// ... add action formatter here
}
}
and use it in your parameter declaration :
public ResponseEntity action(#RequestParam("payload") #Action Action actionJ)
{...}
For simplicity, you could use the code block below. #Request body maps the the payload to the Action class. It also validates to make sure that the type is not blank. The #Valid and #NotBlank is from javax.validation package.
#PostMapping("actions")
public ResponseEntity<?> startApplication(#RequestBody #Valid Action payload) {
// use your payload here
return ResponseEntity.ok('done');
}
class Action {
#NotBlank
private String type;
public Action() {
}
public String getType() {
return type;
}
}

How to get content type of the response in JAX-RS ExceptionMapper

I have a resource:
#GET
#Path("/print-order")
#Produces("application/pdf")
public byte[] printOrder(#QueryParam("id") Long orderId) {
return ...;
}
...which can throw an error that is relevant to the user and must be displayed as a HTML page. So I implemented an ExceptionMapper but I don't know how to get the value of #Produces("application/pdf") annotation of the called resource.
#Provider
public class CustomExceptionMapper implements ExceptionMapper<CustomException> {
#Override
public Response toResponse(CustomException exception) {
if (contentType = "application/pdf")
... html respone
else
... entity response
}
}
I'm using JAX-RS 1.x (jsr311) with Jersy 1.12 implementation but would love to have implementation independent solution.
You can inject different context objects into the ExceptionMapper to get more info on the request it handles. It's convenient to determine what content type the client expects based on HTTP's Accept header (learn more here).
Below is the example on how you can make ExceptionMapper to respond with different formats based on Accept header specified (or not specified) by your APIs client.
#Provider
public class CustomExceptionMapper implements ExceptionMapper<CustomException> {
// Inject headers of the request being processed
#Context
private HttpHeaders headers;
// or even all the request details
#Context
private HttpServletRequest request;
#Override
public Response toResponse(CustomException exception) {
List<MediaType> acceptedTypes = headers.getAcceptableMediaTypes();
if (acceptedTypes.contains(MediaType.APPLICATION_JSON)) {
// respond with entity
} else {
// respond with HTML
}
}
}
You initial idea can be implemented, though. You can inject HttpServletRequest in your resource class and use setAttribute() method to store application/pdf string within the context of current request. It can be later obtained in ExceptionMapper using getAttribute() method. But I wouldn't recommend to do so unless absolutely necessary. It introduces not so obvious dependencies between components of your code.

How to add two different paths to the same endpoint in swagger?

Using javaws we can have multiple endpoints leading to the same method. Example:
#Path("/menus")
public class MenuResource {
#Path("/{menuId}/sections")
#Timed #ExceptionMetered
public MenuSectionResource getSections(#InjectParam MenuSectionResource resource) {
return resource;
}
}
#Path("/sections")
public class MenuSectionResource {
#GET
public Section get(#PathParam("menuId") String menuId, #QueryParam("id") String id) {
/// method accessed by GET in /sections or GET in /menus/{menuid}/sections
}
}
I'm trying to use swagger to document both endpoints, but i can only use one
#Api annotation in each class, so i can generate either /sections or /menus/{menuid}/sections. Is it possible to automatically generate both entries in the swagger.json output?

Overriding a #Path annotation in a class by method in JAX-RS

I have a REST API service i maintain in java (over jersey, JAX-RS)
I want to support the following route in my service:
/api/v1/users/{userId}/cars
however, it concatinates to the class's #Path annotation. e.g.
/api/v1/cars/api/v1/users/{userId}/cars
This is my service class:
#Path("api/v1/cars")
public class CarsService {
#GET
#Path("/api/v1/users/{userId}/cars")
public Response getUserCars(#PathParam("userId") Long userId) {
// ...
}
#GET
public Response getCars() {
// ...
}
}
Is there any way to override it?
Note the following:
The #Path annotation in a class designates a root resource.
The #Path annotation in a method designates a sub-resource of a root resource.
When placed on methods, the #Path annotation does not override the #Path annotation of the class. JAX-RS/Jersey performs a hierarchical matching using the #Path annotations.
So, you can try:
#Path("api/v1")
public class CarsService {
#GET
#Path("/cars")
public Response getCars() {
...
}
#GET
#Path("/users/{userId}/cars")
public Response getUserCars(#PathParam("userId") Long userId) {
...
}
}
However, have you considered using different resource classes?
#Path("api/v1/cars")
public class CarsService {
#GET
public Response getCars() {
...
}
}
#Path("api/v1/users")
public class UsersService {
#GET
#Path("{userId}/cars")
public Response getUserCars(#PathParam("userId") Long userId) {
...
}
}
For more details on resources, have a look at the documentation.
You just should change the #Path annotation of the method to:
#Path("users/{userId}/cars")
In this way, the resulting path of concatenating the class and the method #Path annotations will produce your desired path.

jersey - use custom class in URI of resource

I have a custom data class:
public static class Data {
...
}
I want to use this class in the URI of a resource in Jersey. For example:
#Path("test")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class ResourceTest {
#GET
#Path("/data-{data}")
public Response get(#PathParam("data") final Data data) {
...
}
}
Is this possible? I guess I need to inject some kind of converter, which converts the textual representation of a Data to a Data instance. I have been looking in the documentation, but haven't found something useful so far.
Ofcourse, I can change this to:
#Path("test")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class ResourceTest {
#GET
#Path("/data-{data}")
public Response get(#PathParam("data") final String input) {
final Data data = convert(input);
...
}
}
But I would rather do the conversion elsewhere/automagically wrt. the resource.
From the docs:
The type of the annotated parameter, field or property must either:
...
Have a constructor that accepts a single String argument.
Have a static method named valueOf or fromString that accepts a single String argument (see, for example, Integer.valueOf(String)).
Have a registered implementation of ParamConverterProvider JAX-RS extension SPI that returns a ParamConverter instance capable of a "from string" conversion for the type.
So if you provide a constructor Data(String) you should be fine.

Categories

Resources