resteasy confusion between two URL - java

I have 2 different URLs :
GET /stuff/{id} (where id is an Integer)
GET /stuff/foo?bar={someValue} (foo is not an Integer, it is a hard coded String)
When calling /stuff/foo&bar=someValue, I have the following error:
Failed executing GET /stuff/foo&bar=someValue
...
Caused by: java.lang.NumberFormatException: For input string: "foo&bar=someValue"
The code is :
#GET
#Path("/stuff/{id}")
public Response getById(#PathParam("id") int id) {
// code
}
#GET
#Path("/stuff/foo")
public Response foo(#QueryParam("bar") String bar) {
// code
}
I am using RESTEasy and I would like to keep my URLs that way if possible. Obviously RESTEasy just tries the getById method where it should use the foo method.
How can I do to make this work in RESTEasy ? ("change your URLs" is not an answer without detailed explanations about RESTEasy limitations). I tried (to be sure) putting foo code before getById but I had the same error (of course).
Is there any priority notion between declared URLs ?
As a note : I already implemented that kind of URLs in another framework (python-flask) and it worked just fine : you just have to be careful to declare /stuff/foo before /stuff/{id} (specific case before a more generic one).
EDIT: I just made a stupid mistake ! I was calling /stuff/foo&bar=someValue where I should be calling /stuff/foo?bar=someValue. Thanks #Scobal for pointing it out !

You are calling GET /stuff/foo&bar=someValue
You should be calling GET /stuff/foo?bar=someValue
RESTEasy is trying to parse foo&bar=someValue as the {id} field.
I can't give you an answer about RESTEasy URL priorities but you could do this:
#GET
#Path("/stuff/{id}")
public Response getById(#PathParam("id") String id, #QueryParam("bar") String bar) {
try {
int intId = Integer.parseInt(id);
// do int id things
} catch(NumberFormatException e) {
// do foo + bar things
}
}

Related

Rest controller - Tyr to reduce the number of endpoint with parameters but get Ambiguous mapping error

I want to implement some simple endpoint in spring, trying to be as much Restful as possible and reduce the number of URL to use. Here are the GET url I want to call: (this is a simplified version)
GET /users
GET /users?id=123
GET /users?username=xyz
I used this controller:
#GetMapping()
public #ResponseBody
OutputUserDTO getUserByParameter(#RequestParam(required = false) String id,
#RequestParam(required = false) String username) {
if (id != null && !id.isEmpty()) {
return userService.getUserById(id);
}
if (username != null && !username.isEmpty()) {
return userService.getUserByUsername(username);
}
throw new MissingParameterException("...some message...");
}
#GetMapping()
public #ResponseBody
List<OutputUserDTO> getUsers() {
return userService.getUsers();
}
Of course I get an error, that is Ambiguous mapping.
I thought to always return a List so that I can merge the 2 endpoints and, in case you pass some parameters, return a Singleton... even though I don't know if it's a correct practice.
Or else, create one endpoint for each parameter, GET /users/{userId}, GET /users/{username}, ... but I don't like it neither (If I have 10 different way to get a user then I'll have to implement 10 endpoints :S)
What are some good practices in this case??
Thanks.
Replace MissingParameterException with return userService.getUsers();, and get rid of the other method, you know, the one with exactly the same mapping as the first method.
To make that work, you'd have to change return type to Object, which is not going to be a problem, since it's the actual object returned that controls the effect of #ResponseBody, not the declared type.
#GetMapping()
#ResponseBody
public Object getUserByParameter(#RequestParam(required = false) String id,
#RequestParam(required = false) String username) {
if (id != null && ! id.isEmpty()) {
return userService.getUserById(id);
}
if (username != null && ! username.isEmpty()) {
return userService.getUserByUsername(username);
}
return userService.getUsers();
}
FYI: #ResponseBody is a method-level annotation, so it should be listed before any keyword modifiers.
The Java Language Specification, section 8.3.1. Field Modifiers, says:
FieldModifier:
(one of)
Annotation public protected private
static final transient volatile
[...]
If two or more (distinct) field modifiers appear in a field declaration, it is customary, though not required, that they appear in the order consistent with that shown above in the production for FieldModifier.
It should be like #GetMapping("/users") on respective method
http://www.appsdeveloperblog.com/pathvariable-spring-mvc/
I suppose that the reason for that is, in getUserByParameter, both parameters are optional but if both the parameters are not passed it will conflict with your second getMapping.
more over, what is returned changes in the three scenarios. scenario 1 returns a list of DTOs while scenarios 2 & 3 return a single DTO
i dont think you can handle all three scenarios using your request path /users unless you want to wrap even a single DTO in a list, in which case you can simply merge your two methods. call getUsers() when both parameters are missing, in other cases, do what you currently do but wrap the response in a list.
if you want to keep them separate and return DTO or List, you should probably separate them out into /users and /user by specifying #GetMapping("/user") on method one and #GetMapping("/users") on method two
hope this helps

Angular HTTP POST request to spring boot resulting in null value

I was having some problem when trying to execute POST request with multiple parameters. Here is my service.ts class written in typescript:
retrieve(packageId: any[], date: any) {
console.log(packageId);
console.log(date);
let packgeIdStr: string = JSON.stringify(packageId);
const data = { stringArrDo: packgeIdStr, date: date };
const url = this.serviceAPI + '/...../retrieve';
return this.http.post<SRFDO[]>(
url,
{ params: data }
);
}
When I print out in my console, I managed to get the data here.
However, when I pass the data to my controller written in Java, the parameters became null:
#PostMapping("/retrieve")
public DO[] retrieve(#RequestBody String stringArrDo, java.sql.Date date)
throws RemoteException, JaMClientRemoteException, JaMException {
System.out.println(stringArrDo);
System.out.println("date :"+ date);
// removed code
}
I not sure why the data is printed out at service.ts but when passing it to the controller then became null. Any ideas?
Thanks!
You can open up your Chrome dev tools, go to the Network tab, and inspect your POST to see if angular is actually passing the right stuff to the right endpoint. That will tell you if your problem is client-side vs server-side.
Edit:
It looks like your server may be expecting an array of strings, but you're sending an object. In javascript, when you do {var1, var2}, that's just shorthand for {var1: var1Value, var2: var2Value}
Edit 2: I looked up spring, and it looks like you're using #RequestBody incorrectly. It looks like you're supposed to pass it a class that it can deserialize the JSON into, like
public class RequestData {
private String[] packageIds;
private Date date;
// ...
}
You've also put your data under the top-level variable params, so you'd want to remove that and put all your stuff at the top level of your object.
Try reading through this tutorial for more info: https://www.baeldung.com/spring-request-response-body
The attributes name on two classes may be different.
class A{
// class in springboot
private String name;
}
class A {
//class in angular
data: String;
}
In this case, attribute name is main cause for null pointer exception in server side.

Getting a Return Value from a Bean in Camel (Java)

I am having a bit of trouble getting a return value from a bean in camel and using it in my route.
I have a route that looks like this:
from(file:test/?delete=true)
.unmarshal(jaxb)
.bean(testBean, "testMethod")
.to(direct:nextRoute);
The bean looks something like this:
public void testBean (PojoName pojoInstance){
//do stuff
int i= 75; //a number I generate within the bean after I've started this method
}
I want to use the number I generate inside of my bean and in my route. Something like this:
from(file:test/?delete=true)
.unmarshal(jaxb)
.bean(testBean, "testMethod")
.log(integer generated from testBean)
.to(direct:nextRoute);
What I tried:
So, instead of returning void in my bean, I changed the return type to an int and returned the integer. Then, I was hoping to do something like this in my route:
.log("${body.intFromBean}")
My thinking is that once I return the value from a bean, it should store that value in the exchange body (at least that's what I'm getting from the Camel documentation). Then, I could access it in my route.
The Problem:
However, when I change the testBean return type to an int, I get the following errors:
org.apache.camel.CamelExecutionException: Execution occurred during execution on the exchange
Caused by: org.apache.camel.InvalidPayloadException: No body available of type: PojoName but has value: numberIGenerated of type java.lang.Integer
(Sorry I don't have the full stack trace. I'm using the s.o mobile app)
My question:
From reading some other s.o. submissions, I think I understand the problem. The message body is of one type and the return type is another. However, even when I tried using .
.convertTo(Integer.class)
before calling the bean, but that didn't work either. (Conceptually, that wouldn't work either because if I converted it to an int right after I unmarshalled it, I wouldn't be able to use the unmarshalled data. But I thought I'd try it anyway).
Can someone help me understand how I can properly return the integer and use it in my route?
I've read the documentation on bean binding and the exchange, and I thought I understood it well enough to do this. But I must be missing something.
I think a simpler solution would be:
public class TestBean {
public int testMethod() {
return 75;
}
}
Whether you want the return result to be stored in a header or in the body should be up to the route definition.
As you read in the Camel documentation, the default behavior is to set the return value in the body:
TestBean testBean = new TestBean();
from("file:test/?delete=true")
.unmarshal(jaxb)
.bean(testBean, "testMethod")
.log("${body}")
.to("direct:nextRoute");
And if you want it in a header:
TestBean testBean = new TestBean();
from("file:test/?delete=true")
.unmarshal(jaxb)
.setHeader("MyMagicNumber", method(testBean, "testMethod"))
.log("${header.MyMagicNumber}")
.to("direct:nextRoute");
Be careful that if you use a Camel version older than 2.10, you will need to use the (now deprecated) "bean" method instead of the "method" method :-)
Depending on what you need to use it for you can either add it to the header or you can make it the body.
To add it to the header(key/value) do the following:
public class TestBean
{
#Handler
public void testMethod
(
#Body Message inMessage,
#Headers Map hdr,
Exchange exch
) throws Exception
{
int i= 75;
hdr.put("MyMagicNumber", i);
}
}
Your "return" result is now stored in the header and you can read it from there in the steps that follow.
For the body do the following:
public class TestBean
{
#Handler
public void testMethod
(
#Body Message inMessage,
#Headers Map hdr,
Exchange exch
) throws Exception
{
int i= 75;
inMessage.setBody(i);
}
}
The body of the message will now contain i.

