jersey - use custom class in URI of resource - java

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.

Related

Limiting the values of Query Params JAX-RS with CXF as implementation

I have a use case where I need to limit the values that can be passed as the query param.
#Path("/foo")
public interface Foo {
#GET
#Path("/details/id/{id}")
void getFooDetails(#PathParam("id") String id, #QueryParam("sort") String sortDirection);
}
public class FooImpl {
public void getFooDetails(String id, String sortDir) {
//Implementation
}
}
In the above example, I want to restrict the value of query param sort that can be passed via the API to ASC, DESC.
Is there any existing CXF annotation which I can use to restrict the values on a parameter? I haven't found any and so I tried the following solution.
My Approach:
#Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Inherited
public #interface ValueSet {
String[] allowedValues();
}
The modified interface looks like this.
#Path("/foo")
public interface Foo {
#GET
#PathParam("/details/id/{id}")
void getFooDetails(#PathParam("id") String id, #QueryParam("sort") #ValueSet(allowedValues = {"ASC", "DESC"}) String sortDirection);
}
I wrote a CXF Interceptor which intercepts the API invocation. I used reflection to get a handle on FooImpl.getFooDetails params. But the problem I faced is that the interceptor looks at FooImpl.getFooDetails method and doesn't find the annotations #QueryParam on the method params since #QueryParam is on the base method and the annotation is not inherited.
Interceptor implementation:
#Provider
public class ParamValidationInterceptor extends AbstractPhaseInterceptor<Message> {
public ParamValidationInterceptor() {
super(Phase.PRE_INVOKE);
super.addBefore(someInterceptor);
}
#Override
public void handleMessage(Message message) throws Fault {
UriInfo uriInfo = new UriInfoImpl(message);
MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
Method methodToInvoke = (Method) message.get("org.apache.cxf.resource.method");
Parameter[] parameters = methodToInvoke.getParameters();
for (Parameter parameter : parameters) {
if (parameter.isAnnotationPresent(ValueSet.class)) {
ValueSet valueSet = parameter.getAnnotation(ValueSet.class);
QueryParam queryParam = parameter.getAnnotation(QueryParam.class);
Object invokedVal = queryParams.get(queryParam.value());
String[] allowedValues = valueSet.allowedValues();
if (!Arrays.asList(allowedValues).contains(invokedVal)) {
throw new CustomException();
}
}
}
}
}
Can anyone suggest a way forward? It would be great if anyone can suggest an alternative approach.
P.S: I am using CXF as an implementation for JAX-RS and spring is used as a container.
Update:
Like #Cássio Mazzochi Molin and #Andy McCright suggested, I will go with #Pattern annotation. But I am curious to know why the JAX-RS annotations are not inherited from the interface although the spec says they will be inherited.
Annotation inheritance
According to the section §3.6 Annotation Inheritance of the JAX-RS specification, it is recommended to always repeat annotations instead of relying on annotation inheritance.
Refer to this answer for the complete quote.
#QueryParam can be applied to different targets
Bear in mind that the #QueryParam annotation can be applied to:
Resource method parameters
Resource class fields
Resource class bean properties
Hence a manual validation can be tricky.
Use Bean Validation
For validation purposes, you should consider Bean Validation. Consider a #Pattern annotation with the allowed values:
#Pattern(regexp = "ASC|DESC")
And just annotate your resource method parameter:
#GET
#Path("foo")
public Response getFoos(#QueryParam("sort")
#Pattern(regexp = "ASC|DESC") String sortDirection) {
...
}
If you prefer case insensitive values, use:
#Pattern(regexp = "ASC|DESC", flags = Pattern.Flag.CASE_INSENSITIVE)
If the given value is invalid, a ConstraintViolationException will be thrown. To handle such exception and return a customized response, you can use an ExceptionMapper:
#Provider
public class ConstraintViolationExceptionMapper
implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException exception) {
...
}
}
Perhaps it is just a typo, but CXF may not be recognizing the getFooDetails method (on the interface) because it is annotated with #PathParam instead of #Path.
Instead of using your ValueSet approach, I used BeanValidation, but the following code works for me.
Foo.java
#Path("/foo")
public interface Foo {
#GET
#Path("/details/id/{id}")
Response getFooDetails(
#PathParam("id") #Pattern(regexp="[0-9]*") String id,
#QueryParam("sort") #Pattern(regexp = "ASC|DESC") String sortDirection);
}
FooImpl.java
public class FooImpl implements Foo {
#Override
public Response getFooDetails(String id, String sortDirection) {
Integer idInt = Integer.parseInt(id);
if ("ASC".equals(sortDirection) || sortDirection == null) {
...
} else if ("DESC".equals(sortDirection)) {
...
}
return ...;
}
I've got this working on WebSphere Liberty 17.0.0.2 which is based on CXF 3.1.11.
Hope this helps,
Andy

How to parse RESTful API params with Dropwizard

Let's say I have:
#GET
public UserList fetch(#PathParam("user") String userId) {
// Do stuff here
}
Now, let's say I have my own type for userId, let's call it UserId. Is it possible to parse that String to UserId when it is passed into the fetch method, i.e.:
#GET
public UserList fetch(#PathParam("user") UserId userId) {
// Do stuff here
}
I realize I can parse the String once I am inside the method, but it would be more convenient that my method gets the type I want.
Well you've attempted to make a GET call with a request body is what I find not very helpful. Do read Paul's answer here -
you can send a body with GET, and no, it is never useful to do so
What would be good to practice is, to make a PUT or a POST call (PUT vs POST in REST) as follows -
#POST
#Path("/some-path/{some-query-param}")
public Response getDocuments(#ApiParam("user") UserId userId,
#PathParam("some-query-param") String queryParam) {
UserId userIdInstance = userId; // you can use the request body further
Note - The ApiParam annotation used is imported from the com.wordnik.swagger.annotations package. You can similarily use FormParam,QueryParam according to your source of input.
Dropwizard is using Jersey for HTTP<->Java POJO marshalling. You could use the various annotations from Jersey #*Param (#FormParam, #QueryParam, etc.) for some of the parameters.
If you need to use map/marshall to/from Java POJOs take a look at the test cases in Dropwizard:
#Path("/valid/")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class ValidatingResource {
#POST
#Path("foo")
#Valid
public ValidRepresentation blah(#NotNull #Valid ValidRepresentation representation, #QueryParam("somethingelse") String xer) {
return new ValidRepresentation();
}
This defines an API endpoint responding to HTTP POST method which expects ValidRepresentation object and "somethingelse" as HTTP method query parameter. The endpoint WILL respond ONLY when supplied with JSON parameters and will return only JSON objects (#Produces and #Consumes on the class level). The #NotNull requires that object to be mandatory for the call to succeed and #Valid instructs Dropwizard to call Hibernate validator to validate the object before calling the endpoint.
The ValidRepresentation class is here:
package io.dropwizard.jersey.validation;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;
public class ValidRepresentation {
#NotEmpty
private String name;
#JsonProperty
public String getName() {
return name;
}
#JsonProperty
public void setName(String name) {
this.name = name;
}
}
The POJO is using Jackson annotations to define how JSON representation of this object should look like. #NotEmtpy is annotation from Hibernate validator.
Dropwizard, Jersey and Jackson take care of the details. So for the basic stuff this is all that you need.

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

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.

Declaring a retrofit REST endpoint with constant query value

So I want to get the metadata of a youtube video (say this one: https://www.youtube.com/watch?v=qlTA3rnpgzU).
I'm going to encode it and wrap it in another url like so: http://www.youtube.com/oembed?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DqlTA3rnpgzU&format=json
My interface definition will look like this:
public interface YoutubeApi {
#GET ("oembed")
YoutubeMetaData metaData (#Query (QUERY_VIDEO_URL) final String url,
#Query(QUERY_FORMAT) final String alwaysJson);
}
That's all fine and dandy, but I don't ever want to specify any format other than JSON here (format=json is a fixed part of this url).
Is there a way to specify this in my interface declaration and reduce my interface to:
public interface YoutubeApi {
#GET ("oembed")
#Magic ("format=json")
YoutubeMetaData metaData (#Query (QUERY_VIDEO_URL) final String url);
}
Thanks.
Just put it right in the relative URL:
public interface YoutubeApi {
#GET("oembed?format=json")
YoutubeMetaData metaData(#Query(QUERY_VIDEO_URL) String url);
}
In kotlin you can specify the default parameter:
interface YoutubeApi {
#GET ("oembed")
suspend fun metaData (
#Query (QUERY_VIDEO_URL) url: String,
#Query(QUERY_FORMAT) alwaysJson: String = "json"
): Response<YoutubeMetaData>
}

RESTful web services in java

I am trying to pass list of Long in my resource as post data and consume type is application/xml. I am also passing two path params. It is giving me exception "media type not supported".
Please help me to solve this.
this is the code and I am having exception..
#POST
#Path("/temp/{abc}")
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.APPLICATION_XML)
public List<Long> createUser2(List<User> users,#PathParam("abc") String abc) {
//.................//
List<Long> listLong=new ArrayList<Long>();
listLong.add(1L);
listLong.add(2L);
System.out.println("temp called");
return listLong;
}
> org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException:
> MessageBodyWriter not found for media type=application/xml
The problem is that there's no conversion code that knows how to automatically change a Long or a List<Long> into XML. At the very least, information about what the name of the containing element must be present, and JAXB (the default supported mechanism) only applies that sort of thing at the level of a class.
The fix is to create a wrapper class with suitable JAXB annotations and return that. You might need to tweak the class to get exactly the serialization you want, but that's not hard.
#XmlRootElement(name = "userinfo")
public class UserInfo {
#XmlElement
public List<Long> values;
// JAXB really requires a no-argument constructor...
public UserInfo() {}
// Convenience constructor to make the code cleaner...
public UserInfo(List<Long> theList) {
values = theList;
}
}
#POST
#Path("/temp/{abc}")
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.APPLICATION_XML)
// NOTE THE CHANGE OF RESULT TYPE
public UserInfo createUser2(List<User> users,#PathParam("abc") String abc) {
//.................//
List<Long> listLong=new ArrayList<Long>();
listLong.add(1L);
listLong.add(2L);
System.out.println("temp called");
return new UserInfo(listLong); // <<<< THIS LINE CHANGED TOO
}

Categories

Resources