Spring MVC: Advanced annotation based mapping - java

I am looking for a way to have complex URL mappings that would depend on the content of the database. For instance I show two URL mappings:
/{category}/
/{category}/{slug}/{id}
Categories can be for instance:
apples, pears, lemons -> resolve to FruitListController,
beans, onions, potatoes -> resolve to VegetableListController.
Note that those categories are stored in database and might be changed.
If I have in addition the slug and id, both of them must be checked against the database if they exist.
Can I somehow write some kind "Category handler", which would return the corresponding Category and associated Controller?
Obviously this is a problem of translating URL -> Request. Also, is there a way to generate backwards Request -> URL in similar way? I would like something like in templates:
<c:foreach="apples as apple"
${apple.name}
</c:foreach>

Use WebArgumentResolvers. They have access to the WebRequest to see your URL. The resolvers can do your database lookups. The FruitWebArgumentResolver could return a Fruit or return UNRESOLVED. The VegetableArgumentResolver could return a Vegetable or return UNRESOLVED. After the argument is resolved, the handler adapter can map to the RequestMapping that takes the specific type as an argument. For example,
#RequestMapping("/{catagory}/{slug}")
public void fruitSlugList(Fruit fruit, #PathVariable("slug") String slug){
To see an example of this, I have a blog post here that uses the same type of mechanism:
http://www.adamweigold.com/2012/01/using-multpartrequestresolvers-with.html

Related

Extend Single ID REST endpoint to support multiple IDs

I have a single ID REST API that I need to extend to support multiple (up to 10Ks) IDs. Basically to run update on all relevant IDs instead of sending 10Ks request in network.
Current endpoint:
#POST
#Path("{id}/update")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public ResponseVO updateBlockReason(#PathParam("id") int id, List<RequestVo> requestVo) {
One option suggested is comma-delimited values as stackexchange's answers-by-ids
Usage of /answers/{ids} GET
{ids} can contain up to 100 semicolon delimited ids. To find ids programmatically look for answer_id on answer objects.
This is the case on similar answers
http://our.api.com/Product/<id1>,<id2> :as James suggested can be an option since what comes after the Product tag is a parameter
But it seems awkward to me and RequestVo will be same for all IDs (which is currently is fine, but later to add such support will be harder)
It seems I need to change from Path variable to add it inside RequestVO
Which means the Id will be a JSON key, e.g.
[{
"id" : "1",
"name": "myAttribute"
"toggle": true
},
{
"id" : "2",
"name": "mySecondAttribute"
"toggle": false
}
]
Is this the correct approach or am I missing something?
Thank you in advance for any comments\answers
Current request VO
#Data
#AllArgsConstructor
#NoArgsConstructor
public class RequestVO {
private String name;
private boolean toggle;
// will add now private int id
}
My concern is also if I want (one of the requirement) to update with same request (as name=doA, toggle=true) for 10Ks Ids I'll have to duplicate request VO instead of sending ID separately
The best way is to keep id in your RequestVO DTO itself and not in URL as you have already suggested because even 100 ids in URL can make your URL very big and you are talking about 10K ids.
And again in future, the bit length of a single id may increase or later on you might need to update 50k or even 100K objects.
According to maximum length of a URL, there is no general specification on URL length but extremely long URLs are usually a mistake and URLs over 2,000 characters will not work in the most popular web browsers.
So I think your second approach is best here and will be good for future purposes also.
You may also want to use a PUT request because it makes more sense for an update request. So your code will become like this:
#PUT
#Path("/update")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public ResponseVO updateBlockReason(List<RequestVo> requestVo) {
I find the path product/{id}/update questionable, because you could achieve similar behavior by mapping #Put-request to product/{id} itself. The READ, WRITE differentiation is already explicit by the Request-mapping.
Also, whether or not using verbs in restful urls is a topic for itself.
Assuming you could use plural endpoints, this could look like /products/{id}.
Because you want to batch/bulk update products, you could map #Put-requests to /products now, with a list of updated Products in the RequestBody. Keep in mind, that this somewhat complicates the Response, as you may have to return Http-207 for answering the correct status of the update for each element in the list.
I want 1 logical endpoint for update
You can have a logical service method for this, but not endpoints really.
You already mentioned the problem of /{id} in your path for bulk updates.
If you really, really need to, I would remove the #Put-mapping from /products/{id} and redirect to /products where the update content would be a single element list, or a little more sophisticated, distinguished by a mediaType (what again means two endpints, but a single url).
Edit:
I just happen to understand the VO-issue. You are not updating Products, but parts of it (the name RequestVO was misleading me).
This smells like a #Patch-mapping to me, where parts of a Product get updated.
So I still would use /products but with a #Patch-mapping.
When a client needs to replace an existing Resource entirely, they can use PUT. When they’re doing a partial update, they can use HTTP PATCH.
This brings up another issue, use #Post only if the id is unknown (usually before something is CREATED and gets an id assigned, for UPDATES use #Put and reuse the assigned id)
Using post is technically doable, but because of idempotece not advisable.
Why not just pass the list of your IDs in the body of your request as JSON array? the code would be:
#POST
#Path("/update/ids")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public ResponseVO updateBlockReason(#RequestBody List<Integer> ids, List<RequestVo> requestVo) {
...
}

