How to route JAX-RS request conditionally, depending on the suffix? - java

This is what I'm trying to do:
#Path("/finder")
public class Finder {
#Path("/{name}")
public Proxy find(#PathParam("name") String name) {
Object found = /* some object found by name */
return new Proxy(found);
}
}
public class Proxy {
private Object obj;
public Proxy(Object found) {
this.obj = found;
}
#GET
#Path("/")
public String info() {
return /* some meta-information about the object */
}
#Path("/")
public Object passthru() {
return this.obj;
}
}
I'm trying to enable:
GET /finder/alpha -> Proxy.info()
GET /finder/alpha/something -> obj.something()
Am I going the right way? Meanwhile, Jersey says:
WARNING: A sub-resource method, public final java.lang.String com.XXX.Proxy.info(),
with URI template, "/", is treated as a resource method

Everything is fine with the code above except that I don't need #Path("/") annotation at info().

Related

Redirect to a POST request from one controller to another controller Spring Boot

I have a springboot project with 2 controller files as below:
File1.java
#PostMapping("/test")
public String testMap(String s){
if(s!=null){
return "found it";
}
else {
// need to go to POST request in another controller
}
return "not found";
}
File2.java
#PostMapping("/test2")
public String testMap2(String s){
if(s!=null){
return "found it";
}
return "not found 2";
}
I have tried adding java HttpURLConnection lines to send a POST request in File1.java but it does not perform the operations within testMap2, instead it exits with not found
Could you please give some suggestions on how I could accomplish this?
You could use RestTemplate to create another POST request, although I strongly suggest avoiding that.
Since both of these controllers are in the same project, try extracting the common logic into a #Service which should be injected in both controllers.
For example:
File1.java
#RestController
public class MyFirstController {
private MyBusinessLogic myBusinessLogic;
// Constructor injection
public MyFirstController(MyBusinessLogic myBusinessLogic) {
this.myBusinessLogic = myBusinessLogic;
}
#PostMapping("/test")
public String testMap(String s){
if(s!=null){
return "found it";
}
else {
return myBusinessLogic.doSomething(s);
}
return "not found";
}
}
File2.java:
#RestController
public class MySecondController {
private MyBusinessLogic myBusinessLogic;
// Constructor injection
public MySecondController(MyBusinessLogic myBusinessLogic) {
this.myBusinessLogic = myBusinessLogic;
}
#PostMapping("/test2")
public String testMap2(String s){
if(s!=null){
return myBusinessLogic.doSomething(s);
}
return "not found 2";
}
}
Finally create a service for the common logic:
#Service
public class MyBusinessLogic {
public String doSomething(String s) {
// common logic goes here
}
}
You can use RestTemplate.
Lets say our controller looks like this:
#RestController
#RequestMapping(value = "first/")
public class FirstRestController {
#PostMapping("test")
public String getTest(String s){
return service.doSomething(s);
}
}
Basically, add this method as a bean in one of your config classes. #Bean puts the method in application context. Now we can inject this method in our services.
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
Now, one of our service methods in Second App, we must call the endpoint of First.
#Service
public class SecondAppService{
#Autowired
private RestTemplate restTemplate;
public String callFirst() {
final URI uri =UriComponentsBuilder.fromUriString(PATH+"first/").toUri();
restTemplate.postForEntity(uri, "something", String.class);
// check your resttemplate docs, i used postForEntity here.
// if necessery return something with response, this method expects the return string but you get the idea.
}
}
This should work.

Access URITemplate or RequestLine value in Feign RequestInterceptor / RequestTemplate

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();
}
}

Spring boot restful web service. Xml response wrongly formatted

