JAX-RS and Camel - Except 1st QueryParameter all others are null - java

For some reason, the second and the subsequent Query Parameters are just null. The first one works perfectly fine. I am using Camel+JAX-RS (CXF). This is just a GET request. The URL I am using is
http://localhost:8181/cxf/coreservices/search?q=health&start=100&size=924
Here is my Interface declaration
#Path("search")
public interface SearchRestService {
#GET
#Produces(MediaType.APPLICATION_JSON)
public String searchGet(#QueryParam ("q") String q, #DefaultValue("0") #QueryParam("start") String start, #DefaultValue("10") #QueryParam("size") String size );
Implementation
public SearchResult<WikiSearchHit> searchGet(String q, String start, String size){
logger.info("Inside wiki GET method: " +q + " start:"+start + " size:"+ size);
The q parameter comes in fine as health but the start and the size parameters are just null. Surprisingly, the default values aren't getting picked up too.
I am afraid I am doing something wrong in my camel routing.
Router
#Override
public void configure() throws Exception {
from("cxfrs://bean://rsServer?bindingStyle=SimpleConsumer")
.multicast()
.parallelProcessing()
.aggregationStrategy(new CoreSearchResponseAggregator())
.beanRef("searchRestServiceImpl", "searchGet")
...
Thanks for your time :-)

The Camel team is working on fixing this issue in the near future. Until then use the following workaround.
You could get the entire query string as follows
String query = exchange.getIn().getHeader(Exchange.HTTP_QUERY, String.class);
//would return something like : q=health&start=100&size=924
Interface
#GET
#Path("search")
#Produces(MediaType.APPLICATION_JSON)
public String searchGet();
Implementation - Target method
public SearchResult<WikiSearchHit> wikiGet(Exchange exchange){
String q = exchange.getIn().getHeader("q", String.class);
String size = exchange.getIn().getHeader("size", String.class);
String start = exchange.getIn().getHeader("start", String.class);
Router
public class RestToBeanRouter extends RouteBuilder {
#Override
public void configure() throws Exception {
from("cxfrs://bean://rsServer?bindingStyle=SimpleConsumer")
.process(new ParameterProcessor())
.removeHeaders("CamelHttp*")
.multicast()
.parallelProcessing()
.aggregationStrategy(new CoreSearchResponseAggregator())
.beanRef("searchRestServiceImpl", "wikiGet")
....
.end()
.marshal().json(JsonLibrary.Jackson);
//.to("log://camelLogger?level=TRACE");
}
class ParameterProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
Map<String, String> stringStringMap = convertQueryStringAsMap(exchange.getIn().getHeader(Exchange.HTTP_QUERY, String.class));
//System.out.println("stringStringMap = " + stringStringMap);
for (Map.Entry<String, String> eachParamEntry : stringStringMap.entrySet()) {
exchange.getIn().setHeader(eachParamEntry.getKey(), eachParamEntry.getValue());
}
}
private Map<String,String> convertQueryStringAsMap(String queryString){
//some Guava magic
return Splitter.on("&").omitEmptyStrings().trimResults().withKeyValueSeparator("=").split(queryString);
}
}
}
More details here :
http://camel.465427.n5.nabble.com/JAX-RS-and-Camel-Except-1st-QueryParameter-all-others-are-null-tt5742470.html

Related

Convert #RequestParam to custom Object

Have a problem with optimizing search request.
I have search method that accepts parameters in url query like:
http://localhost:8080/api?code.<type>=<value>&name=Test
Example: http://localhost:8080/api?code.phone=9999999999&name=Test
Defined SearchDto:
public class SearchDto {
String name;
List<Code> code;
}
Defined Code class:
public class Code {
String type;
String value;
}
Currently I'm using Map<String,String> as incoming parameter for the method:
#GetMapping("/search")
public ResponseEntity<?> search(final #RequestParam Map<String, String> searchParams) {
return service.search(searchParams);
}
Then manually converting map values for SearchDto class. Is it possible to get rid of Map<String,String> and pass SearchDto directly as argument in controller method?
Passing a json in querystring is actually a bad practice, since it decrease the security and sets limits on the number of parameters you can send to your endpoint.
Technically speaking, you could make everything work by using your DTO as a controller's parameter, then URL encoding the json before you send it to the backend.
The best option, in your case, is to serve an endpoint that listen to a POST request: it is not an error, neither a bad practise, to use POST when performing a search.
you can customize a HandlerMethodArgumentResolver to implement it.
but , if you want a object receive incoming parameter. why not use POST
#Target({ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Example {
}
public class ExampleArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
Example requestParam = parameter.getParameterAnnotation(Example.class);
return requestParam != null;
}
#Override
public Object resolveArgument(MethodParameter parameter, #Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, #Nullable WebDataBinderFactory binderFactory) throws Exception {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
Map<String, String[]> parameterMap = webRequest.getParameterMap();
Map<String, String> result = CollectionUtils.newLinkedHashMap(parameterMap.size());
parameterMap.forEach((key, values) -> {
if (values.length > 0) {
result.put(key, values[0]);
}
});
//here will return a map object. then you convert map to your object, I don't know how to convert , but you have achieve it.
return o;
}
}
add to container
#Configuration
#EnableWebMvc
public class ExampleMvcConfiguration implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new ExampleArgumentResolver());
}
}
usage
#RestController
public class TestCtrl {
#GetMapping("api")
public Object gg(#Example SearchDto searchDto) {
System.out.println(searchDto);
return "1";
}
#Data
public static class SearchDto {
String name;
List<Code> code;
}
#Data
public static class Code {
String type;
String value;
}
}
Here is a demo.

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

