Files
depot/src/java/com/samskivert/jdbc/depot/ConstructedQuery.java
T

303 lines
11 KiB
Java

//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne
//
// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.jdbc.depot;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.depot.annotation.Computed;
import com.samskivert.jdbc.depot.clause.FieldOverride;
import com.samskivert.jdbc.depot.clause.ForUpdate;
import com.samskivert.jdbc.depot.clause.FromOverride;
import com.samskivert.jdbc.depot.clause.GroupBy;
import com.samskivert.jdbc.depot.clause.Join;
import com.samskivert.jdbc.depot.clause.Limit;
import com.samskivert.jdbc.depot.clause.OrderBy;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.clause.Where;
/**
* Encapsulates a non-modifying query of persistent objects.
*/
public abstract class ConstructedQuery<T>
implements Query<T>
{
/**
* Maps a class referenced by this query to its associated table.
*
* This method is called by individual clauses.
*/
public String getTableName (Class cl)
{
return _classMap.get(cl).getTableName();
}
/**
* Maps a class referenced by this query to the abbreviation used for its associated table. The
* abbreviation is transientand only has meaning within the scope of this particular query.
*
* This method is called by individual clauses.
*/
public String getTableAbbreviation (Class cl)
{
int ix = _classList.indexOf(cl);
if (ix < 0) {
throw new IllegalArgumentException("Unknown persistence class: " + cl);
}
return "T" + (ix+1);
}
// from Query
public CacheKey getCacheKey ()
{
if (_where != null && _where instanceof CacheKey) {
return (CacheKey) _where;
}
return null;
}
/**
* Translates the data in this Query object into a SQL query suited to instantiate a persistent
* object given the supplied key and clauses. These clauses are assumed to contain no
* conflicts.
*
* If no key is supplied all instances will be loaded.
*/
public PreparedStatement createQuery (Connection conn)
throws SQLException
{
if (_mainType == null) { // internal error
throw new RuntimeException("createQuery() called with _mainClass == null");
}
DepotMarshaller<?> mainMarshaller = _classMap.get(_mainType);
String[] fields = mainMarshaller._allFields;
StringBuilder query = new StringBuilder("select ");
boolean skip = true;
for (int ii = 0; ii < fields.length; ii ++) {
if (!skip) {
query.append(", ");
}
skip = false;
FieldOverride clause = _disMap.get(fields[ii]);
if (clause != null) {
clause.appendClause(this, query);
continue;
}
Computed computed = mainMarshaller._fields.get(fields[ii]).getComputed();
if (computed == null) {
// make sure the object corresponds to a table, otherwise the whole thing is
// computed
if (mainMarshaller.getTableName() != null) {
// if it's neither overridden nor computed, it's a standard field
query.append(getTableAbbreviation(_mainType)).append(".").append(fields[ii]);
continue;
}
throw new SQLException(
"@Computed entity field without definition [field=" + fields[ii] + "]");
}
// check if the computed field has a literal SQL definition
if (computed.fieldDefinition().length() > 0) {
query.append(computed.fieldDefinition() + " as " + fields[ii]);
} else if (!computed.required()) {
// or if we can simply ignore the field
skip = true;
} else {
throw new SQLException(
"@Computed(required) field without definition [field=" + fields[ii] + "]");
}
}
if (_fromOverride != null) {
_fromOverride.appendClause(this, query);
} else if (mainMarshaller.getTableName() != null) {
query.append(" from ").append(mainMarshaller.getTableName());
query.append(" as ").append(getTableAbbreviation(_mainType));
} else {
throw new SQLException("Query on @Computed entity with no FromOverrideClause.");
}
for (Join clause : _joinClauses) {
clause.appendClause(this, query);
}
if (_where != null) {
_where.appendClause(this, query);
}
if (_groupBy != null) {
_groupBy.appendClause(this, query);
}
if (_orderBy != null) {
_orderBy.appendClause(this, query);
}
if (_limit != null) {
_limit.appendClause(this, query);
}
if (_forUpdate != null) {
_forUpdate.appendClause(this, query);
}
PreparedStatement pstmt = conn.prepareStatement(query.toString());
int argIdx = 1;
for (Join clause : _joinClauses) {
argIdx = clause.bindArguments(pstmt, argIdx);
}
if (_where != null) {
argIdx = _where.bindArguments(pstmt, argIdx);
}
if (_groupBy != null) {
argIdx = _groupBy.bindArguments(pstmt, argIdx);
}
if (_orderBy != null) {
argIdx = _orderBy.bindArguments(pstmt, argIdx);
}
if (_limit != null) {
argIdx = _limit.bindArguments(pstmt, argIdx);
}
if (_forUpdate != null) {
argIdx = _forUpdate.bindArguments(pstmt, argIdx);
}
return pstmt;
}
/**
* Creates a new Query object to generate one or more instances of the specified persistent
* class, as dictated by the key and query clauses. A persistence context is supplied for
* instantiation of marshallers, which may trigger table creations and schema migrations.
*/
protected ConstructedQuery (PersistenceContext ctx, Class type, QueryClause... clauses)
throws PersistenceException
{
_mainType = type;
Set<Class> classSet = new HashSet<Class>();
if (type != null) {
classSet.add(type);
}
for (QueryClause clause : clauses) {
if (clause instanceof Where) {
if (_where != null) {
throw new IllegalArgumentException(
"Query can't contain multiple Where clauses.");
}
_where = (Where) clause;
} else if (clause instanceof FromOverride) {
if (_fromOverride != null) {
throw new IllegalArgumentException(
"Query can't contain multiple FromOverride clauses.");
}
_fromOverride = (FromOverride) clause;
classSet.addAll(_fromOverride.getClassSet());
} else if (clause instanceof Join) {
_joinClauses.add((Join) clause);
classSet.addAll(((Join) clause).getClassSet());
} else if (clause instanceof FieldOverride) {
_disMap.put(((FieldOverride) clause).getField(),
((FieldOverride) clause));
} else if (clause instanceof OrderBy) {
if (_orderBy != null) {
throw new IllegalArgumentException(
"Query can't contain multiple OrderBy clauses.");
}
_orderBy = (OrderBy) clause;
} else if (clause instanceof GroupBy) {
if (_groupBy != null) {
throw new IllegalArgumentException(
"Query can't contain multiple GroupBy clauses.");
}
_groupBy = (GroupBy) clause;
} else if (clause instanceof Limit) {
if (_limit != null) {
throw new IllegalArgumentException(
"Query can't contain multiple Limit clauses.");
}
_limit = (Limit) clause;
} else if (clause instanceof ForUpdate) {
if (_forUpdate != null) {
throw new IllegalArgumentException(
"Query can't contain multiple For Update clauses.");
}
_forUpdate = (ForUpdate) clause;
}
}
_classMap = new HashMap<Class, DepotMarshaller>();
for (Class<?> c : classSet) {
_classMap.put(c, ctx.getMarshaller(c));
}
_classList = new ArrayList<Class>(classSet);
}
/** The persistent class to instantiate for the results. */
protected Class _mainType;
/** A list of referenced classes, used to generate table abbreviations. */
protected List<Class> _classList;
/** Classes mapped to marshallers, used for table names and field lists. */
protected Map<Class, DepotMarshaller> _classMap;
/** Persistent class fields mapped to field override clauses. */
protected Map<String, FieldOverride> _disMap = new HashMap<String, FieldOverride>();
/** The from override clause, if any. */
protected FromOverride _fromOverride;
/** The where clause. */
protected Where _where;
/** A list of join clauses, each potentially referencing a new class. */
protected List<Join> _joinClauses = new ArrayList<Join>();
/** The order by clause, if any. */
protected OrderBy _orderBy;
/** The group by clause, if any. */
protected GroupBy _groupBy;
/** The limit clause, if any. */
protected Limit _limit;
/** The For Update clause, if any. */
protected ForUpdate _forUpdate;
}