I am having trouble using an #Service annotated JAX-RS resource from within a delegate class. Here is my setup:
MyServlet.java
package com.company.mobileservice.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.HttpRequestHandler;
public class MyServlet implements HttpRequestHandler {
#Autowired
MyDelagate delegate;
private void writeResponse(HttpServletRequest request,
HttpServletResponse response, String delegateResponse) {
try {
response.setContentType("application/json");
String callback = request.getParameter("callback");
if (callback != null) {
delegateResponse = callback + "(" + delegateResponse + ");";
} else {
throw new Exception("Callback was null.");
}
response.getWriter().write(delegateResponse);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private String delegateGetRequest(HttpServletRequest request) {
return delegate.handleRequest(request);
}
#Override
public void handleRequest(HttpServletRequest request,
HttpServletResponse response) {
String delegateResponse = delegateGetRequest(request);
writeResponse(request, response, delegateResponse);
}
}
MyDelegate.java
package com.company.mobileservice.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import com.company.mobileservice.cards.CardResource;
#Component
public class MyDelagate {
#Autowired
private CardResource cardResource;
public String handleRequest(HttpServletRequest request) {
return addCard(request);
}
// Proxies the request to CardResource
private String addCard(HttpServletRequest request) {
String accountID = request.getParameter("accountID");
Card card = new Card();
card.setPan(accountInformation.getAccountNumber().toString());
card.setPaymentTypeId("ABCD1234");
Response response = cardResource.addCard(request, "TEST", null, card);
return response.toString();
}
}
CardResource.java
package com.company.mobileservice.cards;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import com.company.mobileservice.model.Card;
#Path("/{appId}/cards")
#Service
#Scope("singleton")
public class CardResource {
#POST
#Consumes({"application/xml", "application/json"})
#Produces({"application/xml", "application/json"})
public Response addCard(
#Context HttpServletRequest request,
#PathParam("appId") String appId,
#QueryParam("location") String location,
Card card) {
// Do work here
}
}
What I want to do, is call my #Service from within the delegate. I was able to successfully inject the CardResource in to my delegate from the application context, but I was wondering if there is another way to access this service by using the RESTful #Path method.
After some more searching, I've determined that this isn't possible. I was hoping for some kind of service directory that I could to get the appropriate service from. In the end, I decided to just make a call to the service at localhost. It essentially does the job, but it sure would be nice if I could make REST service calls from within my own application without having to configure a client.
Related
I'm struggling a lot with spring-boot custom exception handling. Customer exception is not getting caught with exception handler. REST API works fine for valid request payload. I'm trying to send error response when there's an error. But error response is always empty with status code 200 instead of 404.
Controller
package com.company.paypage.v2.controller;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import org.json.simple.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.company.paypage.exception.*;
import com.company.paypage.model.ErrorMessageConstants;
import com.company.paypage.v2.model.ConfigPayload;
import com.company.paypage.v2.model.ConfigResponse;
import com.company.paypage.v2.services.FeatureConfigService;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#RestController
#RequestMapping(value = "v2/setup")
public class FeatureConfigController {
#Autowired
private FeatureConfigService featureconfigService;
/*
features config endpoint
*/
#RequestMapping(value = "/config", method = POST, consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ConfigResponse setupConfigRequest(#Valid #RequestBody ConfigPayload payload, HttpServletRequest request, HttpServletResponse servResponse) {
log.info("Processing the feature config request for " + payload.getPage_type());
ConfigResponse response = null;
try {
response = featureconfigService.processConfigRequest(payload);
System.out.println(response);
if(response == null) {
throw new FeatureConfigException(ErrorMessageConstants.NOT_FOUND, "Error while generating feature config response.....");
}
} catch (FeatureConfigException e){
log.error("Exception:", e);
}
return response;
}
}
Exception class
package com.company.paypage.exception;
public class FeatureConfigException extends Exception {
String code;
String message;
public FeatureConfigException(String code, String message) {
super(message);
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
#Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Exception handler
package com.company.paypage.exception;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.NoHandlerFoundException;
import com.company.paypage.model.ApplicationConstants;
import com.company.paypage.model.ErrorCodeConstants;
import com.company.paypage.model.ErrorMessageConstants;
import com.company.paypage.model.GeneralErrorInfo;
import com.company.paypage.model.Payment;
import com.company.paypage.model.SetupResponse;
import com.company.paypage.v2.model.ConfigResponse;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#ControllerAdvice
public class GeneralExceptionHandler{
#ExceptionHandler(FeatureConfigException.class)
protected ResponseEntity<ConfigResponse> handleFeatureConfigException(FeatureConfigException ex, HttpServletRequest request){
GeneralErrorInfo generalErrorInfo = new GeneralErrorInfo().withCode(ex.getCode());
generalErrorInfo.setMessage(ex.getMessage());
String referenceId =(String) request.getAttribute(ApplicationConstants.REFERENCE_ID);
ConfigResponse configResponse = buildConfigResponse(generalErrorInfo, referenceId);
log.error("{} {}-{}"
, ex.getMessage()
, request.getHeader(ApplicationConstants.X_GP_REQUEST_ID)
, referenceId
, ex);
return new ResponseEntity<ConfigResponse>(configResponse, addCustomerHeaders(request), HttpStatus.BAD_REQUEST);
}
#SuppressWarnings("null")
ConfigResponse buildConfigResponse(GeneralErrorInfo generalErrorInfo, String referenceId) {
ConfigResponse configResponse = new ConfigResponse();
configResponse.setError(generalErrorInfo);
configResponse.setAutocomplete((Boolean) null);
configResponse.setRefund((Boolean) null);
configResponse.setSplit_payment((Boolean) null);
configResponse.setTender_type(null);
configResponse.setVoidd((Boolean) null);
return configResponse;
}
}
ConfigResponse model
package com.company.paypage.v2.model;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.company.paypage.model.GeneralErrorInfo;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"tender_type",
"autocomplete",
"split_payment",
"refund",
"void",
"error"
})
public class ConfigResponse implements Serializable {
#JsonProperty("tender_type")
private TenderType tender_type;
#JsonProperty("autocomplete")
private boolean autocomplete;
#JsonProperty("split_payment")
private boolean split_payment;
#JsonProperty("refund")
private boolean refund;
#JsonProperty("void")
private boolean voidd;
#JsonProperty("error")
private GeneralErrorInfo error;
public TenderType getTender_type() {
return tender_type;
}
public void setTender_type(TenderType tender_type) {
this.tender_type = tender_type;
}
public boolean isAutocomplete() {
return autocomplete;
}
public void setAutocomplete(boolean autocomplete) {
this.autocomplete = autocomplete;
}
public boolean isSplit_payment() {
return split_payment;
}
public void setSplit_payment(boolean split_payment) {
this.split_payment = split_payment;
}
public boolean isRefund() {
return refund;
}
public void setRefund(boolean refund) {
this.refund = refund;
}
public boolean isVoidd() {
return voidd;
}
public void setVoidd(boolean voidd) {
this.voidd = voidd;
}
public GeneralErrorInfo getError() {
return error;
}
public void setError(GeneralErrorInfo error) {
this.error = error;
}
public ConfigResponse withError(GeneralErrorInfo error) {
setError(error);
return this;
}
}
What could be the issue here? What am I missing to get proper error in JSON format in response?
You have to define the code of an exception. Here you can find different variants of how to handle exceptions for REST: link
I have a self contained Jersey test using JerseyExtension (JerseyExtension) with JUnit5 (since JerseyTest does not work with JUnit5 unless you use the vintage engine) and subsequent calls to the container are getting different session. Is there a way to keep the session store same between the calls?
package com.test.jerseysession;
import com.github.hanleyt.JerseyExtension;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.RegisterExtension;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class JerseyTestWithGrizzly {
private final static TestContainerFactory testContainerFactory;
private final ServletContainer servletContainer;
private final ResourceConfig resourceConfig;
private final DeploymentContext deploymentContext;
static {
testContainerFactory = new GrizzlyWebTestContainerFactory();
}
#RegisterExtension
#SuppressWarnings("unused")
JerseyExtension jerseyExtension = new JerseyExtension(
this::getTestContainerFactory,
this::configureDeploymentContext,
this::configureJerseyClient);
public JerseyTestWithGrizzly() {
this.resourceConfig = new ResourceConfig()
.packages("com.test.jerseysession")
.register(getClass());
this.servletContainer = new ServletContainer(resourceConfig);
this.deploymentContext = ServletDeploymentContext.builder(resourceConfig)
.servlet(servletContainer)
.servletPath("api")
.build();
}
#Path("session")
public static class SessionResource {
#GET
public String get(#Context HttpServletRequest request) {
HttpSession session = request.getSession(true);
Object obj = session.getAttribute("name");
return session.getId() + ": " + obj;
}
#PUT
public String put(#Context HttpServletRequest request) {
HttpSession session = request.getSession(true);
session.setAttribute("name", "foo");
return session.getId()+": Set name attribute called";
}
}
protected ClientConfig configureJerseyClient(ExtensionContext extensionContext, ClientConfig clientConfig) {
assertNotNull(extensionContext);
assertNotNull(clientConfig);
return clientConfig;
}
protected DeploymentContext configureDeploymentContext(ExtensionContext extensionContext) {
assertNotNull(extensionContext);
return deploymentContext;
}
protected TestContainerFactory getTestContainerFactory(ExtensionContext extensionContext) {
assertNotNull(extensionContext);
return testContainerFactory;
}
#Test
public void testSessionSet(WebTarget target) {
// Call PUT which sets attribute called 'name'
Response response0 = target.path("session").request().put(Entity.entity("{}", MediaType.APPLICATION_JSON_TYPE));
System.out.println("PUT: status="+response0.getStatus()+" response="+response0.readEntity(String.class));
// Call GET which should be able to find 'name' in session set by previous call
Response response1 = target.path("session").request().get();
System.out.println("GET: status="+response1.getStatus()+" response="+response1.readEntity(String.class));
}
}
Sample output:
PUT: status=200 response=8373522406385125383: Set name attribute called
GET: status=200 response=8264425692811867393: null
The session ID changed between the call to PUT and GET.
The client used by Jersey test framework, does not behave like a browser when it comes to Set-Cookie/Cookie headers. The two requests are not connected and JSESSIONID set by first response is not propagated to next request. While the framework is aware of the JSESSIONID if present, it does not span requests and needs to be manually copied forward.
Changing the test method to following works:
#Test
public void testSessionSet(WebTarget target) {
Response response0 = target.path("session").request().put(Entity.entity("{}", MediaType.APPLICATION_JSON_TYPE));
System.out.println("PUT: status="+response0.getStatus()+" response="+response0.readEntity(String.class));
Invocation.Builder nextRequestBuilder = target.path("session").request();
NewCookie jsessionid = response0.getCookies().get("JSESSIONID");
if (jsessionid != null) {
nextRequestBuilder.cookie(jsessionid);
}
Response response1 = nextRequestBuilder.get();
System.out.println("GET: status="+response1.getStatus()+" response="+response1.readEntity(String.class));
}
I'm following this tutorial in order to learn how to code RESTful web services - tutorial. Here we are trying to send the status code 201 Created along with the posting of a new message through Postman. When I write the lines of code he writes at 4:47 I get an error in eclipse oxygen:
CREATED cannot be resolved or is not a field
I went through the documentation, but I couldn't find the correct way to type it. Tried to import more Response classes as well, but this didn't fix it.
This is the code where the problem occurs:
return Response.status(Status.CREATED)
.entity(newMessage)
.build();
And this is the whole class:
`import java.net.URI;
import java.util.List;
import javax.net.ssl.SSLEngineResult.Status;
import javax.ws.rs.BeanParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.core.Response.ResponseBuilder;
import org.hristo.javabrains.messenger.model.Message;
import org.hristo.javabrains.messenger.resources.beans.MessageFilterBean;
import org.hristo.javabrains.messenger.service.MessageService;
import com.sun.research.ws.wadl.Response;
#Path("messages")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class MessageResource {
MessageService messageService = new MessageService();
#GET
public List<Message> getMessages(#BeanParam MessageFilterBean filterBean) {
if(filterBean.getYear() > 0) {
return messageService.getAllMessagesForYear(filterBean.getYear());
}
if(filterBean.getStart() >= 0 && filterBean.getSize() >= 0) {
return messageService.getAllMessagesPaginated(filterBean.getStart(), filterBean.getSize());
}
return messageService.getAllMessages();
}
#POST
public Response addMessage(Message message, #Context UriInfo uriInfo) {
Message newMessage = messageService.addMessage(message);
String newId = String.valueOf(newMessage.getId());
URI uri = uriInfo.getAbsolutePathBuilder().path(newId).build();
return Response.status(Status.CREATED)
.entity(newMessage)
.build();
}
#PUT
#Path("{messageId}")
public Message updateMessage(#PathParam("messageId") long messageId, Message message) {
message.setId(messageId);
return messageService.updateMessage(message);
}
#DELETE
#Path("{messageId}")
public Message removeMessage(#PathParam("messageId") long messageId) {
return messageService.removeMessage(messageId);
}
#GET
#Path("{messageId}")
public Message getMessage(#PathParam("messageId") long messageId) {
return messageService.getMessage(messageId);
}
#Path("{messageId}/comments")
public CommentResource getCommentResource() {
return new CommentResource();
} }
The Response import should be javax.ws.rs.core.Response and Status is a static inner class of Response. So either import the static Response.Status or just use Response.Status instead of just Status.
For more information, please refer to the javadoc.
I'm creating an WEB Api with Java and Jersey and now i get 415 status code on a POST request to my API.
The request made by Postman with the Header Content-type application/json
These images show my request
And that is my code.
package api;
import java.sql.SQLException;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import DAO.TransactionDAO;
import DAO.UsersDAO;
import Entity.Transaction;
import Entity.Users;
#Path("/users")
public class UsersController {
private UsersDAO dao;
private static final String CHARSET = ";charset=UTF-8";
#PostConstruct
private void init(){
this.dao = new UsersDAO();
}
#POST
#Path("/add")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.TEXT_PLAIN)
public int insert(Users user){
try{
return this.dao.add(user);
}
catch(Exception e){
e.toString();
return 0;
}
}
}
I found the error. My model didn't have an empty constructor. I just create it and work.
I have a Jersey REST API that returns 204 No content instead of 404 when the entity to be returned back is null.
To address this issue, I'm currently planning to throw an exception within the resource method so that it could force Jersey to return 404 like below.
if (feature == null) throw new WebApplicationException(404);
But I have various REST URIs that suffer from the same problem. Is there a way to address all of them without touching each and every resource method?
My resource methods look like this:
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.example.webapi.domain.Feature;
import com.example.webapi.service.FeatureService;
#Component
#Path("features")
#Produces(MediaType.APPLICATION_JSON)
public class FeatureResource {
#Autowired
private FeatureService featureService;
#GET
public List<Feature> getFeatures() {
return featureService.listAllFeatures();
}
#GET
#Path("{featureCode}")
public Feature getFeature(#PathParam("featureCode") Integer featuresCode) {
return featureService.findFeatureByFeatureCode(featuresCode);
}
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response addFeature(Feature feature, #Context UriInfo uriInfo) {
Integer id = (Integer) featureService.createFeature(feature);
return Response.created(uriInfo.getAbsolutePathBuilder().path(id.toString()).build()).entity(feature).build();
}
}
you can implement ContainerResponseFilter and customise the response based on your requirement.
ex:
#Provider
public class LoggingResponseFilter
implements ContainerResponseFilter {
private static final Logger logger = LoggerFactory.getLogger(LoggingResponseFilter.class);
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
String method = requestContext.getMethod();
logger.debug("Requesting " + method + " for path " + requestContext.getUriInfo().getPath());
Object entity = responseContext.getEntity();
if (entity != null) {
logger.debug("Response " + new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(entity));
}
}
}
Customize the above code and implement your business logic.
You can use Request or Response filters depends on what you want to check for null.
check the docs here