How to code restcontroller for google actions? - java

I wish to code the Rest Controller in spring-boot for my webhook. I am creating a google action, with simple actions.
This is a boilerplate: https://github.com/actions-on-google/dialogflow-webhook-boilerplate-java/blob/master/src/main/java/com/example/ActionsServlet.java.
I want to do the same, only in spring-boot. I want to manipulate JSON body as input, but not sure how to do this.
#RestController
public class indexController extends HttpServlet {
#Autowired
private App actionsApp;
//handle all incoming requests to URI "/"
// #GetMapping("/")
// public String sayHello() {
// return "Hi there, this is a Spring Boot application";}
private static final Logger LOG = LoggerFactory.getLogger(MyActionsApp.class);
//handles post requests at URI /googleservice
#PostMapping(path = "/", consumes = "application/json", produces = "application/json")
public ResponseEntity<String> getPost(#RequestBody String payload,
#RequestHeader String header, HttpServletResponse response) throws IOException {
//Not sure what to do here.
System.out.println(jsonData);
return ResponseEntity.ok(HttpStatus.OK);
try {
//writeResponse(response, jsonResponse);
//String med request body og object that has all request header entries
String jsonResponse = actionsApp.handleRequest(body, listAllHeaders(header)).get();
return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
} catch (
InterruptedException e) {
System.out.println("Something wrong happened, interupted");
} catch (
ExecutionException e) {
System.out.println("Something wrong happened, execution error");
}
}

First, there is an error in your code. There might be a wrong "return" before your function logic.
return ResponseEntity.ok(HttpStatus.OK);
Second, as you are using Spring Framework, and you use "#RequestBody String payload" in the method, the Spring Framework will take the request body and set it to payload. If you set payload as a specific type. The framework will deserialize the body to it.
Finally, you can directly use payload in your code. The value of it would be the request body.
If you want to decode the json string. You can use org.json library.
JSONObject obj = new JSONObject(payload);
String name = obj.optString("name");
The code will get the value of name in the json.

Related

How to return 404 not found in json format using restful api in java?

i would like to implement an exception handler for restful api if the uri is not matched.
For example: url is
localhost:8080\test\generateNumber will return
{"response_code":"200"}
andif the url is wrong for example:
localhost:8080\test\generateNumber2 will return
{"response_code":"404","message":"uri not found"}
i have no idea on how to do it. Can someone help?
I presume you're using Spring?
In that case you can use #ExceptionHandler like this:
#RestController
public class Example1Controller {
#GetMapping(value = "/testExceptionHandler", produces = APPLICATION_JSON_VALUE)
public Response testExceptionHandler(#RequestParam(required = false, defaultValue = "false") boolean exception)
throws BusinessException {
if (exception) {
throw new BusinessException("BusinessException in testExceptionHandler");
}
return new Response("OK");
}
#ExceptionHandler(BusinessException.class)
public Response handleException(BusinessException e) {
return new Response(e.getMessage());
}
}
And get a message in response.
More - in this manual.

Using RestTemplate in spring-boot returns with 404

I am trying to send a body in a post request in a springboot application using rest template. Here is the controller:(I removed #RequestBody because I used application/x-www-form-urlencoded header)
#RestController
#CrossOrigin
#RequestMapping("/api")
public class SentimentParserController {
#Autowired
private SentimentParserService sentimentParserService;
#RequestMapping(value = "/something", method = RequestMethod.POST, consumes="application/x-www-form-urlencoded")
public ResponseEntity<mcResponse>getTheSentiments( mcSentimentRequestDTO sentimentRequestDTO){
return sentimentParserService.getSentimentsMc(sentimentRequestDTO);
}
}
I want to send the sentimentRequestDTO object(lang, key, and text) as the body in a post request to get the mcResponse:
public mcResponse parseTheSentiments(String text, Languages lang, String key) throws Exception {
RestTemplate restTemplate = new RestTemplate();
String request = "http://localhost:8080";
mcSentimentRequestDTO mSentiments =new mcSentimentRequestDTO(key,"EN",text);
HttpHeaders headers = new HttpHeaders();
headers.add("content-type", "application/x-www-form-urlencoded");
MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
map.add("key", key);
map.add("txt", text);
map.add("lang", Languages.ENGLISH.toString());
HttpEntity<MultiValueMap<String, String>> request1 = new HttpEntity<MultiValueMap<String, String>>(map, headers);
mcResponse response = restTemplate.postForObject(request, request1 , mcResponse.class );
return response;
}
However, I am getting the following error: 404 null.
Can you please help me? Thanks in advance
and here is the service class:
public ResponseEntity<mcResponse> getSentimentsMc(mcSentimentRequestDTO sentimentRequestDTO){
ResponseEntity<mcResponse> dto = null;
try {
dto = sentimentConverter.getTheSentiments(mcsParser.parseTheSentiments(sentimentRequestDTO.getText(),
Languages.ENGLISH, sentimentRequestDTO.getKey()));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return dto;
}
Looks like variable request should be
String request = "http://localhost:8080/something";
Also if controller class has prefix, this prefix also should be in request.
I mean if your class looks like this
#RestController
#RequestMapping("/myApi")
public class CertificateController {
....
#RequestMapping(value = "/something", method = RequestMethod.POST)
public ResponseEntity<mcResponse>getTheSentiments( mcSentimentRequestDTO sentimentRequestDTO){
return sentimentParserService.getSentimentsMc(sentimentRequestDTO);
}
Then request should be
String request = "http://localhost:8080/myApi/something";
It sounds like the controller isn't getting included in the spring context. If you just have an app annotated with #SpringBootApplication, then make sure that your controller is in a package that is the same as or lower than your annotated application.
To check the controller is being picked up you can add the following logging options to your application.properties
logging.level.org.springframework.beans=debug
logging.level.org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping=trace
When your server starts up you should see something like the following in the log
1. To show the controller is in the spring-context
DefaultListableBeanFactory : Creating shared instance of singleton bean 'sentimentParserController'
2. To show the mapping for the /api/something url
RequestMappingHandlerMapping : Mapped 1 handler method(s) for class SentimentParserController: {public org.springframework.http.ResponseEntity SentimentParserController.getTheSentiments(mcSentimentRequestDTO)={[/api/something],methods=[POST]}}
If you see both of these, then what you say you're doing should work. Just make sure you are sending the request to /api/something and the server is running on port 8080.

Using #RequestBody and forwarding to another endpoint throws exception Stream closed

My Java spring REST API controller looks like this:
public void signup(#RequestBody RequestBody requestBody) throws IOException, ServletException {
I get this exception:
Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Stream closed; nested exception is java.io.IOException: Stream closed
This happens because I want to cast the request body to RequestBody class (which opens the request input stream and finishes it), and also forward/redirect it to another endpoint.
The actual controller is:
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public void signup(#RequestBody CustomUserDetails user, HttpServletRequest request, HttpServletResponse response) {
String userName = user.getUsername();
logger.debug("User signup attempt with username: " + userName);
try{
if(customUserDetailsService.exists(userName))
{
logger.debug("Duplicate username " + userName);
userName + " already exists");
String newUrl = "login";
RequestDispatcher view = request.getRequestDispatcher(newUrl);
view.forward(request, response);
} else {
customUserDetailsService.save(user);
authenticateUserAndSetSession(user, response);
}
} catch(Exception ex) {
}
}
How should I handle this ?
You can forward to login page in a ExceptionHandler,like this:
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public void signup(#RequestBody CustomUserDetails user, HttpServletResponse response) {
String userName = user.getUsername();
logger.debug("User signup attempt with username: " + userName);
//try{
if (customUserDetailsService.exists(userName)) {
logger.debug("Duplicate username " + userName);
throw new SignupException(userName + " already exists");
} else {
customUserDetailsService.save(user);
authenticateUserAndSetSession(user, response);
}
/*} catch(Exception ex) {
}*/
}
define a ExceptionHandler in the same Controller:
#ExceptionHandler(SignupException.class)
public String duplicateName() {
return "login";
}
and the SignupException could be like this:
public class SignupException extends RuntimeException {
public SignupException(String message) {
super(message);
}
public SignupException() {
}
}
The request body object is a stream which can be read only once. So forwarding it is not very trivial. One way around this is to create a filter which reads the input steam and replace the input stream to something which can be read multiple times. Example can be found at another answer:
How can I read request body multiple times in Spring 'HandlerMethodArgumentResolver'?
As for your method, there is also another problem:
public void signup(#RequestBody RequestBody requestBody)
As far as I know, RequestBody is an annotation and you can't map it like that. But to get the raw data, you can map it as String.
public void signup(#RequestBody String requestBody)
And then you can just manually make an REST call to the api you want to forward it to using the String request body. Just make sure you set the content-type as the original one, which I assume in this case would be JSON.
Root of your problem is using #RequestBody RequestBody requestBody together with HttpServletRequest request.
Opening input stream twice on the same request is not allowed. In your case a system should open a input stream to extract request body and then propagate in forward to reuse.
To handle it you should avoid multiple usage of the same request stream. Possible solutions are:
Wrap request
Copy request body
Use spring native forward
I think you are trying to forward to a url with the RequestBody, please have a check
Spring 3.2 forward request with new object for the answers.
Create the object and add it to the request as an attribute in the first controller,
request.setAttribute("user",user),
return "forward:/login";
Try putting in request mapping consumes= {" application/json"}, produces={"application/json"}
RequestBody is an annotation to process your request object as expected class deserialization. They help you avoid boilerplate code by extracting the logic of messageconversion and making it an aspect.
You can not get RequestBody directly as an object in any controller. It was not designed to use this way.
Though it is not possible to get RequestBody in a RestController so you can't judge whether it is good or bad practice.
If you have to use new controller/endpoint to process.Then i think the better approach is to get the CustomUserDetails as #RequestBody in the controller then process it. Then call nested method or service to further process instead of thinking forwarding to another controller. Then return response from the controller.

How to accept JSON post data

Is there a way I can simplify this:
#PostMapping(value = "create", consumes = "application/json", produces = "application/json")
public Response create(#RequestBody ObjectNode json) {
return new Response(json.get("name").asText(), 200);
}
Mainly I wonder if it's possible to annotate consumes & produces. My app will be an API service so all requests/responses will be JSON based. I don't want to keep that over each controller method.
Less important:
I know I can pass #RequestParam Comment comment if this is a method to create a comment but what if I want to create a comment and something else at the same time from the same method.
Is there a cleaner way than doing ObjectNode and json and than json.get().as...
As it turns out you can annotate your methods/controllers with #ResponseBody and #RequestBody to achieve the same result.
MyServer.class
#POST
public Response save(String data) {
return Response.status(Response.Status.ACCEPTED).entity(repository.save(data)).build();
}
Now it will go to the server as post request.
if no id present, so add this code.
ResourceConverter converter = new ...
converter.disableDeserialisationOption(DeserializationFeature.REQUIRE_RESOURCE_ID);
This allows you to remove id restriction.
Alternative is that you should use current snapshot version
Here is the save method from the repository class
public String save(String data) {
Server myServer= converter.readObject(data.getBytes(), Server.class);
Key<Server> savedMyServer = datastore.save(myServer);
Server usingKey = datastore.getByKey(Server.class, savedMyServer);
try {
return new String(converter.writeObject(usingKey), StandardCharsets.UTF_8);
} catch (JsonProcessingException | IllegalAccessException e) {
LOGGER.debug(e.getMessage());
}
return null;
}

Redirecting to a page using restful methods?

I've created a page which asks user to fill some form fields and when he submits, the form is sent to a Restful method which you can see below:
#POST
#Path("addUser")
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void addUser(#FormParam("username") String username,
#FormParam("password") String password,
#FormParam("id") String id,
#FormParam("group_name") String groupName,
#FormParam("authority_name") String authorityName,
#FormParam("authority_id") String authorityId
)
{
//Something will be done here
}
How can I redirect the user at the end of this function to (let's say) index.jsp?
change your code like this, the addUser() should return a Response Object
public Response addUser(#FormParam("username") String username,
#FormParam("password") String password,
#FormParam("id") String id,
#FormParam("group_name") String groupName,
#FormParam("authority_name") String authorityName,
#FormParam("authority_id") String authorityId
)
{
//Something will be done here
java.net.URI location = new java.net.URI("../index.jsp?msg=A_User_Added");
return Response.temporaryRedirect(location).build()
}
Create a URI using javax.ws.rs.core.UriBuilder that maps the parameters and other data you want to preserve. Then use Response.temporaryRedirect to return a redirect to the client and pass it the URI you’ve built.
Finally I come to this conclusion that there are no other way than what I did:
So here is my solution:
try {
java.net.URI location = new java.net.URI("../index.jsp?msg=A_User_Added");
throw new WebApplicationException(Response.temporaryRedirect(location).build());
} catch (URISyntaxException e) {
e.printStackTrace();
}
By adding this block to my code, I got what I needed. Hope it helps you as well.
See below the usage of redirecting in web services:
public class LoginWebService {
#POST
#Path("/check")
public Response checkDetails(#FormParam("name") String name,#FormParam("pass") String pass ) throws URISyntaxException {
URI uri = new URI("/login/success");
URI uri2= new URI("http://localhost:9090/NewWebServiceproject/new/login/failure");
if(name.equals("admin") && pass.equals("pass"))
//#Path("http://localhost:8010/NewWebServiceproject/new/login/success");
{
return Response.temporaryRedirect(uri).build();
//Response.seeOther(uri);
//return Response.status(200).entity("user successfully login").build();
}
else
{
return Response.temporaryRedirect(uri2).build();
//Response.seeOther(uri2);
//return Response.status(200).entity("user logon failed").build();
}
}
#POST
#Path("/success")
public Response successpage()
{
return Response.status(200).entity("user successfully login").build();
}
#POST
#Path("/failure")
public Response failurepage()
{
return Response.status(200).entity("user logon failed").build();
}
}
It is not good idea to use the "WebApplicationException" in order to redirect the request. in Jersey (2.4.1) you should be able to redirect the request via the normal servlet way, (request.getServletContext().getRequestDispatcher().forward() or just response.sendRedirect())
The following is how Jersey process the request
org.glassfish.jersey.servlet.ServletContainer.service(HttpServletRequest request, HttpServletResponse response)
requestScope.runInScope
final ContainerResponse response = endpoint.apply(data)
methodHandler.invoke(resource, method, args);
Responder.process(ContainerResponse);
That methodHandler is your REST service class, method is the function in that service class.
The step to redirect page become straitforward
Get the (request, response) through Jersey injection (#Context HttpServletRequest request, #Context HttpServletResponse response) in class field or function parameter
Call request.getServletContext().getRequestDispatcher() to get the dispatcher for "forward"
or use Response.sendRedirect(url)
Once you application is returned (just null), Jersey will try to process the result in the "Responder.process(ContainerResponse)". In this step, it will use response to set status (204 no contents for your null return).
So the key point here is you must finalize/close response object before return from your service function. Otherwise, Jersey may overwrite your output.
Small tips on why "WebApplicationException" can overwrite Jersey repsponse. It is because org.glassfish.jersey.server.ServerRuntime.mapException() will use the "webApplicationException.getResponse()" as the return response result.

Categories