Wednesday, October 16, 2013

Simplifying Java Method Construction for Add On Operations

Developing add on functionality with Spring Roo follows a fairly simple process; build an operation, and set up triggers for that operation. One of the predominant development tasks with building add on code for Roo is implementing the operation.  And one of the predominate parts of building those operations will be constructing methods for ITD's.

Since Java and AspectJ is in Roo's DNA, the development support for those resources is rich.  Basically the developer is responsible for defining new Java functionality via an object graph.  There are advantages to doing this.  For example all the messiness of publishing the resulting code to the file system and transaction handling have been abstracted away.  Representing Java in object graphs also allows Roo to perform integrity checks on the code.

Thus a frequent development task is adding fields, methods and annotations to Java classes.  That in turn means that we will have to construct object graphs quite frequently.  Construction of object graphs can get rather messy themselves, having to construct all the objects and then wire them together.

After crawling though Roo's source code and everything I could find on the Internet I could construct add on's following seemingly best practices.  But with following these practices the head winds are strong and the development slow.  And also just a lot of typing.  The problem with object graph construction in code is that it obscures the functionality being put together.  On one hand in code we are building objects and then assembling them together.  But also we are attempting to express a unit of functionality, so more closely we follow that structure then the easier it will be to develop and maintain our code.

For example consider some early code I wrote;


 private MethodMetadata getAssociationDeleteMethod( JavaType withType ) {

  String withClass = withType.getSimpleTypeName() ;
  String withObject = StringUtils.uncapitalize(withClass) ;

  // Specify the desired method name
  JavaSymbolName methodName = new JavaSymbolName("delete" + withClass);

  // set up method annotations
  List methodAnnotations = new ArrayList() ;

  AnnotationMetadataBuilder requestAnnotation = new AnnotationMetadataBuilder(new JavaType( "RequestMapping" ));
  requestAnnotation.addStringAttribute("value", "/{" + thisObject + "Id}/" + withObject );
  requestAnnotation.addEnumAttribute("method", new EnumDetails( new JavaType( "RequestMethod" ), new JavaSymbolName( "DELETE" )));
  requestAnnotation.addStringAttribute("produces", "text/html" ) ;
  
  methodAnnotations.add(requestAnnotation.build()) ;
  AnnotationMetadataBuilder tranactionalAnnotation = new AnnotationMetadataBuilder( new JavaType( "Transactional")) ;
  methodAnnotations.add( tranactionalAnnotation.build() ) ;

  // set up method parameters 
  List parameterTypes = new ArrayList();

  AnnotationMetadataBuilder pathVariable = new AnnotationMetadataBuilder( new JavaType( "PathVariable" )) ;
  pathVariable.addStringAttribute("value", "" + thisObject + "Id" ) ;
  List annotations = new ArrayList() ;
  annotations.add(pathVariable.build()) ;

  AnnotatedJavaType userIdParam = new AnnotatedJavaType(JavaType.LONG_OBJECT, annotations) ;
  parameterTypes.add(AnnotatedJavaType.convertFromJavaType( new JavaType( "HttpServletRequest" )));
  parameterTypes.add( userIdParam );
  parameterTypes.add(AnnotatedJavaType.convertFromJavaType( new JavaType( "Model" )));

  // Define method parameter names (none in this case)
  List parameterNames = new ArrayList();
  parameterNames.add( new JavaSymbolName( "request")) ;
  parameterNames.add( new JavaSymbolName( thisObject + "Id")) ;
  parameterNames.add(new JavaSymbolName("uiModel" )) ;
  
  InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder()
   .appendFormalLine( thisClass + " " + thisObject + " = " + thisClass + ".find" + thisClass + "(" + thisObject + "Id) ;" )
   .appendFormalLine("if (" + thisObject + " == null)" ) 
   .indent() 
   .appendFormalLine("throw new IllegalArgumentException(\"" + thisClass + " not found\");" ) 
   .indentRemove() 
   
   .appendFormalLine("String[] " + withObject + "Ids = request.getParameterValues( \"" + withObject + "Id\" ) ;")  
   .appendFormalLine("for ( int i = 0 ; i < " + withObject + "Ids.length ; i++ ) {" ) 
   .indent()
   .appendFormalLine("Long " + withObject + "Id = Long.parseLong( " + withObject + "Ids[ i ] ) ;") 
   .appendFormalLine( thisObject + ".delete" + withClass + "(" + withObject + "Id) ;" ) 
   .indentRemove() 
   
   .appendFormalLine( "}" ) 
   .appendFormalLine("return \"redirect:/" + thisClass.toLowerCase() + "s/\" + " + thisObject + " + \"/" + withObject + "s\";") ;  

  MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
    getId(), Modifier.PUBLIC, methodName, JavaType.STRING, 
      parameterTypes, parameterNames,
    bodyBuilder);
  methodBuilder.setAnnotations(methodAnnotations);
  
  return methodBuilder.build() ;
 }

This is a big method far beyond recommended practice in length.  Plus it's hard to comprehend.  We know that we are building a method and finding the body content isn't a problem.  But beyond that finding something like the method signature takes some study.

I find writing code like this rather tedious and slow.  It also takes a lot concentration to write.  On one hand one has to maintain a mental model of the object graph itself and on the other the representation of the method.  And unfortunately as a result neither the object graph or the method under construction are clear.

Even more important is consideration about how to support code like this in an enterprise environment.  As much as we would like to deny it, in an enterprise environment code is passed on from either individual to individual or even worse to a group.  Without conciseness code like this deteriorates into spaghetti rapidly.

But in the end I felt that there had to be a simpler and more concise way to assemble methods.  And since for a add on project of any size we are going to be doing a lot of method construction.

What I came up with a facade class to hide mechanics of the object graph assembly.  Instances of the facade wraps the out of the box Roo implementation.  This facade also supplies a series of methods to allow us to declare parts of the method that return the facade instance itself.  Returning the facade allows the methods to be chained together.  With the facade class the original code can now be refactored into;


 private MethodMetadata getAssociationDeleteMethod( JavaType withType ) {

  String withClass = withType.getSimpleTypeName() ;
  String withObject = StringUtils.uncapitalize(withClass) ;

  return new MethodBuilder( getId() )
   .modifier(Modifier.PUBLIC ) 
   .returns(JavaType.STRING ) 
   .named( "delete" + withClass )
    
   .parameter( new JavaType( "HttpServletRequest" ), "request" )
   .parameter(JavaType.LONG_OBJECT,   
     new MethodBuilder.Annotation("PathVariable" )
       .attribute("value", "" + thisObject + "Id" ),
     thisObject + "Id" )
   .parameter( new JavaType( "Model" ), "uiModel" )
      
   .annotation( 
     new MethodBuilder.Annotation( "RequestMapping" )
       .attribute("value", "/{" + thisObject + "Id}/" + withObject )
       .attribute("method", new EnumDetails( new JavaType( "RequestMethod" ), new JavaSymbolName( "DELETE" )))
       .attribute("produces", "text/html" ))
   .annotation( new MethodBuilder.Annotation( "Transactional") )

   .body(
    new InvocableMemberBodyBuilder()
      .appendFormalLine( thisClass + " " + thisObject + " = " + thisClass + ".find" + thisClass + "(" + thisObject + "Id) ;" )
      .appendFormalLine("if (" + thisObject + " == null)" ) 
      .indent() 
      .appendFormalLine("throw new IllegalArgumentException(\"" + thisClass + " not found\");" ) 
      .indentRemove() 
    
      .appendFormalLine("String[] " + withObject + "Ids = request.getParameterValues( \"" + withObject + "Id\" ) ;")  
      .appendFormalLine("for ( int i = 0 ; i < " + withObject + "Ids.length ; i++ ) {" ) 
      .indent()
      .appendFormalLine("Long " + withObject + "Id = Long.parseLong( " + withObject + "Ids[ i ] ) ;") 
      .appendFormalLine( thisObject + ".delete" + withClass + "(" + withObject + "Id) ;" ) 
      .indentRemove() 
    
      .appendFormalLine( "}" ) 
      .appendFormalLine("return \"redirect:/" + thisClass.toLowerCase() + "s/\" + " + thisObject + " + \"/" + withObject + "s\";"))
   .build() ;
 }
 
The above implementation is much improved, well at least in my opinion.  For example with a glance we can determine that the method has three parameters and two annotations.  Additionally with using the facade class also hides the details of coercing objects.

Here is the facade code;


package com.repik.roo.builders;

import java.util.ArrayList;
import java.util.List;

import org.springframework.roo.classpath.details.MethodMetadata;
import org.springframework.roo.classpath.details.MethodMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder;
import org.springframework.roo.model.EnumDetails;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;

/**
 * This class wraps Roo method building support into a series
 * of methods that can be changed together.  Doing this allows
 * simplier representation of method definitions.
 * 
 * @author Dan Repik
 *
 */
public class MethodBuilder {

 public static class Annotation {

  private AnnotationMetadataBuilder annotationBuilder ;
  
  public Annotation( String annotationType ) {
   annotationBuilder = new AnnotationMetadataBuilder(new JavaType( annotationType ));
  }
  
  public Annotation attribute( String name, String value ) {
   annotationBuilder.addStringAttribute( name, value ) ; 
   return this ;
  }
  
