I tried binding N numbers of parameters in a URL like this
/web/appl/<applname>/rule/<rulename>/<attrname1>/<attrval1>/<attrname2>/<attrval2>/.../<attrnameN>/<attrvalN>
with
#RequestMapping(value = "/web/appl/{applname}/rule/{rulename}/{attributes}", method = RequestMethod.GET)
public Object GetService(HttpServletRequest request, #PathVariable("attributes") Map<String, Object> map,
#PathVariable("applname") String applname, #PathVariable("rulename") String rulename)
throws Exception {
...
}
but could not get values of <attrval1>/<attrname2>/<attrval2>/.../<attrnameN>/<attrvalN>
Unfortunately Spring MVC does not provide such a solution. You can consider the Matrix Variables as an alternative.
If you prefer sticking to your current URI scheme then you have to implement a solution yourself. One approach is to use a path pattern. Example:
#RequestMapping(value = "/web/appl/{applname}/rule/{rule name}/**")
public Object getService(HttpServletRequest request,
#PathVariable("applname") String applname ...) {
String attributesPart = new AntPathMatcher()
.extractPathWithinPattern("/web/appl/{applname}/rule/{rule name}/**",
request.getServletPath());
...
You could implement your argument resolver that does that. Something like
#RequestMapping(value = "/web/appl/{applname}/rule/{rule name}/**")
public Object getService(#MyAttributes Map<String, String> attributes,
#PathVariable("applname") String applname ...) {
Use
String urlAttributes = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
and get whole URL. After that parse this URL according to your needs like if you did not get
<attrval1>/<attrname2>/<attrval2>/.../<attrnameN>/<attrvalN>
in proper sequence then you can throw exception.
Related
I have a huge form with around 30 parameters and I don't think it's a good idea to do what I usually do.
The form will be serialized and pass all the parameters via ajax post to spring controller.
I usually do like this:
#RequestMapping(value = "/save-state", method = RequestMethod.POST)
public #ResponseBody
void deleteEnvironment(#RequestParam("environmentName") String environmentName, #RequestParam("imageTag") String imageTag) {
//code
}
but if I have 30 parameters I will have a huge parameter list in the function.
What is the usual and correct way to avoid this?
EDIT: What if I pass the HttpServlet request only?? The request will have all the parameters and I can simple call request.getParameters("").
There are two options I would suggest:
Take an HttpServletRequest object and fetch needed properties separately.
The problem is Spring's controllers are designed to eliminate such low-level API (Servlets API) calls. It's could be the right fit if a controller was too abstract (operates on abstract datasets), which means you wouldn't be able to define a DTO with a fixed-length number of parameters.
Construct a DTO class with the properties needed and take it as a parameter.
The advantage is you completely delegate low-level work to Spring and care only about your application logic.
You can do something like this:
#RequestMapping(value = "/save-state", method = RequestMethod.POST)
public void deleteEnvironment(#RequestBody MyData data) {
//code
}
Create a class containing all your form parameters and receive that on your method.
but if I have 30 parameters I will have a huge parameter list in the
function.
In your request, pass a JSON object that contains these information and create its counterpart in Java.
RequestMethod.POST is not design to perform deletion.
Usr rather RequestMethod.DELETE.
#RequestMapping(value = "/save-state", method = RequestMethod.DELETE)
public #ResponseBody
void deleteEnvironment(MyObject myObject) {
//code
}
Correct way is to serialize all parameters as Json and call back end api with one parameter.
On back-end side get that json and parse as objects.
Example:
` #RequestMapping(method = POST, path = "/task")
public Task postTasks(#RequestBody String json,
#RequestParam(value = "sessionId", defaultValue = "-1") Long sessionId)
throws IOException, AuthorizationException {
Task task = objectMapper.readValue(json, Task.class);
`
in order to write a clean and smart code, I'm wondering what can I do to improve my actual piece of code:
public JSONObject getCustomer(final String customerId) {
if (customerId == null || customerId.equals("")) {
return null;
} else {
final RestTemplate restTemplate = new RestTemplate();
final String result = restTemplate.getForObject("http://localhost:6061/customers/" + customerId,
String.class);
return new JSONObject(result);
}
}
Especially, I didn't like the way I composed the url, neither the check on customerId's value.
I'd like to have something like JPA, where I ask some information passing a parameter, just to be clear (in pseudocode):
public JSONObject getCustomer(final String customerId) {
final RestTemplate restTemplate = new RestTemplate();
final Query query = restTemplate.query("http://localhost:6061/customers/:customerId");
query.addParameter("customerId", customerId);
JSONObject result = query.getForObject();
return result;
}
Then, if customerId would be null or some white spaces or not existing, I'd like that result would be null.
Is there a way to do this with a standard library?
Thanks
First off, I would remove the else branch and refactor the condition to:
public JSONObject getCustomer(final String customerId) {
if (isNull(customerId) || customerId.trim().isEmpty()) {
return null;
}
...
}
Second, if you have a bunch of URI variables, Spring guys recommend using a Map<String, String>:
final String templateURL = "http://localhost:6061/customers/{customerId}";
final Map<String, String> variables = new HashMap<>();
variables.put("customerId", customerId);
...
template.getForObject(templateURL, String.class, variables);
Third, the method shouldn't create a RestTemplate instance on its own. I would prefer injecting the already-tuned object into an instance field:
getTemplate().getForObject(templateURL, String.class, variables);
Finally, I would name the result more meaningful:
final String customerRepresentation = ...;
Some notes:
getCustomer actually returns a JSONObject, not a Customer.
templateURL hardcoded the base URL as well as the URL to customers.
The method does a lot of work (takes too much responsibility) - argument validation, URL construction, making a request. Try to split these responsibilities between corresponding methods.
Firstly I would rather use DTO objects to hold the response data and manipulate them rather than using a String representation of the payload. So you may change it like this. Here Jackson takes care of all the serialization and deserialization of your data.
CustomerDTO customerDTO = restTemplate
.getForEntity("http://localhost:6061/customers/{customerId}", CustomerDTO.class, customerId).getBody();
You can use javax.validators such as #Min, #NotEmpty etc at your controller to check for the empty values. A sample is given below.
#RequestMapping(value = someURL, params = {"id"})
public SomeResponse doSomething(#PathVariable(value = "id") #Size(min=1) String id)
This throws a ValidationException with a relevant error message which can be customized by you. You then need to have an error handling aspect that sets the error message in ErrorDTO object and set the status code appropriately.
That defines multi route method in controller in Spring MVC
#RequestMapping(value={"/path", "/path2"}, method = RequestMethod.GET)
public String MyMethod () {
// Determine which route invoked the method
return null;
}
Is there a way to determine which route invoked the method?
Appreciate your kind help.
You could use HttpServletRequest which has a method called getRequestURL() to retrieve the actual URL, allowing you to parse which path was used.
However, another possibility is using path variables instead:
#RequestMapping(value = "/{path}", method = RequestMethod.GET)
public String myMethod(#PathVariable String path) {
// Do stuff with "path"
return null;
}
In this case, the path variable will contain whatever you enter matching the path given in your #RequestMapping, in your case it would be "path" or "path2". However, this will also allow other path variables as well ("path3" for example, ...), so you might want to validate it first before using.
I believe you can use HttpServletRequest:
#RequestMapping(value={"/path.html", "/path2.html"}, method = RequestMethod.GET)
public String MyMethod (HttpServletRequest request) {
// Determine which route invoked the method
String url = new String(request.getRequestURL());
log.debug("URL: " + url); //use whatever you use to log
return null;
}
I'm using Spring 4 right now and I have a controller defined a such:
#RequestMapping(value = "/{id}/definitions", method = RequestMethod.PUT)
#ResponseBody
public List<ResponseDTO> update(#PathVariable(value = "id") String id,
HttpServletRequest iRequest) throws Exception
{ ... }
I am expecting to receive an encrypted 'id' String coming as part of a path variable in the request itself. But what I need to do is re-write this request URI and decrypt it to another value (an integer, for example) and form another HTTP request with the original URI only with the transformed/decrypted value.
How can I get a hold of the entire URI and substitute the {id} with an integer?
For example, if the original request coming in looks like:
http://mycompany.com/my-service/kjAISOhalkjZjakmbbb/definitions
I want to transform everything after the context path:
/kjAISOhalkjZjakmbbb/definitions
to:
/123456/definitions
So finally, I can form a request to another service that might look like this:
http://mycompany.com/my-service-2/123456/definitions
Thank you!
Decrypt the value (however you do that).
Then just use a redirect or forward (probably a forward in your case):
#RequestMapping(value = "/{id}/definitions", method = RequestMethod.PUT)
public void update(#PathVariable(value = "id") String id,
HttpServletRequest iRequest) throws Exception {
String decryptedId = //decrypt here
//Do whatever else
//either forward: or redirect:
return "forward:my-service-2/" + decryptedId + "/definitions";
}
Why is there this limitation in Google Cloud Endpoints:
Arrays or collections of entity types are not allowed.
For an API with method:
#ApiMethod(name = "getCollection", path = "getCollection", httpMethod = HttpMethod.POST)
public ArrayList<MyObject> getCollection(List<MyObject> pMyObjects) {
And what's the best way to get around this? Thanks!
I think the reason it's not supported is because the named parameters in the method signature end up being URL query parameters, and they don't want to pollute that with long lists of items. Furthermore, they only support a single object of an Entity type in the signature, because this automatically becomes the "request body". You can read about it here in the docs.
As for working around it, you create a container entity object for the "request body". The nice side effect of this is that the APIs Explorer will expand the pieces of your entity object out in the GUI and help you do the JSON correctly.
Here's an example that adds a Map named "patchFieldOps" for implementing partial update. You can put as many fields into your Entity object as you like. I think if you embed more user-defined types they will also need to have the #Entity annotation.
#Entity
public class EndpointUpdateRequestBody {
// Since Google Cloud Endpoints doesn't support HTTP PATCH, we are overloading
// HTTP PUT to do something similar.
private Map<String, String> patchFieldsOps;
public EndpointUpdateRequestBody() {
patchFieldsOps = new HashMap<String, String>();
}
public EndpointUpdateRequestBody(Map<String, String> patchFieldsOps) {
this.patchFieldsOps = patchFieldsOps;
}
public Map<String, String> getPatchFieldsOps() {
return patchFieldsOps;
}
public void setPatchFieldsOps(Map<String, String> patchFieldsOps) {
this.patchFieldsOps = patchFieldsOps;
}
}
...
#ApiMethod(
name = "stuff.update",
path = "stuff/{id}",
httpMethod = ApiMethod.HttpMethod.PUT
)
public Group update(
User apiUser,
#Named("id") String id,
#Nullable #Named("name") String name,
#Nullable #Named("description") String description,
EndpointUpdateRequestBody requestBody)
throws OAuthRequestException, InternalServerErrorException, NotFoundException,
BadRequestException, UnauthorizedException, ConflictException {