Mark Page has created some wonderful Stripes logos. Check them out here and here are a couple of examples:
Wednesday, February 06, 2008
Powered By Stripes
Posted by Gregg Bolinger at 9:02 AM 3 comments
Stripes 1.5 Beta Available For Download
I just copied this straight from the mailing list announcement.
Stripes 1.5 beta 1 is available for download:
http://sourceforge.net/project/showfiles.php?group_id=145476
Some of the major enhancements and bug fixes include:
* Even less configuration! Using the new Extension.Packages configuration parameter, Stripes can automatically locate and use your extensions (TypeConverter, Formatter, ExceptionHandler, ActionBeanContext, etc.). Most applications can now be configured with just two parameters: Extension.Packages and ActionResolver.Packages. Use the @DontAutoLoad annotation to tell Stripes to ignore an extension class.
* Clean URLs. Just declare URI parameters using, for example, @UrlBinding("/SomeBean/{foo}/{bar}.action") on your ActionBean. The parameters will appear as normal request parameters and will be validated, converted, and bound just like normal request parameters, too. Clean URLs are fully supported by s:link, s:url, and s:form.
* Binding access control. Annotate your ActionBean class with @StrictBinding to restrict binding only to those properties you wish to allow. @StrictBinding works with @Validate to determine which properties may be bound to. For special cases, the annotation also supports the "policy," "allow," and "deny" elements. More information can be found in the Javadocs.
* Easier Formatters and TypeConverters. In addition to improvements to the TypeConverterFactory and FormatterFactory APIs, Formatters and TypeConverters can now be loaded using autodiscovery (see above), eliminating the need to extend DefaultTypeConverterFactory and DefaultFormatterFactory. DefaultFormatterFactory has been improved so that it now provides support for inheritance and interfaces. For example, if a Formatter for List is registered, that formatter will be used for any type that implements List.
* DateTypeConverter improvements. A lot of work was put into making DateTypeConverter more robust.
* Transparent encryption of ActionBean property values. You can now use @Validate(encrypted=true) to cause ActionBean property values to be encrypted before being written to a page in a link or form input. This is meant for cases where you want to send a value back to the server with assurance that it is the same value that was written by the server.
* Validation error report. If an ActionBean is called and there are validation errors and no _sourcePage is given, Stripes will generate a nicely formatted report detailing the validation errors that occurred. This is a vast improvement over the StripesRuntimeException that used to be thrown, with no information as to what went wrong.
* New s:format tag can be used to format an object using a Formatter.
* Tags and Resolutions use Formatters. Where String.valueOf(object) used to be called, Stripes will now attempt to format the object using a Formatter and use String.valueOf() only as a last resort.
* @DontBind annotation to completely bypass both binding and validation for an event.
* @DontValidate no longer fails due to type conversion errors.
* Automatic detection of file upload provider. You can select your file upload implementation just by dropping it in. Commons-FileUpload takes precedence over COS if both libraries are present.
* Partial forms. The s:form tag supports a new attribute partial="true|false" that allows for partial forms nested inside another form tag. This facilitates AJAX code that returns HTML snippets to the client that might contain form inputs.
* Optional _sourcePage for links. By default, the _sourcePage parameter is no longer appended to URLs generated by s:url and s:link. This can be overridden with addSourcePage="true". The _sourcePage parameter is still included in forms.
* Encrypted _sourcePage. The _sourcePage parameter is encrypted to prevent revealing sensitive information about the structure of the app.
* Context-relative URLs. The s:link and s:url tags have a new attribute, prependContext="true|false", that can be used to generate URLs relative to the app context (when set to false). Relative URLs can be used with JSTL tags much more easily.
* Client cache control. The new @HttpCache annotation can be applied to an ActionBean class and/or event handler method to control how the client caches the response. Annotations on methods override those on classes and the annotation is inherited from superclasses. This is especially useful for AJAX calls from IE.
* Property labels with annotations. @Validate(label="Some property label") can be used during development to easily label ActionBean properties without having to add them to the resource bundle. Values from the resource bundle override those from the annotation.
* Automatic maxlength on text inputs. The maxlength attribute of a text input is automatically set if a value is specified by @Validate(maxlength=X).
* s:form supports s:param. Parameters can be added to a form URL by included s:param tags within the s:form tag. This is mostly intended for use with clean URLs, but it works with normal parameters as well.
There are many more. The full list can be found here:
http://www.stripesframework.org/jira/browse/STS?report=com.atlassian.jira.plugin.system.project:roadmap-panel
This release is not completely backward compatible with Stripes 1.4. Some changes to your code and configuration may be necessary. Tim has written up a document that will help with the process. It can be found here:
http://stripes.svn.sourceforge.net/viewvc/*checkout*/stripes/trunk/upgrading.txt?revision=831
We encourage everyone to download and test this software out. Your feedback is very important. During the beta period we will be working on updating all the documentation. Any corrections or contributions are greatly appreciated.
Posted by Gregg Bolinger at 8:52 AM 3 comments
Thursday, January 31, 2008
Stripes 1.5 Feature: General Improvements
My last two Stripes articles covered Clean URLs and @StrictBinding. Today I am going a bit more general and discussing several of the smaller improvements.
Disclaimer - We know that other web frameworks have had some of these features in some form or fashion so please don't comment with "ZZZ Framework has been doing that for years.". We know. We get it.
@DontValidate
This annotation has been in Stripes for a while. We would typically use this for cancel events or just any event where you don't want validation to occur. The problem however was that type converting still happens and if it fails errors are generated and your event won't complete. This has been changed in 1.5 so now @DontValidate will also ignore type conversion.
@DontBind
Yesterday I talked about @StrictBinding and how you can control data binding using the annotation and validation. Since @StrictBinding is a class level annotation it makes it nearly impossible to do this on a per event basis. @DontValidate helps but binding still occurs. @DontBind implies @DontValidate and does not attempt any binding of data.
@HttpCache
Back in November of last year I blogged about how easy it is to create an interceptor for Stripes and we created the @NoCache interceptor. This interceptor has been added to Stripes core in 1.5 but with a twist. It is now called @HttpCache and you can control the no-cache and expires parameters via annotation attributes. Lets look at an example.
@HttpCache(expires=600)
public Resolution ajaxUpdate() {
}
The above example will allow caching but expires the document in 10 minutes.
@HttpCache(allow=false)
public Resolution ajaxUpdate() {
}
The above example disables caching and immediately expires the document. This comes in really handy when returning Resolutions to AJAX requests (especially in IE). @HttpCache can be applied at the class or method level. Method level annotations will always override class level annotations.
@Before/@After
These annotations have been available in Stripes for a while but now can be set to run on certain events. Lets look at an example.
@After(on = {"save","edit"}, stages = LifecycleStage.BindingAndValidation)
public Resolution runAfterBinding() {
}
The above method will only execute on the save and edit events. Multiple LifecycleStages can also be specified. @Before works the same way.
There are also quite a few more subtle but significant changes. Here they are in no particular order of importance.
- When you create a link that doesn't pass validation and there is no _sourcePage to go to, you get a nice validation error report that tells you what you did wrong instead of getting a StripesRuntimeException and having to jump through hoops to figure it out
- Incoming requests are no longer wrapped multiple times when you map StripesFilter on FORWARD. Solves problems that existed with wrapping multipart requests more than once (big explosion) and other silly issues that would occasionally arise from wrapping multiple times.
- Enhanced checking of stuff (multiple @Validate, multiple @DefaultHandler) etc. Stripes now does a much better job at notifying you about trivial issues that can be a pain to track down. For example if you annotated two methods with @DefaultHandler Stripes would just pick one and determining why you weren't getting the expected results was time consuming. Now Stripes will throw an exception and tell you exactly what the problem is.
- Reduced Session use. Stripes used to store encryption keys in the HttpSession. Now that it doesn't stripes doesn't force session creation of any kind.
- Lots of changes with classpath scanning so that Stripes plays nicer with container/custom class loaders.
- Most things in Stripes can be easily overridden to support a wide array of scenarios we can come up with as web developers. ActionResolver, ActionBeanContext, TypeConverters, just to name a few. While Stripes prides itself on zero ActionBean configuration you still need to specify init params for the Stripes Filter in the web.xml and that includes any core classes you want replaced with your own custom versions. Stripes now offers an init param, Extension.Packages, that accepts a comma separated list of packages where Stripes will auto discover and load any custom classes that would override Stripes default classes. This can significantly reduce the amount of configuration required in the web.xml
There are a few more enhancements that need to be discusses however they require their own articles respectively. I hope everyone is enjoying these articles. More to come in the days ahead, I assure you.
Posted by Gregg Bolinger at 12:50 PM 7 comments
Wednesday, January 30, 2008
Stripes 1.5 Feature: Control Binding with @StrictBinding
If you missed yesterday's article on Clean URLs you can find it here. Today I want to talk about how we can control what values get bound in an Action Bean when we submit a form. And once again;
Disclaimer - We know that other web frameworks have had some of these features in some form or fashion so please don't comment with "ZZZ Framework has been doing that for years.". We know. We get it.
The Stripes team has added a very useful annotation as well as some supporting changes to an existing annotation to provide a first line of defense against hackers. This annotation is @StrictBinding. This is a class level annotation and uses the @Validate annotation as support. First we can look at a basic example:
@StrictBinding
public class RegisterActionBean extends BaseActionBean {
private User user;
}
In this example any values submitted by a form to this action bean won't be bound at all. Hmm, not very useful. So lets tweak it a bit.
@StrictBinding(allow="*")
public class RegisterActionBean extends BaseActionBean {
private User user;
}
This will allow binding to any top level properties in user. However, nested properties are ignored. Not quite what we need yet. One more modification.
@StrictBinding(allow="**")
public class RegisterActionBean extends BaseActionBean {
private User user;
}
Alright, the double * in the allow attribute says bind everything, no matter how nested. Another way to achieve the same thing is to use defaultPolicy=Policy.ALLOW instead of "allow". If we need finer grained control we can also use the "deny" attribute.
@StrictBinding(allow="**", deny="user.role.id")
public class RegisterActionBean extends BaseActionBean {
private User user;
}
This allows everything to bind except for user.role.id because we don't want a new user to be able to specify their own security level by adding parameters sent to the server. That would be bad. And with that I think you can see the benefit of @StrictBinding. It is a good first line defense mechanism. But we're not done yet.
@StrictBinding all by itself can handle anything you need however the preferred way to control binding is with @StrictBinding + @Validate. Lets look at another example.
@StrictBinding
public class RegisterActionBean extends BaseActionBean {
@ValidateNestedProperties({
@Validate(field="username", required=true),
@Validate(field="password", required=true),
@Validate(field="firstName", required=true),
@Validate(field="lastName", required=true)
})
private User user;
}
First we turn off all binding using the simplest form of @StrictBinding. Then, we control the binding with validation. Any property being validated is bound. Everything else is ignored.
As always data should be sanitized but with the addition of @StrictBinding, Stripes provides a very elegant first line of defense against intruders.
Posted by Gregg Bolinger at 2:44 PM 3 comments
Tuesday, January 29, 2008
Stripes 1.5 Feature: Clean URLs
So I promised a list of some new features coming in Stripes 1.5. Since several of them require some explanation to really get the point across I decided to do a daily article about each of the major feature improvements and then one final article on the smaller things. I finally found a bit of time so here is the first one. Stripes 1.5 is coming along really well. The developers have been committing code like crazy the past month. We should be seeing a beta release really soon.
Disclaimer - We know that other web frameworks have had some of these features in some form or fashion so please don't comment with "ZZZ Framework has been doing that for years.". We know. We get it.
Clean URLs have been possible in Stripes for a while through an add on. However, with Stripes 1.5 they will be an optional feature in the core API. As you may or may not know currently you can define a URL Binding for a Stripes action one of 2 ways. You can let Stripes handle it by inspecting the package and class name and binding it for you or you can explicitly set it using the @UrlBinding annotation. With clean URLs @UrlBinding is required. The format is something like:
@UrlBinding("/action/blog/{$event}/{blog.id}")
public class BlogActionBean...
The resulting URL to edit a blog entry would be:
http://.../app/action/blog/edit/1234
If the event is null it is ignored as well as any given parameter. So a URL like:
http://.../app/action/blog
would result in calling the Default Handler for the BlogActionBean. Support for default parameter values are also available.
@UrlBinding("/action/blog/{$event}/{blog.id=1234")
So accessing the url
http://.../app/action/blog
blog.id will have a value of 1234 however if you pass in a blog.id value it will override the default. Default values for {$event} can also be given. This overrides the @DefaultHandler method if one exists. Otherwise, the @DefaultHandler method is executed.
If you still prefer extensions on your URLs, support for that is also available. The change to the URL binding is trivial:
@UrlBinding("/blog/{$event}/{blog.id}.action")
Stripes JSP tags <s:url />, <s:link />, and <s:form /> all support Clean URLs. For example:
<s:link beanclass="com.app.foo.BlogActionBean" event="edit">
<s:param name="blog.id">1234</s:param>
Edit
</s:link>
Will result in:
<a href="/app/action/blog/edit/1234">Edit</a>
And that is pretty much it for Clean URLs in Stripes 1.5. Very simple, elegant, and as good as the next guys. Possibly better? Let me know.
Posted by Gregg Bolinger at 10:09 AM 15 comments
Wednesday, December 19, 2007
Stripes (1.5) Updates Are Coming...
There's been a lot going on with Stripes lately. A lot of users have been asking when 1.5 is due out. I don't believe there is a set date yet but I do know that something is coming quickly. There's a lot of new features and improvements in the trunk right now that are making it into 1.5. I'm going to try and get together a list and post them here with explinations.
Also, there will be a new website (http://www.stripesframework.org). The current website (wiki) will still be there with all the wonderful documentation that Stripes is known for but now there will be a more elegant entry point.
So check back and I'll see if I can gather up that feature/improvement list for my next post.
Posted by Gregg Bolinger at 11:55 AM 1 comments
Friday, November 16, 2007
Stripes TypeConverters: Populating Domain Objects
Most web frameworks today have type converters. Type converters are chunks of code that takes query parameters as Strings and convert them into their appropriate data type. So numbers become Integers, dates become Dates, etc. Most frameworks also allow you to create custom type converters. For example, you might want to convert a currency form field into a Money object. That's all well and good. But we can take it a bit further with Stripes.
A common scenario in the web world is requesting data from the server and displaying it on the page. I mean, really, all web applications do is send data and get data and display the data they got. Nothing too complex. How we populate domain objects based on query parameters is where everyone's code can differ. Most common is for an action to process the query parameters and we get the one(s) we need and use them to lookup information in a database, shove those into some domain objects or properties of some sort, throw them in the request and forward to our view. Again, nothing too complex.
There are many times when we need to populate the same domain object(s) based on the same parameter on many different pages. I'll set up a scenario that we can use throughout the rest of this article. You created a User management screen. Displaying a list of users you want to edit one. The query parameter you pass in is the user.id. So we might have some code in our Stripes action bean that looks something like this:
private User user;
private UserDao userDao;
public User getUser()
{
return this.user;
}
public void setUser(User user)
{
this.user = user;
}
@SpringBean("userDao")
public void setUserDao(UserDao userDao)
{
this.userDao = userDao;
}
public Resolution edit()
{
user = UserDao.getUser(user.getId());
return new ForwardResolution(EDIT_USER_PAGE);
}
We could of course also discuss the pros and cons of doing the same thing in a method annotated with @After/@Before but for the sake of simplicity we'll say that's possible as well and move on.
So the client comes back and decides that they need an additional page where the administrator can click on a product and see all the users that purchased this product. And further more he can click on a user and view some details about that user. We begin to build the new action and view and notice that we've got to duplicate some code we just wrote. We need the UserDao again and we need to do a lookup so we copy and paste, test it, it works. Yea! And then we need another page, and another page, and we copy and paste and copy and paste. At this point we've got the same code written 10 times. That's bad. So we decide hey, we are using Stripes, let's use a TypeConverter to do this.
Stripes has a TypeConverter interface that requires two methods:
convert(String input, Class targetType, Collection
setLocale(Locale locale)
We are only going to worry about convert right now. So we begin our custom type converter:
public class UserTypeConverter implements TypeConverter
{
private UserDao userDAo;
@SpringBean("userDao")
public void setUserDao(UserDao userDao)
{
this.userDao = userDao;
}
public User convert(String id, Class user, Collectionerror)
{
if (id != null)
{
User user = userDao.getUser(id);
return user;
}
return null;
}
}
This is pretty straightforward. We are just looking the user up from our UserDao using an id. But how does that id get there? Pretty simple as well. Let's look at our action again, only this time using the converter.
User user;
public void setUser(User user)
{
this.user = user;
}
public User getUser()
{
return this.user;
}
public Resolution edit()
{
return new ForwardResolution(EDIT_USER_PAGE);
}
And the URL might looking something like this:
http://localhost:8080/app/User.action?edit=&user=1234
Anytime the id is not null, user gets populated. If it is null, for example when adding a new user, user will only be populated from the normal form to object binding that Stripes normally does. But how does our action know that User is supposed to use the UserTypeConverter? Well, if we weren't needing the Spring Bean injection magic would could have simply done this in our action.
@Validate(converter=UserTypeConverter.class)
User user;
But since we need the Spring Bean magic we need to create our own TypeConverterFactory. Again, another interface. Stripes uses a default to init all the built in type converters. Here is what one might look like:
public class CustomTypeConverterFactory extends DefaultTypeConverterFactory
{
public TypeConverter getInstance(Class clazz, Locale locale) throws Exception
{
TypeConverter tc = super.getInstance(clazz, locale);
ServletContext sc = getConfiguration().getServletContext();
SpringHelper.injectBeans(tc, sc);
return tc;
}
@Override
public void init(Configuration configuration) {
super.init(configuration);
add(User.class, UserTypeConverter.class);
}
}
And then we need to tell Stripes to use our CustomTypeConverterFactory. In the web.xml in the StripesFilter init-params add the following param-name:
TypeConverterFactory.Class
and param-value:
com.app.package.foo.CustomTypeConverterFactory
And we're done. Comments welcome.
Posted by Gregg Bolinger at 2:32 PM 2 comments
Thursday, November 15, 2007
Spring's DAO Support..Other choices?
I've gotten really spoiled to Spring's DAO support classes, Transaction management, etc. But as any good developer should I begin to wonder what alternatives are out there? Do java developers have another choice aside from rolling our own?
Just looking for comments.
Posted by Gregg Bolinger at 9:52 AM 4 comments