  public Annotation attribute( String name, Boolean value ) {
   annotationBuilder.addBooleanAttribute( name, value ) ; 
   return this ;
  }
  
  public Annotation attribute( String name, EnumDetails value ) {
   annotationBuilder.addEnumAttribute( name, value ) ; 
   return this ;
  }
  
  public AnnotationMetadata build() {
   return annotationBuilder.build() ;
  } 
 }
 
 private MethodMetadataBuilder methodBuilder;

 private List annotationList = new ArrayList() ;

 private List parameterTypes = new ArrayList();
 
 private List parameterNames = new ArrayList();

 public MethodBuilder( String metadataId ) {
  methodBuilder = new MethodMetadataBuilder( metadataId ) ;
 }
 
 public MethodBuilder modifier( int modifier ) {
  methodBuilder.setModifier(modifier) ;
  return this ;
 }
 
 

 public MethodBuilder parameter( AnnotatedJavaType parameterType, String parameterName ) {
  parameterTypes.add( parameterType ) ;
  parameterNames.add( new JavaSymbolName(parameterName )) ;
  return this ;
 }
 
 public MethodBuilder parameter( JavaType parameterType, String parameterName ) {
  return parameter( AnnotatedJavaType.convertFromJavaType( parameterType ), parameterName ) ;
 }

 public MethodBuilder parameter( JavaType parameterType, Annotation parameterAnnotation, String parameterName )  {
  return parameter(new AnnotatedJavaType(parameterType, parameterAnnotation.build()), parameterName ) ;

 }
 
 public MethodBuilder annotation( Annotation annotation ) {
  annotationList.add( annotation.build() ) ;
  return this ;
 }
 
 public MethodBuilder body( InvocableMemberBodyBuilder body ) {
  methodBuilder.setBodyBuilder( body ) ;
  return this ;
 }
 

 
 public MethodBuilder returns( JavaType returnType ) {
  methodBuilder.setReturnType(returnType) ;
  return this ;
 }
 
 public MethodBuilder named( String name ) {
  methodBuilder.setMethodName( new JavaSymbolName( name )) ;
  return this ;
 }

 public MethodMetadata build() {
  methodBuilder.setAnnotations( annotationList ) ;
  methodBuilder.setParameterTypes(parameterTypes) ;
  methodBuilder.setParameterNames(parameterNames) ;
  return methodBuilder.build() ;
 }
}


As you can see there really isn't a lot of code here, but a little can go along way towards improving the readability of the code.

Wednesday, August 7, 2013

Building the Association Plugin: Domain Model Design

Here we are going to develop a design for building and Spring Roo add-on that allows management of associations between JPA entities.  From prior implementation it's known that the add-on is going to have to manage code in both the domain and presentation model.

This post focus's on functionality of the add-on involving the domain model.  Design for the presentation models will be discussed in a later posts.

Introduction


Roo interacts with your add on largely with two types of processing requests, command operations and annotation processing.  During both of these processes the add on has the opportunity to modify project assets.

Project assets can be categorized as developer owned and Roo generated assets.  Developer owned assets are assets that the developer has either written by hand or instructed Roo to create,  These assets  define the project and are the system of reference during Roo code generation.  Add on generated assets on the other hand are Roo domain and can change at anytime.

During command processing the developer has requested Roo to perform an operation. This means that for the scope of the command request the add on has access to developer assets.  Generally those modifications are centered on either creating new assets for the developer or adding to existing assets.  In the case of the entity model this means adding annotations or entities.  Add on's should contribute to the construction of the project, this means that removal of assets is discouraged.

In contrast let's consider annotation processing.  During this process modification to developer assets should be considered forbidden. Here the programming sandbox allows you to do anything you want as long as it is limited to maintaining the contents of the file associated with the annotation.  Roo really prefers we design to a one to one relationship between annotation definitions and ITD files.  So, it is best to go with the flow and design solutions to conform to this relationship.

We then need our implementation to follow this structure.  In command processing the add on needs to set everything up in the developer assets so that after the resultant annotation processing all the intended functionality is available to the project.

High Level Design


Right now the add on is going to offer only one command, that of creating an association between two entities.  Functionally this means that;
  • A association entity between the user provided entities must be created.
  • Annotation markup on each of the user provided entities needs to be added.
  • New annotation handlers that inject entity operations that manage the association need to be provided.
For the association plugin the create associations we expect the command to look like;

association add many-to-many --between <entity-1> --and <entity-2>

where <entity-1> and <entity-2> are the two independent entities.

In response to that command the add on then needs to create the association entity.  This entity will contain a reference to each of the provided entities, existence of a entity denotes an association.  The name of the association entity will be built by concatenating the names of the two entities together.  This gives us two possible combinations for the association entity name.  Later we will need to derive that name later so to simplify this the name with the lowest alphabetical sort order will be used first followed by the other.

Next each annotations will need to be injected into each of the independent entities in the relationship.  An important factor here is that an independent entity can participate in more then one association.  While it would have been convenient to specific the association entity directly in the annotation.  There's a host of problems going that route.  But since we were careful about how we named the association entity allows us to derive it rather then have to have it explicitly stated.  We can keep it simple and just accept a list of independent entities that the entity is associated with.  So we can expect the annotation to have the form;

@association( <array of entity names> )

This annotation will then trigger the generation of the ITD's.  From prior development we have a example copy of the ITD that need to be generated already developed from prior posts.

Low Level Design


Now we need to tighten up the definition of the functionality that we need to construct.  First we have the association entity.  We can produce a example from prior development.

package com.repik.multitenant.security.domain;

import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.jpa.activerecord.RooJpaActiveRecord;
import org.springframework.roo.addon.tostring.RooToString;

@RooJavaBean
@RooToString
@RooJpaActiveRecord(identifierColumn = "ORGANIZATION_USER_ID", finders = { "findOrganizationUsersByOrganization", "findOrganizationUsersByAppUser" })
public class OrganizationUser {

    @ManyToOne
    @JoinColumn(name = "ORGANIZATION_ID")
    private Organization organization;

    @ManyToOne
    @JoinColumn(name = "USER_ID")
    private AppUser appUser;
}


The association annotation describing the relationship is then injected into each end of the relationship.  That annotation triggers Roo to invoke the add-on's ITD generation code.  For the ITD implementation we can also produce and example from a prior implementation of the functionality.  The ITD is going to inject four methods into the relationship entity;
  • find instances of a the other entity associated with an entity.
  • find instances of the other entity not associated with the entity.
  • Create an association between the entities
  • Delete an association between the entities
So we also will need a copy of the prior queries to use as a template.

package com.repik.multitenant.security.domain;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

import org.springframework.transaction.annotation.Transactional;