How to resolve ambiguous http method for delete operation. Trying to Resolve using PathParam

I am trying to delete resource using id and name, so I have two different methods to delete the resource using id and name. The think I realized later is that this will throw me ambiguous http operation.
example Delete an animal from the database using following operation:
/animal/1 or animal/elephant
I do not believe query parameters is the right answer for this. You are trying to delete a particular resource and I feel path param would be the right answer for it (delete a resource with a sepcific path, query params are mostly used for Getting a resource). However, I am not sure how can I achieve this without getting an exception. Any ideas?
A resource should be identifiable by only one URL, in your case the ID.
Locating the object by other means is a search, aka a query, e.g. by name, even if names are guaranteed to be unique.
So, the following would be good, clear URLs:
/animal/1
/animal?name=elephant
/animal?color=grey
You could also using matrix parameters, e.g. if the tail of an elephant is a resource:
/animal/1/tail
/animal;name=elephant/tail
See URL matrix parameters vs. request parameters.

Deserializing List<Map<String, String>> QueryParam in jersey 1

I'm trying to implement a method in a dropwizard resource, that will service a call from a JS frontend (that uses DataTables).
The request has query parameters that look like this:
columns[0][data]=0&columns[0][name]=&columns[0][searchable]=false&columns[0][orderable]=false&columns[0][search][value]=&columns[0][search][regex]=false
columns[1][data]=iata&columns[1][name]=iata&columns[1][searchable]=true&columns[1][orderable]=true&columns[1][search][value]=&columns[1][search][regex]=false
The request comes from a JS frontend implemented with DataTables, and uses server-side processing. Info about how datatables sends the requests here:
https://datatables.net/manual/server-side
I'm having issues defining the data type for the above query parameters. With spring data, we can define it as:
List<Map<String, String>> columns
which can be wrapped in an object annotated with ModelAttribute and it will deserialize fine.
In my app I'm using an older version of dropwizard which depends on jersey 1.19.
I've tried annotating it as a QueryParam, but the app fails at startup.
Method:
#Path("/mappings")
#GET
#Timed
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response getMappings(#QueryParam("columns") List<Map<String, String>> columns) {
// processing here.
}
When I do this, I get:
ERROR [2016-11-07 14:16:13,061] com.sun.jersey.spi.inject.Errors: The
following errors and warnings have been detected with resource and/or
provider classes: SEVERE: Missing dependency for method public
javax.ws.rs.core.Response
com.ean.gds.proxy.ams.application.resource.gui.IataMappingGuiResource.getMappings(java.util.List)
at parameter at index 0 WARN [2016-11-07 14:16:13,070] /: unavailable
My question is: do I have any option other than writing a custom deserializer for it ?
Note: If I grab the request with #Context, I can see that the decodedQueryParams are a MultivaluedMap, which maps String keys like "columns[0][data]" to Lists of String values, which always have a single element, that is the value.
Update:
After some digging, I found the following JAX-RS specification (section 3.2) which explains why my approach isn't valid to begin with:
The following types are supported:
Primitive Types
Types that have a constructor that accepts a single String argument.
Types that have a static method named valueOf with a single String argument.
List, Set, or SortedSet where T satisfies 2 or 3 above.
Source: Handling Multiple Query Parameters in Jersey
So I've tried using just a List instead. This doesn't crash the app at startup, but when the request comes in, it deserializes into an empty list. So the question remains as to what approach is correct.
In fact, you're using such a very different structure from all the common ones we have mapped for Rest Web Services consummation. Also, because of this structural compliance problem, trying to use JSON to marshal/unmarshal the values won't suit, once we haven't object-based parameters being transported.
But, we have a couple of options to "work this situation around". Let's see:
Going with the #QueryParam strategy is not possible because of two main reasons:
As you noticed, there are some limitations on its use regarding Collections other than Lists, Sets, etc;
This annotation maps one (or a list) of param(s) by its(their) name(s), so you need every single parameter (separated by &) to have the same name. It's easier when we think about a form that submits (via GET) a list of checkboxes values: once they all have the same name property, they'll be sent in "name=value1&name=value2" format.
So, in order to get this requirement, you'd have to make something like:
#GET
public Response getMappings(#QueryParam("columns") List<String> columns) {
return Response.status(200).entity(columns).build();
}
// URL to be called (with same param names):
// /mappings?columns=columns[1][name]=0&columns=columns[0][searchable]=false
// Result: [columns[1][name]=0, columns[0][searchable]=false]
You can also try creating a Custom Java Type for Param Annotations, like you see here. That would avoid encoding problems, but in my tests it didn't work for the brackets issue. :(
You can use regex along with #Path annotation defining what is going to be accepted by a String parameter. Unfortunately, your URL would be composed by unvalid characteres (like the brackets []), which means your server is going to return a 500 error.
One alternative for this is if you "replace" this chars for valid ones (like underscore character, e.g.):
/mappings/columns_1_=0&columns_1__name_=
This way, the solution can be applied with no worries:
#GET
#Path("/{columns: .*}")
public Response getMappings(#PathParam("columns") String columns) {
return Response.status(200).entity(columns).build();
}
// Result: columns_1_=0&columns_1__name_=
A much better way to do this is through UriInfo object, as you may have tried. This is simpler because there's no need to change the URL and params. The object has a getQueryParameters() that returns a Map with the param values:
#GET
public Response getMappings(#Context UriInfo uriInfo) {
MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
// In case you want to get the whole generated string
String query = uriInfo.getRequestUri().getQuery();
String output = "QueryParams: " + queryParams
+ "<br> Keys: " + queryParams.keySet()
+ "<br> Values: " + queryParams.values()
+ "<br> Query: " + query;
return Response.status(200).entity(output).build();
}
// URL: /mappings?columns[1][name]=0&columns[0][searchable]=false
/* Result:
* QueryParams: {columns[0][searchable]=[false], columns[1][name]=[0]}
* Keys: [columns[0][searchable], columns[1][name]]
* Values: [[false], [0]]
* Query: columns[1][name]=0&columns[0][searchable]=false
*/
However, you must be aware that if you follow this approach (using a Map) you can't have duplicated keys, once the structure doesn't support it. That's why I include the getQuery() option where you get the whole string.
A last possibility is creating a InjectableProvider, but I can't see many diffs to the getQuery() strategy (since you can split it and create your own map of values).

