How to set a multiple parameters using String's Controller and RestTemplate? - java

Hi I need to modify an existent Rest interface. The old Rest interface only take a phone number then look for a record. The phone number used to be unique with the table. Now is the combination of the phone and a number (batchid). So i need to modify the service impl and the client that calls it. Here is the old controller:
#RequestMapping(value="/{phone}", method = RequestMethod.GET)
#ResponseBody
public BatchDetail findByPhone(#PathVariable String phone) {
return batchDetailService.findByPhone(phone);
}
and here is the how the old client is accesing it:
private static final String URL_GET_BATCHDETAIL_PHONE = "http://localhost:8080/Web2Ivr/restful/batchdetail/{phone}";
batchdetail = restTemplate.getForObject(URL_GET_BATCHDETAIL_PHONE, BatchDetail.class, phone);
batchdetail.setStatus("OK");
restTemplate.put(URL_TO_UPDATE_BATCHDETAIL, batchdetail, batchdetail.getId())
;
So my question will be how to modify the controller and the restemplate's client call to support two variables, phone and the number(batchid) something like:
http://localhost:8080/Web2Ivr/restful/batchdetail/{batchid}/{phone}

#RequestMapping(value="/{batchid}/{phone}", method = RequestMethod.GET)
#ResponseBody
public BatchDetail findByPhone(#PathVariable String phone, #PathVariable String batchid) {
return batchDetailService.findByPhone(phone);
}

Related

Spring How can I take only one of two params given?

