Pages

Showing posts with label validation. Show all posts
Showing posts with label validation. Show all posts

Wednesday, January 23, 2013

Integration of Drools and spring to perform business logic validation in JSF

In a previous post I explained how to use AOP to perform custom validation in JSF. Here I will show how you can use the business logic framework Drools to perform validation based upon external (business) rules in conjunction with springframework.

In this example I will perform the bussiness rules validation inside an advice class, but you can implement the same mechanism to perform that validation inside a JSF backing bean or in a spring bean.

Inside your application-context.xml file add your advice in the following way:

<bean id="validatorAspect" 
   class="com.foo.aspects.BackingBeanValidatorAdvice" 
   factory-method="aspectOf">
<property name="resources">
<list>
<value>/com/foo/business/rules.drl</value>
</list>
</property>
</bean>

I'm assuming you have basic knowledge of Drool, I will not get into syntax details here, there is a lot of documentation in Drool's site; instead I will just present a simple validation rule file rules.drl:


package com.foo.business

import com.foo.model.ModelBean;
import com.foo.aspects.Error;

query "Error" ()
    error : Error()
end


rule "Rule 1"
when
   ModelBean( id == 32 && internalId not matches "(J|P|R) ([0-9]){6}")
then
   Error err = new Error();
   err.setSummary("Error in Model Bean. ");
   err.setDetail("Internal Id not matches format: (J, P o R) followed by whitespace and 6 digits");
   insert(err);
end

Error is just a simple Pojo containing two properties (summary and detail).

Then, you must fire the validation inside the advice class:
public aspect BackingBeanValidatorAdvice {

    private KnowledgeBase kbase;

    private StatefulKnowledgeSession ksession;
    
    private String[] resources;

    pointcut doSaveChanges() : execution(* com.foo.beans.BackingBean.saveChanges(..));

    void around() : doSaveChanges() {
        FacesContext context = FacesContext.getCurrentInstance();
        ModelBean mbean = context.getApplication().evaluateExpressionGet(context, "#{bBean.selected}", ModelBean.class); 

        this.init();
        this.getKsession().insert(mbean);
        this.getKsession().fireAllRules();

        QueryResults results = this.getKsession().getQueryResults("Error");
        if (results.size() > 0) {
            for (QueryResultsRow row : results) {
                Error err = (Error) row.get("error");
                context.addMessage("error", new FacesMessage(FacesMessage.SEVERITY_ERROR, err
                        .getSummary(), err.getDetail()));
            }
        }
        proceed();
    }

    public void init() {
        if (this.getKbase() == null) {
            KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
            for (int i = 0; i < this.getResources().length; i++) {
                String rule = this.getResources()[i];
                kbuilder.add(ResourceFactory.newInputStreamResource(this.getClass().getResourceAsStream(rule)),
                    ResourceType.DRL);
            }
    
            if (kbuilder.hasErrors()) {
                System.out.println(kbuilder.getErrors());
                return;
            }
            Collection < KnowledgePackage > kpkgs = kbuilder.getKnowledgePackages();
            KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
            kbase.addKnowledgePackages(kpkgs);
            this.setKbase(kbase);
        }
        this.setKsession(this.getKbase().newStatefulKnowledgeSession());       
    }
    
    public StatefulKnowledgeSession getKsession() {
        return ksession;
    }

    public void setKsession(StatefulKnowledgeSession ksessionToSet) {
        this.ksession = ksessionToSet;
    }

    public String[] getResources() {
        return resources;
    }

    public void setResources(String[] resourcesToSet) {
        this.resources = resourcesToSet;
    }

    public KnowledgeBase getKbase() {
        return kbase;
    }

    public void setKbase(KnowledgeBase kbase) {
        this.kbase = kbase;
    }    
}


That's it. In case the validation fails, a FacesMessage is added to the FacesContext and JSF is responsible for handling and showing the error in a jsf page, eg: <h:messages .../>

Monday, January 7, 2013

Use aspectj-maven-plugin to perform custom validation in JSF

You can use AspectJ to do many things, but its potential arise when you use it in order to apply the SoC paradigm.
In this post I will show you how to use it to perform custom validation in the presentation layer of a JSF webapp. Later I will show how to use spring to define more generic pointcuts in your application.

Note: I'll asume you already have basic knowledge of AOP in general and AspectJ in particular. I will not get into syntax and terminology details here. You can consult AspectJ site for documentation and resources. You can also find some terms definitions used here in Captain Debug's Blog.

It is very straightforward to configure AspectJ when you are using maven in your project. All you have to do is to add the aspectj-maven-plugin in the build section of your pom.xml:

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>
</plugin>

You have to add the aspectjrt and aspectjtools dependencies to the plugin in order to let maven find the necessary to compile the AspectJ source.


Then you can code the validation AspectJ advice BackingBeanValidatorAdvice.aj as a normal source file:

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
...

