Convert Spring MVC to Spring fullREST JSON application - java

I make application in Java Spring similar to:
Java Blog Aggregator
Author of this app use Spring MVC and common http request. Is it fast way to remodel of controllers to full rest application which use AJAX?
I do not know where to start.
I would like to send JSON. I don't have my own code now, cause I just getting started.
Example of controller:
package cz.jiripinkas.jba.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import cz.jiripinkas.jba.service.ItemService;
#Controller
public class IndexController {
#Autowired
private ItemService itemService;
#RequestMapping("/index")
public String index(Model model) {
model.addAttribute("items", itemService.getItems());
return "index";
}
}
Thanks for any help.

Yes, you can do that, follow these steps:-
Download a JSON API jar and include that library in your application. You can download one HERE
Convert your public String index(Model model) method to public void index(Model model, HttpServletResponse response).
Remove the line return "index"; as now your method is of void type.
Contruct a JSON with the library you just added. You will finally have an object of type JSONObject
Once you have this object, convert and store into a String. For example, if you have a variable named returnJSONObj of type JSONObject, you can write convert it into a String as String returnJSONStr = returnJSONObj.toString()
Finally, you have write the output at a JSON String:-
response.setContentType("application/json");
response.getWriter.println(returnJSONStr);
This will make your method return a JSON String, which you can use in your REST API.
EDIT
Added sample code for reference
package cz.jiripinkas.jba.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import cz.jiripinkas.jba.service.ItemService;
#Controller
public class IndexController {
#Autowired
private ItemService itemService;
#RequestMapping("/index")
public void index(Model model, HttpServletResponse response) {
List items = itemService.getItems();
String returnJSONStr = createJSONStr(items);
response.setContentType("application/json");
response.getWriter().println(returnJSONStr); //this method throws IOException
}
private String createJSONStr(List items) {
JSONObject json = new JSONObject();
for(Item item: items) { //For each item
json.put("property1", item.getProperty1()); //replace by your own properties
json.put("property2", item.getProperty2());
}
return json.toString();
}
}

You can make this a REST Webservice by :
1) Replacing #Controller with #RestController.
2) Changing #RequestMapping("/index") to :
#RequestMapping(value="/index",
method=RequestMethod.GET,
produces=MediaType.APPLICATION_JSON_VALUE)
3) And if you intent to on receiving some payload in this index web service then replace RequestMethod.GET with RequestMethod.POST and change parameters of index function to some DTO object which will map with json you want to receive and annotate it with #RequestBody(This is optional in case you are using #RestController).
EDIT :
4) If you want to return a JSON object , you just need to create a bean with attribute names same as your keys :
class User {
private String id;
private String name;
public void setName(String name){
this.name=name;
}
public void getName(){
return this.name;
}
public void getId(){
return this.id;
}
public void setId(String id){
this.id=id;
}
}
and return this object from the method as follows :
#RequestMapping(value="/index",
method=RequestMethod.GET,
produces=MediaType.APPLICATION_JSON_VALUE)
public User index() {
User user = new User();
user.setId("1");
user.setName("Test");
return user;
}

Related

How to convert spring-boot request parameter

Currently a request method receives a encoded token as parameter (this token is a String but not JSON or anything like that).
This token then gets decoded to a data class.
#GetMapping(value = "/api/xyz")
public ResponseEntity<XYZ> xyz(#NotBlank String token) {
Data data = Token.parse(token);
...
}
Is it possible to write a annotation similar to how #RequestBody works that converts a parameter into something else?
The request should just look like that instead:
#GetMapping(value = "/api/xyz")
public ResponseEntity<XYZ> xyz(#TokenParser Data data) {
...
}
This class / annotation should contain the code necessary to convert the token (String) to my data class.
The request method should just have the converted data class as parameter.
I solved it with the spring-boot Converter.
import javax.inject.Inject;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
#Component
public class TokenConverter implements Converter<String, Data> {
private final TokenParser tokenParser;
#Inject
public TokenConverter(TokenParser TokenParser) {
this.tokenParser = tokenParser;
}
#Override
public Data convert(String token) {
return tokenParser.parse(token);
}
}
Just add such a converter anywhere in your project.
The mapping is managed by spring-boot.
New request:
#GetMapping(value = "/api/method")
public ResponseEntity<Data> method(#RequestParam("token") Data data) {
...
}
For more information: https://www.baeldung.com/spring-mvc-custom-data-binder
I hope it helps someone else.

