Archive for May, 2011

22
May
11

JSF Datatable with database sorting, filtering and pagination

When database grows (or may grow) significantly it is necessary to consider database pagination for loading data since the Application Server memory isn’t sized for handling such huge amount of data.

Luckily rich:extendedDataTable provides an easy way of delegating the sorting, filtering and also pagination to the underlying data store. I sincerely remember that the I saw the base for this idea somewhere in Seam Framework forum (if anyone knows something similar please let me know) but I couldn’t find it for referencing, anyways it is now extended and more flexible. Another foundation of this technique is the org.jboss.seam.framework.Query since it provides means of expressing filtering and sorting as a huge string even with JSF variables (eg.: #{something.otherthing}).

Lets start with the EntityExtendedTableDataModel that is responsible for passing filtering and sorting data from view layer to data layer:

<pre>/**
EntityExtendedTableDataModel - Database pagination, sorting and filtering for richfaces datatables
Copyright (C) 2011 Rafael Ribeiro

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/</pre>
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.faces.component.html.HtmlInputText;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

import org.apache.commons.lang.StringUtils;
import org.jboss.seam.core.Expressions;
import org.jboss.seam.core.Expressions.ValueExpression;
import org.jboss.seam.framework.Query;
import org.jboss.seam.ui.AbstractEntityLoader;
import org.richfaces.model.DataProvider;
import org.richfaces.model.ExtendedFilterField;
import org.richfaces.model.ExtendedTableDataModel;
import org.richfaces.model.FilterField;
import org.richfaces.model.Modifiable;
import org.richfaces.model.Ordering;
import org.richfaces.model.SortField2;

public class EntityExtendedTableDataModel extends ExtendedTableDataModel implements Modifiable {
EntityDataProvider entityDataProvider;
public EntityExtendedTableDataModel(Query query) {
super(new EntityDataProvider(query));
entityDataProvider = (EntityDataProvider) getDataProvider();
}

public void modify(List<FilterField> filterFields,
List<SortField2> sortFields) {
performSort(sortFields);
performFilter(filterFields);
}

private void performSort(List<SortField2> sortFields) {
StringBuilder order = new StringBuilder();
Pattern p = Pattern.compile("\\#\\{(.+)\\}");
for (SortField2 s: sortFields) {
if (Ordering.UNSORTED.equals(s.getOrdering()) == false) {
String expr = s.getExpression().getExpressionString();
Matcher m = p.matcher(expr);
if (m.matches()) { //remove the #{} otherwise richfaces wont trigger the sort event
order.append(m.group(1));
}
else {
order.append(expr);
}
order.append(" ");
order.append(Ordering.ASCENDING.equals(s.getOrdering()) ? "ASC" : "DESC");
order.append(", ");
}
}
if (order.length() > 0)
entityDataProvider.getQuery().setOrder(order.delete(order.length()-2, order.length()).toString());
}
//allows us to specify filter as o.name == #{exampleEntity.name}
private void performFilter(List<FilterField> filterFields) {
Expressions expressions = new org.jboss.seam.core.Expressions();
Pattern p = Pattern.compile(".*(\\#\\{.+\\}).*");
List<String> restrictions = new ArrayList<String>();
for (FilterField f: filterFields) {
ExtendedFilterField e = (ExtendedFilterField) f;
if (StringUtils.isEmpty(e.getFilterValue()))
continue;
StringBuilder filter = new StringBuilder();
String expr = e.getExpression().getExpressionString();
Matcher m = p.matcher(expr);
if (!m.matches())
continue;
ValueExpression ve = expressions.createValueExpression(m.group(1));
FacesContext ctx = FacesContext.getCurrentInstance();
Converter c = ctx.getApplication().createConverter(ve.getType());
if (c == null)
ve.setValue(e.getFilterValue());
else {
try {
ve.setValue(c.getAsObject(ctx, new HtmlInputText(), e.getFilterValue()));
} catch (ConverterException ce) {
continue;
}
}
restrictions.add(expr);
}
entityDataProvider.getQuery().setRestrictionExpressionStrings(restrictions);
}

}
class EntityDataProvider implements DataProvider {

private Query query;
public EntityDataProvider(Query query) {
this.query = query;
}

public Object getItemByKey(Object key) {
return AbstractEntityLoader.instance().get(String.valueOf(key));
}

public List getItemsByRange(int firstRow, int endRow) {
query.setFirstResult(firstRow);
query.setMaxResults(endRow-firstRow);
return query.getResultList();
}

public Object getKey(Object item) {
return AbstractEntityLoader.instance().put(item);
}

public int getRowCount() {
return query.getResultCount().intValue();
}
protected Query getQuery() {
return query;
}
}

Now we have to replace Seam datamodels component with ours:

<pre>EntityExtendedTableDataModel - Database pagination, sorting and filtering for richfaces datatables
Copyright (C) 2011 Rafael Ribeiro

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/</pre>
import static org.jboss.seam.ScopeType.STATELESS;
import static org.jboss.seam.annotations.Install.FRAMEWORK;

import javax.faces.model.DataModel;

import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.faces.DataModels;
import org.jboss.seam.framework.EntityQuery;
import org.jboss.seam.framework.Query;

@Name("org.jboss.seam.faces.dataModels")
@Install(precedence=FRAMEWORK)
@Scope(STATELESS)
@BypassInterceptors
public class RichFacesModels extends DataModels
{

@Override
public DataModel getDataModel(Query query)
{
if (query instanceof EntityQuery)
{
return new EntityExtendedTableDataModel((EntityQuery) query);
}
else
{
return super.getDataModel(query);
}
}

}

Finally we specify in rich:column the filtering remembering that the base object name here must be in sync with the referenced query, example:


<rich:extendedDataTable id="listaPacientes" rows="10" value="#{queryAllEntities.dataModel}" var="obj">
<rich:column sortable="true" sortBy="#{o.name}" selfSorted="false"
filterBy="lower(o.name) like concat(lower(#{exampleEntity.name}),'%')" filterEvent="onkeyup"
label="Name">

The trick here is to specify selfSorted=”false” and adding the JSF #{} part for the sorting to work, this way o.name will be appended to Query sorting.

On the other hand, for filtering to work you only need to specify your query, note that what you specify between the #{} will hold the filter data handed to the Query.

Also note that you need to have a query and an entity as a component specified in components.xml, in this example, this could be the query and the example entity:


<component name="exampleEntity" class="br.com.rafaelri.MyEntity" scope="session" />
<framework:entity-query name="queryAllEntities" ejbql="select o from MyEntity o" />

This, combined with rich:dataScroller will result in sorting, pagination and filtering handled by the Database.

21
May
11

Enum backed h:selectOneRadio and h:selectOneMenu

Recently I needed to display a h:selectOneRadio and a h:selectOneMenu with values provided by a Java Enum. After some research on seamframework forum I saw one post that clarified a lot the way I should pursue, specially Pete’s comment. My greatest concern was the same of Pete: wiring up presentation and domain layer. But, after some analysis I found a simple solution that does not wire up domain and presentation layer and also respects the DRY principle. The solution involved crafting a class that would be instantiated as a Seam component on components.xml so it can be instantiated for each and every enum that we want to provide on view layer and also used EnumSet.allOf method so we could automagically iterate for each of the enum values.

Below, you can find the EnumList class:

/**
EnumList Seam Component - Converts Java Enums to List.
Copyright (C) 2011 Rafael Ribeiro

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

public class EnumList<T extends Enum<T>> {
private List<T> list;

public void setEnumClass(Class<T> c) {
list = new ArrayList<T>();
list.addAll(EnumSet.allOf(c));
}

public List<T> getList() {
return list;
}

public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("list", list).toString();

}
}

And the configure it on components.xml as follows:

<component name="myEnumComp" scope="application" auto-create="true">
<property name="enumClass">br.com.rafaelri.MyEnum</property>
</component>
<factory name="myEnum" value="#{myEnumComp.list}" scope="application" auto-create="true"/>

Finally you’ll refer to it on your xhtml as follows:

<h:selectOneMenu id="myEnumSelect" value="#{instanceHome.instance.myEnum}">
<s:selectItems var="enum" value="#{myEnum}" label="#{enum.description()}"/>
<s:convertEnum />
</h:selectOneMenu>


21
May
11

JPA 2 on Seam 2

As intelligently pointed by Thomas on CTP Java blog starting a Java Web application today from scratch presents you an interesting crossroads. crossroadsIf you stick with the proven Seam 2 option you end up also with its deficiencies not to mention some old versioned libraries. The other option is experimenting with Seam 3 but this on the other hand has few knowledge critical mass around. But with a few changes, Seam 2 can still provide value and at least Hibernate/JPA version can be improved bringing a bunch of bugfixes (eg.: proper schema generation that in Hibernate 3.3 was buggy – even though HHH1012 says it is fixed I never saw this on any 3.3 release) and new features (eg.: criteria api as also pointed by Thomas).

I’ve followed Thomas instructions but instead of coding the proxy method by method I suggest using Java Dynamic Proxies:


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import javax.persistence.EntityManager;

import org.jboss.seam.persistence.PersistenceProvider;

public class Jpa2EntityManagerProxy implements InvocationHandler {

private EntityManager delegate;

public Jpa2EntityManagerProxy(EntityManager delegate) {
super();
this.delegate = delegate;
}

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (method.getName().equals("getDelegate")) {
return PersistenceProvider.instance().proxyDelegate(
delegate.getDelegate());
} else {
try {
return method.invoke(delegate, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
}

public static EntityManager newInstance(EntityManager entityManager) {
return (EntityManager) Proxy.newProxyInstance(
Jpa2EntityManagerProxy.class.getClassLoader(),
new Class[] { EntityManager.class },
new Jpa2EntityManagerProxy(entityManager));
}
}

Apart from this change I followed Thomas instructions as-is and it got JPA2 with Hibernate 3.5 working in my project.




ClustrMaps

Blog Stats

  • 354,781 hits since aug'08

%d bloggers like this: