package edu.lmu.cs.headmaster.ws.dao.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
/**
* QueryBuilder is a builder that constructs a Hibernate query (HQL) from an
* initial string and any number of "clauses" or arbitrary strings (which must
* comply with the HQL syntax). Clauses represent conditions based on a
* particular value; the resulting appended string follows Hibernate's format
* for supplying parameters (i.e., ":identifier").
*/
public class QueryBuilder {
private Logger logger = Logger.getLogger(getClass());
// Parameters must start with a lowercase ASCII letter.
private static final Pattern PARAMETER_PATTERN = Pattern.compile(":([a-z]\\w*)");
private StringBuilder stringBuilder;
private Map<String, Object> parameters = new HashMap<String, Object>();
private List<String> clauses = new ArrayList<String>();
private String queryString;
private String afterWhereClauseString;
/**
* Produces a new query builder with the given initial string.
*/
public QueryBuilder(final String initialString) {
this(initialString, null);
}
/**
* Creates a new query builder with the given initial and after-where-clause
* strings.
*/
public QueryBuilder(final String initialString, final String afterWhereClauseString) {
stringBuilder = new StringBuilder(initialString);
this.afterWhereClauseString = afterWhereClauseString;
}
/**
* Throws an <code>IllegalStateException</code> if this builder has already
* built a query. Used to guard the build operations.
*/
private void assertNotBuilt() {
if (queryString != null) {
throw new IllegalStateException("The query has already been built");
}
}
/**
* Appends an arbitrary chunk of text to the query builder.
*/
public QueryBuilder append(String text) {
assertNotBuilt();
stringBuilder.append(text);
return this;
}
/**
* Adds an HQL clause to the list of clauses, finding at most one named
* parameter within the clause, and adding it and its associated value to
* the parameter map. For example, calling <code>clause(":x > 5", 10)</code>
* will add the clause ":x > 5" to clauses, and the mapping
* <code>["x" => 10]</code> to map.
*/
public QueryBuilder clause(String condition, Object paramValue) {
assertNotBuilt();
clauses.add(condition);
Matcher matcher = PARAMETER_PATTERN.matcher(condition);
if (matcher.find()) {
parameters.put(matcher.group(1), paramValue);
}
return this;
}
/**
* Puts the base string, the clauses, and the parameters all together into a
* Hibernate query object.
*
* This is a template method; it uses createQuery to instantiate the query
* object to be built and finishQuery to perform any final operations before
* returning the query. Subclasses can override these methods as needed.
*/
public Query build(Session session) {
boolean first = true;
for (String clause: clauses) {
stringBuilder.append(first ? " where " : " and ").append(clause);
first = false;
}
if (afterWhereClauseString != null) {
stringBuilder.append(" ").append(afterWhereClauseString);
}
queryString = stringBuilder.toString();
// If there's no session, we can't build a query. Bail.
if (session == null) {
return null;
}
Query query = createQuery(session, queryString);
for (Map.Entry<String, Object> e : parameters.entrySet()) {
query.setParameter(e.getKey(), e.getValue());
}
if (logger.isDebugEnabled()) {
logger.debug("QUERY: " + getQueryString() + " PARAMS: " + getParameters());
}
return finishQuery(query);
}
/**
* Returns the query string that has been built so far.
*/
public String getQueryString() {
return stringBuilder.toString();
}
/**
* Retrieves the query parameters that have been added so far.
*/
public Map<String, Object> getParameters() {
return parameters;
}
/**
* Factory method for creating the initial Query object.
*/
protected Query createQuery(Session session, String queryString) {
return session.createQuery(queryString);
}
/**
* Wrap-up method for finishing the Query object before returning.
*/
protected Query finishQuery(Query query) {
// For Hibernate queries, this is a no-op.
return query;
}
}