Details
-
Type:
New Feature
-
Status:
Closed
-
Priority:
Major
-
Resolution: Fixed
-
Affects Version/s: None
-
Fix Version/s: Release 1.5
-
Component/s: ActionBean Dispatching
-
Labels:None
Description
One thing that comes up frequently is support for friendly URLs, e.g.:
/blog/2006/08/22
/user/6282/edit
and so on. While it's possible to acheive URLs like this using 3rd party tools like UrlRewriteFilter it would be nice if they were built directly into stripes because then all URL information could be kept in a single place for each class.
I'm envisaging an annotation something like this:
@UrlInfo("/{year}/{month}/{day}")
@UrlInfo("/{userId}/{event}")
that would inform Stripes how to map the extra pieces of information encoded in the URL.
Optionally this could also be specified with the existing UrlBinding annotations, e.g.
@UrlBinding("/blog/{year}/{month}/{day}")
If done right, the stripes url and link tags could also take advantage of this information to put certain parameters into the URL instead of a parameter string.
I'm very open to hearing alternative ideas around how to specify this, and other functionality that would be desirable.
Attachments
-
- CleanUrlParams.patch
- 11/Jul/08 8:26 AM
- 13 kB
- Andrew Jaquith
-
$i18n.getText("admin.common.words.hide")
- cleanurls-patches.zip
- 11/Jan/07 11:09 AM
- 5 kB
- Remi VANKEISBELCK
- Download Zip
$i18n.getText("admin.common.words.show")- cleanurls-patches.zip
- 11/Jan/07 11:09 AM
- 5 kB
- Remi VANKEISBELCK
-
$i18n.getText("admin.common.words.hide")
- cleanurls-src-01182007.zip
- 18/Jan/07 6:54 AM
- 8 kB
- Remi VANKEISBELCK
-
- cleanurls-src/.DS_Store 6 kB
- __MACOSX/cleanurls-src/._.DS_Store 0.1 kB
- cleanurls-src/net/.../CleanUrl.java 5 kB
- cleanurls-src/.../CleanUrlActionResolver.java 1 kB
- cleanurls-src/.../CleanUrlInterceptor.java 2 kB
- cleanurls-src/.../CleanUrlRequestWrapper.java 11 kB
$i18n.getText("admin.common.words.show")- cleanurls-src-01182007.zip
- 18/Jan/07 6:54 AM
- 8 kB
- Remi VANKEISBELCK
Activity
I would really like to see this in stripes as well
.
Would using the UrlBinding annotation really work? I thought that it is only appropriate on class level and not on method level?
Anyway I vote for this issue ![]()
I think it would be great to add this. As
James Strachan suggested in STS-268, it would be great if there is a
LifecycleStage for UrlParameter binding. This would make CRUD-type
action beans easier to code, because you could just do this:
@UrlBinding(/user/{id})
public class UserActionBean implements ActionBean {
private Long id;
private User user;
@DefaultHandler
public Resolution get() {
return new ForwardResolution("/WEB-INF/views/user.jsp");
}
public Resolution save() {
}
@Before(stage=LifecycleStage.UrlParameterBinding)
public void loadUser() {
if(id != null) {
user = userService.get(id);
}
}
}
Or the safer, more verbose way:
@UrlBinding(/user/{event}/{id})
public class UserActionBean implements ActionBean {
private Long id;
private User user;
public Resolution add() { return new ForwardResolution("/WEB-INF/views/user.jsp"); }
public Resolution edit() { return new ForwardResolution("/WEB-INF/views/user.jsp); }
public Resolution create() { userService.create(user); return new RedirectResolution(SomeOtherActionBean.class); }
public Resolution update() { userService.update(user); return new RedirectResolution(SomeOtherActionBean.class); }
@Before(stage=LifecycleStage.UrlParameterBinding, on={"edit","update"})
public void loadUser() {
//Will throw exception if id is null or user is not found
user = userService.get(id);
}
}
To support this, will there be some kind of stripes url tag that will
allow you to generate a url to this event? Maybe like this:
<stripes:url class="com.mycompany.stripes.UserActionBean">
<stripes:urlParam name="id" value="${user.id}"/>
<stripes:urlParam name="event" value="edit"/>
</stripes:url>
It's not just about the URL pattern, the HTTP method should also come into play - a DELETE to a particular URL is quite different to a GET (although the DispatcherServlet seems to route GETs via the doPost method).
So, IMHO, the binding should allow specification of both a UrlBinding and a HttpMethod
Here under is a patch that shows a possible implementation, using "clean url binding expressions" like this :
- @UrlBinding("/store/:type/:id/:_eventName") -> http://.../app-ctx/store/Book/123/purchase
- @UrlBinding("/store/:type/:id/:_eventName") -> http://.../app-ctx/Book/123
- @UrlBinding("/:type/foo/:id/bar/:_eventName") -> http://.../app-ctx/Book/foo/123/bar/edit
It's been discussed in the stripes ML : search for : "Clean URLs : a simple proposal".
See attached patch
- @UrlBinding("/store/:type/:id/:_eventName") -> http://.../app-ctx/store/Book/123/purchase
- @UrlBinding("/store/:type/:id/:_eventName") -> http://.../app-ctx/Book/123
- @UrlBinding("/:type/foo/:id/bar/:_eventName") -> http://.../app-ctx/Book/foo/123/bar/edit
Been reworking that a little...
It was actually possible to decouple everything from Stripes : this new version allows to use clean URLs as a "plugin" (or "extension"), using a custom ActionResolver, Interceptor and request wrapper. I didn't realize this at first sight.
The principle is still the same (I haven't changed the syntax, it's no big deal) : you define the clean URL expressions in the regular @UrlBinding annot, using '@' and ':' to specify the action's name as well as the request parameters, and your actions can handle clean URLs, with the binding etc.
I've deployed a test page there :
http://195.83.41.200/cleanurls
And you can grab the WAR (including sources, look in WEB-INF/classes) there :
http://jfacets.rvkb.com/pub/cleanurls.war
All this is VERY simple (4 classes !!!). The process goes like this :
1/ Handle the request (e.g. http://.../myApp/actions/store/Book/1)
2/ CleanUrlActionResolver is used to locate the action bean
3/ The bean is obtained
4/ The CleanUrlInterceptor detects clean URL handling and thereby wraps the request (once more) and reassociates this new top-level wrapper to the current ActionBeanContext (@Before(HandlerResolution))
5/ The top-level wrapper goes through the rest of the lifecycle and makes the clean URL thingy completely transparent : you just your the request the same way, and so does stripes, so binding, validation etc works.
Check it out !
I forgot : it still only works one way : you can't use "beanclass" or link-param inside your links, it appends parameters the "ugly" way (it still works of course, but it's not clean URLs).
So the only way yet to get it done is to construct the "href" yourself, like this :
<stripes:link href="/action/store/${productType}/${productId}">
...
If anyone has an idea about how to do this...
Hi again folks !
Here is the latest version of the code. It should have no impact on existing stuff, and allows clean URLs without being intrusive with Stripes itself : it's now all about dropping a jar and modifying web.xml... the plugin way ![]()
Below is a web.xml filter config example, and I'll attach the zipped sources right now.
Even if you don't plan to implement anything in there, please test that and send some feedback if you can ! It's about 15 minutes to integrate that into your apps and try it out :
1/ include the clean URLs sources to your project or recompile it and include a jar...
2/ modify the Stripes filter config in web.xml as shown below
3/ define a clean url expression for some of your actions, using @UrlBinding (e.g. @UrlBinding("/action/@DoIt/:param/") or @UrlBinding("/store/product/:id/:_eventName) etc.)
4/ start the app, open up your web browser, and issue a GET to your action (e.g. http://.../myApp/action/DoIt/blah or http://.../myApp/store/product/123/show etc.)
I'm using it in some application I develop, and so far it's been working fine : all existing stuff still works, and so does clean URLs for the actions that define clean url expressions in their @UrlBinding...
Thanks in advance for any feedback and help with that.
Cheers !
Remi
------------------------------
web.xml example :
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!-- Configuration of the Stripes Filter. -->
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<filter>
<description>
Provides essential configuration and request processing services
for the Stripes framework with clean URLs enabled !
</description>
<display-name>Stripes Filter</display-name>
<filter-name>StripesFilter</filter-name>
<filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
<init-param>
<param-name>ActionResolver.PackageFilters</param-name>
<param-value>net.sourceforge.jfacets.woko.actions.*</param-value>
</init-param>
<init-param>
<param-name>Configuration.Class</param-name>
<param-value>net.sourceforge.stripes.config.RuntimeConfiguration</param-value>
</init-param>
<!-- clean URLs (action resolver + interceptor)... -->
<init-param>
<param-name>ActionResolver.Class</param-name>
<param-value>net.sourceforge.stripes.cleanurls.CleanUrlActionResolver</param-value>
</init-param>
<init-param>
<param-name>Interceptor.Classes</param-name>
<param-value>
net.sourceforge.stripes.cleanurls.CleanUrlInterceptor,
net.sourceforge.stripes.controller.BeforeAfterMethodInterceptor
</param-value>
</init-param>
</filter>
I finally packaged my version as a small extension that can be used before Stripes includes clean URL support :
http://remi.mongus.com/stripes-clean
Check it out !
cool thanks ![]()
as soon as the link tag with the beanclass attribute works as well it's time for integrating this into the core and a new release g
An initial version of clean URL support is now available in the trunk. URLs are of the form @UrlBinding("/path/to/bean/{param1}/{param2}"). Parameters are available through the normal means. The s:link and s:url tags and the RedirectResolution and ForwardResolution classes support constructing URLs using the parameters via UrlBuilder.
There are a couple of gotchas at the moment. It seems that parameter values can get duplicated if a request is received with URI parameters and then forwarded with URI parameters. Also, the $event reserved parameter name is not yet supported so things won't work quite right with that sometimes. I'll work on getting that fixed next.
Yikes ! Thanks Ben !
Why do you say the $event is making things go wrong ? Can we just use {_eventName} ?
Good work
Remi
Sorry, I was tired and didn't want to take the time to explain. Here's an example of where it causes a little trouble at the moment. Say you map a bean like so:
@UrlBinding("/action/blog/{_eventName}/{blogEntry}")
And somewhere you redirect to it like so:
return new RedirectResolution(BlogBean.class, "view").addParameter("blogEntry", 12345);
OnwardResolution will just create a parameter called "view" with an empty value, which will result in a redirect to:
/action/blog?view=&blogEntry=12345
By using the special parameter $event, you could map the bean like this:
@UrlBinding("/action/blog/{$event}/{blogEntry}")
And the resulting URL should look like this instead:
/action/blog/view/12345
The difference being that instead of creating an empty parameter named "view", it sets the $event parameter to the value "view".
Unfortunately, using _eventName doesn't exactly solve this problem. Instead of this:
new RedirectResolution(BlogBean.class, "view")
you currently have to do this instead:
new RedirectResolution(BlogBean.class).addParameter("_eventName", "view")
But like I said in my previous comment, I intend to get this fixed soon. I'll have to make UrlBuilder aware of events and how to handle them when constructing the URL. What used to be such a simple class has gotten so much more complex in the last week or two ![]()
I forgot to mention that parameters can take default values. If the parameter is not present in the URI, then its default value will be appended to the query string on forward. Here's an example.
@UrlBinding("/action/map/{country=US}")
So if the URI is /action/map/CA you'll get a map of Canada, but if it's just /action/map then you'll get a map of the default country, in this case US.
Escaping is also supported with backslashes in case you want to embed something weird:
@UrlBinding("/action/this
{is\\}weird")
{is\\}weird")
This does not seem 'done' to me, so I'm not sure why it's marked as resolved. I feel this is a very important feature for stripes, and so I hope this helps:
1) What changes need to be made to my web.xml to support this? Once I add a URL Binding of "/resource/{whatever}" it no longer get's dispatched to my ActionBean because it doesn't end in .action, etc. Seems some info should be provided lest I have to unecessarily nest custom urls under "/virtual/..."
2) The stripes:link tag should ABSOLUTELY use the URLBinding information to generate the href. This is very very important, and would put stripes in the unique place of abstracting URL management to the most comfortable place.
1) The one change that is required in web.xml is <dispatcher>FORWARD</dispatcher> must be added to the StripesFilter mappings. Other than that, you just have to make sure your URLs always begin with a prefix or end with a suffix that maps to the Stripes dispatcher servlet.
2) It already does this.
Thanks for the response Ben
1) I did setup the forward, and that's why I mentioned having to unecessarily nest urls under "/virtual". I'm trying to get rails-esque URLs, for a webapp rooted at "/" and don't want a extraneous (from a URL engineering point of view) directory or suffix
2) I couldn't make it work, I will try again
Ben,
I've found an issue in build 947 with the implementation of clean URLs, which relates to differences in how the ActionResolver and UrlBuilder handle appended parameters.
With a given "clean" @UrlPattern, if parameters otherwise unspecified in the pattern are added to the UrlBuilder, they are properly appended in the generated URL. But if that generated URL is passed back to the StripesFilter, the trailing parameters are not parsed as expected, even if the ActionBean has properties that those parameters "should" bind do. In other words, it is not a "round trip" operation.
Example: suppose we have an ActionBean with @UrlBinding("/cleanbean1/{param1=Test}/?do={$event}"). It has three properties, param1, param2 and param3 and two events (view, edit). Suppose we create an UrlBuilder for the bean; specify an event of "edit"; and add parameter values for the three properties (param1=Godel, param2= Escher, param3= Bach). We get a properly built URL, with param2 and param3 appended after the clean URL portion, e.g.:
"/cleanbean2/Godel/?do=edit¶m2=Escher¶m3=Bach"
However, passing that URL back into the StripesFilter via MockRoundTrip results in the trailing parameters not being parsed:
param1=Godel, param2=null, param3=null
I've looked into this a little bit further, and it seems that the MergedParameterMap feels that "edit¶m2=Escher¶m3" is the extracted parameter and that its value should be "Bach". That's probably not the root of the issue, but it does suggest that trailing parameters for clean URLs aren't being parsed.
This might be "by design," – but it feels kinda asymmetrical. Can this be fixed?
I have a TestNG class that tests several edge cases, if that's helpful.
I've attached a patch for this that fixes the issue. It adds a separator() method to the UrlBinding annotation (which defaults to "&"), and an equivalent getSeparator() method to the UrlBinding class. It also adds some processing logic to UrlBindingFactory to properly recognize when a clean URL that matches a prototype might have appended parameters.
The patch includes a CleanUrlBindingTests file that exercises several edge cases.
From Barry Davies on the stripes-dev list:
Would any of the following also be what you had in mind: