Dynamic PrimeFaces Datatables

In this post I will explain how to use Java Reflection to extract PrimeFaces models from POJO classes. Specifically we will create a dynamic DataTable using the p:columns component. 

Setup

Using reflection could be quite tediuos, luckily for us theres the Apache Commons BeanUtils API. This library helps us to dynamically read the properties of our POJOs. To use this API we just need to add the dependency to our pom.xml in this way.

<dependency>
	<groupId>commons-beanutils</groupId>
        <artifactId>commons-beanutils</artifactId>
	<version>1.9.2</version>
</dependency>

The ColumnModel Class

We are going to use this POJO class for the first example. As you will notice the class its a quite simple (and useless) implementation of the Active Record pattern.

public class User implements Serializable{

	private static final long serialVersionUID = 1L;

	private static List<User> users = new ArrayList<User>();

	private Integer id;
	private String lastName;
	private String firstName;
	
	static{
		users.add(new User(0, "Solid","Snake"));
		users.add(new User(1, "Vulcan","Raven"));
		users.add(new User(2, "Meryl","Silverburgh"));
		users.add(new User(3, "Hal","Emmerich"));
		users.add(new User(4, "Frank","Jaeger"));
	}

	public User(Integer id, String firstName, String lastName) {
		super();
		this.id = id;
		this.lastName = lastName;
		this.firstName = firstName;
	}

	public User() {}

	public Integer getId() {
		return id;
	}

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

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String name) {
		this.firstName = name;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public static List<User> getAll(){
		return users;

	}

	public static User get(final Integer id){
		return (User) CollectionUtils.find(users, new Predicate() {
			public boolean evaluate(Object object) {
				return ((User) object).getId().equals(id);
			}
		});
	}

	public static User store(User p){
		if(p.getId() == null){
			User maxUserId = Collections.max(users, new Comparator<User>() {
				public int compare(User o1, User o2) {
					return o1.getId().compareTo(o2.getId());
				}
			});
			
			p.setId(maxUserId.getId()+1);
			
			users.add(p);
		}else{
			users.set(p.getId(), p);
		}

		return p;
	}

	public static void delete(User p){
		users.remove(p);
	}
}

What we have to do now is to dynamically create the columns ˜idfirstName and ˜lastName, in order to do that we will use this class: the ColumnModel.

public class ColumnModel implements Serializable {

	private static final long serialVersionUID = 1L;

	private String property;
	private String header;
	private Class<?> type;
	public ColumnModel() {}

	public String getProperty() {
		return property;
	}

	public void setProperty(String property) {
		this.property = property;
	}
	public String getHeader() {
		return header;
	}
	public void setHeader(String header) {
		this.header = header;
	}
	public Class<?> getType() {
		return type;
	}

	public void setType(Class<?> type) {
		this.type = type;
	}

}

This class is just a POJO itself, that holds the data about the tille, header and type of a PrimeFaces column. We need to create a ColumnModel instance for every property of the User class. To achieve this goal I implemented this ReflectionColumnModelBuilder object.

public class ReflectionColumnModelBuilder {
	
	private Class modelClass;

	private Comparator<PropertyDescriptor> propertySortComparator;
	private Predicate propertyFilterPredicate;
	private Set<String> excludedProperties;

	private static Set<String> defaultExcludedProperties = new HashSet<String>(0);
	
	static{
		defaultExcludedProperties.add("class");
	}
	
	public ReflectionColumnModelBuilder(Class modelClass) {

		this.modelClass = modelClass;
		this.propertyFilterPredicate = PredicateUtils.truePredicate();
		this.propertySortComparator = new Comparator<PropertyDescriptor>() {

			public int compare(PropertyDescriptor o1, PropertyDescriptor o2) {

				return o1.getName().compareTo(o2.getName());

			}

		};

		this.excludedProperties = new HashSet<String>(0);

	}

	public ReflectionColumnModelBuilder setPropertyFilterPredicate(Predicate p){

		this.propertyFilterPredicate = p;
		return this;

	}

	public ReflectionColumnModelBuilder setPropertySortComparator(Comparator<PropertyDescriptor> c){

		this.propertySortComparator = c;
		return this;

	}

	public ReflectionColumnModelBuilder setExcludedProperties(Set<String> p){

		this.excludedProperties = p;
		return this;

	}
	public ReflectionColumnModelBuilder setExcludedProperties(String...p){

		this.excludedProperties = new HashSet<String>(0);

		for (String excludedProperty : p) {
			this.excludedProperties.add(excludedProperty);
		}

		return this;
	}