Rest Non CRUD search

I've a problem with a GET operation in a REST WS. We have a Front-end panel with several filters for searching customers. The panel contains these filters:
Customer ID (Customer property)
Customer Name (Customer property)
Account number (Account property)
License plate (Vehicle property)
...
In the domain model we have 3 entities:
Customer
Account (A customer could have 1 or more accounts)
Vehicle (An account could have 1 or more vehicles)
How can I implement REST GET operation for this seach?
GET ..../customers/?name={name}&accountNum={accountNumber}&licensePlate={licensePlate} ?????
I think it is wrong because accountNumber and licensePlate don´t belong to customer resource. I don´t need these properties in the result expected.
I think about create new resource like customerFilter but It is no sense if I have to return a customer resource.
Any idea?
Thank you!
It will not pretend to be a specific answer for your question. But I think it will clarify something related to GET method.
According with URI Specification - RFC 3986 and Http Specification - RFC 7230, there are 3 kinds of ways to send data from client to server: via query, via path or via message-body.
When you are using GET method, it is not recommended to use message-body, because GET method can be cached for improving performance stuffs and these caches could ignore the message-body or reject the request:
A payload within a GET request message has no defined semantics;
sending a payload body on a GET request might cause some existing
implementations to reject the request.
So, you can choose now query or path. Both are in URL in this format:
http://example.com/{path1}/{path2}?query1=value1&query2=value2
What are the differences between these? according with RFC 3986 - Path and RFC 3986 - Query:
The path component contains data, usually organized in hierarchical
form, that, along with data in the non-hierarchical query component
(Section 3.4), serves to identify a resource within the scope of the
URI's scheme and naming authority (if any).
The query component contains non-hierarchical data that, along with
data in the path component (Section 3.3), serves to identify a
resource within the scope of the URI's scheme and naming authority
(if any).
As conclusion, you can design whatever you want. You can use for example:
GET .../customers?name={name}&accountNum={accountNumber}&licensePlate={licensePlate}
GET .../customers/{customerId}/
GET .../customers?customerId=12345
I don't see any issue with the url you have but your concern is also valid.
There is something that we need to take into consideration in this scenario. With this search what you expect to get as the response of the API call?
If you want response should contain information about customer only, when the search condition gets satisfied then /customer is right.
If you want some generic response consisting of Customer, Account and Vehicle info, you can have some generic terminology instead of customer in the url.
I hope this will help you.
Thanks :)

When to go for #RequestParam and #PathVariable

Just curious to know in which scenario we should go for #RequestParam and #PathVariable. I know that:
#RequestParam takes parameter value whereas #PathVariable takes placeholder value
#RequestParam can be optional (required=false) while making request whereas #PathVariable value has to be provided.
When we want to use #RequestParam we have to know the property syntax but for #PathVariable not required
Is there any other reason to go for specific one?
Use #PathVariable if you want to adhere to 'statefull' URLs.
For Example:-
/customer/:id Customer view/edit page
/customer/ Customer Add page
/customer/list List Customer Page
/customer/:cid/order All order of a Customer
/customer/:cid/order/:oid Specific order of a partucular Customer.
Wisely using Path Variable will result in URL that gives you hint/clue about what the resulting view/page means.
This also lets you support refresh,back & forward operation with no
extra effort.
#RequestParams can be used to exatract data which is not passed as path params. Your MVC handler can have combination of two as required.
org.springframework.web.bind.annotation.RequestParam is used to bind Query String.
org.springframework.web.bind.annotation.PathVariable is used to bind URL path.
org.springframework.web.bind.annotation.RequestBody is used to bind HTTP Body.
org.springframework.http.RequestEntity will give you some added flexibility in defining arbitrary HTTP Entity headers along with HTTP Body.
Best Practice:
If you want to identify a resource, you should use Path Variable.
But if you want to sort or filter items, then you should use query parameter.
Example:
/users # Fetch a list of users
/users?occupation=programmer # Fetch a list of user with filter programmer
/users/123 # Fetch a user who has id 123
you can get side effects. You don’t have to define other URL and other query parameter to achieve basic CRUD functions.You change HTTP method depends on what you want to do.
/users [GET] # Fetch a list of users
/users [POST] # Create new user
/users/123 [PUT] # Update user
/users/123 [DELETE] # remove user
Putting optional parameters in the Template URL will end up getting really messy, So I would recommend to put optional parameter in Query String.

Categories

Resources