How to add logic to a GetMapping in Java REST API - java

I am trying to implement a very simple REST API with Spring Boot. In a GET Request I want to do a very basic transliteration. So the requestor will have to send an input string and transliteration string.
Those parameters should be passed to my method, that returns a transliteration. The Response should look like this:
"input": ...
"transliterationrule": ...
"transliteration": ...
To do so, I created a java spring boot project with the following classes:
Transliteration class
import com.ibm.icu.text.Transliterator;
public class Transliteration {
private final String input ;
private final String transliterationRule;
public Transliteration(String input, String transliterationRule) {
this.input = input;
this.transliterationRule = transliterationRule;
}
public String transliterateString(){
Transliterator transliterator = Transliterator.getInstance(this.transliterationRule);
return transliterator.transliterate(this.input);
}
public String getInput(){
return input;
}
public String getTransliterationRule(){
return transliterationRule;
}
}
And controller class:
#RestController
public class TransliterationController {
#GetMapping("/transliteration")
public Transliteration transliteration(#RequestParam(value="input", required=false, defaultValue="TestString") String input,
#RequestParam(value="rule", required=false, defaultValue="Any-Latin") String transliterationRule) {
return new Transliteration(input, transliterationRule);
}
}
Can somebody please explain me, how I can actually pass these parameters to my method transliterateString()? And how can I add the method result do the request?

Change your controller's method to return ResponseEntity and wrap a TransliterationResponse. TransliterationResponse is a dto holding
"input": ...
"transliterationrule": ...
"transliteration": ...
#GetMapping("/transliteration")
public ResponseEntity<TransliterationResponse> transliteration(#RequestParam(value="input", required=false, defaultValue="TestString") String input,
#RequestParam(value="rule", required=false, defaultValue="Any-Latin") String transliterationRule) {
// do your business logic
//build the response dto
TransliterationResponse dto = new TransliterationResponse(input, transliterationrule, transliteration);
ResponseEntity.status(HttpStatus.OK).body(dto);
}

Create a DTO class to contain all response values:
public class TransliterationDTO {
#JsonProperty("input")
private String input;
#JsonProperty("transliterationRule")
private String transliterationRule;
#JsonProperty("transliteration")
private String transliteration;
public TransliterationDTO() {
}
public TransliterationDTO(String input, String transliterationRule, String transliteration) {
this.input = input;
this.transliterationRule = transliterationRule;
this.transliteration = transliteration;
}
// Getters and Setters
}
Update your controller method to return a ResponseEntity :
#RestController
public class TransliterationController {
#GetMapping("/transliteration")
public ResponseEntity<TransliterationDTO> transliteration(#RequestParam(value="input", required=false, defaultValue="TestString") String input,
#RequestParam(value="rule", required=false, defaultValue="Any-Latin") String transliterationRule) {
Transliteration t = new Transliteration(input, transliterationRule);
return ResponseEntity.ok(new TransliterationDTO(t.getInput(), t.getTransliterationRule(), t.getTransliteration));
}
}

Related

Multiple Get Requests with Spring Boot, WebClient and Thymeleaf