	public List<ColumnModel> build(){

		List<ColumnModel> columns = new ArrayList<ColumnModel>(0);

		List<PropertyDescriptor> propertyDescriptors = new ArrayList<PropertyDescriptor>(Arrays.asList(PropertyUtils.getPropertyDescriptors(modelClass)));

		CollectionUtils.filter(propertyDescriptors, PredicateUtils.andPredicate(propertyFilterPredicate, new Predicate() {

			public boolean evaluate(Object object) {

				PropertyDescriptor propertyDescriptor = (PropertyDescriptor) object;

				return 

						propertyDescriptor.getReadMethod() != null && 

						!defaultExcludedProperties.contains(propertyDescriptor.getName()) &&

						!excludedProperties.contains(propertyDescriptor.getName());

			}

		}));
	

		Collections.sort(propertyDescriptors, propertySortComparator);

		for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {

			ColumnModel columnDescriptor = new ColumnModel();

			columnDescriptor.setProperty(propertyDescriptor.getName());

			columnDescriptor.setHeader(StringUtils.capitalize(StringUtils.join(StringUtils.splitByCharacterTypeCamelCase(propertyDescriptor.getName())," ")));

			columnDescriptor.setType(propertyDescriptor.getPropertyType());

			columns.add(columnDescriptor);

		}

		return columns;

	}

}

Lets focus on the build method. We use the instruction PropertyUtils.getPropertyDescriptors(modelClass) to get all the PropertyDescriptor of the POJO class. 

The Javadoc of the PropertyDescriptor class states that this object describes one property that a Java Bean exports via a pair of accessor methods. We sort and filter our descriptors and then we wrap everything in a collection of ColumnModel objects.

A First Example

The Page 

<p:dataTable var="user" value="#{basicExampleBean.users}">

   <p:columns value="#{basicExampleBean.columns}" 
                      var="column" headerText="#{column.header}">

      <h:outputText value="#{user[column.property]}" />

   </p:columns>

</p:dataTable>

The Bean

@ManagedBean
@ViewScoped

public class BasicExampleBean implements Serializable{

private List<ColumnModel> columns = new ArrayList<ColumnModel>(0);

   @PostConstruct
   public void init() {

      columns = new ReflectionColumnModelBuilder(User.class).
                          setExcludedProperties("id").build();

   }

   public List<User> getUsers(){

      return User.getAll();

   }

   public List<ColumnModel> getColumns() {

      return columns;

   }

}

As we can see in the example, to create the columns that only thing that is required is to pass the User class to the builder. Note that we also excluded the id column. The result of the example is this:

datatable primefaces 

Ajax

We can also change class with an Ajax event, lets create a second POJO: a Car model.

public class Car implements Serializable{

	private static final long serialVersionUID = 1L;

	private static List<Car> cars = new ArrayList<Car>();

	private Integer id;
	private String brand;
	private String color;
	private Integer year;
	private boolean used;
	
	static{
		cars.add(new Car(0, "Honda","Yellow",1995,false));
		cars.add(new Car(1, "Volvo","Black",1973,true));
		cars.add(new Car(1, "Audi","Silver",1987,false));
		cars.add(new Car(1, "Renault","White",1963,true));
		cars.add(new Car(1, "Volkswagen","Black",1985,true));
	}	

	public Car(Integer id, String brand, String color, Integer year, boolean used) {
		super();
		this.id = id;
		this.brand = brand;
		this.color = color;
		this.year = year;
		this.used = used;
	}

	public Car() {}

	public Integer getId() {
		return id;
	}

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

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}

	public String getBrand() {
		return brand;
	}

	public void setBrand(String brand) {
		this.brand = brand;
	}

	public Integer getYear() {
		return year;
	}

	public void setYear(Integer year) {
		this.year = year;
	}

	public boolean isUsed() {
		return used;
	}

	public void setUsed(boolean used) {
		this.used = used;
	}

	public static List<Car> getAll(){
		return cars;

	}

	public static Car get(final Integer id){
		return (Car) CollectionUtils.find(cars, new Predicate() {
			public boolean evaluate(Object object) {
				return ((Car) object).getId().equals(id);
			}
		});
	}

	public static Car store(Car p){
		if(p.getId() == null){
			Car maxUserId = Collections.max(cars, new Comparator<Car>() {
				public int compare(Car o1, Car o2) {
					return o1.getId().compareTo(o2.getId());
				}
			});
			
			p.setId(maxUserId.getId()+1);
			
			cars.add(p);
		}else{
			cars.set(p.getId(), p);
		}

		return p;
	}

	public static void delete(Car p){
		cars.remove(p);
	}
}

Note that this class has a boolean value: we are going to use the type property of the ColumnModel object to display a p:selectBooleanCheckbox instead of the classic h:outputText. We use a p:selectOneRadio to switch between the two classes.

The Page

<h:form>

<h:panelGrid columns="2" style="margin-bottom:10px" cellpadding="5">

       <p:outputLabel for="currentClass" value="Class:" />

       <p:selectOneRadio id="currentClass" value="#{dynamicExampleBean.currentClass}">

           <f:selectItem itemLabel="User" itemValue="it.strazz.primefaces.model.User" />

           <f:selectItem itemLabel="Car" itemValue="it.strazz.primefaces.model.Car" />

           <p:ajax listener="#{dynamicExampleBean.onChangeClass}" update="@form"/>

       </p:selectOneRadio>

   </h:panelGrid>