public privileged aspect AppUser_Association {

    public void AppUser.createOrganizationUser(Long organizationId ) {
  Organization organization = Organization.findOrganization( organizationId ) ; 
        if (organization == null) throw new IllegalArgumentException("The organization argument is required");

        OrganizationUser ou = new OrganizationUser() ;
  ou.setOrganization( organization ) ;
  ou.setAppUser( this ) ;
  ou.persist() ;
    }
    
    public Integer AppUser.deleteOrganizationUser(Long organizationId ) {
        if (organizationId == null) throw new IllegalArgumentException("The organizationId argument is required");

        EntityManager em = OrganizationUser.entityManager();
  Query q = em.createQuery("DELETE FROM OrganizationUser ou WHERE ou.organization.id = :organizationId AND ou.appUser.id = :userId" ) ;
  q.setParameter("organizationId", organizationId);
  q.setParameter("userId", id );
  try {
   return q.executeUpdate();
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  return 0 ;
    }

 public static TypedQuery AppUser.findOrganizationsAssociatedWithAppUser(
   Long userId) {
  if (userId == null)
   throw new IllegalArgumentException("The userId argument is required");
  
  EntityManager em = AppUser.entityManager();
  TypedQuery q = em.createQuery(
    "SELECT ou.organization FROM OrganizationUser ou WHERE ou.appUser.id = :userId",
    Organization.class);
  q.setParameter("userId", userId);
  return q;
 }

 public static TypedQuery AppUser.findOrganizationsNotAssociatedWithAppUser(
   Long userId) {
  if (userId == null)
   throw new IllegalArgumentException("The userId argument is required");
  
  EntityManager em = AppUser.entityManager();
  TypedQuery q = em.createQuery(
    "SELECT o FROM Organization o WHERE o.id not in ( SELECT ou.organization.id FROM OrganizationUser ou WHERE ou.appUser.id = :userId )",
    Organization.class);
  q.setParameter("userId", userId);
  return q;
 }
}

With everything is in place from the design perspective we can now proceed to implementation.

Thursday, July 25, 2013

Handling Multi-Selection

In an earlier post Restful URLs for Managing Entity Relationships, I developed the set of restful URL's needed to manage associative relationships between independent entities.  Now in this post it time to implement this design.

In that post we discovered that to support a one to many relationship between two entities the following use case scenarios need to be supported;
  • Show the list of entities associated with an entity
  • Add form to allow the user to select entities for association with the entity.
  • Add associations between an entity and those that the user selected.
  • Remove form allowing the user to select entities associated with an entity for removal.
  • Remove associations between an entity and those that the user selected for removal.
Also in other posts I have noted that adding functionality to Roo applications using a JPA/JSP technology stack involves the following tasks;
  • Building the entity queries needed,
  • Adding the controller methods, 
  • Adding the supporting JSP's.
With that information, scoping out the tasks to implement this can be easily done.

Building the Entity Manipulation Methods

We will need to build four entity manipulation methods to cover the five operations, two queries and two entity modification methods.  They are;
  • Create an association with another entity.
  • Remove an association with another entity.
  • Return the collection of entities that are associated with the entity..
  • Return the collection of entities that are not associated with the entity.
The method to select the associated entities can be used for both the show and add form operations.  The last query will be an expensive query to execute, so controller implementations use this method judiciously.

It's important to realize that through the course of development, the scope of implementation is only one end of one relationship.  In complete applications many such relationships should be expected,  Subsequently one should conclude that code similar to the implementation here will be scattered throughout the domain code base.  Additionally a domain entity can maintain multiple relationships resulting in multiple manifestations of the code within the bounds of an entity. So we need to be methodical in organizing this code.

One final take away when considering the volume of similar code involved here is that we are having to manage multiple manifestations of similar code.  That's what Roo is really good it, so long term it's going to be in our interest to have an add on do this work.  And that's precisely where we are ultimately headed, but we are getting a little ahead of ourselves here.  Still, we need structure the code to be add on implementation friendly.

We will put all these methods in the same ITD and name it '<entity>_Association'. Here I am copying Roo's example and putting all this functionality in a separate ITD.  Here the '<entity>' represents the entity for one end of the association.

The subsequent implementation utilizes typical JPA functionality, with the most thought intensive part was query construction.  The query methods return a TypedQuery object the same as the finders that Roo constructs.  Some care in naming of the methods is required because functionality expressing more than one relationship can occur in the entities class method name namespace.

And finally the biggest gotcha that I stumbled across while implementing was that entity modification cannot be done in static code.  The finder methods are static, which is reasonable since we don't have and entity object to start with.  So it's tempting to want to do the same with the create and delete methods.  Particularity since here we are starting out with two entity id's for the operation.  After putting our RDBMS cap on, the delete method is a simple delete from where statement. allowing us to avoid having to get the entity.  JPA doesn't like that approach, instead one has to retrieve the entity object and then create and delete associations with that object.

Here is the final implementation for the entity methods;

package com.repik.multitenant.security.domain;

import com.repik.multitenant.security.domain.AppUser;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;

public privileged aspect AppUser_Association {

 public static TypedQuery AppUser.findOrganizationsAssociatedWithAppUser(Long userId) {
  if (userId == null)
   throw new IllegalArgumentException("The userId argument is required");
  
  EntityManager em = AppUser.entityManager();
  TypedQuery q = em.createQuery(
    "SELECT ou.organization FROM OrganizationUser ou WHERE ou.appUser.id = :userId",
    Organization.class);
  q.setParameter("userId", userId);
  return q;
 }

 AppUser.findOrganizationsNotAssociatedWithAppUser(Long userId) {
  if (userId == null)
   throw new IllegalArgumentException("The userId argument is required");
  
  EntityManager em = AppUser.entityManager();
  TypedQuery q = em.createQuery(
    "SELECT o FROM Organization o WHERE o.id not in ( "
        + "SELECT ou.organization.id FROM OrganizationUser ou WHERE ou.appUser.id = :userId )",
    Organization.class);
  q.setParameter("userId", userId);
  return q;
 }

  public void AppUser.createOrganizationUser(Long organizationId ) {
    Organization organization = Organization.findOrganization( organizationId ) ; 
      if (organization == null) throw new IllegalArgumentException("The organization argument is required");

      OrganizationUser ou = new OrganizationUser() ;
      ou.setOrganization( organization ) ;
      ou.setAppUser( this ) ;
      ou.persist() ;
    }

  public Integer AppUser.deleteOrganizationUser(Long organizationId ) {
    if (organizationId == null) throw new IllegalArgumentException("The organizationId argument is required");

    EntityManager em = OrganizationUser.entityManager();
    Query q = em.createQuery("DELETE FROM OrganizationUser ou WHERE ou.organization.id = :organizationId AND ou.appUser.id = :userId" ) ;
    q.setParameter("organizationId", organizationId);
    q.setParameter("userId", id );
    try {
      return q.executeUpdate();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return 0 ;
    }
}


Adding the Controller Methods

Now that the entity methods are in place we are ready to work on the presentation side of the application. Starting with the controller methods.  We have the service mappings that a required and now the entity methods the implementation can be completed.

package com.repik.multitenant.security.web;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.http.HttpRequest;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.repik.multitenant.security.domain.AppUser;
import com.repik.multitenant.security.domain.Organization;
import com.repik.multitenant.security.domain.OrganizationUser;

privileged aspect AppUserController_OrganizationAssociations {

 @RequestMapping(value="/{userId}/organizations", produces = "text/html")
    public String AppUserController.showOrganizations(
      @PathVariable( "userId" ) Long userId, Model uiModel ) {
        uiModel.addAttribute("organizations", AppUser.findOrganizationsAssociatedWithAppUser(userId).getResultList());
        uiModel.addAttribute("userId", userId);
        return "appusers/organizations/show";
 }
 
 @RequestMapping(value="/{userId}/organizations", params="form", produces = "text/html")
    public String AppUserController.addOrganizationsForm(
      @PathVariable( "userId" ) Long userId,
      @RequestParam( value="form", required=true ) String form, 
      Model uiModel ) {
  if ( "add".equals( form )) {
   uiModel.addAttribute("organizations", AppUser.findOrganizationsNotAssociatedWithAppUser(userId).getResultList());
   uiModel.addAttribute( "method", "POST" ) ;
  }
  else {
         uiModel.addAttribute("organizations", AppUser.findOrganizationsAssociatedWithAppUser(userId).getResultList());
   uiModel.addAttribute( "method", "DELETE" ) ;
  }
        uiModel.addAttribute("userId", userId);
        return "appusers/organizations/edit";
 }
 
 @RequestMapping(value="/{userId}/organizations", method = RequestMethod.POST, produces = "text/html")
 @Transactional
    public String AppUserController.addOrganizations( HttpServletRequest request,
      @PathVariable( "userId" ) Long userId, Model uiModel ) {
  AppUser user = AppUser.findAppUser(userId) ;
        if (user == null) throw new IllegalArgumentException("User not found");

  String[] organizationIds = request.getParameterValues( "organizationId" ) ;
  for ( int i = 0 ; i < organizationIds.length ; i++ ) {
   Long organizationId = Long.parseLong( organizationIds[ i ] ) ;
   user.createOrganizationUser(organizationId) ;
  }

        return "redirect:/appusers/" + userId + "/organizations";

 }
 
 @RequestMapping(value="/{userId}/organizations", method = RequestMethod.DELETE, produces = "text/html")
 @Transactional
    public String AppUserController.deleteOrganizations( HttpServletRequest request, 
      @PathVariable( "userId" ) Long userId, Model uiModel ) {
        AppUser user = AppUser.findAppUser(userId) ;
        if (user == null) throw new IllegalArgumentException("User not found");

  String[] organizationIds = request.getParameterValues( "organizationId" ) ;
  for ( int i = 0 ; i < organizationIds.length ; i++ ) {
   Long organizationId = Long.parseLong( organizationIds[ i ] ) ;
   user.deleteOrganizationUser(organizationId) ;
  }

        return "redirect:/appusers/" + userId + "/organizations";
 }
}


Adding the JSP's

The final step is to build the JSP to display the lists of entities that are either associated or not with the entities.  We also need to display these lists in either show or update mode, where the difference between the two is the appearance of a check box.

While it's tempting to think that we could use just one JSP template to do it all.  But adding check boxes means we also have to put them in a form.  That means we have to build and keep track form elements and URL's.  So it's best to keep viewing and editing as separate JSP's.

We will need to modify the views.xml and add the two JSP templates.  Recall that in the webapp that Roo setup all of the tiles and JSP files for the entities are located in; '/src/main/webapp/WEB-INF/views'.  In that folder each entity has a folder, so the focus of the modifications we will be making will be to the 'organizations' folder.

Defining the Tiles Views

In the view.xml found in the 'organizations' folder two tiles definitions need to be added to show and update multiple selections.

 <definition extends="default" name="appusers/organizations/show">
    <put-attribute name="body" value="/WEB-INF/views/appusers/showOrganizations.jspx" />
</definition>

<definition extends="default" name="appusers/organizations/edit">
    <put-attribute name="body" value="/WEB-INF/views/appusers/updateOrganizations.jspx" />
</definition>

Show Organizations

Next up is the show organizations JSP (showOrganizations.jspx).  The structure of this file is similar to the list JSP file for the organizations entity that Roo generated.  The differences between the two JSP's is that the ability to create, update, and delete organizations has been turned off.  Deactivating these operations is optional, it's done here because user should be focused on what organizations are associated with a particular user rather then performing organization management functions.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:jsp="http://java.sun.com/JSP/Page" 
  xmlns:form="urn:jsptagdir:/WEB-INF/tags/form" 
  xmlns:table="urn:jsptagdir:/WEB-INF/tags/form/fields" 
  version="2.0">
    <jsp:directive.page contentType="text/html;charset=UTF-8"/>
    <jsp:output omit-xml-declaration="yes"/>
    <table:table data="${organizations}" id="l_com_repik_multitenant_security_domain_Organization" create="false" update="false" delete="false" path="/organizations" z="5U3sseAfezAakwxsdptI+SXks8o=">
        <table:column id="c_com_repik_multitenant_security_domain_Organization_name" property="name" z="8FaKf+BmDqidSHXdwfn1tV1TnQo="/>
        <table:column id="c_com_repik_multitenant_security_domain_Organization_parent" property="parent" z="D0HsRlG9onaVgrMyzSjE6hpa4NU="/>
        <table:column id="c_com_repik_multitenant_security_domain_Organization_roles" property="roles" z="1F8qMV7ifZp90bu0SUpI+uNwzyY="/>
        <table:column id="c_com_repik_multitenant_security_domain_Organization_description" property="description" z="2tWVqb2uDhtkEVsFS0M2anZ68vg="/>
    </table:table>
</div>

Update Organizations

The last part is the update organizations JSP (updateOrganizations.jspx).  This JSP is used for both creating and removing associations between AppUsers and Organizations.  There's a couple of things going on in this file, making it more complex then showOrganizations.jspx.

First of course is that the table showing the organizations is now a multiple selection table using the organizationId for the value of the checkboxes.  While deactivating the create, update and delete operations was a matter of preference before with the showOrganizations.jspx file, doing that here is important.  We want to prevent navigation away from this screen since we want the user focused on managing the associations between an AppUser and Organizations rather then managing the Organizations themselves.

Also the multiple selection table needs to be wrapped in a form so the results of the user selection can be sent to the server.  Adding the form requires; setting up the form itself and of course adding a submit button.


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:jsp="http://java.sun.com/JSP/Page" 
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fn="http://java.sun.com/jsp/jstl/functions" 
  xmlns:form="http://www.springframework.org/tags/form" 
  xmlns:spring="http://www.springframework.org/tags"
  xmlns:table="urn:jsptagdir:/WEB-INF/tags/form/fields" 
  version="2.0">
    <jsp:directive.page contentType="text/html;charset=UTF-8"/>
    <jsp:output omit-xml-declaration="yes"/>
    <spring:url value="/appusers/${userId}/organizations" var="form_url" />

    <c:set var="enctype" value="application/x-www-form-urlencoded"/>
    <c:set var="idField" value="userId" />
    <c:set var="versionField" value="none" />

    <form:form action="${form_url}" method="${method}" modelAttribute="${modelAttribute}" enctype="${enctype}">
        <table:table data="${organizations}" id="l_com_repik_multitenant_security_domain_Organization" 
                path="/organizations" create="false" update="false" delete="false" 
                multiselect="organizationId" show="false" 
                z="5U3sseAfezAakwxsdptI+SXks8o=">
            <table:column id="c_com_repik_multitenant_security_domain_Organization_name" property="name" z="8FaKf+BmDqidSHXdwfn1tV1TnQo="/>
            <table:column id="c_com_repik_multitenant_security_domain_Organization_description" property="description" z="2tWVqb2uDhtkEVsFS0M2anZ68vg="/>
        </table:table>
        <div class="submit" id="${fn:escapeXml(userId)}_submit">
            <spring:message code="button_save" var="save_button" htmlEscape="false" />
            <script type="text/javascript">Spring.addDecoration(new Spring.ValidateAllDecoration({elementId:'proceed', event:'onclick'}));</script>
            <input id="proceed" type="submit" value="${fn:escapeXml(save_button)}" />
        </div>
    </form:form>
</div>


Now that the the entity queries, controller methods and JSP elements are in place multiple selection of elements between an Organization and AppUser is now functionality available.  What still is missing in incorporating the appropriate links into the menu system to access the new URL's.

This code supports one side of a many to many relationship, Organizations to AppUsers.  We will need to build similar code to handle the other direction of the association; AppUsers to Organizations.  Additionally, we will need to do the same for all the other associations appearing in our application entity model.  Building all this code is a lot of work.  But it's tasks like this that Roo excels at, all we need to do is build and add on that generates this code for us.  And that's precisely where we will go next.

Saturday, April 27, 2013

Restful URLs For Managing Entity Relationships

Web applications created by the scaffolding of Spring Roo's Web MVC add on map URLs using restful design principles.  Eventually as a developer you will need to add functionality that involve new request mappings.  So how should one create new mappings while maintaining consistency with both restful design principles and that provided by the scaffolding?

Controllers are generated by Spring Roo's scaffolding on a per entity basis.  For each entity a controller is generated for the following seven operations and URL request mappings are defined;

Operation Request Type URL
list GET /<app-name>/<entity>
show GET /<app-name>/<entity>/{id}
create form GET /<app-name>/<entity>?form
create POST /<app-name>/<entity>
update form GET /<app-name>/<entity>/{id}?form
update PUT /<app-name>/<entity>
delete DELETE /<app-name>/<entity>/{id}

This set of request mappings provide basic CRUD operations (show, create, update, and delete) along with ability to list entities, and display create and update forms.  For an independent entity this is all the operations that are typically needed.

In real life entities rarely exist independently of each other.  To have a consistent restful design for our applications, we need to develop a taxonomy to describe various roles that entities can assume in their interactions with each other.  We then need to develop a scheme to map those relationships into request mappings.  Typically when talking about entity relationships most the focus of the discussion is usually on the cardinality (1:1, 1:m, m:m) between entities, but here we are really more concerned about the nature of those relationships.

In considering the relationship between entities we must recognize entities can be either independent or dependent.  Independent entities do not dependent on other entities for their identity.  Such entities can exist by themselves, and may or may not be aware of dependent entities around them.  Dependent entities on the other hand rely on other or parent entities to establish their identity.  Dependent entities may also be existence dependent, meaning the entities existence is ;inked to its parents existence.  The role a dependent entity plays in regards to its parent can be characterized further into the following types;
  • Subtype -- entities of this type embellish another entity.  We don't have any of this type in the muti-tenant security model.
  • Characteristic -- entities of this type represent a group of attributes that can occur multiple times for another entity.
  • Associative -- entities of this type record relationships between other entities.
Consider the following entity relationship model for a multi-tenant security application;

In this model we do have independent entities like AppUser, Permission, and Organization.  But a majority of the entities in this model define relationships between the independent entities and are dependent.  Of the dependent entities here there are none that establish a subtype relationship.  But the AppRole entity is a characteristic of Organization.  The remaining entities OrganizationUser, OrganizationUserRole, and RolePermission are associative.

Since Roo provides the means to handle independent entities we need not be concerned about them.  And while we could manipulate the dependent and reference entities using Roo's default generated code, typically uses require a less technical and more intuitive interface.

To provide an intuitive interface we are going to have to extend our web application to allow users to manage dependent entities in a implicit manner.  For example we need to provide the ability for users to add and remove AppUsers from Organizations, but without exposing the concept of OrganizationUser.  But how do we want to define the request mappings such that they are restful, complement those that already exist in the application and hide the mechanics of managing relationships?

So lets look at the types of entity relationships and the type of functionality that we would expose to the user.

The functionality for subtypes is fairly easy to describe since both the subtype entity and its parent are locked together,  So when a operation is performed on a subtype we need to do the same to its parent.  For example when updating a subtype we should allow the user to update fields of the parent entity also.  And when that update is submitted both the subtype and its parent should be updated.  We can provide that linkage in code, but it shouldn't affect the subtypes restful URLs.

Functionality of characteristic relationships should be restricted to the scope of the entities parent.  So when listing such entities that list would be for a particular parent.  And when creating an entity the parent should already be known.

With associative entities the typical operations will be focused on adding and removing associations between independent entities. To create associations we will need to provide a form allowing multiple selection of candidate entities for inclusion into a relationship with another entity, along with a response service to handle the form submission.  For removing associations we will need to provide another form to allow multiple selection of current related entities, along with a subsequent service to handle the submission of that form.

Now that we have categorized the nature of the relationships that we could have in a entity model.  And we also have a good idea of type of functionality we want to expose to manage those relationships.  The next step now is to determine how we want to map that into functional units that will ultimately become request mappings.  To do this we will use the functionality associated with the request mappings that the Roo scaffolding already uses as the framework.

Functionality Subtype Characteristic Associative
list Roo default mapping applies. Should be restricted to those belonging to a parent. Not applicable.
show Default mapping applies, but need to include parent entity attributes. Default mapping and functionality applies. Show a list of the other associated entities. 
create form Default mapping applies, but need to include form elements of parent entity. Need to hide parent entity id. Show a multi-select list form with candidate entities for association.
create Default mapping applies, but need to create parent entity along with subtype entity. Default functionality and mapping applies. Provide ability to create multiple association entities.
update form Default mapping applies, but need to include form elements of parent entity. Default functionality and mapping applies. Not applicable.
update Default mapping applies, but need to update parent entity along with subtype entity. Default functionality and mapping applies. Not applicable.
delete form Not applicable. Not applicable. Show a multi-select list form with associated entities.
delete Default mapping applies, but need to delete parent entity along with subtype entity. Default mapping and functionality applies. Provide ability to delete multiple associating entities. 

As an astute reader you probably noticed the functionality we needed to map didn't align perfectly with what the scaffolding provides.  Principally for associative entities, we want to provide a multiple selection form to allow for the bulk deletion of associations.  To handle this we added delete form to our list of functionality.  Since the scaffolding treats all entities as independent entities and the concept of a delete form is not applicable to independent entities it doesn't appear in the controllers generated   But we need to add the operation to handle removing associative relationships.  Additionally adding it completes the symmetry that data modification functions (create, update, and delete) have corresponding form functions.

Finally we have arrived at the stage where we can determine a set of URL patterns that we can apply consistently throughout a web application.  With subtype entities we don't have to provide anything more in terms of URLs then what the scaffolding provides already.  For characteristic entities recall that only two of the operations required new URLs.  Just adding what is needed is going to make an inconsistent interface so lets extend the pattern to be consistent.  The URL patterns for characteristic entities are;

Operation Method URL Pattern
list GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>
Returns the list of entities that are characteristics of the parent entity with an id of entity-id.

show GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>/{characteristic-entity-id}
Shows the characteristic for the supplied characteristic-entity-id.  Provided for consistency.
Redirects to;

/<app-name>/<characteristic-entity>/{characteristic-entitty-id}

create form GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>?form
Returns a form for creating a new characteristic for the parent entity.

create PUT /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>
Create a new characteristic for the parent entity.

update form GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>/{characteristic-entity-id}?form
Returns an update form for the characteristic entity.  Redirects to;

/<app-name>/<characteristic-entity>/{characteristic-entity-id}?form

update POST /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>

Returns the list of entities that are characteristics of the parent entity with an id of entity-id.

delete form GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>
Returns the list of entities that are characteristics of the parent entity with an id of entity-id.

delete DELETE /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>/{characteristic-entity-id}
Deletes the charachteristic entity with the supplied id.  Redirects to;

/<app-name>/<characteristic-entity>/{characteristic-entity-id}


Then for associative entities we have the following URL patterns;

Operation Method URL Pattern
show GET /<app-name>/<entity-a>/{entity-a-id}/<entity-b>
Returns the list of entity-b's that are associated of entity-a with an id of entity-a-id.

add-form GET /<app-name>/<entity-a>/{entity-a-id}/<entity-b>?form=add

Returns a multi-select form to allow the selection of entity-b entities NOT associated to entity-a with a id of entity-a-id.


add POST /<app-name>/<entity-a>/{entity-a-id}/<entity-b>

Creates one or more association entities between entity-a-id and entity-b ids found in the request body.

delete-form GET /<app-name>/<entity-a>/{entity-a-id}/<entity-b>?form=delete
Returns a form allowing selection of entity-b entities associated with entity-a for deletion.

delete DELETE /<app-name>/<entity-a>/{entity-a-id}/<entity-b>

Deletes one for more association entities between entity-a-id and entity-b ids found in the request body.

We now have a road map to provide consistent restful URL's for Spring Roo based web applications.  The restful URL's that spring provides works well handling independent entities.  By doing some research and analysis we now have a logical and consistent design for the development of restful URL's to manage relations between entities.


Sunday, April 21, 2013

Building Multiple Selection Lists

The web application that Spring Roo builds by default is restricted to modifications of a single entity.  This interface treats all objects independently, but real life objects have associations with each other.  Effectively managing those associations for the user then is essential for the applications usability.  One part of doing this is to support multiple selection of entities in the user interface, allowing the application to manage many to many relationships for the user.

Adding multiple selection of entities can be done by making a few modifications to the existing table tag.  The scope of the modifications include;
  • Add a check box for each table row.  
  • Provide the ability to turn off the show links.
  • Provide a master select box.
Adding Multi-Select Check Boxes

Adding a check box to each row of the table is central feature of the modifications being made. To do this a new optional attribute to the tag is added.  When this attribute is omitted the tag operates as it normally does, to maintain compatibility with existing usage.  However when present the tag will present a check box in a table cell at the beginning of each row.  The value of the attribute will be used as the name for the check box and the entities id will be the value.

To do this first we need to add the attribute specification to the tag;

<jsp:directive.attribute name="multiselect" 
 type="java.lang.String" required="false" rtexprvalue="false" 
 description="The name for multiselection checkboxes." />

The attribute is named 'multiselect' and when it's not empty a check box is added to the beginning of each row in the table.  But of course to keep things aligned we must also add a table cell to the heading.  This table cell should also contain a check box to function as a master select box.

<c:if test="${not empty multiselect}">
 <th >
  <input type="checkbox" name="masterSelectBox_${multiselect}" />
  <spring:eval var="colCounter" expression="colCounter  + 1" />
 </td>
</c:if>

We will use the checkbox here 'masterSelectBox_' appended with the multiselect attribute value to provide a unique name in case a page contains more then one table.  All of this of course is placed inside a conditional such that if the 'multiselect' attribute is not empty then we add the table cell.  Next we need to add the check boxes to each tow;

<c:if test="${not empty multiselect}">
 <td class="selectBox">
  <input type="checkbox" 
   name="${multiselect}" value="${item.id}" />
 </td>
</c:if>

Like with the column heading this block of JSP is placed inside a conditional, so it only appears if the multiselect attribute is defined.  The table cell is given the class 'selectBox', that will be used by Javascript later to find the cells.  And finally we have the input checkbox itself, here the name is value of the 'multiselect' attribute.

Provide the Ability to Turn Off Show Links


While the existing table tag allows turning off the update and delete links, it doesn't provide the same for the show (view) links.  We are going to need to able to remove these links to prevent the user from exiting the page during the middle of making selections.

To implement this first we need to add another attribute directive to the tag.

<jsp:directive.attribute name="show" 
 type="java.lang.Boolean" required="false" rtexprvalue="true" 
 description="Include 'show' link into table (default true)" />


Then place a conditional expression around the existing table heading cell.

<c:if test="${show}">
 <th></th>
 <spring:eval var="colCounter" expression="colCounter  + 1" />
</c:if>

And finally place a conditional around the existing table cell containing the show link.

<c:if test="${show}">
 <td class="utilbox">
  <spring:url value="${path}/${itemId}" var="show_form_url" />
  <spring:url value="/resources/images/show.png" var="show_image_url" />
  <spring:message arguments="${typeName}" code="entity_show" var="show_label" htmlEscape="false" />
  <a href="${show_form_url}" alt="${fn:escapeXml(show_label)}" title="${fn:escapeXml(show_label)}">
   <img alt="${fn:escapeXml(show_label)}" class="image" src="${show_image_url}" title="${fn:escapeXml(show_label)}" />
  </a>
 </td>
</c:if>     

Provide a Master Select


Most applications that support multiple selection of list also provide a means to select or deselect all the entries on the list.  In the first section, 'Adding Multi-Select Check Boxes' we added a master select check box to the table heading cell.  So all that is left to do is adding some Javascript to provide that functionality.

<c:if test="${not empty multiselect}">
 <script>
  require( [ "dojo/query", "dojo/on", "dojo/domReady!" ], function( query, on ) {
   var masterSelect = null ;
       
   query( "table#${id} input.masterSelectBox_${multiselect}" ).forEach( function( node ){
    masterSelect = node ;
    on( node, "click", function( e ) {
     query( "table#${id} td.selectBox input[type=checkbox]").forEach( function( node ) {
      node.checked = masterSelect.checked ;
     }) ;
    })
   })
  }) ;
  
 </script>
</c:if>

Here a Dojo query is used to find the master select box that was added to the table in the first stage.  A 'chick' event handler is added to that check box.  When clicked another Dojo query is executed to find the check box associated with each entity.  The check boxes discovered from that query are then set to the current state of the master select check box.

Putting It All Together


Now that all the pieces are in place, here's the complete modified table tag code;

<jsp:root xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:fn="http://java.sun.com/jsp/jstl/functions" 
  xmlns:util="urn:jsptagdir:/WEB-INF/tags/util" xmlns:spring="http://www.springframework.org/tags" xmlns:form="http://www.springframework.org/tags/form" xmlns:fmt="http://java.sun.com/jsp/jstl/fmt" xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0">
  <jsp:directive.tag import="java.util.ArrayList" />
  <jsp:output omit-xml-declaration="yes" />

  <jsp:directive.attribute name="id" type="java.lang.String" required="true" rtexprvalue="true" description="The identifier for this tag (do not change!)" />
  <jsp:directive.attribute name="data" type="java.util.Collection" required="true" rtexprvalue="true" description="The collection to be displayed in the table" />
  <jsp:directive.attribute name="path" type="java.lang.String" required="true" rtexprvalue="true" description="Specify the URL path" />
  <jsp:directive.attribute name="typeIdFieldName" type="java.lang.String" required="false" rtexprvalue="true" description="The identifier field name for the type (defaults to 'id')" />
  <jsp:directive.attribute name="multiselect" type="java.lang.String" required="false" rtexprvalue="false" description="The name for multiselection checkboxes." />
  <jsp:directive.attribute name="show" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Include 'show' link into table (default true)" />
  <jsp:directive.attribute name="create" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Include 'create' link into table (default true)" />
  <jsp:directive.attribute name="update" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Include 'update' link into table (default true)" />
  <jsp:directive.attribute name="delete" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Include 'delete' link into table (default true)" />
  <jsp:directive.attribute name="render" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Indicate if the contents of this tag and all enclosed tags should be rendered (default 'true')" />
  <jsp:directive.attribute name="z" type="java.lang.String" required="false" description="Used for checking if element has been modified (to recalculate simply provide empty string value)" />

  <c:if test="${empty render or render}">

    <c:set var="columnProperties" scope="request" />
    <c:set var="columnLabels" scope="request" />
    <c:set var="columnMaxLengths" scope="request" />
    <c:set var="columnTypes" scope="request" />
    <c:set var="columnDatePatterns" scope="request" />

    <jsp:doBody />

    <c:if test="${empty typeIdFieldName}">
      <c:set var="typeIdFieldName" value="id" />
    </c:if>

    <c:if test="${empty update}">
      <c:set var="update" value="true" />
    </c:if>

    <c:if test="${empty delete}">
      <c:set var="delete" value="true" />
    </c:if>

    <spring:message var="typeName" code="menu_item_${fn:toLowerCase(fn:split(id,'_')[fn:length(fn:split(id,'_')) - 1])}_new_label" htmlEscape="false" />
    <c:set var="lengths" value="${fn:split(columnMaxLengths, '✏')}" scope="request" />
    <c:set var="types" value="${fn:split(columnTypes, '✏')}" scope="request" />
    <c:set var="patterns" value="${fn:split(columnDatePatterns, '✏')}" scope="request" />
    
    <c:if test="${not empty multiselect}">
     <script>
      require( [ "dojo/query", "dojo/on", "dojo/domReady!" ], function( query, on ) {
       var masterSelect = null ;
       
       query( "table#${id} input.masterSelectBox_${multiselect}" ).forEach( function( node ){
        masterSelect = node ;
        on( node, "click", function( e ) {
         query( "table#${id} td.selectBox input[type=checkbox]").forEach( function( node ) {
          node.checked = masterSelect.checked ;
         }) ;
        })
       })
      }) ;
      
     </script>
    </c:if>

    <spring:eval var="colCounter" expression="0" />

    <table id="${id}">
      <thead>
        <tr>
          <c:if test="${not empty multiselect}">
            <th >
    <input class="masterSelectBox_${multiselect}" type="checkbox" name="${column}" />
            </th>
            <spring:eval var="colCounter" expression="colCounter  + 1" />
          </c:if>
          <c:forTokens items="${columnLabels}" delims="${'✏'}" var="columnHeading">
            <th>
             <c:out value="${columnHeading}" />
               <spring:eval var="colCounter" expression="colCounter  + 1" />
            </th>
          </c:forTokens>
          <c:if test="${show}">
            <th></th>
            <spring:eval var="colCounter" expression="colCounter  + 1" />
          </c:if>
          <c:if test="${update}">
            <th></th>
            <spring:eval var="colCounter" expression="colCounter  + 1" />
          </c:if>
          <c:if test="${delete}">
            <th></th>
            <spring:eval var="colCounter" expression="colCounter  + 1" />
          </c:if>
        </tr>
      </thead>
      <c:forEach items="${data}" var="item">
        <tr>
          <c:if test="${not empty multiselect}">
            <td class="selectBox">
           <input type="checkbox" name="${multiselect}" value="${item.id}" />
          </td>
          </c:if>
          <c:forTokens items="${columnProperties}" delims="${'✏'}" var="column" varStatus="num">
            <c:set var="columnMaxLength" value="${lengths[num.count-1]}" />
            <c:set var="columnType" value="${types[num.count-1]}" />
            <c:set var="columnDatePattern" value="${patterns[num.count-1]}" />
            <td>
          <c:choose>
            <c:when test="${columnType eq 'date'}">
               <spring:escapeBody>
                 <fmt:formatDate value="${item[column]}" pattern="${fn:escapeXml(columnDatePattern)}" var="colTxt" />
               </spring:escapeBody>
            </c:when>
            <c:when test="${columnType eq 'calendar'}">
               <spring:escapeBody>
                 <fmt:formatDate value="${item[column].time}" pattern="${fn:escapeXml(columnDatePattern)}" var="colTxt"/>
               </spring:escapeBody>
            </c:when>
            <c:otherwise>
               <c:set var="colTxt">
                 <spring:eval expression="item[column]" htmlEscape="false" />
               </c:set>
            </c:otherwise>
          </c:choose>
          <c:if test="${columnMaxLength ge 0}">
            <c:set value="${fn:substring(colTxt, 0, columnMaxLength)}" var="colTxt" />
          </c:if>
          <c:out value="${colTxt}" />
            </td>
          </c:forTokens>
          <c:set var="itemId"><spring:eval expression="item[typeIdFieldName]"/></c:set>
          <c:if test="${show}">
           <td class="utilbox">
             <spring:url value="${path}/${itemId}" var="show_form_url" />
             <spring:url value="/resources/images/show.png" var="show_image_url" />
             <spring:message arguments="${typeName}" code="entity_show" var="show_label" htmlEscape="false" />
             <a href="${show_form_url}" alt="${fn:escapeXml(show_label)}" title="${fn:escapeXml(show_label)}">
               <img alt="${fn:escapeXml(show_label)}" class="image" src="${show_image_url}" title="${fn:escapeXml(show_label)}" />
             </a>
           </td>
   </c:if>     
          <c:if test="${update}">
            <td class="utilbox">
              <spring:url value="${path}/${itemId}" var="update_form_url">
                <spring:param name="form" />
              </spring:url>
              <spring:url value="/resources/images/update.png" var="update_image_url" />
              <spring:message arguments="${typeName}" code="entity_update" var="update_label" htmlEscape="false" />
              <a href="${update_form_url}" alt="${fn:escapeXml(update_label)}" title="${fn:escapeXml(update_label)}">
                <img alt="${fn:escapeXml(update_label)}" class="image" src="${update_image_url}" title="${fn:escapeXml(update_label)}" />
              </a>
            </td>
          </c:if>
          <c:if test="${delete}">
            <td class="utilbox">
              <spring:url value="${path}/${itemId}" var="delete_form_url" />
              <spring:url value="/resources/images/delete.png" var="delete_image_url" />
              <form:form action="${delete_form_url}" method="DELETE">
                <spring:message arguments="${typeName}" code="entity_delete" var="delete_label" htmlEscape="false" />
                <c:set var="delete_confirm_msg">
                  <spring:escapeBody javaScriptEscape="true">
                    <spring:message code="entity_delete_confirm" />
                  </spring:escapeBody>
                </c:set>
                <input alt="${fn:escapeXml(delete_label)}" class="image" src="${delete_image_url}" title="${fn:escapeXml(delete_label)}" type="image" value="${fn:escapeXml(delete_label)}" onclick="return confirm('${delete_confirm_msg}');" />
                <c:if test="${not empty param.page}">
                  <input name="page" type="hidden" value="1" />
                </c:if>
                <c:if test="${not empty param.size}">
                  <input name="size" type="hidden" value="${fn:escapeXml(param.size)}" />
                </c:if>
              </form:form>
            </td>
          </c:if>
        </tr>
      </c:forEach>
      <tr class="footer">
        <td colspan="${colCounter}">
          <c:if test="${empty create or create}">
            <span class="new">
              <spring:url value="${path}" var="create_url">
                <spring:param name="form" />
              </spring:url>
              <a href="${create_url}">
                <spring:url value="/resources/images/add.png" var="create_img_url" />
                <spring:message arguments="${typeName}" code="global_menu_new" var="add_message" htmlEscape="false" />
                <img alt="${fn:escapeXml(add_message)}" src="${create_img_url}" title="${fn:escapeXml(add_message)}" />
              </a>
            </span>
            <c:out value=" " />
          </c:if>
          <c:if test="${not empty maxPages}">
            <util:pagination maxPages="${maxPages}" page="${param.page}" size="${param.size}" />
          </c:if>
        </td>
      </tr>
    </table>

  </c:if>

</jsp:root>


Using the Modified Table Tag


To use the modified table tag we just need to provide values for the 'multiselect' and 'show' optional attributes.

<table:table data="${organizations}" id="l_com_repik_multitenant_security_domain_Organization" 
  path="/organizations" create="false" update="false" delete="false" 
  multiselect="organizationId" show="false" 
  z="5U3sseAfezAakwxsdptI+SXks8o=">
 <table:column id="c_com_repik_multitenant_security_domain_Organization_name" property="name" z="8FaKf+BmDqidSHXdwfn1tV1TnQo="/>
 <table:column id="c_com_repik_multitenant_security_domain_Organization_description" property="description" z="2tWVqb2uDhtkEVsFS0M2anZ68vg="/>
</table:table>

In this snippet of JSP we have a multi-select table showing the organizations that a user is associated with.  Elsewhere in the code there is a JPQL query that is building that list for use here.  Here the ability for the use to modify or view individual organizations have been turned off by setting the 'create', 'update', 'delete' and 'show' attributes to false.

And now here is the mutli-select table embedded in a form;





Thursday, March 7, 2013

Building a Paginating Finder

My post Using Finders In Roo explored the mechanics of how to utilize a finder in a Roo generated web application.  The use case being implemented there was to allow users to search for other users in an application by filtering on a value.  The application uses a JPQL query to search for users.  That query used the like function on both the name and email address to produce a result list containing users where either the name or email contained the search value.

This filtering capability goes a long way towards assisting users in finding what they want, but there still is a gap in the functionality.  We should expect that our web application to be wildly successful and have millions of users.  Doesn't everyone's?  And with that volume of users we should also anticipate the query filter to return hundreds if not thousands of entities.  To manage this we need provide the ability to paginate the results of our filter.

The table tag that Roo generated for the application has pagination capability built in. But to use that functionality we will have to provide a count of records that match the filter and expose that value to the tag.  Also the pagination tag that Roo provides doesn't handle requests with parameters so this will need to be fixed too.  To do this we will need to;
  • Create a custom JPQL query to return the count of AppUsers that match the finders results.
  • Modify the custom find controller method we built in the last post to handle pagination.
  • Modify the pagination tag to handle request parameters correctly.


Create a Custom JPQL Query


The first step is to create the JPQL query.  Since we need this query to return a count of records that match the finder that was created in the first post (findAppUsersByNameLikeOrEmailLike) we will clone an modify that query.

Recall that Roo considers any file matching *_Roo_*.aj as being managed by the shell so the new query needs to be placed in another file or the entity definition file itself. I tend to take advantage of ITD's and the compartmentalization that they provide, so I created and AppUser_Finder.aj aspect file in the domain package.

Here is the filter count code;

package com.repik.multitenant.security.domain;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;

privileged aspect AppUser_Finder {
    
 public static long AppUser.countAppUsersByNameLikeOrEmailLike(String name, String email) {

 if (name == null || name.length() == 0) 
  throw new IllegalArgumentException("The name argument is required");
 name = name.replace('*', '%');
 if (name.charAt(0) != '%') {
  name = "%" + name;
 }
 if (name.charAt(name.length() - 1) != '%') {
  name = name + "%";
 }

 if (email == null || email.length() == 0) 
  throw new IllegalArgumentException("The email argument is required");
 email = email.replace('*', '%');
 if (email.charAt(0) != '%') {
  email = "%" + email;
 }
 if (email.charAt(email.length() - 1) != '%') {
  email = email + "%";
 }

 EntityManager em = AppUser.entityManager();
 TypedQuery<long> q = em.createQuery("SELECT count(o) FROM AppUser AS o WHERE LOWER(o.name) LIKE LOWER(:name)  OR LOWER(o.email) LIKE LOWER(:email)", Long.class);
 q.setParameter("name", name);
 q.setParameter("email", email);
 return q.getSingleResult();
 }
}

Most of the countAppUsersByNameLikeOrEmailLike method is similar to the findAppUsersByNameLikeOrEmailLike that we cloned it from.  The major difference occurs in line 31 where the count function has been applied to the select result.  Now the query returns a count, so we need to modify the TypedQuery type to long.  Since this query returns a single result, the method should just return that rather then the TypedQuery.  So on line 34 the return statement is modified to return the single result of that query.  Doing this modification means that we also have to change the return type for the method on line 8.

Modify the Controller find Method


Now we need to modify the controller find method to handle the pagination.  There are two pagination parameters that can optionally be passed into the controller method; page and size.  The page parameter represents the page number being requested.  The size parameter represents the number of entities displayed on a page.  Our controller method will use these parameters along the finder method to fetch the AppUsers for display on the page requested.  Additionally, we will need to determine the maximum number of pages that the filter matches so the pagination logic found in the JSP can render correctly.

Here is the modified controller;

package com.repik.multitenant.security.web;

import com.repik.multitenant.security.domain.AppUser;
import org.springframework.roo.addon.web.mvc.controller.scaffold.RooWebScaffold;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@RequestMapping("/appusers")
@Controller
@RooWebScaffold(path = "appusers", formBackingObject = AppUser.class)
public class AppUserController {

 @RequestMapping(value="/find", produces = "text/html")
 public String find(
   @RequestParam(value = "find", required = true) String finder, 
   @RequestParam(value = "filter", required = true) String filter,
   @RequestParam(value = "page", required = false) Integer page, 
   @RequestParam(value = "size", required = false) Integer size, 
   Model uiModel) {
  if ( "findAppUsersByNameLikeOrEmailLike".equals( finder ) && ! filter.isEmpty() ) {
   int sizeNo = size == null ? 10 : size.intValue();
   uiModel.addAttribute( "size", sizeNo ) ;
   final int firstResult = page == null ? 0 : (page.intValue() - 1) * sizeNo;
   uiModel.addAttribute("appusers", AppUser.findAppUsersByNameLikeOrEmailLike(filter, filter).setFirstResult( firstResult ).setMaxResults( sizeNo ).getResultList());
   float nrOfPages = (float) AppUser.countAppUsersByNameLikeOrEmailLike( filter, filter ) / sizeNo;
   uiModel.addAttribute("maxPages", (int) ((nrOfPages > (int) nrOfPages || nrOfPages == 0.0) ? nrOfPages + 1 : nrOfPages));
   uiModel.addAttribute("filter", filter) ;
   return "appusers/list";
  }
  else
   return "redirect:/appusers" ;
 }
}

The following changes were made to the finder method from the earlier post;
  • line 19-20 additional optional request parameters were added to handle the page number and count of elements (size) on the page.
  • lines 23-26 for both the page and size attributes assign default values if missing and then add those attributes to the uiModel.
  • line 27 calculate the first record offset for the page being displayed.
  • line 28 find and add to the uiModel the AppUser records using the filter, offset and size attributes.
  • line 29 use the JPQL query from above to get a count of AppUser's that match the filter, use that to calculate the max page number.
  • line 30 add the max page number to the uiModel.


Modify the Pagination Tag to Handle Request Parameters


Within the functionality of the table tag (tags/forms/table.tagx) is the logic to embed the pagination tag (tags/util/pagination.tagx) if maxPages has been set.  In the controller code we set this attribute in the uiModel so pagination is enabled.

The problem now is that the URL's being generated are incorrect.  Specifically we have a couple of request parameters, find and filter, that are not being propagated into the URL's.  So we need to modify the pagination tag to handle this.

Adding any request parameters to URL's generated by the pagination tag requires that when building a URL we iterate though the request parameters adding those to the URL.  The exception being that if the parameter is either 'page' or 'size' we don't want to propagate those since those are the ones that are particular to the URL.  The following snippet of JSP will do this;

<c:forEach var="paramName" items="${pageContext.request.parameterNames}">
 <c:if test="${paramName ne 'page' and paramName ne 'size' }">
  <spring:param name="${paramName}" value="${param[ paramName ]}"/>
 </c:if>
</c:forEach>

Here is the complete modified pagination.tagx code;

<jsp:root xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:fn="http://java.sun.com/jsp/jstl/functions" xmlns:spring="http://www.springframework.org/tags" xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0">
  <jsp:output omit-xml-declaration="yes" />

  <jsp:directive.attribute name="maxPages" type="java.lang.Integer" required="true" rtexprvalue="true" description="The maximum number of pages available (ie tableRecordCount / size)" />
  <jsp:directive.attribute name="page" type="java.lang.Integer" required="false" rtexprvalue="true" description="The current page (not required, defaults to 1)" />
  <jsp:directive.attribute name="size" type="java.lang.Integer" required="false" rtexprvalue="true" description="The number of records per page (not required, defaults to 10)" />
  <jsp:directive.attribute name="render" type="java.lang.Boolean" required="false" rtexprvalue="true" description="Indicate if the contents of this tag and all enclosed tags should be rendered (default 'true')" />

  <c:if test="${empty render or render}">

    <c:if test="${empty page || page lt 1}">
      <c:set var="page" value="1" />
    </c:if>

    <c:if test="${empty size || size lt 1}">
      <c:set var="size" value="10" />
    </c:if>

    <spring:message code="list_size" var="list_size" htmlEscape="false" />
    <c:out value="${list_size} " />

    <c:forEach var="i" begin="5" end="25" step="5">
      <c:choose>
        <c:when test="${size == i}">
          <c:out value="${i}" />
        </c:when>
        <c:otherwise>
          <spring:url value="" var="sizeUrl">
           <c:forEach var="paramName" items="${pageContext.request.parameterNames}">
            <c:if test="${paramName ne 'page' and paramName ne 'size' }">
             <spring:param name="${paramName}" value="${param[ paramName ]}"/>
            </c:if>
           </c:forEach>
            <spring:param name="page" value="1" />
            <spring:param name="size" value="${i}" />
          </spring:url>
          <a href="${sizeUrl}">${i}</a>
        </c:otherwise>
      </c:choose>
      <c:out value=" " />
    </c:forEach>
    <c:out value="| " />

    <c:if test="${page ne 1}">
      <spring:url value="" var="first">
        <c:forEach var="paramName" items="${pageContext.request.parameterNames}">
         <c:if test="${paramName ne 'page' and paramName ne 'size' }">
          <spring:param name="${paramName}" value="${param[ paramName ]}"/>
         </c:if>
        </c:forEach>
        <spring:param name="page" value="1" />
        <spring:param name="size" value="${size}" />
      </spring:url>
      <spring:url value="/resources/images/resultset_first.png" var="first_image_url" />
      <spring:message code="list_first" var="first_label" htmlEscape="false" />
      <a class="image" href="${first}" title="${fn:escapeXml(first_label)}">
        <img alt="${fn:escapeXml(first_label)}" src="${first_image_url}" />
      </a>
    </c:if>
    <c:if test="${page gt 1}">
      <spring:url value="" var="previous">
        <c:forEach var="paramName" items="${pageContext.request.parameterNames}">
         <c:if test="${paramName ne 'page' and paramName ne 'size' }">
          <spring:param name="${paramName}" value="${param[ paramName ]}"/>
         </c:if>
        </c:forEach>
        <spring:param name="page" value="${page - 1}" />
        <spring:param name="size" value="${size}" />
      </spring:url>
      <spring:url value="/resources/images/resultset_previous.png" var="previous_image_url" />
      <spring:message code="list_previous" var="previous_label" htmlEscape="false" />
      <a class="image" href="${previous}" title="${fn:escapeXml(previous_label)}">
        <img alt="${fn:escapeXml(previous_label)}" src="${previous_image_url}" />
      </a>
    </c:if>
    <c:out value=" " />
    <spring:message code="list_page" arguments="${page},${maxPages}" argumentSeparator="," />
    <c:out value=" " />
    <c:if test="${page lt maxPages}">
      <spring:url value="" var="next">
        <c:forEach var="paramName" items="${pageContext.request.parameterNames}">
         <c:if test="${paramName ne 'page' and paramName ne 'size' }">
          <spring:param name="${paramName}" value="${param[ paramName ]}"/>
         </c:if>
        </c:forEach>
        <spring:param name="page" value="${page + 1}" />
        <spring:param name="size" value="${size}" />
      </spring:url>
      <spring:url value="/resources/images/resultset_next.png" var="next_image_url" />
      <spring:message code="list_next" var="next_label" htmlEscape="false" />
      <a class="image" href="${next}" title="${fn:escapeXml(next_label)}">
        <img alt="${fn:escapeXml(next_label)}" src="${next_image_url}" />
      </a>
    </c:if>
    <c:if test="${page ne maxPages}">
      <spring:url value="" var="last">
        <c:forEach var="paramName" items="${pageContext.request.parameterNames}">
         <c:if test="${paramName ne 'page' and paramName ne 'size' }">
          <spring:param name="${paramName}" value="${param[ paramName ]}"/>
         </c:if>
        </c:forEach>
        <spring:param name="page" value="${maxPages}" />
        <spring:param name="size" value="${size}" />
      </spring:url>
      <spring:url value="/resources/images/resultset_last.png" var="last_image_url" />
      <spring:message code="list_last" var="last_label" htmlEscape="false" />
      <a class="image" href="${last}" title="${fn:escapeXml(last_label)}">
        <img alt="${fn:escapeXml(last_label)}" src="${last_image_url}" />
      </a>
    </c:if>
  </c:if>
</jsp:root>

Where the modifications are that we injected the request parameter handling snippet in at lines; 29, 46, 62, 81, and 97.

All the implementation pieces are in place now and we can support pagination of a filter.

Using Finders In Roo

The Spring Roo shell provides commands to create finder methods for entity classes.  Where finders are Roo maintained static methods wrapping JPQL queries to retrieve subsets of application entities.  Additionally as part of Spring Roo's web tier JSP implementation a filter tag is generated.  However neither the tags usage or how to use this tag with finders is in the documentation.

The out of the box implementation generated by Spring Roo provides basic listing functionality.  However often times in real life applications lists of some entities can be rather lengthy making it difficult for users to find particular entities without having to traverse multiple pages.  Applying a finder method allows the user to narrow the scope of entities being shown and subsequently finding the entity(s) of interest easier to find.

So lets first get basic filtering up and running in a web application. Setting up an using a finder involves multiple steps;
  • First we will need to create the entity finder using the Roo shell.
  • Then a new filter method to access the finder will need to be added to the controller.
  • Modify the list JSP to render the filter.
The example here will focus on the entity named AppUser.  This entity represents users in an application.  Our filter is going to address the use case where a user wants to search for other users.  The AppUser entity has two fields, name and email address, that we are going to filter on.  The user will be able to enter in a single string and the application will display users where either the name the name or email address contains that string.

First we will create the finder using the Roo shell with the command:

finder add findAppUsersByNameLikeOrEmailLike


This results in Roo generating the following code;

    public static TypedQuery AppUser.findAppUsersByNameLikeOrEmailLike(String name, String email) {
        if (name == null || name.length() == 0) throw new IllegalArgumentException("The name argument is required");
        name = name.replace('*', '%');
        if (name.charAt(0) != '%') {
            name = "%" + name;
        }
        if (name.charAt(name.length() - 1) != '%') {
            name = name + "%";
        }
        if (email == null || email.length() == 0) throw new IllegalArgumentException("The email argument is required");
        email = email.replace('*', '%');
        if (email.charAt(0) != '%') {
            email = "%" + email;
        }
        if (email.charAt(email.length() - 1) != '%') {
            email = email + "%";
        }
        EntityManager em = AppUser.entityManager();
        TypedQuery q = em.createQuery("SELECT o FROM AppUser AS o WHERE LOWER(o.name) LIKE LOWER(:name)  OR LOWER(o.email) LIKE LOWER(:email)", AppUser.class);
        q.setParameter("name", name);
        q.setParameter("email", email);
        return q;
    }

Note that Roo has done all the work of preparing the like parameters for us, substituting the '*' with '%' and placing a '%' on each end of the parameter.  Thus when a parameter such as, 'foo*bar' is supplied to the finder method the parameter provided to the query will be '%foo%bar%'.

In the JSP we will be using the find tag (tags/form/find.tagx) this tag embeds a form containing the filter parameters on that page.  Within the form is a hidden field named 'finder' that contains a symbolic name of the finder we want to use.   The Roo generated web application does not contain any special logic to auto-magically interpret the finder name and invoke that finder without having to provide hand written logic.  That logic is what we will have to provide in the controller method.  The finder name allows a controller method to support access to multiple finders.  We will not be using that functionality here but will still include the logic to test the name as a defense against wayward JSP errors else where in the application.

Finally, note that the return type is a JPA TypedQuery object.  Compare this to the findAllAppUsers method in the AppUser_Roo_Jpa_ActiveRecord.aj file, that returns a list of AppUsers.  The list JSP (/tags/form/list.tagx) expects a list of entities, so the controller method will have to invoke getResultList on the results of the entity finder and place that list in the uiModel.

Now that the finder is available we can now code the controller method.

package com.repik.multitenant.security.web;

import com.repik.multitenant.security.domain.AppUser;
import org.springframework.roo.addon.web.mvc.controller.scaffold.RooWebScaffold;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@RequestMapping("/appusers")
@Controller
@RooWebScaffold(path = "appusers", formBackingObject = AppUser.class)
public class AppUserController {

   @RequestMapping(value="/find", produces = "text/html")
    public String find(
         @RequestParam(value = "find", required = true) String finder,
         @RequestParam(value = "filter", required = true) String filter,  Model uiModel) {

      // test that the finder parameter is correct
      if ( "findAppUsersByNameLikeOrEmailLike".equals( finder ) ) {

         // notice that getResultList() is invoked on the finder result
         uiModel.addAttribute("appusers", AppUser.findAppUsersByNameLikeOrEmailLike(filter, filter).getResultList()) ;

         uiModel.addAttribute("filter", filter) ;
         return "appusers/list";
      }
      else
         return "redirect:/appusers" ;
    }
}

Finally we need to add the find tag to the AppUser list JSP (views/appuser/list.jspx) to generate the HTML that invokes the controller method that we just created.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:jsp="http://java.sun.com/JSP/Page" 
  xmlns:page="urn:jsptagdir:/WEB-INF/tags/form" 
  xmlns:table="urn:jsptagdir:/WEB-INF/tags/form/fields" version="2.0">
 <jsp:directive.page contentType="text/html;charset=UTF-8"/>
 <jsp:output omit-xml-declaration="yes"/>
    
 <page:find finderName="findAppUsersByNameLikeOrEmailLike" id="ff_com_repik_multitenant_security_domain_appuser" path="/appusers/find">
  <input data-dojo-props="trim:true" data-dojo-type="dijit/form/TextBox" id="toolbarFilter" name="filter" type="text" value="${filter}"/>
 </page:find>
 
 <page:list id="pl_com_repik_multitenant_security_domain_AppUser" items="${appusers}" z="HqueBBVm/RaI2SUiUNBFWmNCOZ0=">
  <table:table create="false" data="${appusers}" delete="false" id="l_com_repik_multitenant_security_domain_AppUser" path="/appusers" update="false" multiselect="true" z="user-managed">
   <table:column id="c_com_repik_multitenant_security_domain_AppUser_name" property="name" z="hitrYtAgDxnq3qbzqZWR5cdLT4o="/>
   <table:column id="c_com_repik_multitenant_security_domain_AppUser_email" property="email" z="gzkRNxtd6O1mO8woYZje/UYflcg="/>
  </table:table>
 </page:list>
</div>


The JSP we added was the page:find tag.  The finderName attribute value must be 'findAppUsersByNameLikeOrEmailLike' that is the value being tested in the controller method.  Within the tag itself is a input text element that the user can enter the filter text into.  Notice that a data-dojo-props attribute has been added with the value of "trim:true" and the element has been identified as a text box to Dojo using the data-dojo-type attribute.  This allows Dojo to trim the filter text for us.

 <page:find finderName="findAppUsersByNameLikeOrEmailLike" id="ff_com_repik_multitenant_security_domain_AppUser_finder" path="/appusers/find">
  <input data-dojo-props="trim:true" data-dojo-type="dijit/form/TextBox" id="toolbarFilter" name="filter" type="text" value="${filter}"/>
 </page:find>

We now completed using a finder in Spring Roo.