Tuesday, April 24, 2012

Reusable search component using - java reflection, custom annotation, jsp:usebean and tiles




If 'searching of tabular list' is present in a number of pages in the application, a reusable search component can be used. Search component contains, a combo box, listing items, populated from the DTO class fields, using reflection. To restrict, which fields to be listed, and to show, custom labels for each filed, a custom annotation class is created, and that annotation is applied in the DTO class.








1. Need to add only the  "tiles:insertTemplate" on all pages, where the search component is required, and pass the className as an attribute and its fields will be listed in the Search by item combo.
 
<tiles:insertTemplate template="/WEB-INF/jsp/searchComponent.jsp">
   <tiles:putAttribute name="className" type="string" cascade="true" value="com.rocky.dto.Customer"></tiles:putAttribute>
</tiles:insertTemplate>


2. Create a reusable search component jsp

searchComponent.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>

<tiles:useAttribute id="className" name="className" classname="java.lang.String" />

<jsp:useBean id = "searcItemsPopulatorBean" class="com.rocky.common.SearcItemsPopulatorBean" >
   <jsp:setProperty name="searcItemsPopulatorBean" property="entityClassName"  value="${className}"/>
</jsp:useBean>    

<table width="80%" >
   <tr>
       <td width="15%">Search by item:</td>
       <td align="left" width="20%">
           <select name="searchByItem" >
               <c:forEach items="${searcItemsPopulatorBean.searchItems}" var="item">
                   <option value="${item.key}">
                       <c:out value="${item.value}"/>
                   </option>
               </c:forEach>
           </select>
       </td>
       <td width="15%">Search criteria:</td>
       <td width="20%" align="left">
           <select name="searchCriteria">
               <option  value="startsWith">Starts with</option>
               <option value="endsWith">Ends with</option>
               <option value="contains">Contains</option>
               <option value="exact">Exact</option>
           </select>
       </td>
       <td width="15%">
           <input type="text" name="searchText"/>
       </td>
       <td width="15%">
           <input type="submit" value="Search"/>
       </td>
   </tr>
</table>


3. Define a Bean class to include in the searchComponent.jsp


SearchItemsPopulatorBean.java

package com.rocky.common;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class SearcItemsPopulatorBean {
   private String entityClassName;
   private Map<String, String> searchItems;

   public SearcItemsPopulatorBean() {
       this.searchItems = new HashMap<String, String>();
   }

   public String getEntityClassName() {
       return entityClassName;
   }

   public void setEntityClassName(String entityClassName) {
       this.entityClassName = entityClassName;
       Field fieldArray[] = null;
       try {
           fieldArray = Class.forName(entityClassName).getDeclaredFields();
       } catch (SecurityException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       }
       if (fieldArray != null) {
           for (int i = 0; i < fieldArray.length; i++) {
               ColumnHeaderSpecifier columnHeaderSpecifier = fieldArray[i]
                       .getAnnotation(ColumnHeaderSpecifier.class);
               if (columnHeaderSpecifier.isSearchable()) {
                   searchItems.put(fieldArray[i].getName(),
                           columnHeaderSpecifier.columnHeader());
               }
           }
       }
   }

   public Map<String, String> getSearchItems() {
       return searchItems;
   }
}




4. Define a custom annotation class ColumnHeaderSpecifier.java


package com.rocky.common;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ColumnHeaderSpecifier {
   
   boolean isSearchable() default true;
   String columnHeader() default "";

}



5. Define DTO class with annotation to configure, which field to be listed in the search combo and the label to be displayed for each field.

Customer.java 

package com.rocky.dto;

import java.io.Serializable;

import com.rocky.common.ColumnHeaderSpecifier;

public class Customer implements Serializable{

   @ColumnHeaderSpecifier(isSearchable = false)
   private static final long serialVersionUID = -5235355839338180539L;

   @ColumnHeaderSpecifier(columnHeader = "ID")
   int id;
   
   @ColumnHeaderSpecifier(columnHeader = "Customer Name")
   String customerName;
   
   @ColumnHeaderSpecifier(columnHeader = "State")
   String state;
   
   public Customer(int id, String name, String state) {
       super();
       this.id = id;
       this.customerName = name;
       this.state = state;
   }

   public int getId() {
       return id;
   }

   public void setId(int id) {
       this.id = id;
   }

   public String getCustomerName() {
       return customerName;
   }

   public void setCustomerName(String customerName) {
       this.customerName = customerName;
   }

   public String getState() {
       return state;
   }

   public void setState(String state) {
       this.state = state;
   }
   
}

No comments: