Collections of web application techniques

Monday, May 18, 2009

Custom Pagination With JBoss Seam

Seam has its own pagination and you can get paginations for practically free. All you need to do is to extend your component from org.jboss.seam.framework.EntityQuery. In fact if you use seam-gen to generate the UI, you'll get all the search form, sorting and paging without knowing much. But this is not much fun for a software developer trapped in a cubicle, so I had to create something.

My custom pagination described here has these additional features:
- Selectable page sizes
- Total counts are displayed
- Navigations are not limited to just first, previous, next and last result sets. Mine shows the current and a fixed number of pages before or after the current similarly to how Google does it.

Below is an example of the output:



import com.inventasoft.ui.Pagination;
import com.inventasoft.ui.PageControls;
import com.inventasoft.ui.QueryHelper;

public class YourBean implements QueryHelper {
private Pagination pagination;
private PageControls pageControls = new PageControls();

public void setUp() {
pagination = new Pagination
}

public List anActionListener() {
pagination.execute(pageControls.getPage(), pageControls.getPageSize());
return pagination.getResults();
}

public Query getCountQuery() {
Object o2 = Component.getInstance("dataEM");
EntityManager em = (EntityManager) o2;

queryEntity.setEjbql("from AResultClass");
queryEntity.setRestrictions(Arrays.asList(RESTRICTIONS));
queryEntity.setOrder(selection.getOrder());

String sqlStr = "select count(*) " + queryEntity.getCountQuery();
Query query = em.createQuery(sqlStr);
return query;
}

public Query getSelectQuery(int start, int pageSize) {
Object o2 = Component.getInstance("dataEM");
EntityManager em = (EntityManager) o2;

String selectQuery = queryEntity.getQuery();
Query query = em.createQuery(selectQuery);
query.setFirstResult(start);
query.setMaxResults(pageSize);
return query;
}
}

Friday, May 15, 2009

JBoss Seam and Native Query

I have been working with Oracle for years and feel more familiar with its SQL. Seam and Hibernate are more of a new thing for me. In some cases, I couldn’t find the right syntax to get Hibernate Query Language (HQL) to work but an equivalent in the underlying database would be more obvious. Perhaps I wasn’t look hard enough but it’s sure nice to have such option thank to the EntityManager’s createNativeQuery method.

For instance instead of inheriting from org.jboss.seam.framework.EntityQuery and have these restrictions:


private static final String[] RESTRICTIONS = {
"lower(appUser.userId) like concat(lower(#{appUserList.appUser.userId}),'%')",
"lower(appUser.firstName) like concat(lower(#{appUserList.appUser.firstName}),'%')",
"lower(appUser.lastName) like concat(lower(#{appUserList.appUser.lastName}),'%')",};


I would like to have restrictions similarly to below:


private static final String[] RESTRICTIONS = {
"begin_date >= to_date(''#{employeeList.selection.searchParams.beginDate}'',''MM/DD/YYYY'')",
"end_date <= to_date(''#{employeeList.selection.searchParams.endDate}'',''MM/DD/YYYY'')",};


Where the query will become something like “…to_date('05/14/2009','MM/DD/YYYY')…” which is the syntax for Oracle. So I came up with two classes NativeQueryEntity and NativeQueryParser which are really Seam code with some minor modifications.

To use them, my bean would use composition instead and look something like:


public class EmployeeList {

private NativeQueryEntity queryEntity = new NativeQueryEntity();
private static final String[] RESSTRICTIONS ....

public Query getCountQuery() {
queryEntity.setEjbql("from employees");
queryEntity.setRestrictions(Arrays.asList(RESTRICTIONS));
String sqlStr = "select count(*) " + queryEntity.getCountQuery();
Query query = em.createNativeQuery(sqlStr);
return query;
}
}


Once you have the query, you can proceed to execute and get the results as usual.





package com.inventasoft.ui;

import java.util.List;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import java.io.Serializable;
import java.text.MessageFormat;

import org.jboss.seam.persistence.QueryParser;
import org.jboss.seam.core.Expressions.ValueExpression;

/**
* This class is adapted from Seam's EntityQuery and Query classes.
* Changed to composition instead of inheritance usage.
*/
public class NativeQueryEntity implements Serializable {

private static final Pattern FROM_PATTERN = Pattern.compile(
"(^|\\s)(from)\\s", Pattern.CASE_INSENSITIVE);

private static final Pattern ORDER_PATTERN = Pattern.compile(
"\\s(order)(\\s)+by\\s", Pattern.CASE_INSENSITIVE);

private static final Pattern WHERE_PATTERN = Pattern.compile(
"\\s(where)\\s", Pattern.CASE_INSENSITIVE);

private String parsedEjbql;

private List restrictions;

private List conditions;

private String order;

private String ejbql;

private String renderedQuery;

public void setEjbql(String ejbql) {
this.ejbql = ejbql;
}

// Unparsed list of restrictions
public void setRestrictions(List restrictions) {
this.restrictions = restrictions;
}

public void setOrder(String order) {
this.order = order;
}

private void parseEjbql() {
QueryParser qp = new QueryParser(ejbql);
List queryParameters = qp.getParameterValueBindings();

parsedEjbql = qp.getEjbql();

List parsedRestrictions =
new ArrayList(restrictions.size());

List restrictionParameters =
new ArrayList(restrictions.size());

conditions = new ArrayList();
for (String restriction : restrictions) {
NativeQueryParser rqp = new NativeQueryParser(restriction,
queryParameters.size() + restrictionParameters.size());
if (rqp.getParameterValueBindings().size() != 1) {
throw new IllegalArgumentException(
"there should be exactly one value binding in a restriction: "
+ restriction);
}




package com.inventasoft.ui;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.jboss.seam.core.Expressions.ValueExpression;
import org.jboss.seam.core.Expressions;

/**
* This is modified from Seam QueryParser to work better with native
* query by using parameter substitutions.
*/
public class NativeQueryParser implements Serializable {

/** The parameter value bindings. */
private List parameterValueBindings = new ArrayList();

private StringBuilder ejbqlBuilder;

public static String getParameterName(int loc) {
return "{" + loc + "}";
}

public String getEjbql() {
return ejbqlBuilder.toString();
}

public List getParameterValueBindings() {
return parameterValueBindings;
}

public NativeQueryParser(String ejbql) {
this(ejbql, 0);
}

public NativeQueryParser(String ejbql,
int startingParameterNumber) {
StringTokenizer tokens = new StringTokenizer(ejbql, "#}", true);
ejbqlBuilder = new StringBuilder(ejbql.length());
while (tokens.hasMoreTokens()) {
String token = tokens.nextToken();
if ("#".equals(token)) {
if (!tokens.hasMoreTokens()) {
throw new IllegalArgumentException(
"query fragment terminates in #");
}
String expressionToken = tokens.nextToken();
if (!expressionToken.startsWith("{")) {
throw new IllegalArgumentException(
"missing { after # in query fragment");
}
if (!tokens.hasMoreTokens()) {
throw new IllegalArgumentException(
"missing } after expression in query fragment");
}
String expression = token + expressionToken
+ tokens.nextToken();
ejbqlBuilder.append(getParameterName(startingParameterNumber
+ parameterValueBindings.size()));
parameterValueBindings.add(Expressions.instance()
.createValueExpression(expression));
} else {
ejbqlBuilder.append(token);
}
}
}
}