public aspect BackingBeanValidatorAdvice {

    pointcut doSaveChanges() : execution(* com.foo.beans.BackingBean.saveChanges(..));

    void around() : doSaveChanges() {
        FacesContext context = FacesContext.getCurrentInstance();
        BBean bbean = context.getApplication().evaluateExpressionGet(context, "#{bBean.selected}", BBean.class); 

        // perform custom validation

        // if validation fails
        if (..) {
         String sum = "...";
         String det = "...";
         context.addMessage("error", new FacesMessage(FacesMessage.SEVERITY_ERROR, sum, det);
         return;
        }
        proceed();
    }
}

Doing this, when your backing bean's saveChanges(..) method is invoked, the pointcut is reached and the advice performing the custom validation is executed. Then, back to the jsf page, you can show the error's custom message as usual using <h:messages ... />

As you can see, you can apply this mechanism also to perform logging, database audits, notifications, etc. That's precisely the potential of AspectJ used in conjunction with SoC design principle.


Automatic pointcut configuracion using Spring

In this article, you had to add the pointcut manually in your advice's source. If you are using springframework, you can configure the pointcuts in the application-context.xml. Here is an example of using AOP in spring to implement a database logging concern without modifying a single line of code in your existing application. In your application-context.xml file add the following:

...

<bean id="databaseAuditAutoProxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
  <property name="beanNames">
<list>
<value>*Dao</value>
</list>
  </property>
  <property name="interceptorNames">
<list>
<value>regexpAdvisor</value>
</list>
  </property>
</bean>

<bean id="regexpAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice">
<ref local="databaseLoggerAuditAdvisor"/>
</property>
<property name="pointcut">
   <bean class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="patterns">
<value>.*read.*,.*load.*,.*get.*,.*save.*,.*Username.*</value>
</property>
<property name="excludedPatterns">
<value>.*find.*,.*History.*</value>
</property>
   </bean>
</property>
</bean>

<bean id="databaseLoggerAuditAdvisor" class="com.foo.aspects.DatabaseLoggerAuditAdvice">
<property name="auditService" ref="auditService"/>
</bean>
...

The databaseAuditAutoProxy automatically create proxies for the beans names that match beanNames attribute. The regexpAdvisor references the advisor implementation itself and defines a pointcut that uses regular expressions to define method names to intercept.

Then you have to code the DatabaseLoggerAuditAdvice.aj class:

public class DatabaseLoggerAuditAdvice implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice {

    private AuditService auditService;

    public LoggerAuditAdvice() {
    }

    /**
     * @see org.springframework.aop.MethodBeforeAdvice#before(java.lang.reflect.Method,
     *      java.lang.Object[], java.lang.Object)
     */
    public void before(Method method, Object[] args, Object target) throws Throwable {

        log = LogFactory.getLog(target.getClass());
        if (log.isInfoEnabled()) {
            Audit audit = new Audit();
            audit.setUsername(SecurityHelper.getUserName());
            audit.setTime(new Date());
            audit.setMethodName(method.getName());
            audit.setMethodArgumentsValue(ArrayUtils.toString(args, "null"));
            log.info(" [[[BEFORE]]] " + audit);
        }
    }

    /**
     * @see org.springframework.aop.AfterReturningAdvice#afterReturning(java.lang.Object,
     *      java.lang.reflect.Method, java.lang.Object[], java.lang.Object)
     */
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {

        log = LogFactory.getLog(target.getClass());
        if (log.isInfoEnabled()) {
            Audit audit = new Audit();
            audit.setUsername(SecurityHelper.getUserName());
            audit.setTime(new Date());
            audit.setMethodName(method.getName());
            audit.setMethodArgumentsValue(ArrayUtils.toString(args, "null"));
            audit.setTarget(target.getClass().getSimpleName());
            log.info(" [[[AFTER]]] " + audit);
            this.getAuditService().save(audit);
        }
    }

    public void afterThrowing(Method m, Object[] args, Object target, Throwable ex) {
        log = LogFactory.getLog(target.getClass());
        log.error("Exception in method: " + m.getName() + " Exception is: " + ex.getMessage() + " for user "
            + SecurityHelper.getUserName());
    }

    public AuditService getAuditService() {
        return auditService;
    }

    public void setAuditService(AuditService auditServiceToSet) {
        this.auditService = auditServiceToSet;
    }
}

Hope it helps, comment and give feedback.

PS: If you get errors in your project, you have to make sure you have added  *.aj as source files, and to make the project as a "AspectJ Project", ie, add the AspectJ runtime. The method may vary depending on the IDE you are using. In Eclipse, you have to configure the project as an AspectJ Project (right click on the project, Configure > Convert to AspectJ Project..) and add *.aj fileset mapping to the source code in the Java Build Path of your project.


Thursday, December 6, 2012

Avoid closing a rich:popupPanel in richfaces when validation error occurs

In richfaces is common to have a rich:popupPanel that allows some input. If you must validate that input, you may want to left the panel open when a validation error occurs to show error messages. See the following picture that shows the case:




You can achieve this using the oncomplete attribute of the a4j:commandButton tag.


<rich:popupPanel 
  id="cp" minHeight="200" minWidth="450" modal="true"
     domElementAttachment="form" autosized="true">
....


   <a4j:commandButton
      value="Save"
      disabled="#{not securityScope.userInRole['ROLE_ABM']}"
  action="#{backingBean.save}"
      oncomplete="if (#{facesContext.maximumSeverity==null}) 
                  #{rich:component('cp')}.hide(); return false;">
   </a4j:commandButton>
...
</rich:popupPanel>

The important part is  #{facesContext.maximumSeverity == null} to ensure that there are no errors present after validation.