I'm trying to make a RESTClient application for consuming the Starwars API.
I take the following URL: https://swapi.py4e.com/api/films/
The user logs in my website and can search for a film (introducing the id in a form).
Later, the website shows title, director, release date and a the list of characters.
For instance, if the user inserts 1 (https://swapi.py4e.com/api/films/1), he should get the following info on screen:
{
"title": "A New Hope",
"episode_id": 4,
"director": "George Lucas",
"characters": [
"https://swapi.py4e.com/api/people/1/",
"https://swapi.py4e.com/api/people/2/",
"https://swapi.py4e.com/api/people/3/",
"https://swapi.py4e.com/api/people/4/",
"https://swapi.py4e.com/api/people/5/",
"https://swapi.py4e.com/api/people/6/",
"https://swapi.py4e.com/api/people/7/",
"https://swapi.py4e.com/api/people/8/",
"https://swapi.py4e.com/api/people/9/",
"https://swapi.py4e.com/api/people/10/",
"https://swapi.py4e.com/api/people/12/",
"https://swapi.py4e.com/api/people/13/",
"https://swapi.py4e.com/api/people/14/",
"https://swapi.py4e.com/api/people/15/",
"https://swapi.py4e.com/api/people/16/",
"https://swapi.py4e.com/api/people/18/",
"https://swapi.py4e.com/api/people/19/",
"https://swapi.py4e.com/api/people/81/"
],
The urls from the characters should show the name of the characters.
This should make, I think, an in-between get request to (for example) the urls and stock them in a list, set or array.
For instance: https://swapi.py4e.com/api/people/1/, I only need the name.
{
"name": "Luke Skywalker",
}
For that, I have the following code snippets (I'm using Spring Boot, MVC and working with layers, as in an enterprise application):
DefaultSWAPIClient (with an interface SWAPIClient):
#Component
class DefaultSWAPIClient implements SWAPIClient {
private final WebClient client;
private final String filmURI;
DefaultSWAPIClient(
WebClient.Builder builder,
#Value("${swapi.films}") String filmURI) {
client = builder.build();
this.filmURI = filmURI;
}
#Override
public Optional<Film> findByEpisodeId(long id) {
try {
return Optional.of(
client.get()
.uri(filmURI, uriBuilder -> uriBuilder.build(id))
.retrieve().bodyToMono(Film.class).block());
} catch (WebClientResponseException.NotFound ex) {
return Optional.empty();
}
}
}
DTO (of DAO) film class:
public class Film {
#JsonProperty("title")
private String title;
#JsonProperty("episode_id")
private long episodeId;
#JsonProperty("director")
private String regisseur;
#JsonProperty("release_date")
private String releaseDate;
#JsonProperty("characters")
private LinkedHashSet<String> characters = new LinkedHashSet<String>();
public String getTitle() {
return title;
}
public long getEpisodeId() {
return episodeId;
}
public String getRegisseur() {
return regisseur;
}
public String getReleaseDate() {
return releaseDate;
}
public LinkedHashSet<String> getCharacters() {
return characters;
}
}
The search film Controller (FilmZoekenController):
#Controller
#RequestMapping("filmzoeken")
class FilmZoekenController {
private final SWAPIClient client;
FilmZoekenController(SWAPIClient client) {
this.client = client;
}
#GetMapping
public ModelAndView toonForm() {
return new ModelAndView("filmzoeken");
}
#GetMapping("/film/{id}")
public ModelAndView getData(#PathVariable long id) {
var modelAndView = new ModelAndView("film");
client.findByEpisodeId(id)
.ifPresent(film -> modelAndView.addObject(film));
return modelAndView;
}
}
Then I show all of it in a thymeleaf template.
I don't understand how to make another GET request to the api before it returns the data.
To sum up: I want to do a get request to films/{id} through a form, get some attributes (one being an array of urls, which need to returns the name value from each direction, so I assume another get request within) and show them to the user.
I hope that this should be enough info/code to help me out! Thanks in advance.

Convert List of Enums to List of String for Spring #RequestParam using feign client

I have an enum class as such:
ONE("1", "Description1"),
TWO("2", "Description2");
String value;
String description;
MyEnum(String value, String description) {
this.value = value;
this.description = description;
}
#Override
public String toString() {
return this.value;
}
#JsonValue
public String value() {
return this.value;
}
The API I am interacting with is expecting a param with type String and the values can be comma separated.
For example: api.com/test?param1=1,2
I configured a feign client with the url api.com/test
And then created a POJO like so
public class POJO {
private List<MyEnum> param1;
}
And in my feign client I have:
#RequestMapping(method = RequestMethod.GET)
MyResponse getResponse(#SpringQueryMap POJO request);
Is it possible to somehow turn the List of Enums to a List of String before the API call is made via some Spring approach?
As of right now, when I pass a List of Enums, it is only taking into account the last Enum within this list.
UPDATE: I annotated the property I want to convert to a list using #JsonSerialize(converter=abc.class). However #SpringQueryMap doesn't seem to honor that serialization..
Yes is possible, you need to create an interceptor and in that method do the mapping.
This topic may be for you.
Spring - Execute code before controller's method is invoked
So turns out #JsonSerialize was not working with #SpringQueryMap
So I did have to add an interceptor.
Like so:
public class MyInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate requestTemplate) {
if(requestTemplate.queries().containsKey("param1")) {
requestTemplate.query("param1", convert(requestTemplate.queries().get("param1")));
}
}
//convert list to a string
public String convert(Collection<String> values) {
final String s = String.join(",", values.stream().map(Object::toString).collect(Collectors.toList()));
return s;
}
}
And then in my Feign config class added this:
#Bean
public MyInterceptor myInterceptor() {
return new MyInterceptor();
}

How to automatic decode Dto from GET query parameters

I Have a GET request with some parameters which I handle as an object on the controller, consider it could be several parameters.
The problem is that the values for the properties on the dto are being filled using url encoding which I dont want because it messes up queries to a database later on, ie.: name gets populated with "some%20name" instead of "some name" as I would expect.
How can I avoid this encoding problem?
Bellow is a small scenario that represents my issue:
public class SomeDto {
private String name;
private String hex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHex() {
return hex;
}
public void setHex(String hex) {
this.hex = hex;
}
}
#RestController
#RequestMapping("example")
public class RestController {
#GetMapping
public void example(final SomeDto someDto) {
System.out.println(someDto.getName());
System.out.println(someDto.getHex());
}
}
public class ClientApi {
private RestTemplate restTemplate;
private String hostUri;
public ClientApi(RestTemplate restTemplate, String hostUri) {
this.restTemplate = restTemplate;
this.hostUri = hostUri;
}
public void test(SomeDto someDto) {
var uri = UriComponentsBuilder.fromUriString(hostUri + "/example");
if(someDto != null) {
uri.queryParam("name", someDto.getName())
.queryParam("hex", someDto.getHex());
}
restTemplate.exchange(uri.toUriString(), HttpMethod.GET, null, Void.class);
}
}
#SpringBootTest(
classes = DemoApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
class ClientApiTest {
#LocalServerPort
private String port;
private ClientApi clientApi;
#BeforeEach
void before() {
clientApi = new ClientApi(new RestTemplate(), "http://localhost:" + port);
}
#Test
void testMethod() {
SomeDto someDto = new SomeDto();
someDto.setName("some name");
someDto.setHex("#ffffff");
clientApi.test(someDto);
}
}
UPDATE:
I was able to partially fix it by decoding the URL, however it only fixes name "some name" to reach the controller correctly, hex "#ffffff" on the other hand reaches as null.
var decodedUri = URLDecoder.decode(uri.toUriString(), Charset.defaultCharset());
Spring uses some symbols as service symbols.
E.g. you cannot parse param value if it contains a comma.
?someParam=some,value
Would be parsed as two params: some and value. But if receive type is not array or collection then the second value will be ignored. Hence, you'll get someParam=some.
The simplest way to avoid it is URL params base64 encoding.
For me, the convenient way was to encode params as json in Base64.
{
"name": "some name",
"hex": "fffffff"
}
Why json? Because there are many ready-made solutions for parsing JSON into an object.
So, your controller will receive Base64 value which is eyJuYW1lIjoic29tZSBuYW1lIiwgImhleCI6ImZmZmZmZmYifQ==
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.Base64;
import java.util.Objects;
#RestController
public class RestController {
#GetMapping("/example")
public void example(String params) {
String decoded = decodeBase64(params);
SomeDto dto = parseTo(decodedFilters, SomeDto.class);
}
public String decodeBase64(String encoded) {
if (Objects.nonNull(encoded)) {
return new String(Base64.getDecoder().decode(encoded));
}
return "";
}
public <T> T parseTo(String jsonAsString, Class<T> classType) {
String toParse = Objects.nonNull(jsonAsString) ? jsonAsString : "{}";
try {
return new ObjectMapper().readValue(toParse, classType);
} catch (IOException e) {
throw new ValidationException(e.getMessage());
}
}
}

How do I pass list of objects to Rest API POST Method?

I'm creating a Spring boot REST API which should take 2 Lists of custom objects. I'm not able to correctly pass a POST body to the API I've created. Any idea what might be going wrong ?
Below is my code :
Controller Class Method :
// Main controller Class which is called from the REST API. Just the POST method for now.
#RequestMapping(value = "/question1/solution/", method = RequestMethod.POST)
public List<Plan> returnSolution(#RequestBody List<Plan> inputPlans, #RequestBody List<Feature> inputFeatures) {
logger.info("Plans received from user are : " + inputPlans.toString());
return planService.findBestPlan(inputPlans, inputFeatures);
}
Plan Class , this will contain the Feature class objects in an array:
public class Plan {
public Plan(String planName, double planCost, Feature[] features) {
this.planName = planName;
this.planCost = planCost;
this.features = features;
}
public Plan() {
}
private String planName;
private double planCost;
Feature[] features;
public String getPlanName() {
return planName;
}
// getters & setters
}
Feature POJO Class :
// Feature will contain features like - email , archive etc.
public class Feature implements Comparable<Feature> {
public Feature(String featureName) {
this.featureName = featureName;
}
public Feature() {
}
private String featureName;
// Getters / Setters
#Override
public int compareTo(Feature inputFeature) {
return this.featureName.compareTo(inputFeature.getFeatureName());
}
}
You cannot use #RequestBody twice!
You should create a class that holds the two lists and use that class with #RequestBody
You should create json like this:
{
"inputPlans":[],
"inputFeatures":[]
}
and create Class like this:
public class SolutionRequestBody {
private List<Plan> inputPlans;
private List<Feature> inputFeatures;
//setters and getters
}
POST mapping like this:
#RequestMapping(value = "/question1/solution/", method = RequestMethod.POST)
public List<Plan> returnSolution(#RequestBody SolutionRequestBody solution) {
logger.info("Plans received from user are : " + solution.getInputPlans().toString());
return planService.findBestPlan(solution);
}

MethodArgumentConversionNotSupportedException when I try to map json string onto java domain class in Spring controller's method

From the frontend I receive GET-request which contains encoded json string as one of its parameters:
http://localhost:8080/engine/template/get-templates?context=%7B%22entityType%22%3A%22DOCUMENT%22%2C%22entityId%22%3A%22c7a2a0c6-fd34-4f33-9cb8-14c2090565ea%22%7D&page=1&start=0&limit=25
Json-parameter 'context' without encoding (UUID is random):
{"entityType":"DOCUMENT","entityId":"c7a2a0c6-fd34-4f33-9cb8-14c2090565ea"}
On backend my controller's method which handle that request looks like this:
#RequestMapping(value = "/get-templates", method = RequestMethod.GET)
public List<Template> getTemplates(#RequestParam(required = false, name = "context") Context context) {
//...
}
'Context' domain class:
public class Context {
private String entityType;
private UUID entityId;
public String getEntityType() {
return entityType;
}
public void setEntityType(String entityType) {
this.entityType = entityType;
}
public UUID getEntityId() {
return entityId;
}
public void setEntityId(UUID entityId) {
this.entityId = entityId;
}
}
I believed Spring's Jackson module would automatically convert that kind of json to java object of Context class, but when I run this code it gives me exception:
org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'com.company.domain.Context'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.company.domain.Context': no matching editors or conversion strategy found
On StackOverflow I've seen similar questions, but those were about POST-requests handling (with #RequestBody annotation), which doesn't fit with GET-request.
Could you help me to solve this problem?
Thanks in advance.
I think you need to specify that your GET mapping is looking to consume JSON:
#RequestMapping(value = "/get-templates", method = RequestMethod.GET, consumes = "application/json")
public List<Template> getTemplates(#RequestParam(required = false, name = "context") Context context) {
//...
}
If this doesn't work then you can call the Jackson ObjectMapper yourself:
#RequestMapping(value = "/get-templates", method = RequestMethod.GET)
public List<Template> getTemplates(#RequestParam(required = false, name = "context") String context) {
ObjectMapper mapper = new ObjectMapper();
Context myContext = mapper.readValue(context, Context.class);
//...
}
As far as I know Spring does not have the mechanism to convert from a String to a UUID in older releases. In such case you should declare you entityId as a String and then use a converter in order to convert it to UUID.
So your Context class should be like below:
public class Context {
private String entityType;
private String entityId;
public String getEntityType() {
return entityType;
}
public void setEntityType(String entityType) {
this.entityType = entityType;
}
public String getEntityId() {
return entityId;
}
public void setEntityId(String entityId) {
this.entityId = entityId;
}
public UUID getEntityIdAsUUID() {
return convertToUUID(this.entityId);
}
// Helper Conversion String to UUID method
private UUID convertToUUID(String entityId){
return UUID.fromString(entityId);
}
}
I faced the same issue in both Jersey and Spring MVC when trying to convert the JSON String {"x":"1001822.831","y":"200716.8913"} to a object of a class called Point
Point class is as below
public class Point
{
private Double x;
private Double y;
//getters and setters
}
As per Jersey documentation, I added the below method to Point class and it worked for both Jersey and Spring MVC.
//used by jax rs & spring mvc for converting queryParam String to Point
public static Point valueOf(String json) throws IOException
{
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, Point.class);
}
Please refer to section 3.2 here https://jersey.github.io/documentation/latest/jaxrs-resources.html#d0e2271

Categories

Resources