I want to create a Spring controller where I retrieve 4 params: Id, Name, Description and Qty but I need only take Id or Name, if user send both is wrong, How Can I do this?
Im trying followgin:
#GetMapping(value="/purchase")
public model purchase(#Valid #PathVariable String Id,
#Valid #PathVariable String Name){
...
}
You can ensure in code, that only one parameter (id or name, but not both) is sent to your method. Instead of path variables you should use query parameters in your GET method. So a solution could look like this:
#GetMapping("/purchase")
public ResponseEntity<Object> getItemByName(#RequestParam(required = false) String name,
#RequestParam(required = false) String id) {
if (!StringUtils.isEmpty(name) && !StringUtils.isEmpty(id))
throw new IllegalArgumentException("Only on parameter allowed.");
...
Then you can test you method with calls like these:
http://localhost:8080/purchase?name=ball&id=123
http://localhost:8080/purchase?id=123
In my German blog I wrote an article about how to create RestController in Spring and how you can handle parameters, this might give you more background information:
https://agile-coding.blogspot.com/2020/10/rest-json-apis-in-java-leicht-gemacht.html

Correct way to put parameters in a function

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);
`

How to return a "lighter" version of the data from a Controller?

I'm using Spring 4.3, and I have a REST Controller that returns a User object to the UI (javascript).
The problem is that I get a User object from the Database (say with Hibernate) that contains a password. I don't want to expose the password by actually returning it. Instead, I want the controller method to put NULL in it before returning it (I could use Optional or other solutions to avoid nulls, but I'm keeping it simple in this question).
public class User {
private String username;
private String password;
//setters and getters
}
#Controller
public class MainController {
#RequestMapping(value = "/user/getOne", method = RequestMethod.GET)
public User getOneUser() {
User user = //getUser
//something to nullify the password?
return user;
}
This question concerns a User and a password for clarity, but I'm looking for a wide solution that would take care of all my data models and the values I don't want them to include in some returns.
Solutions I don't like :)
Disliked solution #1: Remove the password in a private method or a utility class' method or an Adapter class
I don't like this because it makes the code very long. Most controller methods will need their own adaptation of the data.
I prefer something more clean and short.
Disliked solution #2: Use #JsonIgnore annotation
I don't want to bind my data models with Jackson package.
Disliked solution #3: Use a smaller data model class, and blind-copy everything that the smaller can contain
This solution refers to a code such as this:
public class ReturnUser {
private String username;
}
#Controller
public class MainController {
#RequestMapping(value = "/user/getOne", method = RequestMethod.GET)
public User getOneUser() {
User user = //getUser
ReturnUser smaller = copyWhatsInCommon(user, User.class, ReturnUser.class); //sees that there's only username common to both, so copies only it
return smaller;
This also increases the quantity of code, so I don't like it.
Any ideas?
Option 1:
You can add a transformation layer between your controller and the facade (or the service which populates the entity from the database). The transformation layer classes can convert the entities into value objects. The VOs will only contain the minimal information that your view needs. If there are more entities than 1 that you need to transform into value objects, you can also use reflections to read the properties (from a config file or something) that need to be read from the entities and copied to the VOs. However, this is not quite different from the solution 3 in your question that you don't like much. While it serves from performance and security perspective, it does add additional code in form of a transformation layer.
Option 2: An alternate and straightforward option I can propose is read the required attributes from 'User' class and populate them as model attributes.
#RequestMapping(value = "/user/getOne", method = RequestMethod.GET)
public User getOneUser(ModelMap modelMap) {
User user = //getUser
modelMap.addAttribute("userName", user.getName());
modelMap.addAttribute("userEmail", user.getEmail());
...
...
}
}
From experience:
1.) You should not return your business objects from the View layer ie Controller. You see this in many tutorials, but this is poor design.
2.) You should create a response object. This response object will only contain the fields you want to return to the user.
3.) You should instantiate the fields for UserResponse in the constructor with the user object.
Using since you are creating a resposne object, you using the #JsonIgnore annotation doesn't make sense.
While this may be more code, it is a better design with a clear separation of responsibility. The controller only needs to worry about the view object and the business layer never needs to know anything about the view.
Ex
public class UserResponse {
private String firstName;
private String lastName;
public UserResponse(User user){
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
}
...
//The getters
}
In the controller:
return new UserResponse(user);
Why do you want absolutely to return the User as it is represented in your entity?
The service and the controller layers should even not get a User object that contains a password field. So your 1 and 3 solution should be avoided.
In your case returning a view of the User class seems the most relevant way to achieve your need. Just use a DTO
Either you could return the User DTO from a service layer that accesses to the Data Access layer.
Or if you don't have a service layer, you could provide a method in the data access layer that returns a User DTO without the password field.
I am going to offer one more solution. Just for coverage. This is very ugly and not recommended. You can create an object mapper and filter the object:
static ObjectMapper mapper = new ObjectMapper();
public static String filterOutAllExcept(Object obj, String filterName, String... properties) throws YpException {
mapper.registerModule(new Hibernate4Module());
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept(properties);
FilterProvider filterProvider = new SimpleFilterProvider().addFilter(filterName, filter).setFailOnUnknownId(false);
String strValue;
try {
strValue = mapper.writer(filterProvider).writeValueAsString(obj);
} catch (JsonProcessingException e) {
// handle exception
}
return strValue;
}
Then you can call it like:
String filterApplied = ObjectMapperHelper.filterOutAllExcept(user, JsonDTOFilter.SOMEFILTER, "firstName", "lastName");
This will give you a json string with the fields firstName and lastName

Hide ID on POST in RequestBody, but return ID on created

For example I have a bean
public class Order
{
int orderID;
String name;
}
And I have a POST operation
#ApiOperation(value = "Insert a new order", response = Order.class)
#RequestMapping(value = "/addOrder", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public Order addOrder(#Valid #RequestBody Order order)
{
//Set random id here
order.id = 'xxxxx';
Order o = orderService.insertOrder(order);
return o;
}
And in Swagger I have the following:
So my question is, how do I hide id on POST but show ID on GET?
Or should I add a description saying that even if you choose to add an ID it wont do anything and just return my random id? Just like in Kubernetes (uid)
And properties like read-only in #ApiModelProperty will solve anything?
A simple approach is to split your bean in two - one for creating a new object, and another one which extends that for data about an existing object.
e.g.
public class IncompleteOrder {
String name;
}
public class ExistingOrder extends IncompleteOrder {
int id;
}
Then have your POST method take an object of IncompleteOrder and return one of ExistingOrder. I'd also delegrate responsibility for assigning a random order id to the underlying service...
public ExistingOrder addOrder(#Valid #RequestBody IncompleteOrder order) {
ExistingOrder o = orderService.insertOrder(order);
return o;
}
The same thing could be achieved by having two completely separate classes with no inheritance relationship, which would probably be appropriate if there was a significant divergence between the information needed to create a new order from the information which is on an existing order.
An alternative is to ask what the id is actually for - why are your clients getting integer id's for anything? Ideally, if they want any information about the order they should be querying the API for the resource, and to do that they need the URI of the order rather than the integer id. So external services communicating about an order should be passing the URIs back and forth rather than ids. Perhaps you could encourage your clients to communicate with each via the URI you return in the Location header from your POST request? Then you could do away with exposing the id on your response and have a purely symmetric request / response body.

Spring: #RequestMapping with multiple values

I don't know this is the right approach but the easiest solution I can come up with in the moment.
I want to use multiple values in #RequestMapping and do the business logic in the method according which value is called the method. Example:
#RequestMapping(value = {"/delete", "/save"}, method = RequestMethod.POST)
public String crudOps(#ModelAttribute ("userForm") User user) {
// find user in repository....
if(value is delete) // don't know how to make this check
delete(user);
else
save(user);
}
How can I make that if statement work?
fter adding a comment above, I thought of a different solution by accessing the HttpServletRequest getServletPath method,
#RequestMapping(value = {"/delete", "/save"}, method = RequestMethod.POST)
public String crudOps(#ModelAttribute ("userForm") User user, HttpServletRequest request) {
// find user in repository....
if(request.getServletPath().equals("/delete"))
delete(user);
else
save(user);
}
You can use #PathVariale to grab part of the URL as follows
#RequestMapping(value = /{action}, method = RequestMethod.POST)
public String crudOps(#PathVariable String action, #ModelAttribute ("userForm") User user) {
// find user in repository....
if(action.equals("delete"))
delete(user);
else
save(user);}
I would personally go ahead with something like below,
#RequestMapping(value = "user/{userid}", method = RequestMethod.DELETE)
And use
#RequestMapping(value = "/save", method = RequestMethod.POST)
for creating an user. This helps in Single Responsibility Principle and more modular code. Let your method do one thing, and do it right!
Would recommend this link for using which HttpMethod for which purpose.

Categories