Custom error message containing parameter names when validation fails

I would like my API to return errorMessage when the request lacks of required parameters. For example let's say there is a method:
#GET
#Path("/{foo}")
public Response doSth(#PathParam("foo") String foo, #NotNull #QueryParam("bar") String bar, #NotNull #QueryParam("baz") String baz)
where #NotNull is from package javax.validation.constraints.
I wrote an exception mapper which looks like this:
#Provider
public class Mapper extends ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException) {
Iterator<ConstraintViolation<?>> it= exception.getConstraintViolations().iterator();
StringBuilder sb = new StringBuilder();
while(it.hasNext()) {
ConstraintViolation<?> next = it.next();
sb.append(next.getPropertyPath().toString()).append(" is null");
}
// create errorMessage entity and return it with apropriate status
}
but next.getPropertyPath().toString() returns string in format method_name.arg_no, f.e. fooBar.arg1 is null
I'd like to receive output fooBar.baz is null or simply baz is null.
My solution was to include -parameters parameter for javac but to no avail.
Probably I could somehow achieve it with the use of filters:
public class Filter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
UriInfo uriInfo = requestContext.getUriInfo();
UriRoutingContext routingContext = (UriRoutingContext) uriInfo;
Throwable mappedThrowable = routingContext.getMappedThrowable();
if (mappedThrowable != null) {
Method resourceMethod = routingContext.getResourceMethod();
Parameter[] parameters = resourceMethod.getParameters();
// somehow transfer these parameters to exceptionMapper (?)
}
}
}
The only problem with the above idea is that ExeptionMapper is executed first, then the filter is executed. Also I have no idea how could I possibly transfer errorMessage between ExceptionMapper and Filter. Maybe there is another way?
You can inject ResourceInfo into the exception mapper to get the resource method.
#Provider
public class Mapper extends ExceptionMapper<ConstraintViolationException> {
#Context
private ResourceInfo resourceInfo;
#Override
public Response toResponse(ConstraintViolationException ex) {
Method resourceMethod = resourceInfo.getResourceMethod();
Parameter[] parameters = resourceMethod.getParameters();
}
}

Jersey 2 - How to access multiple resource in a single request

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

cxf interceptor intercept the request, but i can not find out the request params in the message

My native language is not English and my English is poor,so I apologize if anything isn't clear.
I have searched, but it did not help.
I have a web service using cxf framework, I want to use an interceptor to intercept the request which is passed by the service invoker. I can intercept the request but I can`t find out the request params.
Here is my service interface:
public int modCredenceForUser(#WebParam(name = "operatorId", mode = WebParam.Mode.IN) String operatorId,
#WebParam(name = "userCredenceVO", mode = WebParam.Mode.IN) Holder<UserCredenceVO> userCredenceVO,
#WebParam(name = "res", mode = WebParam.Mode.OUT) Holder<Response> res)
Here is part of my service invoker code:
userCredenceVO = new UserCredenceVO();
......
......
Holder<UserCredenceVO> userCredenceVOHolder = new Holder<UserCredenceVO>(userCredenceVO);
String operatorId = ServiceInvokeUtil.getOperatorId();
int result = service.modCredenceForUser(operatorId, userCredenceVOHolder, res);
Here is part of my service provider code:
public class AuthenticationInterceptor extends
AbstractPhaseInterceptor {
public AuthenticationInterceptor(){
super(Phase.RECEIVE);
}
/*
#Override
public void handleFault(Message message) {
super.handleFault(message);
}
*/
public void handleMessage(Message message) {
......
......
}
}
I want to find out the request param "userCredenceVO", but how could I find it inthe message.
This problem confused me a day, any help I would appreciate.
The problem had been solved, the right codes as follows:
public class AuthenticationInterceptor extends
AbstractPhaseInterceptor<SoapMessage> {
public AuthenticationInterceptor(){
super(Phase.PRE_INVOKE);
}
#Override
public void handleFault(SoapMessage message) {
super.handleFault(message);
}
#Override
public void handleMessage(SoapMessage message) throws Fault {
Set _set = message.getContentFormats();
Iterator classIterator = _set.iterator();
while (classIterator.hasNext()) {
Class _class = (Class) classIterator.next();
Object _obj = message.getContent(_class);
}
}
}
The _obj contains the request params which is passed by service invoker.

Categories

Resources