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

Reply via email to