<p:dataTable var="obj" value="#{dynamicExampleBean.data}">

   <p:columns value="#{dynamicExampleBean.columns}" 
   var="column" headerText="#{column.header}">

      <p:selectBooleanCheckbox value="#{obj[column.property]}" 
    disabled="true" rendered="#{column.type.toString() == 'boolean'}"/>

      <h:outputText value="#{obj[column.property]}" rendered="#{column.type.toString() != 'boolean'}" />

   </p:columns>

</p:dataTable>

</h:form>

The Bean

@ManagedBean
@ViewScoped
public class DynamicExampleBean implements Serializable{
	
	private List<ColumnModel> columns = new ArrayList<ColumnModel>(0);
	private String currentClass = User.class.getName();
	
	@PostConstruct
	public void init() {
		onChangeClass();
	}
	
	public void onChangeClass(){
		try {
			columns = new ReflectionColumnModelBuilder(Class.forName(currentClass)).
					setExcludedProperties("id").
					build();
		} catch (ClassNotFoundException e) {
			//Will not happen
			columns = null;
		}
	}
	
	public List<Object> getData(){
		try {
			return (List<Object>) MethodUtils.invokeExactStaticMethod(Class.forName(currentClass), "getAll", null);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public List<ColumnModel> getColumns() {
		return columns;
	}
	public String getCurrentClass() {
		return currentClass;
	}

	public void setCurrentClass(String currentClass) {
		this.currentClass = currentClass;
	}
}

The only thing that may need some explanation is the getData method: we use the MethodUtils class to dynamically invoke the getAll static method of one of the two classes. The result of this example is this:

primefaces datatable

Using ViewParam

For our last example we are going to use a f:viewParam to choose the data to display. This example its similar to the previous one but the ColumnModels are generated during the preRender event.

The Page

<f:metadata>
	<f:viewParam name="class" value="#{viewParamExampleBean.currentClass}"></f:viewParam>
	<f:event listener="#{viewParamExampleBean.onPreRender}" type="preRenderView"></f:event>
</f:metadata>
<h:body>
	<p:dataTable var="obj" value="#{viewParamExampleBean.data}">
		<p:columns 
			value="#{viewParamExampleBean.columns}" 
			var="column"
			headerText="#{column.header}">
			<p:selectBooleanCheckbox 
				value="#{obj[column.property]}" 
				disabled="true"
				rendered="#{column.type.toString() == 'boolean'}"/>
			<h:outputText value="#{obj[column.property]}" rendered="#{column.type.toString() != 'boolean'}" />
		</p:columns>
	</p:dataTable>
</h:body>

The Bean

@ManagedBean
@ViewScoped
public class ViewParamExampleBean implements Serializable{
	
	private List<ColumnModel> columns = new ArrayList<ColumnModel>(0);
	private String currentClass = User.class.getName();
	
	public void onPreRender() {
		try {
			columns = new ReflectionColumnModelBuilder(Class.forName(currentClass)).
					setExcludedProperties("id").
					build();
		} catch (ClassNotFoundException e) {
			//Will not happen
			columns = null;
		}
	}
	
	public List<Object> getData(){
		try {
			return (List<Object>) MethodUtils.invokeExactStaticMethod(Class.forName(currentClass), "getAll", null);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public List<ColumnModel> getColumns() {
		return columns;
	}
	
	public String getCurrentClass() {
		return currentClass;
	}

	public void setCurrentClass(String currentClass) {
		this.currentClass = currentClass;
	}
}

We can now use a single page for every model in our application, and we can change the data displayed with a simple menu like this:

<h:form>

   <p:menubar>

       <p:menuitem value="View Param (User)" action="param?faces-redirect=true&amp;class=it.strazz.primefaces.model.User"/>

       <p:menuitem value="View Param (Car)" action="param?faces-redirect=true&amp;class=it.strazz.primefaces.model.Car"/>

   </p:menubar>

</h:form>

To create new pages the only thing that we need to implement its the new POJO class. Obviously in a real life application you will need a better persistence layer (JPA, Hibernate) than our ˜dummy active record.

Conclusions

During one of my first Java lessons at the university, my teacher said Reflection is Devil!or something like that. It’s true, but it could be a useful hack sometimes. The code of this article would be the base of a quick prototyping framework for PrimeFaces: it will not be a scaffolder like the CRUD generator bundled with NetBeans, but almost everything will be built at runtime just like this example. If you want to contribute just send me an email or create a pull request at the GitHub project repository. 

In the next post I will use the same reflection technique to create a detail page for our POJOs.

Forked source code for WildFly: https://github.com/fmarchioni/primefaces-reflection-crud

 

Found the article helpful? if so please follow us on Socials