How to have to different GET methods in Controller Java Spring

I'm trying to retrieve data from a database to an mobile app through a REST web service. I've managed to make some basic functions but when I try to add functions I run into problems. For example I want to be able to find "Customers" by their Id and their name. When I have two Get methods, one with "/{id}" and one with "/{name}" the app does not know what to use. What can I do to search by name?
This is the controller from the web service.
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#RestController
#RequestMapping("/customers")
public class CustomerController {
private CustomerRepository repository;
#Autowired
public CustomerController(CustomerRepository repository) {
this.repository = repository;
}
#RequestMapping(value = "/{name}", method = RequestMethod.GET)
public ResponseEntity<Customer> get(#PathVariable("name") String name) {
Customer customer = repository.findByName(name);
if (null == customer) {
return new ResponseEntity<Customer>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<Customer>(customer, HttpStatus.OK);
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ResponseEntity<Customer> get(#PathVariable("id") Long id) {
Customer customer = repository.findOne(id);
if (null == customer) {
return new ResponseEntity<Customer>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<Customer>(customer, HttpStatus.OK);*
}
#RequestMapping(value = "/new", method = RequestMethod.POST)
public ResponseEntity<Customer> update(#RequestBody Customer customer) {
repository.save(customer);
return get(customer.getName());
}
#RequestMapping
public List<Customer> all() {
return repository.findAll();
}
}
This is the service from the android application
package com.ermehtar.poppins;
import java.util.List;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.PATCH;
import retrofit2.http.POST;
import retrofit2.http.Path;
public interface CustomerService {
#GET("customers")
Call<List<Customer>> all();
#GET("customers/{id}")
Call<Customer> getUser(#Path("id") Long id);
#GET("customers/{name}")
Call<Customer> getUser(#Path("name") String name);
#POST("customers/new")
Call<Customer> create(#Body Customer customer);
}
Then this is the function that I use to call the service by name. The response.body will be null when both /name and /id functions are in the web service controller but when one of them is commented out this works just fine.
findUsernameButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Call<Customer> createCall = service.getUser("John");
createCall.enqueue(new Callback<Customer>() {
#Override
public void onResponse(Call<Customer> _, Response<Customer> resp) {
findUsernameButton.setText(resp.body().name);
}
#Override
public void onFailure(Call<Customer> _, Throwable t) {
t.printStackTrace();
allCustomers.setText(t.getMessage());
}
});
}
});
Hope I've made myself understandable. Please ask if there is something unclear or you need more information.
Your restful design can improve. I suggest defining something like this:
New:
/customers/new
This is not correct, in restful a resource creation should be defined by method type. I suggest this:
/customers with POST method.
Search by ID:
/customers/{id}
This is correct, in restful a resource should be access by id using path variable.
Search by name:
/customers/{name}
This is not correct, here you are querying the customers resource, so, you should use query params, I suggest this:
/customers?name=<<name>>
If you have multiple query methods, you will get a conflict because you cannot have more than one GET method in a controller with the same path. So, you can modify #RequestMapping to explicit assert which query params are required like this:
#RequestMapping(value = "/", method = RequestMethod.GET, , params = "name")
public ResponseEntity<Customer> getByName(#RequestParam("name") String name) {
...
}
#RequestMapping(value = "/", method = RequestMethod.GET, , params = "lastname")
public ResponseEntity<Customer> getByLastname(#RequestParam("lastname") String lastname) {
...
}
Differentiate the URLs with a different path which will also make them more RESTful.
Search by name:
/customers/names/{name}
Search by ID:
/customers/ids/{id}
In the future you might want to add another search, perhaps by city:
/customers/cities/{city}
Your controller has ambiguous handler methods mapped so when calling the endpoint you will actually get an exception. Fix this by creating different mappings for get by id and get by name.
Resource is uniquely identified by its PATH (and not by it's params). So, there're few resources with the same PATH: "customers/"
You can create two different resources like:
#RequestMapping(value = "/id", method = RequestMethod.GET)
#RequestMapping(value = "/name", method = RequestMethod.GET)
Or you can make one resource with many request parameters:
#RequestMapping(value = "/get", method = RequestMethod.GET)
public ResponseEntity<Customer> get(#RequestParam ("id") Long id, #RequestParam ("name") String name)

In Spring-MVC, how do I dynamically set constructor arguments of a session attribute?

We are building a webapp that communicates with a remote API. I would like to design the client for this remote API like this:
def RemoteApi
constructor (username, password)
getCurrentUser() //implementation will use username and password
getEmployees() //implementation will use username and password
...
The point being, I want to pass in the credentials to this client during construction, and have all the other methods use these credentials. My second requirement is I want this RemoteApi instance to be in the session.
I have found out how to pass dynamic constructor arguments here.
I have found out how to create a session attribute here.
But I can't figure out a way to combine these two techniques. From what I gather, you have to instantiate a session attribute in its own getter-like method. This getter-like method won't have access to the form's fields so I won't be able to pass in credentials at this point.
Here's where I'm stuck:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import java.util.Map;
#Controller
#SessionAttributes("remoteClient")
public class LoginController {
#Value("${company.name}")
private String companyName;
#Autowired
private RemoteClient remoteClient;
#ModelAttribute("remoteClient")
public RemoteClient addRemoteClientToSession() {
return remoteClient; //how do initialize this with credentials in here?
}
#RequestMapping("/")
public String showLogin(Model model) {
model.addAttribute("credentials", new Credentials());
return "login";
}
#RequestMapping("/login")
public String login(#ModelAttribute("credentials") Credentials credentials, Map<String, Object> model) {
if (remoteClient.auth(companyName, credentials.getUsername(), credentials.getPassword())) {
model.put("fullname", remoteClient.findCurrentUser().getName());
return "projectView";
} else {
return "login";
}
}
}
Update: Maybe the solution has to do with this technique. I didn't know about ModelAndView before this.
The article I linked to in the question did have the necessary technique in it. What I would do is create an #Autowired RemoteApiFactory that has a newInstance(Credentials) method and pass that into addObject. The implementation of newInstance would probably end up using the new keyword. If anyone knows how to avoid this detail, I'd love to hear it.
Here's its example tweaked for my needs:
#Controller
#SessionAttributes("remoteApi")
public class SingleFieldController {
#Autowired
private RemoteApiFactory remoteApiFactory;
#RequestMapping(value="/single-field")
public ModelAndView singleFieldPage() {
return new ModelAndView("single-field-page");
}
#RequestMapping(value="/remember")
public ModelAndView rememberThought(#MethodAttribute("credentials") Credentials credentials) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("remoteApi", remoteApiFactory.newInstance(credentials));
modelAndView.setViewName("single-field-page");
return modelAndView;
}
}

How to check spring RestController for unknown query params?

I have a basic rest controller taking parameters.
How can I refuse the connection if the query string contains parameters that I did not define?
#RestController
#RequestMapping("/")
public class MyRest {
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
#ResponseBody
public String content(#PathVariable id, #RequestParam(value = "page", required = false) int page) {
return id;
}
}
localhost:8080/myapp/123?pagggge=1
Currently when calling this url, the method is executed with just the id, and the unknown paggge parameter is just neglected. Which is fine in general, but how can I validate them and in case return a HTTP status code?
You may get all parameters incoming and handle in the way you want.
Quoting spring documentation:
When an #RequestParam annotation is used on a Map<String, String> or MultiValueMap<String, String> argument, the map is populated with all request parameters.
In your controller method, you can include an argument of type #RequestParam Map<String, String> to get access to all query parameters passed in. A generic ArgsChecker service class can be used to check whether the user passed in an invalid argument. If so, you can throw an exception, which can be handled by an #ControllerAdvice class.
#RestController
#ExposesResourceFor(Widget.class)
#RequestMapping("/widgets")
public class WidgetController {
#Autowired
ArgsChecker<Widget> widgetArgsChecker;
#RequestMapping(value = "", method = RequestMethod.GET, produces = {"application/hal+json"})
public HttpEntity<PagedResources<WidgetResource>> findAll(#RequestParam #ApiIgnore Map<String, String> allRequestParams, Pageable pageable, PagedResourcesAssembler pageAssembler) {
Set<String> invalidArgs = widgetArgsChecker.getInvalidArgs(allRequestParams.keySet());
if (invalidArgs.size() > 0) {
throw new QueryParameterNotSupportedException("The user supplied query parameter(s) that are not supported: " + invalidArgs + " . See below for a list of query paramters that are supported by the widget endpoint.", invalidArgs, widgetArgsChecker.getValidArgs());
}
The ArgsChecker can be defined as follows:
import com.widgetstore.api.annotation.Queryable;
import lombok.Getter;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
public class ArgsChecker<T> {
#Getter
private Set<String> validArgs;
private ArgsChecker(){};
public ArgsChecker(Class<T> someEntityClass){
validArgs= FieldUtils.getFieldsListWithAnnotation(someEntityClass,Queryable.class)
.stream()
.map(Field::getName)
.collect(Collectors.toSet());
validArgs.add("page");
validArgs.add("size");
}
public Set<String> getInvalidArgs(final Set<String> args){
Set<String> invalidArgs=new HashSet<>(args);
invalidArgs.removeAll(validArgs);
return invalidArgs;
}
}
, which uses reflection to find fields which are annotated with the "#Queryable" annotation:
package com.widgetstore.api.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface Queryable {
}
Now mark the fields of your domain class which you want queryable with that
annotation:
#Getter
#Setter
public class Widget {
#Queryable
private String productGuid;
#Queryable
private String serialNumber;
private String manufacturer;
Now make sure the ArgsChecker bean is created at application startup:
#SpringBootApplication
public class StartWidgetApi{
public static void main(String[] args){
SpringApplication.run(StartWidgetApi.class);
}
#Bean(name="widgetArgsChecker")
public ArgsChecker<Widget> widgetArgsChecker(){
return new ArgsChecker<Widget>(Widget.class);
}
//Other ArgsCheckers of different types may be used by other controllers.
#Bean(name="fooArgsChecker")
public ArgsChecker<Foo> fooArgsChecker(){
return new ArgsChecker<Foo>(Foo.class);
}
}
Finally,
Define a #ControllerAdvice class which will listen for exceptions thrown by your application:
package com.widgetstore.api.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
#ControllerAdvice
#RequestMapping(produces = "application/json")
#ResponseBody
public class RestControllerAdvice {
#ExceptionHandler(QueryParameterNotSupportedException.class)
public ResponseEntity<Map<String,Object>> unrecogonizedParameter(final QueryParameterNotSupportedException e){
Map<String,Object> errorInfo = new LinkedHashMap<>();
errorInfo.put("timestamp",new Date());
errorInfo.put("errorMessage",e.getMessage());
errorInfo.put("allowableParameters",e.getValidArgs());
return new ResponseEntity<Map<String, Object>>(errorInfo,HttpStatus.BAD_REQUEST);
}
}
, and define the QueryParameterNotSupportedException:
import lombok.Getter;
import java.util.Set;
#Getter
public class QueryParameterNotSupportedException extends RuntimeException{
private Set<String> invalidArgs;
private Set<String> validArgs;
public QueryParameterNotSupportedException(String msg, Set<String> invalidArgs, Set<String> validArgs){
super(msg);
this.invalidArgs=invalidArgs;
this.validArgs=validArgs;
}
}
Now, when the user hits /widgets?someUnknownField=abc&someOtherField=xyz he will get a json response along the lines of
{"timestamp": 2017-01-10'T'blahblah, "errorMessage": "The user supplied query parameter(s) that are not supported: ["someUnknownField","someOtherField"]. See below for a list of allowed query parameters." ,
"allowableParameters": ["productGuid","serialNumber"]
}
Add HttpServletRequest request in method parameters, do
String query = request.getQueryString()
in the method body and validate that.
I wanted to share a different way since I found ametiste's answer too manual and mancini0's overly verbose.
Suppose, you use Spring framework validation API to bind your request parameters to an object ApiRequest.
#InitBinder
public void initBinder(WebDataBinder binder, WebRequest request) {
binder.setAllowedFields(ApiRequest.getAllowedRequestParameters());
}
#RequestMapping("/api")
public String content(#Valid ApiRequest request, BindingResult bindingResult) {
return request.getId();
}
With following ApiRequest definition.
public class ApiRequest {
private String id;
public static String[] getAllowedRequestParameters() {
return new String[]{
"id"
};
}
}
Then you can use the BindingResult to perform the check whether there were any unexpected request parameters, like so:
String[] suppressedFields = bindingResult.getSuppressedFields();
if (suppressedFields.length > 0) {
// your code here
}

ResponseBody in Spring MVC 2

I am currently working on implementing REST webservices into existing system. This system is using Spring in version 2 (particularly 2.5.6.SEC02). I can't upgrade it to version 3 as it could break existing system components. We didn't make the rest of this system, don't have source codes and don't want to lose warranty, so Spring version should basically stay as it is :)
The question is, how can I implement Rest WS with automatic DTO serialization from/to JSON? I have appropriate Jackson libraries on the classpath. Spring 2 doesn't seem to know about #RequestBody and #ResponseBody yet. Are there any other annotations that can be used, or some alternative way?
You may need to manually parse the JSON String and write it to the response for this to work for you. I suggest using the jackson2 API.
https://github.com/FasterXML/jackson
First, accept a json String as the argument from the request, and then manually parse the String to and from a POJO using the jackson ObjectMapper.
Here's the jQuery/JavaScript:
function incrementAge(){
var person = {name:"Hubert",age:32};
$.ajax({
dataType: "json",
url: "/myapp/MyAction",
type: "POST",
data: {
person: JSON.stringify(person)
}
})
.done(function (response, textStatus, jqXHR) {
alert(response.name);//Hubert
alert(response.age);//33
//Do stuff here
});
}
The person pojo:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
//Allows name or age to be null or empty, which I like to do to make things easier on the JavaScript side
#JsonIgnoreProperties(ignoreUnknown = true)
public class Person{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
And here's the the controller:
import com.fasterxml.jackson.databind.ObjectMapper;
/*snip*/
#Controller
public class MyController{
//I prefer to have a single instance of the mapper and then inject it using Spring autowiring
private ObjectMapper mapper;
#Autowired
public MyController(ObjectMapper objectMapper){
this.objectMapper = objectMapper;
}
#RequestMapping(value="/myapp/MyAction", method= {RequestMethod.POST})
public void myAction(#RequestParam(value = "person") String json,
HttpServletResponse response) throws IOException {
Person pojo = objectMapper.readValue(new StringReader(json), Person.class);
int age = pojo.getAge();
age++;
pojo.setAge(age);
objectMapper.writeValue(response.getOutputStream(),pojo);
}
}
You can try a DIY approach with Spring MVC controllers and JSONObject from json.org. Just use it to json-serialize your returned objects and flush it down the response, with the appropiate headers.
It has its pitfalls (I would recommend you use a simple wrapper class with a getter when you try to send a collection), but I have been happy with it for some years.

Categories