Since a few people replied to my original post asking to see it here is my integration with JSR 303 for stripes.
The key is an interceptor that intercepts the binding and validation steps in the stripes lifecycle. --------------------------------------------------------------------------------------- package com.ecompanies.web.interceptor; import com.ecompanies.validation.ValidationGroup; import net.sourceforge.stripes.action.ActionBean; import net.sourceforge.stripes.action.DontValidate; import net.sourceforge.stripes.action.Resolution; import net.sourceforge.stripes.controller.ExecutionContext; import net.sourceforge.stripes.controller.Interceptor; import net.sourceforge.stripes.controller.Intercepts; import net.sourceforge.stripes.controller.LifecycleStage; import net.sourceforge.stripes.validation.SimpleError; import net.sourceforge.stripes.validation.ValidationErrors; import javax.validation.ConstraintViolation; import javax.validation.Validator; import javax.validation.groups.Default; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.logging.Logger; import static com.ecompanies.validation.Util.getValidator; /** * JSR 303 integration for stripes. * Originally created for use in <a href="http://www.ecompanies.com.au">ecompanies</a> */ @Intercepts({LifecycleStage.BindingAndValidation}) public class ValidationInterceptor implements Interceptor { private static final Logger log = Logger.getLogger(ValidationInterceptor.class.getName()); @SuppressWarnings("unchecked") public Resolution intercept(ExecutionContext ctx) throws Exception { //let stripes finish binding into the bean Resolution resolution = ctx.proceed(); //if we do a useactionbean in a page there will be no handler. useactionbean can bind and validate //for me I never want it to so just return. if (ctx.getHandler() == null) return resolution; //check if we want to do any validation boolean doValidate = ctx.getHandler().getAnnotation(DontValidate.class) == null; if (doValidate) { Validator v = getValidator(); //support for groups via custom annotation ValidationGroup gs = ctx.getHandler().getAnnotation(ValidationGroup.class); List<Class> groups = new ArrayList<Class>(); groups.add(Default.class); if (gs != null) { groups.addAll(Arrays.asList(gs.value())); } Set<ConstraintViolation<ActionBean>> validationMessages = v.validate(ctx.getActionBean(), groups.toArray(new Class[groups.size()])); ValidationErrors errors = ctx.getActionBeanContext().getValidationErrors(); //convert the errors to stripes for (ConstraintViolation invalidValue : validationMessages) { SimpleError error = new SimpleError(invalidValue.getMessage()); error.setFieldName(invalidValue.getPropertyPath().toString()); if (invalidValue.getPropertyPath().toString().contains("VALID")) { String s = invalidValue.getPropertyPath().toString().substring(0, invalidValue.getPropertyPath().toString().indexOf("VALID")); errors.add(s, error); } else { errors.add(invalidValue.getPropertyPath().toString(), error); } } //I display errors next to fields and add a global error at the top if there were any field errors. if (!errors.isEmpty() && errors.hasFieldErrors()) { errors.addGlobalError(new SimpleError("Please correct the error(s) highlighted below")); } } return resolution; } } ---------------------------------------------------------------------------------------------------------------------------------------------- This utility class bootstraps and creates the validators. Its in a separate class so I can make a validator anywhere in my application. ---------------------------------------------------------------------------------------------------------------------------------------------- package com.ecompanies.validation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; /** * JSR 303 integration for stripes. * Originally created for use in <a href="http://www.ecompanies.com.au">ecompanies</a> */ public class Util { private static ValidatorFactory validatorFactory = Validation.byDefaultProvider().configure() .traversableResolver(new SimpleTraversableResolver()) .buildValidatorFactory(); public static boolean empty(String value) { return (value == null || value.trim().equals("")); } public static Validator getValidator() { return validatorFactory.getValidator(); } } ---------------------------------------------------------------------------------------------------------------------------------------------- TraversableResolver class, I'm not sure if this is a great idea but you need to do this when bootstrapping to avoid problems with hibernate entities. ---------------------------------------------------------------------------------------------------------------------------------------------- package com.ecompanies.validation; import javax.validation.Path; import javax.validation.TraversableResolver; import java.lang.annotation.ElementType; /** * JSR 303 integration for stripes. * Originally created for use in <a href="http://www.ecompanies.com.au">ecompanies</a> */ public class SimpleTraversableResolver implements TraversableResolver { public boolean isTraversable(Object o, String s, Class<?> aClass, String s1, ElementType elementType) { return true; } public boolean isReachable(Object o, Path.Node node, Class<?> aClass, Path path, ElementType elementType) { return true; //To change body of implemented methods use File | Settings | File Templates. } public boolean isCascadable(Object o, Path.Node node, Class<?> aClass, Path path, ElementType elementType) { return true; //To change body of implemented methods use File | Settings | File Templates. } } ---------------------------------------------------------------------------------------------------------------------------------------------- This class uses a custom annotation to add easy support for validation groups. ---------------------------------------------------------------------------------------------------------------------------------------------- package com.ecompanies.validation; import java.lang.annotation.ElementType; import java.lang.annotation.RetentionPolicy; @java.lang.annotation.Target({ElementType.METHOD}) @java.lang.annotation.Retention(RetentionPolicy.RUNTIME) public @interface ValidationGroup { java.lang.Class<?>[] value(); } ---------------------------------------------------------------------------------------------------------------------------------------------- And that is basically all you need to start doing JSR 303 validation in your action beans! It should be pretty self explanatory especially after looking at the example below but there area a couple of notes 1. Bootstrapping may be different for you. 2. My application has lots of one off checks in its validation and I almost despaired at having to write individual constraints to achieve them. I toyed with the idea of class level constraints but then building the error path needed for stripes to display the error was not easy so I use the @AssertTrue a lot in my application. I put this on a getter with the same name as the field I want the error on but with VALID or VALIDPostcode or something similar on the end. Then in the interceptor I check for this, chop of the end and viola the error appears next to the field. It actual makes for nice validation in the domain class. Since the bean has had all its properties populated you can perform cross field validation to your hearts content. 3. I am just adding jsr303 validation into the mix, if for some things you prefer the Stripes annotations, especially for nested validation you can mix and match and it will all work 4. You can customise error messages with a ValidationMessages.properties file in your classpath so here is an example, first the page, validationExample.jsp ------------------------------------------------------------------------------------------------------------------------------------------- <%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head><title>Validation example</title></head> <body> <h1>Try out some JSR 303 validation with Stripes!</h1> <stripes:errors globalErrorsOnly="false"/> <stripes:form beanclass="com.iregister.web.action.ValidationExample"> <stripes:label for="firstName">First Name</stripes:label><stripes:text name="firstName"/><stripes:errors field="firstName"/><br/> <stripes:label for="lastName">Last Name</stripes:label><stripes:text name="lastName"/><stripes:errors field="lastName"/><br/> <stripes:label for="foo.bar">Foo Bar</stripes:label><stripes:text name="foo.bar"/><stripes:errors field="foo.bar"/><br/> <stripes:submit name="first" value="first"/> <stripes:submit name="second" value="second"/> <stripes:submit name="third" value="third"/> <stripes:submit name="fourth" value="fourth"/> </stripes:form> </body> </html> ------------------------------------------------------------------------------------------------------------------------------------------- then the action ------------------------------------------------------------------------------------------------------------------------------------------- package com.ecompanies.web.action; import com.iregister.validation.ValidationGroup; import net.sourceforge.stripes.action.*; import javax.validation.Valid; import javax.validation.constraints.AssertTrue; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; public class ValidationExample implements ActionBean { protected ActionBeanContext context; @NotNull private String firstName; @NotNull @Size(groups = Group1.class, min = 10, max = 20) //only called on group1 validation private String lastName; //do some nested validation @Valid private Foo foo = new Foo(); //don't get caught out. Object won't get created if you don't submit anything and therefore validation won't get run!! //perform default validation @DefaultHandler public Resolution first() { return new ForwardResolution("/validationExample.jsp"); } //perform default validation and group 1 @ValidationGroup(Group1.class) public Resolution second() { return new ForwardResolution("/validationExample.jsp"); } //perform default validation and group 1 and group 2 @ValidationGroup({Group1.class, Group2.class}) public Resolution third() { return new ForwardResolution("/validationExample.jsp"); } @DontValidate public Resolution fourth() { return new ForwardResolution("/validationExample.jsp"); } //example of the AssertTrue style validation only called in a group @AssertTrue(message = "If your firstname starts with 'A' your second must start with 'B'", groups = Group2.class) public boolean getLastNameVALIDSilly() { if (firstName == null || lastName == null) return true; //picked up by @NotNull if (firstName.startsWith("A")) return lastName.startsWith("B"); else return true; } //interfaces for group style validation public interface Group1 {} public interface Group2 {} public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Foo getFoo() { return foo; } public void setFoo(Foo foo) { this.foo = foo; } @Override public void setContext(ActionBeanContext actionBeanContext) { this.context = actionBeanContext; } @Override public ActionBeanContext getContext() { return context; } } ------------------------------------------------------------------------------------------------------------------------------------------ Finally the Foo class that is nested in the action ------------------------------------------------------------------------------------------------------------------------------------------ package com.ecompanies.web.action; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; public class Foo { @NotNull @Size(min = 10, max = 20) //only called on group1 validation private String bar; public String getBar() { return bar; } public void setBar(String bar) { this.bar = bar; } } ------------------------------------------------------------------------------------------------------------------------------------------ So that's it. All the key points are illustrated. I have done a few extra things specific to my application but this integration works perfectly for me. I have nasty requirements since I have a data model from an early 90s government application with lots of validation checks with the same class that can have entirely different validation behaviour depending on the context. If I've left anything out or you want some clarification please let me know! I hope its useful to people and if its good enough It can go up on the wiki as a user addition. Dan ------------------------------------------------------------------------------ This SF.net email is sponsored by Make an app they can't live without Enter the BlackBerry Developer Challenge http://p.sf.net/sfu/RIM-dev2dev _______________________________________________ Stripes-users mailing list Stripes-users@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/stripes-users