I've noticed a weird behaviour in #QueryParam annotation from javax.ws.rs.QueryParam;
With reference to the snippet of code below, when I set the url to something like:
http://host:port/services/serv123?test=OK
I can retrieve the value of 'test' as expected.
However, when I set the url to something like:
http://host:port/services/serv123#top?test=OK
#QueryParam("test") returns null.
Here is the code I am using. Each annotation is explicitly imported.
#Path("/services")
public class Services {
[...]
#GET
#Path("/{srvID}")
#Produces(MediaType.TEXT_HTML)
public ServicesView getServiceDetailPage(#PathParam("srvID") String srvId,
#QueryParam("test") String test) {
[...]
return new ServicesView([...]);
}
[...]
}
I have tested this in Dropwizard 0.9.1 (and included Jersey)
Please note:
I'm interested in the explanation of the behaviour, not in a workaround (I have already a couple and can post them if someone is interested).
To my knowledge, http://host:port/services/serv123#top?test=OK is a perfectly legitimate url and #QueryParam should be able to handle it (I'm happy to be proven wrong, just explain why it would not be a legal url).
For the sake of testing, I have tried also encoding '#' into %23%3F but, as expected, it did not work.
What am I missing?
Instead of
http://host:port/services/serv123#top?test=OK
use
http://host:port/services/serv123?test=OK#top
if you want ?test=OK to be a query parameter instead of part of the anchor.
Related
I'm facing the following issue and have been able to find a proper fix.
As a use case example, let's imagine a Rest client that fetches a json object from a server, where the request is the path to the object. This api does not accept query parameters nor bodies, and no catalogue is available.
say that I have the following RestClient:
#Path("/json")
#RegisterRestClient(configKey="json-api")
public interface JsonService {
#GET
#Path("/{MyVariableLengthEndpoint}")
Response getJson(#PathParam("MyVariableLengthEndpoint") ????? endpoint);
}
Examples of requests could be :
/json/employees/Dwight/jobs/assistantRegionalManager/salary
/json/games/theLastOfUs/rating
Passing a string with / characters gets encoded with %. To bypass this, I've tried:
Using the #Encoded annotation
Adding a regex in the pathParameter {MyVariableLengthEndpoint: .*}
Passing a List<PathSegment>
None of those worked.
Is there a proper way to do this ?
You should use Regex in your #Path definition, something like this:
#GET
#Path(“/{varPath : .+}”)
Response getJson(#PathParam(“varPath”) String endpoint);
This will match anything that comes after /json.
For more info search: “JAXRS path Regex”, on Google.
I have a problem getting Swagger to generate correct documentation for an API call that has an optional path parameter.
I'm building an API that peers into a hierarchical structure, similar to a file system. I want to call the same method to get the root structure as I do to get a sub resource. E.g:
Get the root: /folder
Get a sub folder: /folder/path/to
My Jax-rs method looks like this:
#GET #Path("folder{path:.*}")
Response folderContents(#ApiParam(value = "The folder to list", required = false) #PathParam("path") String path)
{...}
My method call works, but my swagger documentation is incorrect and doesn't work. Swagger-ui generates GET calls that look like this when I run it:
http://localhost:8080/storage-war/rest/filestore/folder{path:.*}
I'm looking for a way to either force Swagger to generate the correct signature or rebuild my regular expression so that my generated Swagger is correct.
Previously I'v tried using #Path("folder/{path:.}")*; his generated correct Swagger documentation but didn't match my no path given case. I've also tried #Path("/folder{p:/?}{path:(.)}")*; This produced a working method call but incorrect Swagger docs.
Is there a straightforward way to do what I'm looking for?
Edit:
In the end I created separate method calls for root and folders. Then I decorated the root call with it with #ApiOperation(hidden = true). This way I have an extra method in my code but only one method show up in my Swagger docs.
#GET #Path("folder/{path:.*}")
Response folderContents(#PathParam("path") String path)
{...}
#GET #Path("folder")
#ApiOperation(hidden = true)
Response rootContents()
{...}
In swagger, path parameters are always required. Understanding that in many frameworks and in practice they can be optional, but in the swagger definition they are required. See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#fixed-fields-7
Using JAX-RS, I have the following 3 #Paths.
#Path(JobRest.PATH)
#Api(value = JobRest.PATH, description = "REST APIs for Jobs")
public interface JobRest {
public static final String PATH = "/job";
#GET
#Path("/last")
#Produces(MediaType.APPLICATION_JSON)
public Job retrieveLastJob(...);
#GET
#Path("/{jobId}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Job retrieveJob(...., #PathParam("jobId") String jobId, );
#GET
#Produces(MediaType.APPLICATION_JSON)
public JobList retrieveAllJobs(....);
}
/job correctly calls retrieveAllJobs()
/job/1236 correctly calls retrieveJob(..., "1236", ...).
I expected that /job/last would call retrieveLastJob(...), since it matches, but it calls retrieveJob(..., "last", ...) instead.
How do I change the notation so that /job/last will call retrieveLastJob(...)?
TL;DR
Remove the #Consumes(MediaType.APPLICATION_JSON) on the retrieveJob method. For one, it does not accept a body, so it does not consume anything. Secondly it conflicts with the expected behavior.
I've tested with both Jersey and RESTeasy and it seems to be a difference in implementation. Jersey works fine with your code, while RESTeasy always hits the retrieveJob method, as you are experiencing.
Here's my take. If you look at the JAX-RS spec; 3.7.2 Request Matching, there's a semi-cryptic algorithm for matching resources, that goes something like this.
Get all matching resource class (by path), put them into a set.
Get all matching resource methods (by path), put them into a set.
Sort the methods by best matching path (most literal characters go first).
Sort by media type (with consumes and produces).
From my perspective, in this particular case, after step 3, the retrieveLastJob should automatically win, as it has the most literal characters. The producing media types are the same, and the consumes media type should not even matter, since it is a GET request with no Content-Type to do any matching.
My guess it RESTeasy still uses the annotation to sort even though it should not even be taken into consideration in this case. So it would appear that the method with the annotation is given more precedence, as it appears to be more specific, by way of just having an annotation, while the other does not. But that (step 4) level of specificity really shouldn't matter in this case.
I don't know if it's a bug against the spec. It's not too clear on how it should be handled, but I personally think the Jersey behavior is the correct behavior, for the simple fact that this level of specificity should not matter in this particular case. In any case, it is not correct to have the #Consumes annotation anyway for a GET request with no body.
I am trying to interact with a third party web service, who requires me to send a security token as a part of each request. The token is a node by itself, and I acquire it from the response of an initial call.
The web service endpoint is dotNet, and I have a Java client.
Apparently, the server side expects me to send the security token exactly like it was provided to me: literally the same string: so it won't do if its content has a different size, order, etc.
So, in SoapUI, everything works fine. There is a token in the response of the initial 'startSession' call, which I copy into the request of a next call.
But in Java (I tried JAX-WS and CXF generated code, both rely on JAXB) it doesn't work. I receive the token as an object after it is unmarshalled, and I use this object in the next call.
When marshalled and send, it is missing a namespace attribute in a subnode. The server side says it won't continue because the token is incorrect.
So, by using JAXB outbound logical handler functionality, I am able to add the missing namespace without any problems in the DOM source (I was also able to achieve this with a CXF interceptor).
The problem now is, that the attributes, when marshalled, are ordered in such a way that the result still not matches the provided token as it was before it was unmarshalled. Alhough it should not matter, the order of these attributes is crucial.
I have no idea how to solve this, unless it is possible to actually modify the output XML string. I even tried a dirty hack by removing all attributes from the subnode and replacing them with one attribute that visually looks the same; but then the outer two double quotes become single quotes...
I hope anyone has an idea. Because I have none.
Cheers.
UPDATE:
I should have mentioned that the attributes in question are namespace(d) attributes. The node should look like this:
<HawanedoSessionInfo xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.thecompany.com/Hawanedo/Business/v2.0c">
However, after using outbound JAXB handler to add the missing xmlns="...", my result looks like this:
<HawanedoSessionInfo xmlns="http://schemas.thecompany.com/Hawanedo/Business/v2.0c" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
In the HawanedoSessionInfo class, I used XmlType.proporder and #XmlAttribute like so:
#XmlType(name = "HawanedoSessionInfo", propOrder = {
"xsd",
"xsi",
"xmlns",
and some other non-attribute sub-elements..
private String xsd;
private String xsi;
private String xmlns;
#XmlAttribute(ns="http://schemas.thecompany.com/Hawanedo/Business/v2.0c")
public String getXsd() {
return xsd;
}
public void setXsd(final String xsd) {
this.xsd = xsd;
}
#XmlAttribute(ns="http://schemas.thecompany.com/Hawanedo/Business/v2.0c")
public String getXsi() {
return xsi;
}
public void setXsi(final String xsi) {
this.xsi = xsi;
}
#XmlAttribute
public String getXmlns() {
return xmlns;
}
public void setXmlns(final String xmlns) {
this.xmlns = xmlns;
}
So apparently the proporder option does not help in this case?
UPDATE 2:
Like I wrote in my answer, it now works. Based on this LINK,
in the HawanedoSessionInfo class I added:
#XmlCustomizer(HawanedoSessionInfoCustomizer.class)
I created the customizer class exactly as described in the linked page, and I added the jaxb.properties.
So I did two things:
1) I added my attributes to (the top of the already existing) propOrder attribute. I added the attributes as instance variables and created the getters/setters. I annotated the getters with XmlAttribute.
2) I implemented the XmlCustomizer solution.
Now comes the strange part. According to Fiddler, the order of the attributes is still not changed! But I must stress that this is now working, ONLY after implementing the Customizer. What is happening here? :)
So in principle you cannot control order of attributes in a standard way, but ....
Depending on jaxb /java version the order can be determined by alphabetical order of the names, the order of declaration.
You could try in your code if a) moving the fields around changes anything, b) renaming the fields (the XMLAttribute than have to map to original name).
If you are lucky, it will work. But of course it is a hack and will work till next jaxb/java update.
The JAXB providers (the actuall implementation can have extra features), that can be used to customized the marshalling process). For example I found that: https://community.oracle.com/thread/977397 abut eclipselink.
I am sure there was a way of intercepting the soap body before it is send or governing the data serialization before it is send. I can think how it was called but try to google the jaxws client customization. If you capture the whole soap message simple xslt transforamation could fix the attributes order.
I feel your pain. The whole point of using xml, jaxws and such is to make our life easier and then someone providers decide not to follow standards and you end up with a mess that you were trying to clean for few days. Good luck and maybe try to contact xml gurus from Eclipse Moxy
I am so happy right now, because I got it working and it only cost me a full week to do so...:) With help of #Zielu, I was pointed to this link with the EclipseLink XMLCustomizer solution as suggested by Blaise Doughan: XMLCustomizer solution
I took the code in my original question (underneath 'UPDATE') and added the exact solution as suggested. Not sure if it is all necessary, but it works. Thanks guys.
It's possible you can control the order by using,
#XmlType (propOrder={"prop1","prop2",..."propN"})
I should probably point out that Spring is not in and of itself necessarily crucial to this question, but I encountered this behavior while using Spring, so the question uses the situation in Spring in which I encountered this.
I have a controller class that maps requests for GET and POST requests to the same set of URLs for a particular form. This form has different URLs for different locales, but there is only one method for the GET request, and one for the POST, since the logic at the controller level for the form is identical for each locale site (but things deeper in the logic, like locale-specific validation, may be different). Example:
#Controller
public class MyFormController {
// GET request
#RequestMapping(value={"/us-form.html", "/de-form.html", "/fr-form.html"},
method={RequestMethod.GET})
public String showMyForm() {
// Do some stuff like adding values to the model
return "my-form-view";
}
// POST request
#RequestMapping(value={"/us-form.html", "/de-form.html", "/fr-form.html"},
method={RequestMethod.POST})
public String submitMyForm() {
// Do stuff like validation and error marking in the model
return "my-form-view"; // Same as GET
}
}
The form GET and POST works just fine when written like this. You'll notice that the String arrays used for the #RequestMapping values are identical. What I want to do is put those URLs into one spot (ideally a static final field in the controller) so that when we add new URLs (which correspond to the form in future localized sites), we can just add them in one spot. So I tried this modification to the controller:
#Controller
public class MyFormController {
// Moved URLs up here, with references in #RequestMappings
private static final String[] MY_URLS =
{"/us-form.html", "/de-form.html", "/fr-form.html"};
// GET request
#RequestMapping(value=MY_URLS, // <-- considered non-constant
method={RequestMethod.GET})
public String showMyForm() {
// Do some stuff like adding values to the model
return "my-form-view";
}
// POST request
#RequestMapping(value=MY_URLS, // <-- considered non-constant
method={RequestMethod.POST})
public String submitMyForm() {
// Do stuff like validation and error marking in the model
return "my-form-view"; // Same as GET
}
}
The problem here is that the compiler complains about the value attribute no longer being a constant. I am aware that Spring requires that value must be a constant, but I had thought that using a final field (or static final in my case) with an Array literal containing String literals would have passed as "constant". My suspicion here is that the array literal has to be constructed on the fly in such a way that it is uninitialized when the value attribute is parsed.
I feel like this shouldn't be a hard thing to figure out with a basic Java knowledge, but something is escaping me that I haven't been able to find any answers for after some research. Can someone confirm my suspicion and give a citation or good explanation for why that may be so, or deny my suspicion and explain what the actual issue is?
Note: I cannot simply combine the URLs into a Path Pattern, as each form URL is in its localized site's language, and matching on that would be impossible. I merely give the "/{locale}-form.html" strings above as my URLs for example's sake.
You're right, this is nothing to do with Spring, all Annotation parameters must be compile-time constants. That's a basic java language rule.
Marking the array reference as final doesn't cut it because this is still perfectly legal:
MY_URLS[0] = "es-form.html";
Also, how locked in are you into embedding locale into the url like that in the first place? Are you emulating legacy links? Spring has plenty of built in support for using the browser's actual locale.