I am using JAX-RS with RESTEasy.
I want to know if we can have represent different resources with path differentiated only by order and number of query parameters?
e.g.
/customer/1234
/customer?id=1234
/customer?name=James
Can I create three different methods e.g.
#Path("customer/{id}")
public Response get(#PathParam("id") final Long id) {
..
}
#Path("customer?id={id}")
public Response get(#QueryParam("id") final Long id) {
..
}
#Path("customer?name={name}")
public Response get(#QueryParam("name") final String name) {
..
}
Will this work, can I invoke different methods by differentiating path like this?
Thanks
This is a valid #Path:
#Path("customer/{id}") // (1)
These are not:
#Path("customer?id={id}") // (2)
#Path("customer?name={name}") // (3)
They are the same because the boil down to
#Path("customer")
which you could use.
So you can have (1) and one of (2) and (3). But you can't have (2) and (3) at the same time.
#QueryParam parameters are not part of the #Path. You can access them as you do in the signature of the method but you can't base the routing of JAX-RS on them.
Edit:
You can write one method that accepts both id and name as #QueryParam. These query parameters are optional.
#Path("customer")
public Response get(#QueryParam("id") final String id,
#QueryParam("name") final String name) {
// Look up the Customers based on 'id' and/or 'name'
}
Related
I have two Rest URIs :
// URI n1 : GET /users/{userName}
public ResponseEntity<userDto> findUserByName(
#PathVariable( value = "userName", required = true)
String userName
);
// URI n2 : GET /users/{userID}
public ResponseEntity<userDto> findUserByID(
#PathVariable( value = "userID", required = true)
Long userID
);
When I call GET /users/SuperUser123 I want the first function to respond and when I call GET /users/1854 I want the second one respond. What really happens is that the first function is always called for both cases (as the param is always of type String).
So how can I achieve what I want while respecting REST API URI recommendations ?
It will give ambiguous mapping runtime exception as the url pattern is same for both the methods.
If your url has some pattern like starting for superuser or something then you can use regex patterns to make it work.
In below example first method method will get called if the path variable is a digit otherwise second method for alphabets.you can change regex pattern accordingly.
#RequestMapping("{id:[0-9]+}")
public String handleRequest(#PathVariable("id") String userId, Model model){
model.addAttribute("msg", "profile id: "+userId);
return "my-page";
}
#RequestMapping("{name:[a-zA-Z]+}")
public String handleRequest2 (#PathVariable("name") String deptName, Model model) {
model.addAttribute("msg", "dept name : " + deptName);
return "my-page";
}
I'm trying to understand how to properly implement a microservice pattern with REST endpoints.
I understand the very basics of it. The internet is full of that. Typical example:
class User
{
public User GetUser(int id) { ... }
public User GetUsers() { ... }
public User PutUser(User user) { ... }
public User PostUser(User user) { ... }
}
So if I want one User then I'd do a GET request to GetUser(100), and it would return a User object as JSON.
But suppose I have a page where I want to list the firstname and lastname of all users. Then I could do a GET request to GetUsers(). But if the User table has 100 columns in the Database and I only need to display two columns (firstname, lastname), then it's overkill to get the other 98 columns along with it.
Maybe on another page I need to display 40 of the 100 columns. And on some other page 20 of the 100 columns.
So that means I need 3 extra end points that all return all users. But each end point should return different data.
How would I name those new endpoints?
Do I have to call those 3 endpoints explicitly by name? (Ex: GetUsersNames() GetUsersAge() etc?)
PS. This is probably a poor example, but I hope you understand what I'm getting at. I don't know what to do / how to name the endpoints when I go beyond the default GET/PUT/POST/DELETE methods.
What you can do is to ask for the particular fields explicitly, so:
GET /api/users/?fields=firstName,lastName
This is much better than introducing new endpoints.
You can define custom views by using Attribute Routing. The methods you have in your controller default to the route pattern that you define in your configuration.
You can however explicitely define additional routes. You need to include this in your Configuration:
// this enables route attributes (route annotations at the actions in the controller)
config.MapHttpAttributeRoutes();
Then you can define additional routes in your controller and add the required filter logic:
[Route("api/users/range/{rangeFrom}/{rangeTo}")]
[HttpGet]
public IHttpActionResult GetUsers(int from, int to)
{
return Ok(Users.Skip(from).Take(to-from));
}
[Route("api/users/namesonly")]
[HttpGet]
public IHttpActionResult GetUsers()
{
return Ok(Users.Select(u => new { firstName = u.FirstName, lastName = u.LastName }));
}
Reference: Attribute Routing in ASP.NET Web API 2
EDIT:
Yes, not all options should be resolved in separate endpoints, but it's an option at a certain point. What others do: Use URI parameters to limit returned data count or set flags, and have them available through the method's signature e.g.:
[Route("api/users/")]
[HttpGet]
public IHttpActionResult GetUsers([FromUri]int? from, [FromUri]int? count)
{
return Ok(Users.Skip(from ?? 0).Take(count ?? Users.Count));
}
Or to have shorter user objects returned:
[Route("api/users/")]
[HttpGet]
public IHttpActionResult GetUsers([FromUri]bool? shortVersion)
{
if (shortVersion.HasValue && shortVersion.Value)
return Ok(Users.Select(u => new { firstName = u.FirstName, lastName = u.LastName }));
else
return Ok(Users);
}
We are trying to provide a clean URI structure for external endpoints to pull json information from CQ5.
For example, if you want to fetch information about a particular users history (assuming you have permissions etc), ideally we would like the endpoint to be able to do the following:
/bin/api/user/abc123/phone/555-klondike-5/history.json
In the URI, we would specifying /bin/api/user/{username}/phone/{phoneNumber}/history.json so that it is very easy to leverage the dispatcher to invalidate caching changes etc without invalidating a broad swath of cached information.
We would like to use a sling servlet to handle the request, however, I am not aware as to how to put variables into the path.
It would be great if there were something like #PathParam from JaxRS to add to the sling path variable, but I suspect it's not available.
The other approach we had in mind was to use a selector to recognise when we are accessing the api, and thus could return whatever we wanted to from the path, but it would necessitate a singular sling servlet to handle all of the requests, and so I am not happy about the approach as it glues a lot of unrelated code together.
Any help with this would be appreciated.
UPDATE:
If we were to use a OptingServlet, then put some logic inside the accepts function, we could stack a series of sling servlets on and make the acceptance decisions from the path with a regex.
Then during execution, the path itself can be parsed for the variables.
If the data that you provide comes from the JCR repository, the best is to structure it exactly as you want the URLs to be, that's the recommended way of doing things with Sling.
If the data is external you can create a custom Sling ResourceProvider that you mount on the /bin/api/user path and acquires or generates the corresponding data based on the rest of the path.
The Sling test suite's PlanetsResourceProvider is a simple example of that, see http://svn.apache.org/repos/asf/sling/trunk/launchpad/test-services/src/main/java/org/apache/sling/launchpad/testservices/resourceprovider/
The Sling resources docs at https://sling.apache.org/documentation/the-sling-engine/resources.html document the general resource resolution mechanism.
It is now possible to integrate jersy(JAX-RS) with CQ. We are able to create primitive prototype to say "Hello" to the world.
https://github.com/hstaudacher/osgi-jax-rs-connector
With this we can use the #PathParam to map the requests
Thanks and Regards,
San
There is no direct way to create such dynamic paths. You could register servlet under /bin/api/user.json and provide the rest of the path as a suffix:
/bin/api/user.json/abc123/phone/555-klondike-5/history
^ ^
| |
servlet path suffix starts here
then you could parse the suffix manually:
#SlingServlet(paths = "/bin/api/user", extensions = "json")
public class UserServlet extends SlingSafeMethodsServlet {
public void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) {
String suffix = request.getRequestPathInfo().getSuffix();
String[] split = StringUtils.split(suffix, '/');
// parse split path and check if the path is valid
// if path is not valid, send 404:
// response.sendError(HttpURLConnection.HTTP_NOT_FOUND);
}
}
The RESTful way to approach this would be to have the information stored in the structure that you want to use. i.e. /content/user/abc123/phone/555-klondike-5/history/ would contain all the history nodes for that path.
In that usage. you can obtain an out of the box json response by simply calling
/content/user/abc123/phone/555-klondike-5/history.json
Or if you need something in a specific json format you could use the sling resource resolution to use a custom json response.
Excited to share this! I've worked ~ a week solving this, finally have the best Answer.
First: Try to use Jersey
The osgi-jax-rs-connector suggested by kallada is best, but I couldn't get it working on Sling 8. I lost a full day trying, all I have to show for it are spooky class not found errors and dependency issues.
Solution: The ResourceProvider
Bertrand's link is for Sling 9 only, which isn't released. So here's how you do it in Sling 8 and older!
Two Files:
ResourceProvider
Servlet
The ResourceProvider
The purpose of this is only to listen to all requests at /service and then produce a "Resource" at that virtual path, which doesn't actually exist in the JCR.
#Component
#Service(value=ResourceProvider.class)
#Properties({
#Property(name = ResourceProvider.ROOTS, value = "service/image"),
#Property(name = ResourceProvider.OWNS_ROOTS, value = "true")
})
public class ImageResourceProvider implements ResourceProvider {
#Override
public Resource getResource(ResourceResolver resourceResolver, String path) {
AbstractResource abstractResource;
abstractResource = new AbstractResource() {
#Override
public String getResourceType() {
return TypeServlet.RESOURCE_TYPE;
}
#Override
public String getResourceSuperType() {
return null;
}
#Override
public String getPath() {
return path;
}
#Override
public ResourceResolver getResourceResolver() {
return resourceResolver;
}
#Override
public ResourceMetadata getResourceMetadata() {
return new ResourceMetadata();
}
};
return abstractResource;
}
#Override
public Resource getResource(ResourceResolver resourceResolver, HttpServletRequest httpServletRequest, String path) {
return getResource(resourceResolver , path);
}
#Override
public Iterator<Resource> listChildren(Resource resource) {
return null;
}
}
The Servlet
Now you just write a servlet which handles any of the resources coming from that path - but this is accomplished by handling any resources with the resource type which is produced by the ResourceProvider listening at that path.
#SlingServlet(
resourceTypes = TypeServlet.RESOURCE_TYPE,
methods = {"GET" , "POST"})
public class TypeServlet extends SlingAllMethodsServlet {
static final String RESOURCE_TYPE = "mycompany/components/service/myservice";
#Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
final String [] pathParts = request.getResource().getPath().split("/");
final String id = pathParts[pathParts.length-1];
response.setContentType("text/html");
PrintWriter out = response.getWriter();
try {
out.print("<html><body>Hello, received this id: " + id + "</body></html>");
} finally {
out.close();
}
}
}
Obviously your servlet would do something much more clever, such as process the "path" String more intelligently and probably produce JSON.
Lets say I have an already functioning Play 2.0 framework based application in Scala that serves a URL such as:
http://localhost:9000/birthdays
which responds with a listing of all known birthdays
I now want to enhance this by adding the ability to restrict results with optional "from" (date) and "to" request params such as
http://localhost:9000/birthdays?from=20120131&to=20120229
(dates here interpreted as yyyyMMdd)
My question is how to handle the request param binding and interpretation in Play 2.0 with Scala, especially given that both of these params should be optional.
Should these parameters be somehow expressed in the "routes" specification? Alternatively, should the responding Controller method pick apart the params from the request object somehow? Is there another way to do this?
Encode your optional parameters as Option[String] (or Option[java.util.Date], but you’ll have to implement your own QueryStringBindable[Date]):
def birthdays(from: Option[String], to: Option[String]) = Action {
// …
}
And declare the following route:
GET /birthday controllers.Application.birthday(from: Option[String], to: Option[String])
A maybe less clean way of doing this for java users is setting defaults:
GET /users controllers.Application.users(max:java.lang.Integer ?= 50, page:java.lang.Integer ?= 0)
And in the controller
public static Result users(Integer max, Integer page) {...}
One more problem, you'll have to repeat the defaults whenever you link to your page in the template
#routes.Application.users(max = 50, page = 0)
In Addition to Julien's answer. If you don't want to include it in the routes file.
You can get this attribute in the controller method using RequestHeader
String from = request().getQueryString("from");
String to = request().getQueryString("to");
This will give you the desired request parameters, plus keep your routes file clean.
Here's Julien's example rewritten in java, using F.Option: (works as of play 2.1)
import play.libs.F.Option;
public static Result birthdays(Option<String> from, Option<String> to) {
// …
}
Route:
GET /birthday controllers.Application.birthday(from: play.libs.F.Option[String], to: play.libs.F.Option[String])
You can also just pick arbitrary query parameters out as strings (you have to do the type conversion yourself):
public static Result birthdays(Option<String> from, Option<String> to) {
String blarg = request().getQueryString("blarg"); // null if not in URL
// …
}
For optional Query parameters, you can do it this way
In routes file, declare API
GET /birthdays controllers.Application.method(from: Long, to: Long)
You can also give some default value, in case API doesn't contain these query params it will automatically assign the default values to these params
GET /birthdays controllers.Application.method(from: Long ?= 0, to: Long ?= 10)
In method written inside controller Application these params will have value null if no default values assigned else default values.
My way of doing this involves using a custom QueryStringBindable. This way I express parameters in routes as:
GET /birthdays/ controllers.Birthdays.getBirthdays(period: util.Period)
The code for Period looks like this.
public class Period implements QueryStringBindable<Period> {
public static final String PATTERN = "dd.MM.yyyy";
public Date start;
public Date end;
#Override
public F.Option<Period> bind(String key, Map<String, String[]> data) {
SimpleDateFormat sdf = new SimpleDateFormat(PATTERN);
try {
start = data.containsKey("startDate")?sdf.parse(data.get("startDate") [0]):null;
end = data.containsKey("endDate")?sdf.parse(data.get("endDate")[0]):null;
} catch (ParseException ignored) {
return F.Option.None();
}
return F.Option.Some(this);
}
#Override
public String unbind(String key) {
SimpleDateFormat sdf = new SimpleDateFormat(PATTERN);
return "startDate=" + sdf.format(start) + "&" + "endDate=" + sdf.format(end);
}
#Override
public String javascriptUnbind() {
return null;
}
public void applyDateFilter(ExpressionList el) {
if (this.start != null)
el.ge("eventDate", this.start);
if (this.end != null)
el.le("eventDate", new DateTime(this.end.getTime()).plusDays(1).toDate());
}
}
applyDateFilter is just a convienence method i use in my controllers if I want to apply date filtering to the query. Obviously you could use other date defaults here, or use some other default than null for start and end date in the bind method.
How should I define route in my global.asax to be able use nullable parameters and coma as separator?
I'm trying to implement routing rule for my search users page like
"{Controller}/{Action},{name},{page},{status}"
Full entry from the Global.asax:
routes.MapRoute(
"Search",
"{controller}/{action},{name},{page},{status}",
new { controller = "User", action = "Find",
name = UrlParameter.Optional,
page = UrlParameter.Optional,
status = UrlParameter.Optional }
);
Routine defined like above works fine when I'm entering all parameters, but when some parameters are equal to null routing fails (for example "user/find,,,")
According to Clicktricity comment bellow - the singature of action method that handes the request:
public ActionResult Find(string userName, int? page, int? status)
{
// [...] some actions to handle the request
}
On the beginning I was testing the route by VS debugger, now I'm using route debugger described on Phil's Haack blog. The tool confirm - that routing with null values is not properly handled (or I'm doing something wrong ;) )
As far as I know .Net routing doesn't let you do multiple nullable parameters like that. Multiple parameters will only work if they are missing working backwards from the end and with the separator also missing so you'd get matches on
user/find,bob,2,live
user/find,bob,2
user/find,bob
user/find
It'd be a lot easier to use querystrings for what you're trying to do.
Edit based on comment:
If this is necessary then you could try doing it this way (though it's not a nice approach)
Change your path to match
{Controller}/{Action},{*parameters}
Make sure to put a constraint on the action and controller so this is limited to as few as possible.
Rename each action that would take your full list to something else, adding a standard prefix to each one would be the cleanest way, and add the [NonAction] attribute. Add a new method with the original name that takes a string, this string is a comma separated string of your variables. In this method split the string and return the original action passing in the values from the split.
So you go from:
public ActionResult Find(string name, int page, string status){
//Do stuff here
return View(result);
}
To
public ActionResult Find(string parameters){
string name;
int? page;
string status;
//split parameters and parse into variables
return FindAction(name, page, status);
}
[NonAction]
public ActionResult FindAction(string parameters){
//Do what you did in your previous Find action
return View(results);
}