In Jersey 2, I'm trying to develop a method that allows me to pass a JSON list of couple (service, method) that representing the access path to a resource in a REST request and aggregate the result in a single response. So, the JSON list could be like this:
[
{
service : "customerService",
method : "getCustomer",
params : {
id:57
}
},
{
service : "customerService",
method : "getContacts",
params : {
idContact : 75
}
}
]
The corresponding command bean could be like this:
public class Command {
private String method;
private String service;
public Command() {
}
public Command(final String service, final String method) {
this.service = service;
this.method = method;
}
public String getMethod() {
return method;
}
public String getService() {
return service;
}
public void setMethod(final String method) {
this.method = method;
}
public void setService(final String service) {
this.service = service;
}
}
And the customer service class could be like this:
#Path("/customerService")
public class CustomerService {
#GET
#Path("/getCustomer/{id}")
#Produces(MediaType.APPLICATION_JSON)
public Customer getCustomer(#PathParam("id") final int id) {
...
}
#GET
#Path("/getContacts/{idCustomer}")
#Produces(MediaType.APPLICATION_JSON)
public List<Contact> getContacts(#PathParam("idCustomer") final int idCustomer) {
...
}
}
Thus, I could make one single Ajax call to the REST and get the the contacts list and the customer data and gain an Ajax call.
My question is How dispatch command in order to execute the methods of the service. I tried to do this:
#Context
ExtendedResourceContext context;
#POST
#Path("/exec")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.TEXT_PLAIN)
public String exec(List<Command> commands) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
final List<Resource> resources = context.getResourceModel().getRootResources();
for (final Command command : commands) {
for (final Resource serviceResource : resources) {
if (serviceResource.getPath().equals("/" + command.getService())) {
System.out.println("Service found " + serviceResource.getPath());
for (final Resource methodResource : serviceResource.getChildResources()) {
if (methodResource.getPath().equals("/" + command.getMethod())) {
for (ResourceModelComponent component : methodResource.getComponents()) {
if (component instanceof ResourceMethod) {
final ResourceMethod m = (ResourceMethod) component;
if (m.getHttpMethod().equals("GET") || m.getHttpMethod().equals("POST")) {
final Invocable invocable = m.getInvocable();
Method method = invocable.getHandlingMethod();
method.invoke(this);
}
}
}
}
}
}
}
}
return "ok";
}
But I can't instantiate some Jersey object like ExtendedResourceContext.
I've found this topic but it seems to be applied to version 1 of Jersey:
How to access multiple resources in a single request : Jersey Rest
Thank you for your answers and sorry for my bad english.
JSONObject jo=new JSONObject();
JSONObject jo1=new JSONObject();
JSONArray jarr=new JSONArray();
jo.put("service","customerService");
jo.put("method","getCustomer");
jo1.put("id","57");
jo.put("params",jo1);
jarr.put(jo);
By using the above example you can resolve your problem.hope this will helpful
Related
I have an object, and of its attributes is a List. I want to send this object from Postman to my service. I'm using Spring 5.2.7 (Spring MVC, not SpringBoot) and Hibernate 5.4.17 and Java 8. My problem is very similar to this one: I want to send a Postman POST request with an Array: members: ["william", "eric", "ryan"]
This is the class I'm trying to pass in Postman (POST method):
public class ChatDescriptionDto {
private String chatID;
private List<String> members;
private String chatType;
public String getChatID() {
return chatID;
}
public void setChatID(String chatID) {
this.chatID = chatID;
}
public List<String> getMembers() {
return members;
}
public void setMembers(List<String> members) {
this.members = members;
}
public void addMembers(List<String> members)
{
if(this.members == null)
this.members = new ArrayList<>();
this.members.addAll(members);
}
public void addMember(String member)
{
if(this.members == null)
this.members = new ArrayList<>();
this.members.add(member);
}
public String getChatType() {
return chatType;
}
public void setChatType(String chatType) {
this.chatType = chatType;
}
}
I've tried this and it didn't work:
{
"chatID": "123",
"members": ["P2001222833","P2001640916"],
"chatType": "personal"
}
Edit: This is my controller:
#PostMapping("/initiateChat")
public String initiateChat(#RequestBody ChatDescriptionDto chat)
{
return chatServiceLocal.initiateChat(chat)?"Chat Description created":"Failure! Could not save.";
}
Edit 2: The method which I've written in the question, "members": ["P2001222833","P2001640916"], is the correct one. Turns out, there was some error in the server so it never started and I didn't check that.
Having no information about the Controller class you're using, the first thing I'd assume is that you're receiving an empty object, which means that Spring simply skipped the serialization. This is the case when you don't specify the parameter of the method as #RequestBody. First, make sure that you do have the annotation.
#RestController
#RequestMapping("/")
public class TestController {
#RequestMapping(value = "/test", method = RequestMethod.POST)
public ResponseEntity test(#RequestBody ChatDescriptionDto dto) {
System.out.println(dto);
return ResponseEntity.ok().build();
}
}
If that's not the case, I'd assume that the problem is with the content type you're using. Spring uses JSON by default, but you can change it in your endpoint's configuration.
To send a simple object request, you do:
{
"member":"kola"
}
To send a list object request, you do:
{
"member": ["kola","wale","ojo"]
}
This is more like listing array elements.
Any error that pops up after this, is basically not because of the request you sent.
I'm developing an app against a cloud application that has hard api rate limits in place. In order to have my team get a feeling for how close we are in regards to those limits I want to count all API calls made from our app in a meaningful way.
We use Feign as access layer, and I was hoping to be able to use the RequestInterceptor to count the different API endpoints we call:
RequestInterceptor ri = rq -> addStatistics(rq.url());
Now this does not work, as the resulting URLs almost always count "1" afterwards, as they already contain all resolved path variables, so I get counts for
1 - /something/id1valueverycryptic/get
1 - /something/anothercrypticidkey/get
and so on.
I was hoping to somehow get access to either the #ResuqestLine mapping value (GET /something/{id}/get) or at least the uri template pre-resolve (/somethine/{id}/get)
Is there a way to do this?
Thanks!
Maybe you could try using custom feign InvocationHandlerFactory.
I've managed to log RequestInterceptor using code like this:
change EnableFeignClients and add defaultConfiguration
#EnableFeignClients(defaultConfiguration = FeignConfig.class)
add default feign config
#Configuration
public class FeignConfig {
#Bean
#ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
#Bean
#Scope("prototype")
#ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder()
.retryer(retryer)
.invocationHandlerFactory((target, dispatch) -> new CountingFeignInvocationHandler(target, dispatch));
}
}
create your invocation handler (code based on feign.ReflectiveFeign.FeignInvocationHandler)
public class CountingFeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
public CountingFeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
RequestLine requestLine = method.getAnnotation(RequestLine.class);
addStatistics(requestLine.value());
return dispatch.get(method).invoke(args);
}
#Override
public boolean equals(Object obj) {
if (obj instanceof CountingFeignInvocationHandler) {
CountingFeignInvocationHandler other = (CountingFeignInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
#Override
public int hashCode() {
return target.hashCode();
}
#Override
public String toString() {
return target.toString();
}
}
Be careful and check if you feign configuration wasn't more complex and in that case extend classes as needed.
If you are using spring-cloud-starter-openfeign , You could do something like below
add the a primary contract bean
#Bean("YourContract")
#Primary
public Contract springpringContract() {
return (targetType) -> {
List<MethodMetadata> parseAndValidatateMetadata = new SpringMvcContract().parseAndValidatateMetadata(targetType);
parseAndValidatateMetadata.forEach(metadata -> {
RequestTemplate template = metadata.template();
template.header("unresolved_uri", template.path().replace("{", "[").replace("}", "]"));
});
return parseAndValidatateMetadata;
};
}
Add the contract to the feign client builder
#Bean
public <T> T feignBuilder(Class<T> feignInterface, String targetURL) {
return Feign.builder().client(getClient())
.contract(contract)
.
.
}
Once you are done with the above you should be able to access the unresolved path in the RequestTemplate
#component
public class FeignRequestFilter implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
String unresolvedUri = template.headers().getOrDefault("unresolved_uri", Collections.singleton(template.path()))
.iterator().next();
}
}
Maybe you could try overwriting feign Logger.
Suppose we have a feign client,
#FeignClient(name = "demo-client", url = "http://localhost:8080/api", configuration = FeignConfig.class)
public interface DemoClient {
#GetMapping(value = "/test/{id}")
void test(#PathVariable(name = "id") Integer id) {
}
}
import feign.Logger;
import feign.Request;
import feign.Response;
import java.io.IOException;
public class CustomFeignRequestLogging extends Logger {
#Override
protected void logRequest(String configKey, Level logLevel, Request request) {
super.logRequest(configKey, logLevel, request);
// targetUrl = http://localhost:8080/api
String targetUrl = request.requestTemplate().feignTarget().url();
// path = /test/{id}
String path = request.requestTemplate().methodMetadata().template().path();
}
}
I am trying to hit a service using postman by providing required request in JSON format :
{
"baseParam": {
"userId": "u1234"
},
"resource": {
"name": "resource"
}
}
along with a multipart file as shown in the below postman screen
Postman screen
But I am getting the error :
'Failed to convert value of type 'java.lang.String' to required type 'SampleRequestObject'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type'
Controller
#RequestMapping(method = RequestMethod.POST, value = "/sample/resource",
produces = "application/json")
#ResponseBody
public final ResponseObject sampleResource(#RequestParam("request") final
SampleRequestObject requestObject,#RequestParam("file") final
MultipartFile file,BindingResult result ) throws IOException {
String success = “SUCCESS”;
requestObject.setFile(file);
String id=sampleService.sampleResourceService(resourceObject);
//sampleService Object has been autowired earlier in controller
return success;
}
}
SampleRequestObject – Model class
public class SampleRequestObject extends SampleMainObject {
#NotNull
#IsValid(baseParam = { "userId"})
private BaseParam baseParam;
#NotNull
private SampleResource resource; // ANOTHER OBJECT OF SAMPLE RESOURCE
CLASS
private MultipartFile file;
public SampleResource getResource() {
return resource;
}
public void setResource(SampleResource resource) {
this.resource = resource;
}
#Override
public Param getBaseParam() {
return baseParam;
}
public final void setBaseParam(final BaseParam baseParam) {
this.baseParam = baseParam;
}
public MultipartFile getFile() {
return file;
}
public void setFile(MultipartFile file) {
this.file = file;
}
}
Function in SampleServiceImpl.class (SampleService Implemetation)
#Override
#Transactional
public final String sampleResourceService( SampleRequestObject
requestObject) {throws IOException
String Id=null;
try
{
MainResourceObject resourceObject = new MainResourceObject();
resourceObject.setActualName(requestObject.getFile()
.getOriginalFilename());
resourceObject.setName(requestObject.getResource().getName()); );
.
.
.
.
.
.
.
.
.
}
catch (Exception e) {
log.error("Service Execution failed",e);
}
}
I also tried giving #ModelAttribute instead of #RequestParam. Then, it was possible to enter the controller as well as service, but all values of requestObject (in the controller) are being populated as null values. Hence, it throws NullPointeException from within the service implementation.
Please provide a solution.
Can you try this
public final ResponseObject sampleResource(SampleRequestObject request,
#RequestParam("file") final MultipartFile file,
BindingResult result ) throws IOException {
...
I have just remove the #RequestParam and changed variable as request
I use Jersey API for my REST service. My question is: Is there a more elegant way of returning exceptions in a JSON form? Is it better to concern myself with creating a json object myself and attaching it to the response directly?
This is a simplified example of one of the methods in the service. As you see, I use HashMap only because the method may throw an exception, in which case I need to return information about It.
#Path("/admin")
public class AdminService {
#Context
HttpServletRequest request;
#POST
#Produces(MediaType.APPLICATION_JSON)
public Map<Integer, String> createCompany(Company company){
Map<Integer, String> map = new HashMap<>();
try{
AdminFacade adminFacade = (AdminFacade)Utility.getFacade(request);
adminFacade.createCompany(company);
map.put(1,"success");
} catch (ExceptionREST e) {
map.put(e.getErrorNumber(), e.getMessage());
} finally {
return map;
}
}
}
You can create a class like the one below to represent an error,
#JsonPropertyOrder({ "code", "field", "message" })
public class ErrorInfo {
private String code;
private String field;
private String message;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
You can create a class which extends an exception like this,
public class InvalidInputException extends RuntimeException {
private static final long serialVersionUID = -5027121014723838738L;
private List<ErrorInfo> errors;
public List<ErrorInfo> getErrors() {
return this.errors;
}
public InvalidInputException(List<ErrorInfo> errors) {
super();
this.errors = errors;
}
public InvalidInputException(String message, List<ErrorInfo> errors) {
super(message);
this.errors = errors;
}
}
And have a exception mapper, where you can convert the List to json and return to the user with http status code 400 (Bad Request).
#Provider
public class InvalidInputExceptionMapper implements ExceptionMapper<InvalidInputException> {
#Override
#Produces(MediaType.APPLICATION_JSON)
public Response toResponse(InvalidInputException e) {
ResponseBuilder rb = Response.status(Status.BAD_REQUEST);
rb.entity(e.getErrors());
return rb.build();
}
}
Http Response will be,
HTTP/1.1 400 BAD REQUEST
{
"errors": [{
"error": {
"code": "100",
"field": null,
"message": "Name is required"
},
"error": {
"code": "100",
"field": null,
"message": "Age is required"
}
}]
}
I believe it is quite popular that people use http response status code to handle the error. E.g. 404 status is not found 5xx is server internal error e.t.c.
You can easily set the error code by using the Response object.
Instead of returning a map, return a Response object.
#Path("/admin")public class AdminService {
#Context
HttpServletRequest request;
#POST
#Produces(MediaType.APPLICATION_JSON)
public Response createCompany(Company company){
Map<Integer, String> map = new HashMap<>();
try{
AdminFacade adminFacade = (AdminFacade)Utility.getFacade(request);
Company commpany=adminFacade.createCompany(company);//this entity annotated by XmlRootElement
Response response=Response.ok().entity(company).build();
} catch (ExceptionREST e) {
response=Response.status(404).build();
} return response;
}}
To make the Restful api more robust, some will return an OK response to prevent "smart redirect" from the server and output some weird html.
you can refer here for a list of http status code and what it mean.
For Java EE Response class, you can refer the official javadoc
You can wrap your error into a class, say I have an ErrorData class which has status, message and stacktrace. Everytime an exception occurs, I throw a GeneralAppException with the errordata object.
public class GeneralAppException extends WebApplicationException {
public GeneralAppException(ErrorData er) {
super(Response.status(er.getStatusCode()).
entity(er).type(MediaType.APPLICATION_JSON).build());
}
}
I have another class which has all the known errors, eg.
public static final ErrorData NODATAFOUND = new ErrorData(Response.Status.NOT_FOUND.getStatusCode(),"No data was found for given query");
public static final ErrorData CODEERROR = new ErrorData(502,"CodeError");
Your catch can look like
catch (ExceptionREST e) {
throw new GeneralAppException(ErrorData.NODATAFOUND);
}
Reference used : https://jersey.java.net/documentation/latest/representations.html#d0e6665
I have developed a simple RESTful web service.
Root Resource Class:
#Path("/order")
#RequestScoped
public class CustOrderContainerResource {
//<editor-fold defaultstate="collapsed" desc="Instance Variable">
#Context
private UriInfo myUriInfo;
#Context
private ResourceContext myResourceContext;
#Context
private SecurityContext mySecurityContext;
#Inject
private CustOrderDAO myCustOrderDAO;
public CustOrderContainerResource() {
}
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML})
public List<Custorder> ReadCustomerOrder(#QueryParam("min")int min,
#QueryParam("max")int max, #Context Request myRequest,
#Context HttpHeaders myHeader) {
int totalOrder = 0;
List<Custorder> resultList = null;
totalOrder = myCustOrderDAO.count();
if(min == 0 && max == 0) {
throw new QueryParamException("Order ID is empty");
}
else if(max > totalOrder) {
throw new QueryParamException("Order ID Range is invalid");
}
resultList = myCustOrderDAO.findRange(min, max, "findOrderIDRange");
return resultList;
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Custorder> ReadCustomerOrder() {
// Check conditional get here
return myCustOrderDAO.findAll();
}
#POST
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_FORM_URLENCODED})
public Response createOrder(Custorder myCustOrder) {
String orderID = null;
myCustOrder.setStatus("pending");
myCustOrder.setOrderdate(new Date());
myCustOrder.setTotal("");
// Persist
myCustOrderDAO.create(myCustOrder);
// Get Order ID
// Embedded created URL for new customer order in response
return Response.created(myUriInfo.getAbsolutePath().resolve(myCustOrder.getOrderid() + "/")).build();
}
#Path("{orderID}")
// #Produces(MediaType.TEXT_HTML)
public CustOrderResource ReadSingleCustomerOrder(#PathParam("orderID") String orderID) {
int userOrderID = Integer.parseInt(orderID);
int myOrderID = myCustOrderDAO.count();
CustOrderResource myCustorder = null;
if(userOrderID > myOrderID
|| myCustOrderDAO.find(orderID) == null) {
throw new OrderNotFoundException("Order ID Not Found");
}
if(!mySecurityContext.isUserInRole("admin")) {
// Propogates to specific resource class
myCustorder = myResourceContext.getResource(CustOrderResource.class);
myCustorder.setOrderID(orderID);
}
return myCustorder;
// return CustOrderResource.getInstance(myCustOrderDAO, orderID);
}
}
Sub Resource Locator Class :
#RequestScoped
public class CustOrderResource {
//<editor-fold defaultstate="collapsed" desc="Instance Variable">
#Inject
private CustOrderDAO myCustOrderDAO;
private String orderID;
private static final Logger myLogger = Logger.getLogger(CustOrderResource.class.getName());
//</editor-fold>
// ========================================================
public CustOrderResource() {
}
private CustOrderResource(String orderID) {
this.orderID = orderID;
}
public static Custorder getInstance(CustOrderDAO myCustOrderDAO, String orderID) {
return myCustOrderDAO.find(orderID);
}
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_ATOM_XML})
public Custorder getCustomerOrder() {
return myCustOrderDAO.find(orderID);
}
#POST
#Consumes(MediaType.APPLICATION_XML)
public String updateCustomerOrder() {
return "so";
/*try {
myCustOrderDAO.update(myCustOrder);
}
catch(Exception e) {
myLogger.log(Level.ALL, e.toString());
throw new WebApplicationException(
Response.status(Status.INTERNAL_SERVER_ERROR)
.entity("Cust Order Update Failed").build());
}*/
}
#DELETE
// 415 Unsupported media type
public String deleteCustomerOrder() {
return "Deleted";
// myCustOrderDAO.delete(myCustOrder);
}
public String getOrderID() {
return orderID;
}
public void setOrderID(String orderID) {
this.orderID = orderID;
}
}
My question is
AFAIK, the resource context will propagate to specific resource class
when we specify it as an argument according to the HTTP method like
POST or DELETE. How do I pass the parameter from sub resource locator
method into sub resource class method?
I tried to update customer order using post method with XML data but unfortunately the JAX-RS runtime returns 415 Unsupported media type.
I am using the REST client from http://code.google.com/p/rest-client/ to test my application, by pasting an XML file into the body tab content. What is wrong with it?
Does the JAXB automatically convert to XML when I return a list of
objects? I have tested and it return xml format but just want
confirmation. Is it more flexible to return response object?
I wonder how to build a response object with list of object and list of URI or Atom XML with list of object (Apache Abdera).
How to find out a id of a newly persisted object into database in my
createCustomerOrder method ?
Thanks.
Please help.
Passing object into sub resource locator class is solve by using QueryParam annotation. Newly persisted object is finding using EntityManager util.