I've got a simple Restful webService using Spring Boot 2.1, Java 8, running on Eclipse Neon. Im sending the following request:
<patentListWrapper>
<patentList>
<patent>
<guid>bbb</guid>
</patent>
<patent>
<guid>ccc</guid>
</patent>
</patentList>
</patentListWrapper>
and im getting back the following (incorrect) response:
<patentListWrapper>
<patentList>
<patentList>
<guid>ddd</guid>
</patentList>
<patentList>
<guid>eee</guid>
</patentList>
</patentList>
</patentListWrapper>
ie i've got 2 patentList elements in the response ,instead of an inner patent element, and I don't know why. My 2 POJO classes to map the request are:
public class PatentListWrapper {
private List<Patent> patents;
public List<Patent> getPatentList() {
return patents;
}
public void setPatentList(List<Patent> patents) {
this.patents = patents;
}
}
and:
public class Patent {
private String guid;
public String getGuid() {
return guid;
}
public void setGuid(String guid) {
this.guid = guid;
}
public Patent() {
super();
}
}
my REST Controller class is:
#RestController
public class PndController {
#Autowired
ReadFromDb db;
#RequestMapping(value = "/guidRequest/xmlList", method = RequestMethod.POST, produces = { "application/xml", "text/xml" }, consumes = MediaType.ALL_VALUE )
public PatentListWrapper guidSearchList(#RequestBody PatentListWrapper patentListWrapper) {
System.out.println("DS in guidSearchList()");
patentListWrapper = db.readGuidsFromDb(patentListWrapper); // Set the guid in the patents List in patentListWrapper
return patentListWrapper;
}
}
and ReadFromDb class:
#Repository
public class ReadFromDb {
public PatentListWrapper readGuidsFromDb(PatentListWrapper patentListWrapper) {
List<Patent> patents= patentListWrapper.getPatentList();
for(Patent patent : patents) {
patent.setGuid("aaa");
}
patentListWrapper.setPatentList(patents);
return patentListWrapper;
}
}
I'm sending my resuest using the windows ARC Advanced Rest Client:
Rest client with Content-type=application/xml
I've established that both patentList element names map to getPatentList() in PatentListWrapper. How do I get the response envelope to match the request envelop? Any help appreciated.
it is true , just create the getter setter method with the same variable name like below instead of using different names for getter setter methods
private List<Patent> patents;
public List<Patent> getPatents() {
return patents;
}
public void setPatents(List<Patent> patents) {
this.patents = patents;
}
or use the GSON and use #JsonProperty and define the required value name , further if you are not using the IDE to generate getters and setters you better use lombok plugin .

How CXF Handles APIs If those are not annotated with the #Path Variable?

Scenario-1 : During my work I encountered below scenario, On which : getText1, getText2,getText3,getText4,getText5,getText6 are without #Path annotations,
But when I call the API (http://localhost:8080/.../testqa/ )it always returns following result :
{
"name" : "Sumit1 Arora",
"age" : 21,
"address" : "Lakshay1 Arora"
}
SimpleQAImpl
#Service("qaservice")
#Path("/testqa")
public class SimpleQAImpl {
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/simpleqa")
public Person getText() {
return new Person("Sumit Arora",21,"Lakshay Arora");
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Person getText1() {
return new Person("Sumit1 Arora",21,"Lakshay1 Arora");
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Person getText3() {
return new Person("Sumit3 Arora",21,"Lakshay3 Arora");
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Person getText4() {
return new Person("Sumit4 Arora",21,"Lakshay4 Arora");
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Person getText5() {
return new Person("Sumit5 Arora",21,"Lakshay5 Arora");
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Person getText6() {
return new Person("Sumit6 Arora",21,"Lakshay6 Arora");
}
}
May you please tell me how Apache CXF works, if #Path not given like the case above or on other scenarios as well?
Is there any reference to understand such stuff?
Scenario-2 : On this scenario, No #Path variable defined on top of API Call, how all of these API would be called from URI ?
#Service
#Path("/customer")
public class CustomerResource {
private final Logger logger = LoggerFactory.getLogger(CustomerResource.class);
#Autowired
private CustomerService customerService;
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response create(Customer customer) {
if(customerService.createCustomer(customer).isPresent()) {
return Response.ok().build();
} else
return Response.status(Response.Status.BAD_REQUEST).entity(new Error(1,"test")).build();
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getAll() {
logger.debug("Received request to fetch all the customers.");
List<Customer> customers = customerService.fetchAll();
GenericEntity<List<Customer>> customerEntities = new GenericEntity<List<Customer>>(customers) {};
return Response.ok(customerEntities).build();
}
#PUT
#Consumes(MediaType.APPLICATION_JSON)
public Response update(Customer customer) {
return Response.status(Response.Status.NO_CONTENT).build();
}
}
The documentation for how CXF selects which method is executed is here: CXF resource selection overview. The docs talks about which method it prefers by looking at which has more path parameters or more a more specific path but each method in your first scenario has the same path so the first one is chosen. To differentiate between them you could use a path parameter.
The Second scenario requires you to change the HTTP method used with the URL so:
POST /customer
GET /customer
PUT /customer
would each invoke the different methods.

RESTful Web Service - resource context and response questions

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.

Categories

Resources