Trigger Spring argument mapping manually?

I've a nicely working Spring argument mapping for my arguments, unfortunately I've one component which isn't passed through the DispatcherServlet and I struggle to find a way to get the mapping to run somehow manually.
An example would look like this:
public class Bar {
public Bar(#RequestParam("map") String map) {
System.out.println("Found: " + map); // Expected result "Found: value
}
}
public class Foo {
public static void main(String[] args) {
context = new ClassPathXmlApplicationContext("META-INF/context.xml");
String pseudoUrl = "/?map=value"
Bar bar = context.getBeans('bar', pseudoUrl);
}
}
It that something which could work somehow? As you can imaging the passed around data has a more complex structure and therefore I'd like to avoid that I've to write an parser on my own.
Cheers.
You could basically follow the same algorithm that DispatcherServlet.doDispatch() does:
for each HandlerMapping in the context, (line 1058 of DispatcherServlet in spring-webmvc.jar 3.1.2-RELEASE), test if HandlerMapping.getHandler(request) returns non-null
for each HandlerAdapter in the context, (line 1091) test if HandlerAdapter.supports(handlerMapping) returns true
execute HandlerAdapter.handle(request, ...) and handle the output (line 923)
This would require you to bundle up your pseudoUrl into a pseudo-HttpServletRequest, etc.
On the other hand, if you know that you always want to get a specific bean from the context in your main() method, I would just ask Spring for that bean and invoke the method explicitly, rather than dealing with mapping beans to a URL (which I assume does not change).

Jersey RESTfull service with XML (encapsulation outside the JSON-Object needed)

I try to implement a restful webservice in Java together with Jersey.
To communicate between the client- and server-side i´m watching out for XML.
I already tried JSON.
When using JSON, the encapsulation is in the POJO-Object like:
#XmlRootElement
public class MyPojo {
public int a;
public int[] b;
}
Then I just got a header in the Rest-Class like
public String classname(MyPojo p)
But I need a header like
public String classname(int a, int [] b)
to create Form-Elements automatically by reading the Rest-Headers.
An example showed me that:
#Consumes("application/xml")
public classname methodname(#QueryParam("a") Integer a, #QueryParam("b") IntArray b)
should work.
Question: How can I create a XML-Request (like JSON.stringify() in XML) for this method? Is there maybe a better way doing this?
Not sure if I understand the question, but will try to provide some hints - hopefully at least some of it will be relevant. If not, please share more about your app (i.e. is this for GET or POST requests? why is it important to have 2 separate parameters, etc.)
If you need to send XML or JSON in the request entity (e.g. in a POST request), then it is not possible to retrieve these in multiple parameters - you have to live with the single object parameter as you have above. What you could do is the following:
#POST
#Consumes("application/xml")
public ClassName postMethod(MyPojo p) {
return postMethod(p.a, p.b);
}
public ClassName postMethod(int a, int[] b) {
// do something
}
Or, if you don't really need XML/JSON, if you are POSTing using HTML forms, typically you do the following:
#POST
#Consumes("application/x-www-form-urlencoded")
public ClassName postMethod(#FormParam("a") Integer a, #FormParam("b") String b /*I think arrays are not supported - will have to parse it yourself*/) {
// do something
}

Categories

Resources