Depot now lives in its own project.

git-svn-id: https://samskivert.googlecode.com/svn/trunk@2480 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
samskivert
2008-11-16 07:49:17 +00:00
parent 206433c80b
commit 7f76e4f876
82 changed files with 0 additions and 11659 deletions
-1
View File
@@ -1,6 +1,5 @@
<!-- $Id$ -->
<!-- Exports various samskivert utilities to GWT. -->
<module>
<source path="jdbc/depot"/>
<source path="text"/>
</module>
@@ -1,307 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.nio.ByteBuffer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Map;
import java.util.Set;
import com.samskivert.jdbc.depot.clause.DeleteClause;
import com.samskivert.jdbc.depot.clause.FieldDefinition;
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.InsertClause;
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.SelectClause;
import com.samskivert.jdbc.depot.clause.UpdateClause;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.EpochSeconds;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.FunctionExp;
import com.samskivert.jdbc.depot.expression.LiteralExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.expression.ValueExp;
import com.samskivert.jdbc.depot.operator.Conditionals.Exists;
import com.samskivert.jdbc.depot.operator.Conditionals.FullTextMatch;
import com.samskivert.jdbc.depot.operator.Conditionals.In;
import com.samskivert.jdbc.depot.operator.Conditionals.IsNull;
import com.samskivert.jdbc.depot.operator.Logic.Not;
import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator;
import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator;
import com.samskivert.util.StringUtil;
/**
* Implements the base functionality of the argument-binding pass of {@link SQLBuilder}. Dialectal
* subclasses of this should be created and returned from {@link SQLBuilder#getBindVisitor}.
*
* This class is intimately paired with {#link BuildVisitor}.
*/
public class BindVisitor implements ExpressionVisitor
{
public void visit (FromOverride override)
{
// nothing needed
}
public void visit (FieldDefinition fieldOverride)
{
// nothing needed
}
public void visit (Key.Expression<? extends PersistentRecord> key)
{
for (Comparable<?> value : key.getValues()) {
if (value != null) {
writeValueToStatement(value);
}
}
}
public void visit (MultiKey<? extends PersistentRecord> key)
{
for (Map.Entry<String, Comparable<?>> entry : key.getSingleFieldsMap().entrySet()) {
if (entry.getValue() != null) {
writeValueToStatement(entry.getValue());
}
}
Comparable<?>[] values = key.getMultiValues();
for (int ii = 0; ii < values.length; ii++) {
writeValueToStatement(values[ii]);
}
}
public void visit (FunctionExp functionExp)
{
visit(functionExp.getArguments());
}
public void visit (EpochSeconds epochSeconds)
{
epochSeconds.getArgument().accept(this);
}
public void visit (MultiOperator multiOperator)
{
visit(multiOperator.getConditions());
}
public void visit (BinaryOperator binaryOperator)
{
binaryOperator.getLeftHandSide().accept(this);
binaryOperator.getRightHandSide().accept(this);
}
public void visit (IsNull isNull)
{
}
public void visit (In in)
{
Comparable<?>[] values = in.getValues();
for (int ii = 0; ii < values.length; ii++) {
writeValueToStatement(values[ii]);
}
}
public void visit (FullTextMatch match)
{
// we never get here
}
public void visit (ColumnExp columnExp)
{
// no arguments
}
public void visit (Not not)
{
not.getCondition().accept(this);
}
public void visit (GroupBy groupBy)
{
visit(groupBy.getValues());
}
public void visit (ForUpdate forUpdate)
{
// do nothing
}
public void visit (OrderBy orderBy)
{
visit(orderBy.getValues());
}
public void visit (WhereClause where)
{
where.getWhereExpression().accept(this);
}
public void visit (Join join)
{
join.getJoinCondition().accept(this);
}
public void visit (Limit limit)
{
try {
_stmt.setInt(_argIdx++, limit.getCount());
_stmt.setInt(_argIdx++, limit.getOffset());
} catch (SQLException sqe) {
throw new DatabaseException("Failed to configure statement with limit clause " +
"[count=" + limit.getCount() +
", offset=" + limit.getOffset() + "]", sqe);
}
}
public void visit (LiteralExp literalExp)
{
// do nothing
}
public void visit (ValueExp valueExp)
{
writeValueToStatement(valueExp.getValue());
}
public void visit (Exists<? extends PersistentRecord> exists)
{
exists.getSubClause().accept(this);
}
public void visit (SelectClause<? extends PersistentRecord> selectClause)
{
for (Join clause : selectClause.getJoinClauses()) {
clause.accept(this);
}
if (selectClause.getWhereClause() != null) {
selectClause.getWhereClause().accept(this);
}
if (selectClause.getGroupBy() != null) {
selectClause.getGroupBy().accept(this);
}
if (selectClause.getOrderBy() != null) {
selectClause.getOrderBy().accept(this);
}
if (selectClause.getLimit() != null) {
selectClause.getLimit().accept(this);
}
if (selectClause.getForUpdate() != null) {
selectClause.getForUpdate().accept(this);
}
}
public void visit (UpdateClause<? extends PersistentRecord> updateClause)
{
DepotMarshaller<?> marsh = _types.getMarshaller(updateClause.getPersistentClass());
// bind the update arguments
Object pojo = updateClause.getPojo();
if (pojo != null) {
for (String field : updateClause.getFields()) {
try {
marsh.getFieldMarshaller(field).getAndWriteToStatement(_stmt, _argIdx++, pojo);
} catch (Exception e) {
throw new DatabaseException(
"Failed to read field from persistent record and write it to prepared " +
"statement [field=" + field + "]", e);
}
}
} else {
visit(updateClause.getValues());
}
updateClause.getWhereClause().accept(this);
}
public void visit (InsertClause<? extends PersistentRecord> insertClause)
{
DepotMarshaller<?> marsh = _types.getMarshaller(insertClause.getPersistentClass());
Object pojo = insertClause.getPojo();
Set<String> idFields = insertClause.getIdentityFields();
for (String field : marsh.getColumnFieldNames()) {
if (!idFields.contains(field)) {
try {
marsh.getFieldMarshaller(field).getAndWriteToStatement(_stmt, _argIdx++, pojo);
} catch (Exception e) {
throw new DatabaseException(
"Failed to read field from persistent record and write it to prepared " +
"statement [field=" + field + "]", e);
}
}
}
}
public void visit (DeleteClause<? extends PersistentRecord> deleteClause)
{
deleteClause.getWhereClause().accept(this);
}
protected BindVisitor (DepotTypes types, Connection conn, PreparedStatement stmt)
{
_types = types;
_conn = conn;
_stmt = stmt;
_argIdx = 1;
}
protected void visit (SQLExpression[] expressions)
{
for (int ii = 0; ii < expressions.length; ii ++) {
expressions[ii].accept(this);
}
}
// write the value to the next argument slot in the prepared statement
protected void writeValueToStatement (Object value)
{
try {
// TODO: how can we abstract this fieldless marshalling
if (value instanceof ByteEnum) {
// byte enums require special conversion
_stmt.setByte(_argIdx++, ((ByteEnum)value).toByte());
} else if (value instanceof int[]) {
// int arrays require conversion to byte arrays
int[] data = (int[])value;
ByteBuffer bbuf = ByteBuffer.allocate(data.length * 4);
bbuf.asIntBuffer().put(data);
_stmt.setObject(_argIdx++, bbuf.array());
} else {
_stmt.setObject(_argIdx++, value);
}
} catch (SQLException sqe) {
throw new DatabaseException("Failed to write value to statement [idx=" + (_argIdx-1) +
", value=" + StringUtil.safeToString(value) + "]", sqe);
}
}
protected DepotTypes _types;
protected Connection _conn;
protected PreparedStatement _stmt;
protected int _argIdx;
}
@@ -1,611 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.samskivert.jdbc.depot.annotation.Computed;
import com.samskivert.jdbc.depot.clause.DeleteClause;
import com.samskivert.jdbc.depot.clause.FieldDefinition;
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.InsertClause;
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.SelectClause;
import com.samskivert.jdbc.depot.clause.UpdateClause;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.EpochSeconds;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.FunctionExp;
import com.samskivert.jdbc.depot.expression.LiteralExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.expression.ValueExp;
import com.samskivert.jdbc.depot.operator.Conditionals.Exists;
import com.samskivert.jdbc.depot.operator.Conditionals.FullTextMatch;
import com.samskivert.jdbc.depot.operator.Conditionals.In;
import com.samskivert.jdbc.depot.operator.Conditionals.IsNull;
import com.samskivert.jdbc.depot.operator.Logic.Not;
import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator;
import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator;
/**
* Implements the base functionality of the SQL-building pass of {@link SQLBuilder}. Dialectal
* subclasses of this should be created and returned from {@link SQLBuilder#getBuildVisitor()}.
*
* This class is intimately paired with {#link BindVisitor}.
*/
public abstract class BuildVisitor implements ExpressionVisitor
{
public String getQuery ()
{
return _builder.toString();
}
public void visit (FromOverride override)
{
_builder.append(" from " );
List<Class<? extends PersistentRecord>> from = override.getFromClasses();
for (int ii = 0; ii < from.size(); ii++) {
if (ii > 0) {
_builder.append(", ");
}
appendTableName(from.get(ii));
_builder.append(" as ");
appendTableAbbreviation(from.get(ii));
}
}
public void visit (FieldDefinition definition)
{
definition.getDefinition().accept(this);
if (_enableAliasing) {
_builder.append(" as ");
appendIdentifier(definition.getField());
}
}
public void visit (WhereClause where)
{
_builder.append(" where ");
where.getWhereExpression().accept(this);
}
public void visit (Key.Expression<? extends PersistentRecord> key)
{
Class<? extends PersistentRecord> pClass = key.getPersistentClass();
String[] keyFields = KeyUtil.getKeyFields(pClass);
List<Comparable<?>> values = key.getValues();
for (int ii = 0; ii < keyFields.length; ii ++) {
if (ii > 0) {
_builder.append(" and ");
}
// A Key's WHERE clause must mirror what's actually retrieved for the persistent
// object, so we turn on overrides here just as we do when expanding SELECT fields
boolean saved = _enableOverrides;
_enableOverrides = true;
appendRhsColumn(pClass, keyFields[ii]);
_enableOverrides = saved;
_builder.append(values.get(ii) == null ? " is null " : " = ? ");
}
}
public void visit (MultiKey<? extends PersistentRecord> key)
{
_builder.append(" where ");
boolean first = true;
for (Map.Entry<String, Comparable<?>> entry : key.getSingleFieldsMap().entrySet()) {
if (first) {
first = false;
} else {
_builder.append(" and ");
}
// A MultiKey's WHERE clause must mirror what's actually retrieved for the persistent
// object, so we turn on overrides here just as we do when expanding SELECT fields
boolean saved = _enableOverrides;
_enableOverrides = true;
appendRhsColumn(key.getPersistentClass(), entry.getKey());
_enableOverrides = saved;
_builder.append(entry.getValue() == null ? " is null " : " = ? ");
}
if (!first) {
_builder.append(" and ");
}
appendRhsColumn(key.getPersistentClass(), key.getMultiField());
_builder.append(" in (");
Comparable<?>[] values = key.getMultiValues();
for (int ii = 0; ii < values.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
_builder.append("?");
}
_builder.append(")");
}
public void visit (FunctionExp functionExp)
{
_builder.append(functionExp.getFunction());
_builder.append("(");
SQLExpression[] arguments = functionExp.getArguments();
for (int ii = 0; ii < arguments.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
arguments[ii].accept(this);
}
_builder.append(")");
}
public void visit (MultiOperator multiOperator)
{
SQLExpression[] conditions = multiOperator.getConditions();
for (int ii = 0; ii < conditions.length; ii++) {
if (ii > 0) {
_builder.append(" ").append(multiOperator.operator()).append(" ");
}
_builder.append("(");
conditions[ii].accept(this);
_builder.append(")");
}
}
public void visit (BinaryOperator binaryOperator)
{
_builder.append('(');
binaryOperator.getLeftHandSide().accept(this);
_builder.append(binaryOperator.operator());
binaryOperator.getRightHandSide().accept(this);
_builder.append(')');
}
public void visit (IsNull isNull)
{
isNull.getColumn().accept(this);
_builder.append(" is null");
}
public void visit (In in)
{
in.getColumn().accept(this);
_builder.append(" in (");
Comparable<?>[] values = in.getValues();
for (int ii = 0; ii < values.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
_builder.append("?");
}
_builder.append(")");
}
public abstract void visit (EpochSeconds seconds);
public abstract void visit (FullTextMatch match);
public void visit (ColumnExp columnExp)
{
appendRhsColumn(columnExp.getPersistentClass(), columnExp.getField());
}
public void visit (Not not)
{
_builder.append(" not (");
not.getCondition().accept(this);
_builder.append(")");
}
public void visit (GroupBy groupBy)
{
_builder.append(" group by ");
SQLExpression[] values = groupBy.getValues();
for (int ii = 0; ii < values.length; ii++) {
if (ii > 0) {
_builder.append(", ");
}
values[ii].accept(this);
}
}
public void visit (ForUpdate forUpdate)
{
_builder.append(" for update ");
}
public void visit (OrderBy orderBy)
{
_builder.append(" order by ");
SQLExpression[] values = orderBy.getValues();
OrderBy.Order[] orders = orderBy.getOrders();
for (int ii = 0; ii < values.length; ii++) {
if (ii > 0) {
_builder.append(", ");
}
values[ii].accept(this);
_builder.append(" ").append(orders[ii]);
}
}
public void visit (Join join)
{
switch (join.getType()) {
case INNER:
_builder.append(" inner join " );
break;
case LEFT_OUTER:
_builder.append(" left outer join " );
break;
case RIGHT_OUTER:
_builder.append(" right outer join " );
break;
}
appendTableName(join.getJoinClass());
_builder.append(" as ");
appendTableAbbreviation(join.getJoinClass());
_builder.append(" on ");
join.getJoinCondition().accept(this);
}
public void visit (Limit limit)
{
_builder.append(" limit ? offset ? ");
}
public void visit (LiteralExp literalExp)
{
_builder.append(literalExp.getText());
}
public void visit (ValueExp valueExp)
{
_builder.append("?");
}
public void visit (Exists<? extends PersistentRecord> exists)
{
_builder.append("exists ");
exists.getSubClause().accept(this);
}
public void visit (SelectClause<? extends PersistentRecord> selectClause)
{
Class<? extends PersistentRecord> pClass = selectClause.getPersistentClass();
boolean isInner = _innerClause;
_innerClause = true;
if (isInner) {
_builder.append("(");
}
_builder.append("select ");
if (_definitions.containsKey(pClass)) {
throw new IllegalArgumentException(
"Can not yet nest SELECTs on the same persistent record.");
}
Map<String, FieldDefinition> definitionMap = new HashMap<String, FieldDefinition>();
for (FieldDefinition definition : selectClause.getFieldDefinitions()) {
definitionMap.put(definition.getField(), definition);
}
_definitions.put(pClass, definitionMap);
try {
// iterate over the fields we're filling in and figure out whence each one comes
boolean skip = true;
// while expanding column names in the SELECT query, do aliasing and expansion
_enableAliasing = _enableOverrides = true;
for (String field : selectClause.getFields()) {
if (!skip) {
_builder.append(", ");
}
skip = false;
int len = _builder.length();
appendRhsColumn(pClass, field);
// if nothing was added, don't add a comma
if (_builder.length() == len) {
skip = true;
}
}
// then stop
_enableAliasing = _enableOverrides = false;
if (selectClause.getFromOverride() != null) {
selectClause.getFromOverride().accept(this);
} else {
Computed computed = _types.getMarshaller(pClass).getComputed();
Class<? extends PersistentRecord> tClass;
if (computed != null && !PersistentRecord.class.equals(computed.shadowOf())) {
tClass = computed.shadowOf();
} else if (_types.getTableName(pClass) != null) {
tClass = pClass;
} else {
throw new IllegalStateException(
"Query on @Computed entity with no FromOverrideClause.");
}
_builder.append(" from ");
appendTableName(tClass);
_builder.append(" as ");
appendTableAbbreviation(tClass);
}
for (Join clause : selectClause.getJoinClauses()) {
clause.accept(this);
}
if (selectClause.getWhereClause() != null) {
selectClause.getWhereClause().accept(this);
}
if (selectClause.getGroupBy() != null) {
selectClause.getGroupBy().accept(this);
}
if (selectClause.getOrderBy() != null) {
selectClause.getOrderBy().accept(this);
}
if (selectClause.getLimit() != null) {
selectClause.getLimit().accept(this);
}
if (selectClause.getForUpdate() != null) {
selectClause.getForUpdate().accept(this);
}
} finally {
_definitions.remove(pClass);
}
if (isInner) {
_builder.append(")");
}
}
public void visit (UpdateClause<? extends PersistentRecord> updateClause)
{
if (updateClause.getWhereClause() == null) {
throw new IllegalArgumentException(
"I dare not currently perform UPDATE without a WHERE clause.");
}
Class<? extends PersistentRecord> pClass = updateClause.getPersistentClass();
_innerClause = true;
_builder.append("update ");
appendTableName(pClass);
_builder.append(" as ");
appendTableAbbreviation(pClass);
_builder.append(" set ");
String[] fields = updateClause.getFields();
Object pojo = updateClause.getPojo();
SQLExpression[] values = updateClause.getValues();
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
appendLhsColumn(pClass, fields[ii]);
_builder.append(" = ");
if (pojo != null) {
_builder.append("?");
} else {
values[ii].accept(this);
}
}
updateClause.getWhereClause().accept(this);
}
public void visit (DeleteClause<? extends PersistentRecord> deleteClause)
{
_builder.append("delete from ");
appendTableName(deleteClause.getPersistentClass());
_builder.append(" as ");
appendTableAbbreviation(deleteClause.getPersistentClass());
_builder.append(" ");
deleteClause.getWhereClause().accept(this);
}
public void visit (InsertClause<? extends PersistentRecord> insertClause)
{
Class<? extends PersistentRecord> pClass = insertClause.getPersistentClass();
DepotMarshaller<?> marsh = _types.getMarshaller(pClass);
_innerClause = true;
String[] fields = marsh.getColumnFieldNames();
_builder.append("insert into ");
appendTableName(insertClause.getPersistentClass());
_builder.append(" (");
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
appendLhsColumn(pClass, fields[ii]);
}
_builder.append(") values(");
Set<String> idFields = insertClause.getIdentityFields();
for (int ii = 0; ii < fields.length; ii++) {
if (ii > 0) {
_builder.append(", ");
}
if (idFields.contains(fields[ii])) {
_builder.append("DEFAULT");
} else {
_builder.append("?");
}
}
_builder.append(")");
}
protected abstract void appendIdentifier (String field);
protected void appendTableName (Class<? extends PersistentRecord> type)
{
appendIdentifier(_types.getTableName(type));
}
protected void appendTableAbbreviation (Class<? extends PersistentRecord> type)
{
appendIdentifier(_types.getTableAbbreviation(type));
}
// Constructs a name used for assignment in e.g. INSERT/UPDATE. This is the SQL
// equivalent of an lvalue; something that can appear to the left of an equals sign.
// We do not prepend this identifier with a table abbreviation, nor do we expand
// field overrides, shadowOf declarations, or the like: it is just a column name.
protected void appendLhsColumn (Class<? extends PersistentRecord> type, String field)
{
DepotMarshaller<?> dm = _types.getMarshaller(type);
if (dm == null) {
throw new IllegalArgumentException(
"Unknown field on persistent record [record=" + type + ", field=" + field + "]");
}
FieldMarshaller<?> fm = dm.getFieldMarshaller(field);
appendIdentifier(fm.getColumnName());
}
// Appends an expression for the given field on the given persistent record; this can
// appear in a SELECT list, in WHERE clauses, etc, etc.
protected void appendRhsColumn (Class<? extends PersistentRecord> type, String field)
{
DepotMarshaller<?> dm = _types.getMarshaller(type);
if (dm == null) {
throw new IllegalArgumentException(
"Unknown field on persistent record [record=" + type + ", field=" + field + "]");
}
// first, see if there's a field definition
FieldMarshaller<?> fm = dm.getFieldMarshaller(field);
Map<String, FieldDefinition> fieldDefs = _definitions.get(type);
if (fieldDefs != null) {
FieldDefinition fieldDef = fieldDefs.get(field);
if (fieldDef != null) {
boolean useOverride;
if (fieldDef instanceof FieldOverride) {
if (fm.getComputed() != null && dm.getComputed() != null) {
throw new IllegalArgumentException(
"FieldOverride cannot be used on @Computed field: " + field);
}
useOverride = _enableOverrides;
} else if (fm.getComputed() == null && dm.getComputed() == null) {
throw new IllegalArgumentException(
"FieldDefinition must not be used on concrete field: " + field);
} else {
useOverride = true;
}
if (useOverride) {
// If a FieldOverride's target is in turn another FieldOverride, the second one
// is ignored. As an example, when creating ItemRecords from CloneRecords, we
// make Item.itemId = Clone.itemId. We also make Item.parentId = Item.itemId
// and would be dismayed to find Item.parentID = Item.itemId = Clone.itemId.
boolean saved = _enableOverrides;
_enableOverrides = false;
fieldDef.accept(this);
_enableOverrides = saved;
return;
}
}
}
Computed entityComputed = dm.getComputed();
// figure out the class we're selecting from unless we're otherwise overriden:
// for a concrete record, simply use the corresponding table; for a computed one,
// default to the shadowed concrete record, or null if there isn't one
Class<? extends PersistentRecord> tableClass;
if (entityComputed == null) {
tableClass = type;
} else if (!PersistentRecord.class.equals(entityComputed.shadowOf())) {
tableClass = entityComputed.shadowOf();
} else {
tableClass = null;
}
// handle the field-level @Computed annotation, if there is one
Computed fieldComputed = fm.getComputed();
if (fieldComputed != null) {
// check if the computed field has a literal SQL definition
if (fieldComputed.fieldDefinition().length() > 0) {
_builder.append(fieldComputed.fieldDefinition());
if (_enableAliasing) {
_builder.append(" as ");
appendIdentifier(field);
}
return;
}
// or if we can simply ignore the field
if (!fieldComputed.required()) {
return;
}
// else see if there's an overriding shadowOf definition
if (fieldComputed.shadowOf() != null) {
tableClass = fieldComputed.shadowOf();
}
}
// if we get this far we hopefully have a table to select from
if (tableClass != null) {
appendTableAbbreviation(tableClass);
_builder.append(".");
appendIdentifier(fm.getColumnName());
return;
}
// else owie
throw new IllegalArgumentException(
"Persistent field has no definition [class=" + type + ", field=" + field + "]");
}
protected BuildVisitor (DepotTypes types)
{
_types = types;
}
protected DepotTypes _types;
/** A StringBuilder to hold the constructed SQL. */
protected StringBuilder _builder = new StringBuilder();
/** A mapping of field overrides per persistent record. */
protected Map<Class<? extends PersistentRecord>, Map<String, FieldDefinition>> _definitions=
new HashMap<Class<? extends PersistentRecord>, Map<String,FieldDefinition>>();
/** A flag that's set to true for inner SELECT's */
protected boolean _innerClause = false;
protected boolean _enableOverrides = false;
protected boolean _enableAliasing = false;
}
@@ -1,39 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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;
/**
* An enum value can be used as a field in a persistent object if it implements this interface and
* also declares a public static method with the following signature:
*
* <code>
* public static YourEnum fromByte (byte value)
* </code>
*
* which must return the appropriate instance of your enum for the supplied byte.
*/
public interface ByteEnum
{
/**
* Returns the byte value to which to map this enum value.
*/
public byte toByte ();
}
@@ -1,77 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.io.Serializable;
/**
* Implementations of this interface are responsible for all the caching needs of Depot.
*
* The cache consists of many {@link CacheBin}s. Each bin its own key space. Look-ups and storage
* occur on a per-bin per-key basis.
*/
public interface CacheAdapter
{
/** The encapsulated result of a cache lookup. */
public interface CachedValue<T>
{
/** Returns the cached value, which can be null. */
public T getValue ();
}
/**
* A reference to a specific bin within the cache; this is the type where most of the actual
* caching functionality occurs.
*/
public interface CacheBin<T>
{
/**
* Searches this bin using the given key and returns the resulting {@link CachedValue}, or
* null if nothing exists in the cache for this key.
*/
public CachedValue<T> lookup (Serializable key);
/**
* Stores a new value in this cache bin under the given key.
*/
public void store (Serializable key, T value);
/**
* Removes the cache entry, if any, associated with the given key.
*/
public void remove (Serializable key);
/**
* Provides a way to enumerate the currently cached entries in this bin.
*/
public Iterable<Serializable> enumerateKeys ();
}
/**
* Fetch the {@link CacheBin} associated with the given ID, creating one on the fly if needed.
*/
public <T> CacheBin<T> getCache (String id);
/**
* Shut down all operations, e.g. persisting memory contents to disk.
*/
public void shutdown ();
}
@@ -1,61 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.io.Serializable;
/**
* Implementors of this interface performs perform cache invalidation for calls to
* {@link DepotRepository#updateLiteral}, {@link DepotRepository#updatePartial} and
* {@link DepotRepository#deleteAll}.
*/
public interface CacheInvalidator
{
public static abstract class TraverseWithFilter<T extends Serializable>
implements CacheInvalidator
{
public TraverseWithFilter (Class<T> pClass) {
this(pClass.getName());
}
public TraverseWithFilter (String cacheId) {
_cacheId = cacheId;
}
public void invalidate (PersistenceContext ctx) {
ctx.cacheTraverse(_cacheId, new PersistenceContext.CacheEvictionFilter<T>() {
@Override protected boolean testForEviction (Serializable key, T record) {
return TraverseWithFilter.this.testForEviction(key, record);
}
});
}
protected abstract boolean testForEviction (Serializable key, T record);
protected String _cacheId;
}
/**
* Must invalidate all cache entries that depend on the records being modified or deleted.
* This method is called just before the database statement is executed.
*/
public void invalidate (PersistenceContext ctx);
}
@@ -1,42 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.io.Serializable;
/**
* This interface uniquely identifies a single persistent entry for caching purposes.
* Queries that are given a {@link CacheKey} consult the cache before they hit the
* database.
*/
public interface CacheKey
{
/**
* Returns the id of the cache in whose scope this key makes sense.
*/
public String getCacheId ();
/**
* Returns the actual opaque serializable cache key under which results are stored
* in the cache identified by {@link #getCacheId}.
*/
public Serializable getCacheKey ();
}
@@ -1,73 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2001-2008 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;
/**
* Encapsulates a migration of data between entities that should be run only once and using the
* same safeguards applied to entity migrations. Note: this should not be used for schema
* migrations, use {@link SchemaMigration} for that. Data migrations are registered on a specific
* repository via {@link DepotRepository#registerMigration} and should be registered in the
* repository's constructor as they will be invoked (if appropriate) in the repository's {@link
* DepotRepository#init} method.
*
* <p> In general one will register an anonymous inner class in a repository's constructor and can
* then access methods in the repository directly:
*
* <pre>
* public class FooRepository extends DepotRepository {
* public FooRepository (PersistenceContext ctx) {
* super(ctx);
* registerMigration(new DataMigration("2008_09_25_referral_to_tracking_id") {
* public void invoke () throws DatabaseException {
* // feel free to use load() findAll(), update(), etc.
* }
* });
* }
* </pre>
*/
public abstract class DataMigration
{
/**
* Creates a data migration with the specified unique identifier. The identifier must be unique
* across all users of the database and for all time, so be careful. Best to include the date
* and pertintent information, e.g. "2008_09_25_referral_to_tracking_id".
*/
public DataMigration (String ident)
{
_ident = ident;
}
/**
* Returns the identifier of this migration.
*/
public String getIdent ()
{
return _ident;
}
/**
* Effects the data migration.
*/
public abstract void invoke () throws DatabaseException;
/** The unique identifier for this migration. */
protected String _ident;
}
@@ -1,53 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2001-2008 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;
/**
* Represents a failure reported by the underlying database.
*/
public class DatabaseException extends RuntimeException
{
/**
* Constructs a database exception with the specified error message.
*/
public DatabaseException (String message)
{
super(message);
}
/**
* Constructs a database exception with the specified error message and the chained causing
* event.
*/
public DatabaseException (String message, Throwable cause)
{
super(message);
initCause(cause);
}
/**
* Constructs a database exception with the specified chained causing event.
*/
public DatabaseException (Throwable cause)
{
initCause(cause);
}
}
File diff suppressed because it is too large Load Diff
@@ -1,73 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2001-2008 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.Timestamp;
import com.samskivert.jdbc.depot.annotation.Column;
import com.samskivert.jdbc.depot.annotation.Id;
import com.samskivert.jdbc.depot.expression.ColumnExp;
/**
* Maintains a record of all successfully invoked data migrations.
*/
public class DepotMigrationHistoryRecord extends PersistentRecord
{
// AUTO-GENERATED: FIELDS START
/** The column identifier for the {@link #ident} field. */
public static final String IDENT = "ident";
/** The qualified column identifier for the {@link #ident} field. */
public static final ColumnExp IDENT_C =
new ColumnExp(DepotMigrationHistoryRecord.class, IDENT);
/** The column identifier for the {@link #whenCompleted} field. */
public static final String WHEN_COMPLETED = "whenCompleted";
/** The qualified column identifier for the {@link #whenCompleted} field. */
public static final ColumnExp WHEN_COMPLETED_C =
new ColumnExp(DepotMigrationHistoryRecord.class, WHEN_COMPLETED);
// AUTO-GENERATED: FIELDS END
/** Our schema version. Probably not likely to change. */
public static final int SCHEMA_VERSION = 1;
/** The unique identifier for this migration. */
@Id public String ident;
/** The time at which the migration was completed. */
@Column(nullable=true)
public Timestamp whenCompleted;
// AUTO-GENERATED: METHODS START
/**
* Create and return a primary {@link Key} to identify a {@link DepotMigrationHistoryRecord}
* with the supplied key values.
*/
public static Key<DepotMigrationHistoryRecord> getKey (String ident)
{
return new Key<DepotMigrationHistoryRecord>(
DepotMigrationHistoryRecord.class,
new String[] { IDENT },
new Comparable[] { ident });
}
// AUTO-GENERATED: METHODS END
}
@@ -1,988 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.samskivert.util.ArrayUtil;
import com.samskivert.jdbc.ConnectionProvider;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.depot.Modifier.*;
import com.samskivert.jdbc.depot.clause.DeleteClause;
import com.samskivert.jdbc.depot.clause.FieldOverride;
import com.samskivert.jdbc.depot.clause.InsertClause;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.clause.SelectClause;
import com.samskivert.jdbc.depot.clause.UpdateClause;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.expression.ValueExp;
import static com.samskivert.jdbc.depot.Log.log;
/**
* Provides a base for classes that provide access to persistent objects. Also defines the
* mechanism by which all persistent queries and updates are routed through the distributed cache.
*/
public abstract class DepotRepository
{
/**
* Creates a repository with the supplied persistence context. Any schema migrations needed by
* this repository should be registered in its constructor. A repository should <em>not</em>
* perform any actual database operations in its constructor, only register schema
* migrations. Initialization related database operations should be performed in {@link #init}.
*/
protected DepotRepository (PersistenceContext context)
{
_ctx = context;
_ctx.repositoryCreated(this);
}
/**
* Creates a repository with the supplied connection provider and its own private persistence
* context. This should generally not be used for new systems, and is only included to
* facilitate the integration of small numbers of Depot-based repositories into systems using
* the older samskivert SimpleRepository system.
*/
protected DepotRepository (ConnectionProvider conprov)
{
_ctx = new PersistenceContext();
_ctx.init(getClass().getName(), conprov, null);
_ctx.repositoryCreated(this);
}
/**
* Resolves all persistent records registered to this repository (via {@link
* #getManagedRecords}. This will be done before the repository is initialized via {@link
* #init}.
*/
protected void resolveRecords ()
throws DatabaseException
{
Set<Class<? extends PersistentRecord>> classes =
new HashSet<Class<? extends PersistentRecord>>();
getManagedRecords(classes);
for (Class<? extends PersistentRecord> rclass : classes) {
_ctx.getMarshaller(rclass);
}
}
/**
* Provides a place where a repository can perform any initialization that requires database
* operations.
*/
protected void init ()
throws DatabaseException
{
// run any registered data migrations
for (DataMigration migration : _dataMigs) {
runMigration(migration);
}
_dataMigs = null; // note that we've been initialized
}
/**
* Registers a data migration for this repository. This migration will only be run once and its
* unique identifier will be stored persistently to ensure that it is never run again on the
* same database. Nonetheless, migrations should strive to be idempotent because someone might
* come along and create a brand new system installation and all registered migrations will be
* run once on the freshly created database. As with all database migrations, understand
* clearly how the process works and think about edge cases when creating a migration.
*
* <p> See {@link PersistenceContext#registerMigration} for details on how schema migrations
* operate and how they might interact with data migrations.
*/
protected void registerMigration (DataMigration migration)
{
if (_dataMigs == null) {
// we've already been initialized, so we have to run this migration immediately
runMigration(migration);
} else {
_dataMigs.add(migration);
}
}
/**
* Adds the persistent classes used by this repository to the supplied set.
*/
protected abstract void getManagedRecords (Set<Class<? extends PersistentRecord>> classes);
/**
* Loads the persistent object that matches the specified primary key.
*/
protected <T extends PersistentRecord> T load (Class<T> type, Comparable<?> primaryKey,
QueryClause... clauses)
throws DatabaseException
{
clauses = ArrayUtil.append(clauses, _ctx.getMarshaller(type).makePrimaryKey(primaryKey));
return load(type, clauses);
}
/**
* Loads the persistent object that matches the specified primary key.
*/
protected <T extends PersistentRecord> T load (Class<T> type, String ix, Comparable<?> val,
QueryClause... clauses)
throws DatabaseException
{
clauses = ArrayUtil.append(clauses, new Key<T>(type, ix, val));
return load(type, clauses);
}
/**
* Loads the persistent object that matches the specified two-column primary key.
*/
protected <T extends PersistentRecord> T load (Class<T> type, String ix1, Comparable<?> val1,
String ix2, Comparable<?> val2,
QueryClause... clauses)
throws DatabaseException
{
clauses = ArrayUtil.append(clauses, new Key<T>(type, ix1, val1, ix2, val2));
return load(type, clauses);
}
/**
* Loads the persistent object that matches the specified three-column primary key.
*/
protected <T extends PersistentRecord> T load (Class<T> type, String ix1, Comparable<?> val1,
String ix2, Comparable<?> val2, String ix3,
Comparable<?> val3, QueryClause... clauses)
throws DatabaseException
{
clauses = ArrayUtil.append(clauses, new Key<T>(type, ix1, val1, ix2, val2, ix3, val3));
return load(type, clauses);
}
/**
* Loads the first persistent object that matches the supplied query clauses.
*/
protected <T extends PersistentRecord> T load (
Class<T> type, Collection<? extends QueryClause> clauses)
throws DatabaseException
{
return load(type, clauses.toArray(new QueryClause[clauses.size()]));
}
/**
* Loads the first persistent object that matches the supplied query clauses.
*/
protected <T extends PersistentRecord> T load (Class<T> type, QueryClause... clauses)
throws DatabaseException
{
return _ctx.invoke(new FindOneQuery<T>(_ctx, type, clauses));
}
/**
* Loads up all persistent records that match the supplied set of raw primary keys.
*/
protected <T extends PersistentRecord> List<T> loadAll (
Class<T> type, Collection<? extends Comparable<?>> primaryKeys)
throws DatabaseException
{
// convert the raw keys into real key records
DepotMarshaller<T> marsh = _ctx.getMarshaller(type);
List<Key<T>> keys = new ArrayList<Key<T>>();
for (Comparable<?> key : primaryKeys) {
keys.add(marsh.makePrimaryKey(key));
}
return loadAll(keys);
}
/**
* Loads up all persistent records that match the supplied set of primary keys.
*/
protected <T extends PersistentRecord> List<T> loadAll (Collection<Key<T>> keys)
throws DatabaseException
{
return (keys.size() == 0) ? Collections.<T>emptyList() :
_ctx.invoke(new FindAllQuery.WithKeys<T>(_ctx, keys));
}
/**
* A varargs version of {@link #findAll(Class,Collection)}.
*/
protected <T extends PersistentRecord> List<T> findAll (Class<T> type, QueryClause... clauses)
throws DatabaseException
{
return findAll(type, Arrays.asList(clauses));
}
/**
* Loads all persistent objects that match the specified clauses.
*
* We have two strategies for doing this: one performs the query as-is, the second executes two
* passes: first fetching only key columns and consulting the cache for each such key; then, in
* the second pass, fetching the full entity only for keys that were not found in the cache.
*
* The more complex strategy could save a lot of data shuffling. On the other hand, its
* complexity is an inherent drawback, and it does execute two separate database queries for
* what the simple method does in one.
*/
protected <T extends PersistentRecord> List<T> findAll (
Class<T> type, Collection<? extends QueryClause> clauses)
throws DatabaseException
{
return findAll(type, false, clauses);
}
/**
* Loads all persistent objects that match the specified clauses.
*
* @param skipCache if true, our normal mixed select strategy that allows cached records to be
* loaded from the cache will not be used even if it otherwise could. See {@link
* #findAll(Class,Collection)} for details on the mixed strategy.
*/
protected <T extends PersistentRecord> List<T> findAll (
Class<T> type, boolean skipCache, Collection<? extends QueryClause> clauses)
throws DatabaseException
{
DepotMarshaller<T> marsh = _ctx.getMarshaller(type);
boolean useExplicit = skipCache || (marsh.getTableName() == null) ||
!marsh.hasPrimaryKey() || !_ctx.isUsingCache();
// queries on @Computed records or the presence of FieldOverrides use the simple algorithm
for (QueryClause clause : clauses) {
useExplicit |= (clause instanceof FieldOverride);
}
return _ctx.invoke(useExplicit ? new FindAllQuery.Explicitly<T>(_ctx, type, clauses) :
new FindAllQuery.WithCache<T>(_ctx, type, clauses));
}
/**
* Looks up and returns {@link Key} records for all rows that match the supplied query clauses.
*
* @param forUpdate if true, the query will be run using a read-write connection to ensure that
* it talks to the master database, if false, the query will be run on a read-only connection
* and may load keys from a slave. For performance reasons, you should always pass false unless
* you know you will be modifying the database as a result of this query and absolutely need
* the latest data.
*/
protected <T extends PersistentRecord> List<Key<T>> findAllKeys (
Class<T> type, boolean forUpdate, QueryClause... clause)
throws DatabaseException
{
return findAllKeys(type, forUpdate, Arrays.asList(clause));
}
/**
* Looks up and returns {@link Key} records for all rows that match the supplied query clauses.
*
* @param forUpdate if true, the query will be run using a read-write connection to ensure that
* it talks to the master database, if false, the query will be run on a read-only connection
* and may load keys from a slave. For performance reasons, you should always pass false unless
* you know you will be modifying the database as a result of this query and absolutely need
* the latest data.
*/
protected <T extends PersistentRecord> List<Key<T>> findAllKeys (
Class<T> type, boolean forUpdate, Collection<? extends QueryClause> clauses)
throws DatabaseException
{
final List<Key<T>> keys = new ArrayList<Key<T>>();
final DepotMarshaller<T> marsh = _ctx.getMarshaller(type);
SelectClause<T> select = new SelectClause<T>(type, marsh.getPrimaryKeyFields(), clauses);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, select));
builder.newQuery(select);
if (forUpdate) {
_ctx.invoke(new Modifier(null) {
@Override public Integer invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
keys.add(marsh.makePrimaryKey(rs));
}
return 0;
} finally {
JDBCUtil.close(stmt);
}
}
});
} else {
_ctx.invoke(new Query.Trivial<Void>() {
@Override
public Void invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
keys.add(marsh.makePrimaryKey(rs));
}
return null;
} finally {
JDBCUtil.close(stmt);
}
}
});
}
return keys;
}
/**
* Inserts the supplied persistent object into the database, assigning its primary key (if it
* has one) in the process.
*
* @return the number of rows modified by this action, this should always be one.
*/
protected <T extends PersistentRecord> int insert (T record)
throws DatabaseException
{
@SuppressWarnings("unchecked") final Class<T> pClass = (Class<T>) record.getClass();
final DepotMarshaller<T> marsh = _ctx.getMarshaller(pClass);
Key<T> key = marsh.getPrimaryKey(record, false);
DepotTypes types = DepotTypes.getDepotTypes(_ctx);
types.addClass(_ctx, pClass);
final SQLBuilder builder = _ctx.getSQLBuilder(types);
// key will be null if record was supplied without a primary key
return _ctx.invoke(new CachingModifier<T>(record, key, key) {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
// if needed, update our modifier's key so that it can cache our results
Set<String> identityFields = Collections.emptySet();
if (_key == null) {
// set any auto-generated column values
identityFields = marsh.generateFieldValues(conn, liaison, _result, false);
updateKey(marsh.getPrimaryKey(_result, false));
}
builder.newQuery(new InsertClause<T>(pClass, _result, identityFields));
PreparedStatement stmt = builder.prepare(conn);
try {
int mods = stmt.executeUpdate();
// run any post-factum value generators and potentially generate our key
if (_key == null) {
marsh.generateFieldValues(conn, liaison, _result, true);
updateKey(marsh.getPrimaryKey(_result, false));
}
return mods;
} finally {
JDBCUtil.close(stmt);
}
}
});
}
/**
* Updates all fields of the supplied persistent object, using its primary key to identify the
* row to be updated.
*
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int update (T record)
throws DatabaseException
{
@SuppressWarnings("unchecked") Class<T> pClass = (Class<T>) record.getClass();
requireNotComputed(pClass, "update");
DepotMarshaller<T> marsh = _ctx.getMarshaller(pClass);
Key<T> key = marsh.getPrimaryKey(record);
if (key == null) {
throw new IllegalArgumentException("Can't update record with null primary key.");
}
UpdateClause<T> update = new UpdateClause<T>(pClass, key, marsh._columnFields, record);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update));
builder.newQuery(update);
return _ctx.invoke(new CachingModifier<T>(record, key, key) {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
return stmt.executeUpdate();
} finally {
JDBCUtil.close(stmt);
}
}
});
}
/**
* Updates just the specified fields of the supplied persistent object, using its primary key
* to identify the row to be updated. This method currently flushes the associated record from
* the cache, but in the future it should be modified to update the modified fields in the
* cached value iff the record exists in the cache.
*
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int update (T record, final String... modifiedFields)
throws DatabaseException
{
@SuppressWarnings("unchecked") Class<T> pClass = (Class<T>) record.getClass();
requireNotComputed(pClass, "updatePartial");
DepotMarshaller<T> marsh = _ctx.getMarshaller(pClass);
Key<T> key = marsh.getPrimaryKey(record);
if (key == null) {
throw new IllegalArgumentException("Can't update record with null primary key.");
}
UpdateClause<T> update = new UpdateClause<T>(pClass, key, modifiedFields, record);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update));
builder.newQuery(update);
return _ctx.invoke(new CachingModifier<T>(record, key, key) {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
// clear out _result so that we don't rewrite this partial record to the cache
_result = null;
try {
return stmt.executeUpdate();
} finally {
JDBCUtil.close(stmt);
}
}
});
}
/**
* Updates the specified columns for all persistent objects matching the supplied primary key.
*
* @param type the type of the persistent object to be modified.
* @param primaryKey the primary key to match in the update.
* @param updates an mapping from the names of the fields/columns ti the values to be assigned.
*
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int updatePartial (
Class<T> type, Comparable<?> primaryKey, Map<String,Object> updates)
throws DatabaseException
{
Object[] fieldsValues = new Object[updates.size()*2];
int idx = 0;
for (Map.Entry<String,Object> entry : updates.entrySet()) {
fieldsValues[idx++] = entry.getKey();
fieldsValues[idx++] = entry.getValue();
}
return updatePartial(type, primaryKey, fieldsValues);
}
/**
* Updates the specified columns for all persistent objects matching the supplied primary key.
*
* @param type the type of the persistent object to be modified.
* @param primaryKey the primary key to match in the update.
* @param fieldsValues an array containing the names of the fields/columns and the values to be
* assigned, in key, value, key, value, etc. order.
*
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int updatePartial (
Class<T> type, Comparable<?> primaryKey, Object... fieldsValues)
throws DatabaseException
{
return updatePartial(_ctx.getMarshaller(type).makePrimaryKey(primaryKey), fieldsValues);
}
/**
* Updates the specified columns for all persistent objects matching the supplied two-column
* primary key.
*
* @param type the type of the persistent object to be modified.
* @param fieldsValues an array containing the names of the fields/columns and the values to be
* assigned, in key, value, key, value, etc. order.
*
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int updatePartial (
Class<T> type, String ix1, Comparable<?> val1, String ix2, Comparable<?> val2,
Object... fieldsValues)
throws DatabaseException
{
return updatePartial(new Key<T>(type, ix1, val1, ix2, val2), fieldsValues);
}
/**
* Updates the specified columns for all persistent objects matching the supplied three-column
* primary key.
*
* @param type the type of the persistent object to be modified.
* @param fieldsValues an array containing the names of the fields/columns and the values to be
* assigned, in key, value, key, value, etc. order.
*
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int updatePartial (
Class<T> type, String ix1, Comparable<?> val1, String ix2, Comparable<?> val2,
String ix3, Comparable<?> val3, Object... fieldsValues)
throws DatabaseException
{
return updatePartial(new Key<T>(type, ix1, val1, ix2, val2, ix3, val3), fieldsValues);
}
/**
* Updates the specified columns for all persistent objects matching the supplied key.
*
* @param key the key for the persistent objects to be modified.
* @param fieldsValues an array containing the names of the fields/columns and the values to be
* assigned, in key, value, key, value, etc. order.
*
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int updatePartial (Key<T> key, Object... fieldsValues)
throws DatabaseException
{
return updatePartial(key.getPersistentClass(), key, key, fieldsValues);
}
/**
* Updates the specified columns for all persistent objects matching the supplied key. This
* method currently flushes the associated record from the cache, but in the future it should
* be modified to update the modified fields in the cached value iff the record exists in the
* cache.
*
* @param type the type of the persistent object to be modified.
* @param key the key to match in the update.
* @param invalidator a cache invalidator that will be run prior to the update to flush the
* relevant persistent objects from the cache, or null if no invalidation is needed.
* @param fieldsValues an array containing the names of the fields/columns and the values to be
* assigned, in key, value, key, value, etc. order.
*
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int updatePartial (
Class<T> type, final WhereClause key, CacheInvalidator invalidator, Object... fieldsValues)
throws DatabaseException
{
if (invalidator instanceof ValidatingCacheInvalidator) {
((ValidatingCacheInvalidator)invalidator).validateFlushType(type); // sanity check
}
key.validateQueryType(type); // and another
// separate the arguments into keys and values
final String[] fields = new String[fieldsValues.length/2];
final SQLExpression[] values = new SQLExpression[fields.length];
for (int ii = 0, idx = 0; ii < fields.length; ii++) {
fields[ii] = (String)fieldsValues[idx++];
values[ii] = new ValueExp(fieldsValues[idx++]);
}
UpdateClause<T> update = new UpdateClause<T>(type, key, fields, values);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update));
builder.newQuery(update);
return _ctx.invoke(new Modifier(invalidator) {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
return stmt.executeUpdate();
} finally {
JDBCUtil.close(stmt);
}
}
});
}
/**
* Updates the specified columns for all persistent objects matching the supplied primary
* key. The values in this case must be literal SQL to be inserted into the update statement.
* In general this is used when you want to do something like the following:
*
* <pre>
* update FOO set BAR = BAR + 1;
* update BAZ set BIF = NOW();
* </pre>
*
* @param type the type of the persistent object to be modified.
* @param primaryKey the key to match in the update.
* @param fieldsValues an array containing the names of the fields/columns and the values to be
* assigned, in key, literal value, key, literal value, etc. order.
*
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int updateLiteral (
Class<T> type, Comparable<?> primaryKey, Map<String, SQLExpression> fieldsValues)
throws DatabaseException
{
Key<T> key = _ctx.getMarshaller(type).makePrimaryKey(primaryKey);
return updateLiteral(type, key, key, fieldsValues);
}
/**
* Updates the specified columns for all persistent objects matching the supplied two-column
* primary key. The values in this case must be literal SQL to be inserted into the update
* statement. In general this is used when you want to do something like the following:
*
* <pre>
* update FOO set BAR = BAR + 1;
* update BAZ set BIF = NOW();
* </pre>
*
* @param type the type of the persistent object to be modified.
* @param fieldsValues an array containing the names of the fields/columns and the values to be
* assigned, in key, literal value, key, literal value, etc. order.
*
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int updateLiteral (
Class<T> type, String ix1, Comparable<?> val1, String ix2, Comparable<?> val2,
Map<String, SQLExpression> fieldsValues)
throws DatabaseException
{
Key<T> key = new Key<T>(type, ix1, val1, ix2, val2);
return updateLiteral(type, key, key, fieldsValues);
}
/**
* Updates the specified columns for all persistent objects matching the supplied three-column
* primary key. The values in this case must be literal SQL to be inserted into the update
* statement. In general this is used when you want to do something like the following:
*
* <pre>
* update FOO set BAR = BAR + 1;
* update BAZ set BIF = NOW();
* </pre>
*
* @param type the type of the persistent object to be modified.
* @param fieldsValues an array containing the names of the fields/columns and the values to be
* assigned, in key, literal value, key, literal value, etc. order.
*
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int updateLiteral (
Class<T> type, String ix1, Comparable<?> val1, String ix2, Comparable<?> val2,
String ix3, Comparable<?> val3, Map<String, SQLExpression> fieldsValues)
throws DatabaseException
{
Key<T> key = new Key<T>(type, ix1, val1, ix2, val2, ix3, val3);
return updateLiteral(type, key, key, fieldsValues);
}
/**
* Updates the specified columns for all persistent objects matching the supplied primary
* key. The values in this case must be literal SQL to be inserted into the update statement.
* In general this is used when you want to do something like the following:
*
* <pre>
* update FOO set BAR = BAR + 1;
* update BAZ set BIF = NOW();
* </pre>
*
* @param type the type of the persistent object to be modified.
* @param key the key to match in the update.
* @param fieldsValues an array containing the names of the fields/columns and the values to be
* assigned, in key, literal value, key, literal value, etc. order.
*
* @return the number of rows modified by this action.
*/
protected <T extends PersistentRecord> int updateLiteral (
Class<T> type, final WhereClause key, CacheInvalidator invalidator,
Map<String, SQLExpression> fieldsValues)
throws DatabaseException
{
requireNotComputed(type, "updateLiteral");
if (invalidator instanceof ValidatingCacheInvalidator) {
((ValidatingCacheInvalidator)invalidator).validateFlushType(type); // sanity check
}
key.validateQueryType(type); // and another
// separate the arguments into keys and values
final String[] fields = new String[fieldsValues.size()];
final SQLExpression[] values = new SQLExpression[fields.length];
int ii = 0;
for (Map.Entry<String, SQLExpression> entry : fieldsValues.entrySet()) {
fields[ii] = entry.getKey();
values[ii] = entry.getValue();
ii ++;
}
UpdateClause<T> update = new UpdateClause<T>(type, key, fields, values);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update));
builder.newQuery(update);
return _ctx.invoke(new Modifier(invalidator) {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
return stmt.executeUpdate();
} finally {
JDBCUtil.close(stmt);
}
}
});
}
/**
* Stores the supplied persisent object in the database. If it has no primary key assigned (it
* is null or zero), it will be inserted directly. Otherwise an update will first be attempted
* and if that matches zero rows, the object will be inserted.
*
* @return true if the record was created, false if it was updated.
*/
protected <T extends PersistentRecord> boolean store (T record)
throws DatabaseException
{
@SuppressWarnings("unchecked") final Class<T> pClass = (Class<T>) record.getClass();
requireNotComputed(pClass, "store");
final DepotMarshaller<T> marsh = _ctx.getMarshaller(pClass);
Key<T> key = marsh.hasPrimaryKey() ? marsh.getPrimaryKey(record) : null;
final UpdateClause<T> update =
new UpdateClause<T>(pClass, key, marsh._columnFields, record);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, update));
// if our primary key isn't null, we start by trying to update rather than insert
if (key != null) {
builder.newQuery(update);
}
final boolean[] created = new boolean[1];
_ctx.invoke(new CachingModifier<T>(record, key, key) {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = null;
try {
if (_key != null) {
// run the update
stmt = builder.prepare(conn);
int mods = stmt.executeUpdate();
if (mods > 0) {
// if it succeeded, we're done
return mods;
}
JDBCUtil.close(stmt);
}
// if the update modified zero rows or the primary key was unset, insert
Set<String> identityFields = Collections.emptySet();
if (_key == null) {
// first, set any auto-generated column values
identityFields = marsh.generateFieldValues(conn, liaison, _result, false);
// update our modifier's key so that it can cache our results
updateKey(marsh.getPrimaryKey(_result, false));
}
builder.newQuery(new InsertClause<T>(pClass, _result, identityFields));
stmt = builder.prepare(conn);
int mods = stmt.executeUpdate();
// run any post-factum value generators and potentially generate our key
if (_key == null) {
marsh.generateFieldValues(conn, liaison, _result, true);
updateKey(marsh.getPrimaryKey(_result, false));
}
created[0] = true;
return mods;
} finally {
JDBCUtil.close(stmt);
}
}
});
return created[0];
}
/**
* Deletes all persistent objects from the database with a primary key matching the primary key
* of the supplied object.
*
* @return the number of rows deleted by this action.
*/
protected <T extends PersistentRecord> int delete (T record)
throws DatabaseException
{
@SuppressWarnings("unchecked") Class<T> type = (Class<T>)record.getClass();
Key<T> primaryKey = _ctx.getMarshaller(type).getPrimaryKey(record);
if (primaryKey == null) {
throw new IllegalArgumentException("Can't delete record with null primary key.");
}
return delete(type, primaryKey);
}
/**
* Deletes all persistent objects from the database with a primary key matching the supplied
* primary key.
*
* @return the number of rows deleted by this action.
*/
protected <T extends PersistentRecord> int delete (Class<T> type, Comparable<?> primaryKeyValue)
throws DatabaseException
{
return delete(type, _ctx.getMarshaller(type).makePrimaryKey(primaryKeyValue));
}
/**
* Deletes all persistent objects from the database with a primary key matching the supplied
* primary key.
*
* @return the number of rows deleted by this action.
*/
protected <T extends PersistentRecord> int delete (Class<T> type, Key<T> primaryKey)
throws DatabaseException
{
return deleteAll(type, primaryKey, primaryKey);
}
/**
* Deletes all persistent objects from the database that match the supplied where clause.
*
* @return the number of rows deleted by this action.
*/
protected <T extends PersistentRecord> int deleteAll (Class<T> type, final WhereClause where)
throws DatabaseException
{
if (_ctx.getMarshaller(type).hasPrimaryKey()) {
// look up the primary keys for all rows matching our where clause and delete using those
KeySet<T> pwhere = new KeySet<T>(type, findAllKeys(type, true, where));
return deleteAll(type, pwhere, pwhere);
} else {
// otherwise just do the delete directly as we can't have cached a record that has no
// primary key in the first place
return deleteAll(type, where, null);
}
}
/**
* Deletes all persistent objects from the database that match the supplied key.
*
* @return the number of rows deleted by this action.
*/
protected <T extends PersistentRecord> int deleteAll (
Class<T> type, final WhereClause where, CacheInvalidator invalidator)
throws DatabaseException
{
if (invalidator instanceof ValidatingCacheInvalidator) {
((ValidatingCacheInvalidator)invalidator).validateFlushType(type); // sanity check
}
where.validateQueryType(type); // and another
DeleteClause<T> delete = new DeleteClause<T>(type, where);
final SQLBuilder builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(_ctx, delete));
builder.newQuery(delete);
return _ctx.invoke(new Modifier(invalidator) {
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
PreparedStatement stmt = builder.prepare(conn);
try {
return stmt.executeUpdate();
} finally {
JDBCUtil.close(stmt);
}
}
});
}
// make sure the given type corresponds to a concrete class
protected void requireNotComputed (Class<? extends PersistentRecord> type, String action)
throws DatabaseException
{
DepotMarshaller<?> marsh = _ctx.getMarshaller(type);
if (marsh == null) {
throw new DatabaseException("Unknown persistent type [class=" + type + "]");
}
if (marsh.getTableName() == null) {
throw new DatabaseException(
"Can't " + action + " computed entities [class=" + type + "]");
}
}
/**
* If the supplied migration has not already been run, it will be run and if it completes, we
* will note in the DepotMigrationHistory table that it has been run.
*/
protected void runMigration (DataMigration migration)
throws DatabaseException
{
// attempt to get a lock to run this migration (or detect that it has already been run)
DepotMigrationHistoryRecord record;
while (true) {
// check to see if the migration has already been completed
record = load(DepotMigrationHistoryRecord.class, migration.getIdent());
if (record != null && record.whenCompleted != null) {
return; // great, no need to do anything
}
// if no record exists at all, try to insert one and thereby obtain the migration lock
if (record == null) {
try {
record = new DepotMigrationHistoryRecord();
record.ident = migration.getIdent();
insert(record);
break; // we got the lock, break out of this loop and run the migration
} catch (DuplicateKeyException dke) {
// someone beat us to the punch, so we have to wait for them to finish
}
}
// we didn't get the lock, so wait 5 seconds and then check to see if the other process
// finished the update or failed in which case we'll try to grab the lock ourselves
try {
log.info("Waiting on migration lock for " + migration.getIdent() + ".");
Thread.sleep(5000);
} catch (InterruptedException ie) {
throw new DatabaseException("Interrupted while waiting on migration lock.");
}
}
log.info("Running data migration", "ident", migration.getIdent());
try {
// run the migration
migration.invoke();
// report to the world that we've done so
record.whenCompleted = new Timestamp(System.currentTimeMillis());
update(record);
} finally {
// clear out our migration history record if we failed to get the job done
if (record.whenCompleted == null) {
try {
delete(record);
} catch (Throwable dt) {
log.warning("Oh noez! Failed to delete history record for failed migration. " +
"All clients will loop forever waiting for the lock.",
"ident", migration.getIdent(), dt);
}
}
}
}
protected PersistenceContext _ctx;
protected List<DataMigration> _dataMigs = new ArrayList<DataMigration>();
}
@@ -1,221 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.SQLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
* Maintains a record of the persistent classes brought into the context of the associated SQL,
* i.e. any class associated with a concrete table that would appear in FROM or JOIN clauses or as
* the target of an UPDATE or an INSERT or any other place where a table abbreviation could be
* constructed.
*
* The main motivation for breaking this functionality out into its own class is to encapsulate the
* operation that throws {@link DatabaseException} as separate from the operations that throw
* {@link SQLException}. Once this class has been constructed, it may be used to create {@link
* SQLBuilder} instances without any {@link DatabaseException} worries.
*/
public class DepotTypes
{
/** A trivial instance that is accessible in places where we want the dialectal benefits of the
* SQLBuilder without really requiring per-persistent-class context. */
public static DepotTypes TRIVIAL = new DepotTypes();
/**
* Conveniently constructs a {@link DepotTypes} object given {@link QueryClause} objects, which
* are interrogated for their class definition sets through {@link SQLExpression#addClasses}.
*/
public static <T extends PersistentRecord> DepotTypes getDepotTypes (
PersistenceContext ctx, Collection<? extends QueryClause> clauses)
throws DatabaseException
{
Set<Class<? extends PersistentRecord>> classSet =
new HashSet<Class<? extends PersistentRecord>>();
for (QueryClause clause : clauses) {
if (clause != null) {
clause.addClasses(classSet);
}
}
return new DepotTypes(ctx, classSet);
}
/**
* A varargs version of {@link #getDepotTypes(PersistenceContext,Collection)}.
*/
public static <T extends PersistentRecord> DepotTypes getDepotTypes (
PersistenceContext ctx, QueryClause... clauses)
throws DatabaseException
{
return getDepotTypes(ctx, Arrays.asList(clauses));
}
/**
* Create a new DepotTypes with the given {@link PersistenceContext} and a collection of
* persistent record classes.
*/
public DepotTypes (PersistenceContext ctx, Collection<Class<? extends PersistentRecord>> others)
throws DatabaseException
{
for (Class<? extends PersistentRecord> c : others) {
addClass(ctx, c);
}
}
/**
* Create a new DepotTypes with the given {@link PersistenceContext} and the given
* persistent record.
*/
public DepotTypes (PersistenceContext ctx, Class<? extends PersistentRecord> pClass)
throws DatabaseException
{
addClass(ctx, pClass);
}
/**
* Return the full table name of the given persistent class, which must have been previously
* registered with this object.
*/
public String getTableName (Class<? extends PersistentRecord> cl)
{
return getMarshaller(cl).getTableName();
}
/**
* Return the current abbreviation by which we refer to the table associated with the given
* persistent record -- which must have been previously registered with this object. If the
* useTableAbbreviations flag is false, we return the full table name instead.
*
* @exception IllegalArgumentException thrown if the specified class is not known.
*/
public String getTableAbbreviation (Class<? extends PersistentRecord> cl)
{
if (_useTableAbbreviations) {
Integer ix = _classIx.get(cl);
if (ix == null) {
throw new IllegalArgumentException("Unknown persistence class: " + cl);
}
return "T" + (ix+1);
}
return getTableName(cl);
}
/**
* Return the associated database column of the given field of the given persistent class,
* throwing an exception if the record has not been registered with this object, or if the
* field is unknown on the record.
*
* @exception IllegalArgumentException thrown if the specified field is not part of the
* specified persistent class.
*/
public String getColumnName (Class<? extends PersistentRecord> cl, String field)
{
FieldMarshaller<?> fm = getMarshaller(cl).getFieldMarshaller(field);
if (fm == null) {
throw new IllegalArgumentException(
"Field not known on class [field=" + field + ", class=" + cl + "]");
}
return fm.getColumnName();
}
/**
* Return the {@link DepotMarshaller} associated with the given persistent class, if it's been
* registered with this object.
*
* @exception IllegalArgumentException thrown if the specified class is not known.
*/
public DepotMarshaller<?> getMarshaller (Class<? extends PersistentRecord> cl)
{
DepotMarshaller<?> marsh = _classMap.get(cl);
if (marsh == null) {
throw new IllegalArgumentException("Persistent class not known: " + cl);
}
return marsh;
}
/**
* Register a new persistent class with this object.
*/
public void addClass (PersistenceContext ctx, Class <? extends PersistentRecord> type)
throws DatabaseException
{
if (_classMap.containsKey(type)) {
return;
}
// add the class in question
DepotMarshaller<?> marsh = ctx.getMarshaller(type);
_classMap.put(type, marsh);
_classIx.put(type, _classIx.size());
// if this class is @Computed and has a shadow, add its shadow
if (marsh.getComputed() != null &&
!PersistentRecord.class.equals(marsh.getComputed().shadowOf())) {
addClass(ctx, marsh.getComputed().shadowOf());
}
}
/**
* Return the value of the useTableAbbreviations flag, which governs the behaviour when
* referencing columns during SQL construction. Normally, this flag is on, and tables are
* referenced as e.g. T1.itemId, but there are cases of weak/broken SQL where abbreviations
* may not be brought into play. In these cases we prepend the full table name.
*/
public boolean getUseTableAbbreviations ()
{
return _useTableAbbreviations;
}
/**
* Sets the value of the useTableAbbreviations flag, which governs the behaviour when
* referencing columns during SQL construction. Normally, this flag is on, and tables are
* referenced as e.g. T1.itemId, but there are cases of weak/broken SQL where abbreviations
* may not be brought into play. In these cases we prepend the full table name.
*/
public void setUseTableAbbreviations (boolean doUse)
{
_useTableAbbreviations = doUse;
}
// constructor used to create TRIVIAL
protected DepotTypes ()
{
}
/** Classes mapped to integers, used for table abbreviation indexing. */
protected Map<Class<?>, Integer> _classIx = new HashMap<Class<?>, Integer>();
/** Classes mapped to marshallers, used for table names and field lists. */
protected Map<Class<?>, DepotMarshaller<?>> _classMap =
new HashMap<Class<?>, DepotMarshaller<?>>();
/** When false, override the normal table abbreviations and return full table names instead. */
protected boolean _useTableAbbreviations = true;
}
@@ -1,33 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2001-2008 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;
/**
* Thrown when an insert or update results in a duplicate key on a column that has a uniqueness
* constraint.
*/
public class DuplicateKeyException extends DatabaseException
{
public DuplicateKeyException (String message)
{
super(message);
}
}
@@ -1,169 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.io.Serializable;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.distribution.CacheManagerPeerListener;
import net.sf.ehcache.distribution.CacheManagerPeerProvider;
import net.sf.ehcache.distribution.RMIAsynchronousCacheReplicator;
import static com.samskivert.jdbc.depot.Log.log;
/**
* An implementation of {@link CacheAdapter} for ehcache.
*/
public class EHCacheAdapter
implements CacheAdapter
{
/**
* Creates an adapter using the supplied cache manager. Note: this adapter does not shut down
* the supplied manager when it is shutdown. The caller is responsible for shutting down the
* cache manager when it knows that Depot and any other clients no longer need it.
*/
public EHCacheAdapter (CacheManager cachemgr)
{
_cachemgr = cachemgr;
CacheManagerPeerListener listener = _cachemgr.getCachePeerListener();
CacheManagerPeerProvider provider = _cachemgr.getCachePeerProvider();
if ((provider != null) != (listener != null)) {
// we want either both listener and provider, or neither
log.warning("EHCache misconfigured, distributed mode disabled [listener =" +
listener + ", provider=" + provider);
_distributed = false;
} else {
_distributed = (listener != null);
}
}
public <T> CacheBin<T> getCache (String id)
{
return new EHCacheBin<T>(id);
}
/**
* The main ehcache-bridging class, a {@link CacheBin} interface against {@link Cache}.
*/
protected class EHCacheBin<T> implements CacheBin<T>
{
// from CacheBin
public CachedValue<T> lookup (Serializable key)
{
Element hit = _cache.get(key);
if (hit == null) {
return null;
}
Serializable rawValue = hit.getValue();
@SuppressWarnings("unchecked")
final T value = (T) (rawValue instanceof NullValue ? null : rawValue);
return new CachedValue<T>() {
public T getValue () {
return value;
}
@Override public String toString () {
return String.valueOf(value);
}
};
}
// from CacheBin
public void store (Serializable key, T value)
{
_cache.put(new Element(key, value != null ? value : NULL));
}
// from CacheBin
public void remove (Serializable key)
{
_cache.remove(key);
}
// from CacheBin
public Iterable<Serializable> enumerateKeys ()
{
@SuppressWarnings("unchecked") Iterable<Serializable> keys = _cache.getKeys();
return keys;
}
protected EHCacheBin (String id)
{
synchronized (_cachemgr) {
_cache = _cachemgr.getCache(id);
if (_cache == null) {
// create the cache programatically with reasonable settings
// TODO: we will eventually need this to be configurable in .properties
_cache = new Cache(id,
1000, // keep 1000 elements in RAM
true, // overflow the rest to disk
false, // don't keep records around eternally
300, // keep them for 5 minutes after they're created
20); // or 20 seconds after last access
if (_distributed) {
// a programatically created cache has to have its replicator event
// listener programatically added.
_cache.getCacheEventNotificationService().registerListener(
new RMIAsynchronousCacheReplicator(true, true, true, true, 1000));
}
_cachemgr.addCache(_cache);
}
}
}
protected Cache _cache;
}
// from CacheAdapter
public void shutdown ()
{
}
protected boolean _distributed;
protected CacheManager _cachemgr;
// this is just for convenience and memory use; we don't rely on pointer equality anywhere
protected static Serializable NULL = new NullValue() {};
/** A class to represent an explicitly Serializable concept of null for EHCache. */
protected static class NullValue implements Serializable
{
@Override public String toString ()
{
return "<EHCache Null>";
}
@Override public boolean equals (Object other)
{
return other != null && other.getClass().equals(NullValue.class);
}
@Override public int hashCode ()
{
return 1;
}
}
}
@@ -1,477 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.jdbc.depot.annotation.Column;
import com.samskivert.jdbc.depot.annotation.Computed;
import com.samskivert.jdbc.depot.annotation.GeneratedValue;
import com.samskivert.util.StringUtil;
/**
* Handles the marshalling and unmarshalling of a particular field of a persistent object.
*
* @see DepotMarshaller
*/
public abstract class FieldMarshaller<T>
{
/**
* Creates and returns a field marshaller for the specified field. Throws an exception if the
* field in question cannot be marshalled.
*/
public static FieldMarshaller<?> createMarshaller (Field field)
{
Class<?> ftype = field.getType();
FieldMarshaller<?> marshaller;
// primitive types
if (ftype.equals(Boolean.TYPE)) {
marshaller = new BooleanMarshaller();
} else if (ftype.equals(Byte.TYPE)) {
marshaller = new ByteMarshaller();
} else if (ftype.equals(Short.TYPE)) {
marshaller = new ShortMarshaller();
} else if (ftype.equals(Integer.TYPE)) {
marshaller = new IntMarshaller();
} else if (ftype.equals(Long.TYPE)) {
marshaller = new LongMarshaller();
} else if (ftype.equals(Float.TYPE)) {
marshaller = new FloatMarshaller();
} else if (ftype.equals(Double.TYPE)) {
marshaller = new DoubleMarshaller();
// "natural" types
} else if (ftype.equals(Byte.class) ||
ftype.equals(Short.class) ||
ftype.equals(Integer.class) ||
ftype.equals(Long.class) ||
ftype.equals(Float.class) ||
ftype.equals(Double.class) ||
ftype.equals(String.class)) {
marshaller = new ObjectMarshaller();
// some primitive array types
} else if (ftype.equals(byte[].class)) {
marshaller = new ByteArrayMarshaller();
} else if (ftype.equals(int[].class)) {
marshaller = new IntArrayMarshaller();
// SQL types
} else if (ftype.equals(Date.class) ||
ftype.equals(Time.class) ||
ftype.equals(Timestamp.class) ||
ftype.equals(Blob.class) ||
ftype.equals(Clob.class)) {
marshaller = new ObjectMarshaller();
// special Enum types
} else if (ByteEnum.class.isAssignableFrom(ftype)) {
marshaller = new ByteEnumMarshaller(ftype);
} else {
throw new IllegalArgumentException(
"Cannot marshall field of type '" + ftype.getName() + "'.");
}
marshaller.create(field);
return marshaller;
}
/**
* Initializes this field marshaller with a SQL builder which it uses to construct its column
* definition according to the appropriate database dialect.
*/
public void init (SQLBuilder builder)
throws DatabaseException
{
_columnDefinition = builder.buildColumnDefinition(this);
}
/**
* Returns the {@link Field} handled by this marshaller.
*/
public Field getField ()
{
return _field;
}
/**
* Returns the Computed annotation on this field, if any.
*/
public Computed getComputed ()
{
return _computed;
}
/**
* Returns the GeneratedValue annotation on this field, if any.
*/
public GeneratedValue getGeneratedValue ()
{
return _generatedValue;
}
/**
* Returns the name of the table column used to store this field.
*/
public String getColumnName ()
{
return _columnName;
}
/**
* Returns the SQL used to define this field's column.
*/
public ColumnDefinition getColumnDefinition ()
{
return _columnDefinition;
}
/**
* Reads this field from the given persistent object.
*/
public abstract T getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException;
/**
* Sets the specified column of the given prepared statement to the given value.
*/
public abstract void writeToStatement (PreparedStatement ps, int column, T value)
throws SQLException;
/**
* Reads the value of our field from the supplied persistent object and sets that value into
* the specified column of the supplied prepared statement.
*/
public void getAndWriteToStatement (PreparedStatement ps, int column, Object po)
throws SQLException, IllegalAccessException
{
writeToStatement(ps, column, getFromObject(po));
}
/**
* Reads and returns this field from the result set.
*/
public abstract T getFromSet (ResultSet rs)
throws SQLException;
/**
* Writes the given value to the given persistent value.
*/
public abstract void writeToObject (Object po, T value)
throws IllegalArgumentException, IllegalAccessException;
/**
* Reads the specified column from the supplied result set and writes it to the appropriate
* field of the persistent object.
*/
public void getAndWriteToObject (ResultSet rset, Object po)
throws SQLException, IllegalAccessException
{
writeToObject(po, getFromSet(rset));
}
protected void create (Field field)
{
_field = field;
_columnName = field.getName();
Column column = _field.getAnnotation(Column.class);
if (column != null) {
if (!StringUtil.isBlank(column.name())) {
_columnName = column.name();
}
}
_computed = field.getAnnotation(Computed.class);
if (_computed != null) {
return;
}
// figure out how we're going to generate our primary key values
_generatedValue = field.getAnnotation(GeneratedValue.class);
}
protected static class BooleanMarshaller extends FieldMarshaller<Boolean> {
@Override public Boolean getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getBoolean(po);
}
@Override public Boolean getFromSet (ResultSet rs)
throws SQLException {
return rs.getBoolean(getColumnName());
}
@Override public void writeToObject (Object po, Boolean value)
throws IllegalArgumentException, IllegalAccessException {
_field.setBoolean(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Boolean value)
throws SQLException {
ps.setBoolean(column, value);
}
}
protected static class ByteMarshaller extends FieldMarshaller<Byte> {
@Override public Byte getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getByte(po);
}
@Override public Byte getFromSet (ResultSet rs)
throws SQLException {
return rs.getByte(getColumnName());
}
@Override public void writeToObject (Object po, Byte value)
throws IllegalArgumentException, IllegalAccessException {
_field.setByte(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Byte value)
throws SQLException {
ps.setByte(column, value);
}
}
protected static class ShortMarshaller extends FieldMarshaller<Short> {
@Override public Short getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getShort(po);
}
@Override public Short getFromSet (ResultSet rs)
throws SQLException {
return rs.getShort(getColumnName());
}
@Override public void writeToObject (Object po, Short value)
throws IllegalArgumentException, IllegalAccessException {
_field.setShort(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Short value)
throws SQLException {
ps.setShort(column, value);
}
}
protected static class IntMarshaller extends FieldMarshaller<Integer> {
@Override public Integer getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getInt(po);
}
@Override public Integer getFromSet (ResultSet rs)
throws SQLException {
return rs.getInt(getColumnName());
}
@Override public void writeToObject (Object po, Integer value)
throws IllegalArgumentException, IllegalAccessException {
_field.setInt(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Integer value)
throws SQLException {
ps.setInt(column, value);
}
}
protected static class LongMarshaller extends FieldMarshaller<Long> {
@Override public Long getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getLong(po);
}
@Override public Long getFromSet (ResultSet rs)
throws SQLException {
return rs.getLong(getColumnName());
}
@Override public void writeToObject (Object po, Long value)
throws IllegalArgumentException, IllegalAccessException {
_field.setLong(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Long value)
throws SQLException {
ps.setLong(column, value);
}
}
protected static class FloatMarshaller extends FieldMarshaller<Float> {
@Override public Float getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getFloat(po);
}
@Override public Float getFromSet (ResultSet rs)
throws SQLException {
return rs.getFloat(getColumnName());
}
@Override public void writeToObject (Object po, Float value)
throws IllegalArgumentException, IllegalAccessException {
_field.setFloat(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Float value)
throws SQLException {
ps.setFloat(column, value);
}
}
protected static class DoubleMarshaller extends FieldMarshaller<Double> {
@Override public Double getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.getDouble(po);
}
@Override public Double getFromSet (ResultSet rs)
throws SQLException {
return rs.getDouble(getColumnName());
}
@Override public void writeToObject (Object po, Double value)
throws IllegalArgumentException, IllegalAccessException {
_field.setDouble(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Double value)
throws SQLException {
ps.setDouble(column, value);
}
}
protected static class ObjectMarshaller extends FieldMarshaller<Object> {
@Override public Object getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return _field.get(po);
}
@Override public Object getFromSet (ResultSet rs)
throws SQLException {
return rs.getObject(getColumnName());
}
@Override public void writeToObject (Object po, Object value)
throws IllegalArgumentException, IllegalAccessException {
_field.set(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, Object value)
throws SQLException {
ps.setObject(column, value);
}
}
protected static class ByteArrayMarshaller extends FieldMarshaller<byte[]> {
@Override public byte[] getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return (byte[]) _field.get(po);
}
@Override public byte[] getFromSet (ResultSet rs)
throws SQLException {
return rs.getBytes(getColumnName());
}
@Override public void writeToObject (Object po, byte[] value)
throws IllegalArgumentException, IllegalAccessException {
_field.set(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, byte[] value)
throws SQLException {
ps.setBytes(column, value);
}
}
protected static class IntArrayMarshaller extends FieldMarshaller<byte[]> {
@Override public byte[] getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
int[] values = (int[]) _field.get(po);
if (values == null) {
return null;
}
ByteBuffer bbuf = ByteBuffer.allocate(values.length * 4);
bbuf.asIntBuffer().put(values);
return bbuf.array();
}
@Override public byte[] getFromSet (ResultSet rs)
throws SQLException {
return (byte[]) rs.getObject(getColumnName());
}
@Override public void writeToObject (Object po, byte[] data)
throws IllegalArgumentException, IllegalAccessException {
int[] value = null;
if (data != null) {
value = new int[data.length/4];
ByteBuffer.wrap(data).asIntBuffer().get(value);
}
_field.set(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, byte[] value)
throws SQLException {
ps.setObject(column, value);
}
}
protected static class ByteEnumMarshaller extends FieldMarshaller<ByteEnum> {
public ByteEnumMarshaller (Class<?> clazz) {
try {
_factmeth = clazz.getMethod("fromByte", new Class[] { Byte.TYPE });
} catch (Exception e) {
throw new IllegalArgumentException(
"Could not locate fromByte() method on enum field " + _field.getType() + ".");
}
if (!Modifier.isPublic(_factmeth.getModifiers()) ||
!Modifier.isStatic(_factmeth.getModifiers())) {
throw new IllegalArgumentException(
_field.getType() + ".fromByte() must be public and static.");
}
}
@Override public ByteEnum getFromObject (Object po)
throws IllegalArgumentException, IllegalAccessException {
return (ByteEnum) _field.get(po);
}
@Override public ByteEnum getFromSet (ResultSet rs)
throws SQLException {
try {
return (ByteEnum) _factmeth.invoke(null, rs.getByte(getColumnName()));
} catch (SQLException se) {
throw se;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override public void writeToObject (Object po, ByteEnum value)
throws IllegalArgumentException, IllegalAccessException {
_field.set(po, value);
}
@Override public void writeToStatement (PreparedStatement ps, int column, ByteEnum value)
throws SQLException {
ps.setByte(column, value.toByte());
}
protected Method _factmeth;
}
protected Field _field;
protected String _columnName;
protected ColumnDefinition _columnDefinition;
protected Computed _computed;
protected GeneratedValue _generatedValue;
}
@@ -1,303 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.depot.clause.FieldOverride;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.clause.SelectClause;
import com.samskivert.jdbc.depot.operator.Conditionals.*;
import static com.samskivert.jdbc.depot.Log.log;
/**
* This class implements the functionality required by {@link DepotRepository#findAll}: fetch
* a collection of persistent objects using one of two included strategies.
*/
public abstract class FindAllQuery<T extends PersistentRecord>
implements Query<List<T>>
{
/**
* The two-pass collection query implementation. See {@link DepotRepository#findAll} for
* details.
*/
public static class WithCache<T extends PersistentRecord> extends FindAllQuery<T>
{
public WithCache (PersistenceContext ctx, Class<T> type,
Collection<? extends QueryClause> clauses)
throws DatabaseException
{
super(ctx, type);
if (_marsh.getComputed() != null) {
throw new IllegalArgumentException(
"This algorithm doesn't work on @Computed records.");
}
for (QueryClause clause : clauses) {
if (clause instanceof FieldOverride) {
throw new IllegalArgumentException(
"This algorithm doesn't work with FieldOverrides.");
}
}
SelectClause<T> select =
new SelectClause<T>(_type, _marsh.getPrimaryKeyFields(), clauses);
_builder = _ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, select));
_builder.newQuery(select);
}
public List<T> invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
Map<Key<T>, T> entities = new HashMap<Key<T>, T>();
List<Key<T>> allKeys = new ArrayList<Key<T>>();
Set<Key<T>> fetchKeys = new HashSet<Key<T>>();
PreparedStatement stmt = _builder.prepare(conn);
String stmtString = stmt.toString(); // for debugging
try {
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
Key<T> key = _marsh.makePrimaryKey(rs);
allKeys.add(key);
// TODO: All this cache fiddling needs to move to PersistenceContext?
CacheAdapter.CachedValue<T> hit = _ctx.cacheLookup(key);
if (hit != null) {
T value = hit.getValue();
if (value != null) {
@SuppressWarnings("unchecked") T newValue = (T) value.clone();
entities.put(key, newValue);
continue;
}
}
fetchKeys.add(key);
}
} finally {
JDBCUtil.close(stmt);
}
return loadAndResolve(conn, allKeys, fetchKeys, entities, stmtString);
}
}
/**
* The two-pass collection query implementation. See {@link DepotRepository#findAll} for
* details.
*/
public static class WithKeys<T extends PersistentRecord> extends FindAllQuery<T>
{
public WithKeys (PersistenceContext ctx, Collection<Key<T>> keys)
throws DatabaseException
{
super(ctx, keys.iterator().next().getPersistentClass());
_keys = keys;
_builder = ctx.getSQLBuilder(new DepotTypes(ctx, _type));
}
public List<T> invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
Map<Key<T>, T> entities = new HashMap<Key<T>, T>();
Set<Key<T>> fetchKeys = new HashSet<Key<T>>();
for (Key<T> key : _keys) {
// TODO: All this cache fiddling needs to move to PersistenceContext?
CacheAdapter.CachedValue<T> hit = _ctx.cacheLookup(key);
if (hit != null) {
T value = hit.getValue();
if (value != null) {
@SuppressWarnings("unchecked") T newValue = (T) value.clone();
entities.put(key, newValue);
continue;
}
}
fetchKeys.add(key);
}
return loadAndResolve(conn, _keys, fetchKeys, entities, null);
}
protected Collection<Key<T>> _keys;
}
/**
* The single-pass collection query implementation. See {@link DepotRepository#findAll} for
* details.
*/
public static class Explicitly<T extends PersistentRecord> extends FindAllQuery<T>
{
public Explicitly (PersistenceContext ctx, Class<T> type,
Collection<? extends QueryClause> clauses)
throws DatabaseException
{
super(ctx, type);
SelectClause<T> select = new SelectClause<T>(type, _marsh.getFieldNames(), clauses);
_builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, select));
_builder.newQuery(select);
}
public List<T> invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
List<T> result = new ArrayList<T>();
PreparedStatement stmt = _builder.prepare(conn);
try {
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
result.add(_marsh.createObject(rs));
}
} finally {
JDBCUtil.close(stmt);
}
return result;
}
}
public FindAllQuery (PersistenceContext ctx, Class<T> type)
throws DatabaseException
{
_ctx = ctx;
_type = type;
_marsh = _ctx.getMarshaller(type);
}
// from Query
public CacheKey getCacheKey ()
{
return null;
}
// from Query
public void updateCache (PersistenceContext ctx, List<T> result) {
if (_marsh.hasPrimaryKey()) {
for (T bit : result) {
ctx.cacheStore(_marsh.getPrimaryKey(bit), bit.clone());
}
}
}
// from Query
public List<T> transformCacheHit (CacheKey key, List<T> bits)
{
if (bits == null) {
return bits;
}
List<T> result = new ArrayList<T>();
for (T bit : bits) {
if (bit != null) {
@SuppressWarnings("unchecked") T cbit = (T) bit.clone();
result.add(cbit);
} else {
result.add(null);
}
}
return result;
}
protected List<T> loadAndResolve (Connection conn, Collection<Key<T>> allKeys,
Set<Key<T>> fetchKeys, Map<Key<T>, T> entities,
String origStmt)
throws SQLException
{
// if we're fetching a huge number of records, we have to do it in multiple queries
if (fetchKeys.size() > In.MAX_KEYS) {
int keyCount = fetchKeys.size();
do {
Set<Key<T>> keys = new HashSet<Key<T>>();
Iterator<Key<T>> iter = fetchKeys.iterator();
for (int ii = 0; ii < Math.min(keyCount, In.MAX_KEYS); ii++) {
keys.add(iter.next());
iter.remove();
}
keyCount -= keys.size();
loadRecords(conn, keys, entities, origStmt);
} while (keyCount > 0);
} else if (fetchKeys.size() > 0) {
loadRecords(conn, fetchKeys, entities, origStmt);
}
List<T> result = new ArrayList<T>();
for (Key<T> key : allKeys) {
T value = entities.get(key);
if (value != null) {
result.add(value);
}
}
return result;
}
protected void loadRecords (Connection conn, Set<Key<T>> keys, Map<Key<T>, T> entities,
String origStmt)
throws SQLException
{
_builder.newQuery(new SelectClause<T>(_type, _marsh.getFieldNames(),
new KeySet<T>(_type, keys)));
PreparedStatement stmt = _builder.prepare(conn);
try {
Set<Key<T>> got = new HashSet<Key<T>>();
ResultSet rs = stmt.executeQuery();
int cnt = 0, dups = 0;
while (rs.next()) {
T obj = _marsh.createObject(rs);
Key<T> key = _marsh.getPrimaryKey(obj);
if (entities.put(key, obj) != null) {
dups++;
}
got.add(key);
cnt++;
}
// if we get more results than we planned, or if we're doing a two-phase query and got
// fewer, then complain
if (cnt > keys.size() || (origStmt != null && cnt < keys.size())) {
log.warning("Row count mismatch in second pass", "origQuery", origStmt,
"wanted", keys, "got", got, "dups", dups, new Exception());
}
} finally {
JDBCUtil.close(stmt);
}
}
protected PersistenceContext _ctx;
protected SQLBuilder _builder;
protected DepotMarshaller<T> _marsh;
protected Class<T> _type;
}
@@ -1,110 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.ResultSet;
import java.sql.SQLException;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.clause.SelectClause;
/**
* The implementation of {@link DepotRepository#load} functionality.
*/
public class FindOneQuery<T extends PersistentRecord>
implements Query<T>
{
public FindOneQuery (PersistenceContext ctx, Class<T> type, QueryClause[] clauses)
throws DatabaseException
{
_marsh = ctx.getMarshaller(type);
_select = new SelectClause<T>(type, _marsh.getFieldNames(), clauses);
WhereClause where = _select.getWhereClause();
if (where != null) {
_select.getWhereClause().validateQueryType(type); // sanity check
}
_builder = ctx.getSQLBuilder(DepotTypes.getDepotTypes(ctx, _select));
_builder.newQuery(_select);
}
// from Query
public CacheKey getCacheKey ()
{
WhereClause where = _select.getWhereClause();
if (where != null && where instanceof CacheKey) {
return (CacheKey) where;
}
return null;
}
// from Query
public T invoke (Connection conn, DatabaseLiaison liaison) throws SQLException
{
PreparedStatement stmt = _builder.prepare(conn);
try {
T result = null;
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
result = _marsh.createObject(rs);
}
// TODO: if (rs.next()) issue warning?
rs.close();
return result;
} finally {
JDBCUtil.close(stmt);
}
}
// from Query
public void updateCache (PersistenceContext ctx, T result)
{
CacheKey key = getCacheKey();
if (key == null) {
// no row-specific cache key was given
if (result == null || !_marsh.hasPrimaryKey()) {
return;
}
// if we can, create a key from what was actually returned
key = _marsh.getPrimaryKey(result);
}
ctx.cacheStore(key, (result != null) ? result.clone() : null);
}
// from Query
public T transformCacheHit (CacheKey key, T value)
{
if (value == null) {
return null;
}
// we do not want to return a reference to the actual cached entity so we clone it
@SuppressWarnings("unchecked") T cvalue = (T) value.clone();
return cvalue;
}
protected DepotMarshaller<T> _marsh;
protected SelectClause<T> _select;
protected SQLBuilder _builder;
}
@@ -1,65 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.SQLException;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.depot.annotation.GeneratedValue;
/**
* Generates primary keys using an identity column.
*/
public class IdentityValueGenerator extends ValueGenerator
{
public IdentityValueGenerator (GeneratedValue gv, DepotMarshaller<?> dm, FieldMarshaller<?> fm)
{
super(gv, dm, fm);
}
@Override // from ValueGenerator
public boolean isPostFactum ()
{
return true;
}
@Override // from ValueGenerator
public void create (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
liaison.createGenerator(conn, _dm.getTableName(), _fm.getColumnName(), _initialValue);
}
@Override // from ValueGenerator
public int nextGeneratedValue (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
return liaison.lastInsertedId(conn, _dm.getTableName(), _fm.getColumnName());
}
@Override // from ValueGenerator
public void delete (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
liaison.deleteGenerator(conn, _dm.getTableName(), _fm.getColumnName());
}
}
-246
View File
@@ -1,246 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.util.StringUtil;
/**
* A special form of {@link WhereClause} that uniquely specifies a single database row and
* thus also a single persistent object. Because it implements both {@link CacheKey} and
* {@link CacheInvalidator} it also uniquely indexes into the cache and knows how to invalidate
* itself upon modification. This class is created by many {@link DepotMarshaller} methods as
* a convenience, and may also be instantiated explicitly.
*/
public class Key<T extends PersistentRecord> extends WhereClause
implements SQLExpression, CacheKey, ValidatingCacheInvalidator, Serializable
{
/** Handles the matching of the key columns to its bound values. This is needed so that we can
* combine a buncy of keys into a {@link KeySet}. */
public static class Expression<U extends PersistentRecord> implements SQLExpression
{
public Expression (Class<U> pClass, List<Comparable<?>> values) {
_pClass = pClass;
_values = values;
}
public Class<U> getPersistentClass () {
return _pClass;
}
public List<Comparable<?>> getValues () {
return _values;
}
public void accept (ExpressionVisitor builder) {
builder.visit(this);
}
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet) {
classSet.add(getPersistentClass());
}
protected Class<U> _pClass;
protected List<Comparable<?>> _values;
}
/**
* Constructs a new single-column {@code Key} with the given value.
*/
public Key (Class<T> pClass, String ix, Comparable<?> val)
{
this(pClass, new String[] { ix }, new Comparable[] { val });
}
/**
* Constructs a new two-column {@code Key} with the given values.
*/
public Key (Class<T> pClass, String ix1, Comparable<?> val1,
String ix2, Comparable<?> val2)
{
this(pClass, new String[] { ix1, ix2 }, new Comparable[] { val1, val2 });
}
/**
* Constructs a new three-column {@code Key} with the given values.
*/
public Key (Class<T> pClass, String ix1, Comparable<?> val1,
String ix2, Comparable<?> val2, String ix3, Comparable<?> val3)
{
this(pClass, new String[] { ix1, ix2, ix3 }, new Comparable[] { val1, val2, val3 });
}
/**
* Constructs a new multi-column {@code Key} with the given values.
*/
public Key (Class<T> pClass, String[] fields, Comparable<?>[] values)
{
if (fields.length != values.length) {
throw new IllegalArgumentException("Field and Value arrays must be of equal length.");
}
// keep this for posterity
_pClass = pClass;
// build a local map of field name -> field value
Map<String, Comparable<?>> map = new HashMap<String, Comparable<?>>();
for (int i = 0; i < fields.length; i ++) {
map.put(fields[i], values[i]);
}
// look up the cached primary key fields for this object
String[] keyFields = KeyUtil.getKeyFields(pClass);
// now extract the values in field order and ensure none are extra or missing
_values = new ArrayList<Comparable<?>>();
for (int ii = 0; ii < keyFields.length; ii++) {
Comparable<?> nugget = map.remove(keyFields[ii]);
if (nugget == null) {
// make sure we were provided with a value for this primary key field
throw new IllegalArgumentException("Missing value for key field: " + keyFields[ii]);
}
if (nugget instanceof Serializable) {
_values.add(nugget);
continue;
}
throw new IllegalArgumentException(
"Non-serializable argument [key=" + keyFields[ii] + ", arg=" + nugget + "]");
}
// finally make sure we were not given any fields that are not in fact primary key fields
if (map.size() > 0) {
throw new IllegalArgumentException(
"Non-key columns given: " + StringUtil.join(map.keySet().toArray(), ", "));
}
}
/**
* Returns the persistent class for which we represent a key.
*/
public Class<T> getPersistentClass ()
{
return _pClass;
}
/**
* Returns the values bound to this key.
*/
public List<Comparable<?>> getValues ()
{
return _values;
}
// from WhereClause
public SQLExpression getWhereExpression ()
{
return new Expression<T>(_pClass, _values);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from CacheKey
public String getCacheId ()
{
return _pClass.getName();
}
// from CacheKey
public Serializable getCacheKey ()
{
return _values;
}
// from ValidatingCacheInvalidator
public void validateFlushType (Class<?> pClass)
{
if (!pClass.equals(_pClass)) {
throw new IllegalArgumentException(
"Class mismatch between persistent record and cache invalidator " +
"[record=" + pClass.getSimpleName() + ", invtype=" + _pClass.getSimpleName() + "].");
}
}
// from CacheInvalidator
public void invalidate (PersistenceContext ctx)
{
ctx.cacheInvalidate(this);
}
@Override // from WhereClause
public void validateQueryType (Class<?> pClass)
{
super.validateQueryType(pClass);
validateTypesMatch(pClass, _pClass);
}
@Override
public boolean equals (Object obj)
{
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return _values.equals(((Key<?>) obj)._values);
}
@Override
public int hashCode ()
{
return _values.hashCode();
}
@Override
public String toString ()
{
StringBuilder builder = new StringBuilder(_pClass.getSimpleName());
builder.append("(");
String[] keyFields = KeyUtil.getKeyFields(_pClass);
for (int ii = 0; ii < keyFields.length; ii ++) {
if (ii > 0) {
builder.append(", ");
}
builder.append(keyFields[ii]).append("=").append(_values.get(ii));
}
builder.append(")");
return builder.toString();
}
/** The persistent record type for which we are a key. */
protected final Class<T> _pClass;
/** The expression that identifies our row. */
protected final ArrayList<Comparable<?>> _values; // must declare as ArrayList for Serializable
}
@@ -1,160 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2001-2008 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.util.Collection;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.LiteralExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.operator.Conditionals;
import com.samskivert.jdbc.depot.operator.Logic;
/**
* Contains a set of primary keys that match a set of persistent records. This is used internally
* in Depot when decomposing queries into two parts: first a query for the primary keys that
* identify the records that match a free-form query and then another query that operates on the
* previously identified keys. The keys obtained in the first query are used to create a KeySet and
* modifications and deletons using this set will automatically flush the appropriate records from
* the cache.
*/
public class KeySet<T extends PersistentRecord> extends WhereClause
implements SQLExpression, ValidatingCacheInvalidator
{
/**
* Creates a set from the supplied primary keys.
*/
public KeySet (Class<T> pClass, Collection<Key<T>> keys)
{
_pClass = pClass;
_keys = keys;
String[] keyFields = KeyUtil.getKeyFields(pClass);
if (keys.size() == 0) {
_condition = new LiteralExp("false");
} else if (keyFields.length == 1) {
// TODO: remove when we update to 1.6 and change Postgres In handling
if (keys.size() > Conditionals.In.MAX_KEYS) {
throw new IllegalArgumentException("Cannot create where clause for more than " +
Conditionals.In.MAX_KEYS + " at a time.");
}
// Single-column keys result in the compact IN(keyVal1, keyVal2, ...)
Comparable<?> first = keys.iterator().next().getValues().get(0);
Comparable<?>[] keyFieldValues;
if (first instanceof Integer) {
keyFieldValues = new Integer[keys.size()];
} else if (first instanceof Integer) {
keyFieldValues = new String[keys.size()];
} else {
keyFieldValues = new Comparable<?>[keys.size()];
}
int ii = 0;
for (Key<T> key : keys) {
keyFieldValues[ii++] = key.getValues().get(0);
}
_condition = new Conditionals.In(pClass, keyFields[0], keyFieldValues);
} else {
// TODO: is there a maximum size of an or query? 32768?
// Multi-column keys result in OR'd AND's, of unknown efficiency (TODO check).
SQLExpression[] keyArray = new SQLExpression[keys.size()];
int ii = 0;
for (Key<T> key : keys) {
keyArray[ii++] = key.getWhereExpression();
}
_condition = new Logic.Or(keyArray);
}
}
// from WhereClause
public SQLExpression getWhereExpression ()
{
return _condition;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from ValidatingCacheInvalidator
public void validateFlushType (Class<?> pClass)
{
if (!pClass.equals(_pClass)) {
throw new IllegalArgumentException(
"Class mismatch between persistent record and cache invalidator " +
"[record=" + pClass.getSimpleName() + ", invtype=" + _pClass.getSimpleName() + "].");
}
}
// from CacheInvalidator
public void invalidate (PersistenceContext ctx)
{
for (Key<T> key : _keys) {
ctx.cacheInvalidate(key);
}
}
@Override // from WhereClause
public void validateQueryType (Class<?> pClass)
{
super.validateQueryType(pClass);
validateTypesMatch(pClass, _pClass);
}
@Override
public boolean equals (Object obj)
{
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return _condition.equals(((KeySet<?>) obj)._condition);
}
@Override
public int hashCode ()
{
return _condition.hashCode();
}
@Override
public String toString ()
{
return _keys.toString();
}
protected Class<T> _pClass;
protected Collection<Key<T>> _keys;
protected SQLExpression _condition;
}
@@ -1,59 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2001-2008 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.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.samskivert.jdbc.depot.annotation.Id;
/**
* Simple utility methods used by {@link Key} and {@link KeySet}.
*/
public class KeyUtil
{
/**
* Returns an array containing the names of the primary key fields for the supplied persistent
* class. The values are introspected and cached for the lifetime of the VM.
*/
public static String[] getKeyFields (Class<?> pClass)
{
String[] fields = _keyFields.get(pClass);
if (fields == null) {
List<String> kflist = new ArrayList<String>();
for (Field field : pClass.getFields()) {
// look for @Id fields
if (field.getAnnotation(Id.class) != null) {
kflist.add(field.getName());
}
}
_keyFields.put(pClass, fields = kflist.toArray(new String[kflist.size()]));
}
return fields;
}
/** A (never expiring) cache of primary key field names for all persistent classes (of which
* there are merely dozens, so we don't need to worry about expiring). */
protected static Map<Class<?>,String[]> _keyFields = new HashMap<Class<?>,String[]>();
}
@@ -1,32 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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 com.samskivert.util.Logger;
/**
* Contains a reference to the log object used by this package.
*/
public class Log
{
/** We dispatch our log messages through this logger. */
public static Logger log = Logger.getLogger("com.samskivert.jdbc.depot");
}
@@ -1,144 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.SQLException;
import java.sql.Statement;
import com.samskivert.jdbc.DatabaseLiaison;
/**
* Encapsulates a modification of persistent objects.
*/
public abstract class Modifier implements Operation<Integer>
{
/**
* A simple modifier that executes a single SQL statement. No cache flushing is done as a
* result of this operation.
*/
public abstract static class Simple extends Modifier
{
public Simple () {
super(null);
}
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
Statement stmt = conn.createStatement();
try {
return stmt.executeUpdate(createQuery(liaison));
} finally {
stmt.close();
}
}
protected abstract String createQuery (DatabaseLiaison liaison);
}
/**
* A convenience modifier that can perform cache updates in addition to invalidation:
* - Before {@link #invoke}, the {@link CacheInvalidator} is run, if given.
* - After {@link #invoke}, the cache is updated with the modified object,
* presuming both _key and _result are non-null. These variables may be set or modified
* during execution in addition to being supplied to the constructor.
*/
public static abstract class CachingModifier<T extends PersistentRecord> extends Modifier
{
/**
* Construct a new CachingModifier with the given result, cache key, and invalidator,
* all of which are optional, and may also be set during execution.
*/
protected CachingModifier (T result, CacheKey key, CacheInvalidator invalidator)
{
super(invalidator);
_result = result;
_key = key;
}
/**
* Update this {@link CachingModifier}'s cache key, e.g. during insertion when a
* persistent object first receives a generated key.
*/
protected void updateKey (CacheKey key)
{
if (key != null) {
_key = key;
}
}
@Override // from Modifier
public void cacheUpdate (PersistenceContext ctx)
{
super.cacheUpdate(ctx);
// if we have both a key and a record, cache
if (_key != null && _result != null) {
ctx.cacheStore(_key, _result.clone());
}
}
protected CacheKey _key;
protected T _result;
}
/**
* Overriden to perform the actual database modifications represented by this object; should
* return the number of modified rows.
*/
public abstract Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException;
/**
* Constructs a {@link Modifier} without a cache invalidator.
*/
public Modifier ()
{
this(null);
}
/**
* Constructs a {@link Modifier} with the given cache invalidator.
*/
public Modifier (CacheInvalidator invalidator)
{
_invalidator = invalidator;
}
/**
* Do any cache invalidation needed for this modification. This method is called just
* before the database statement is executed.
*/
public void cacheInvalidation (PersistenceContext ctx)
{
if (_invalidator != null) {
_invalidator.invalidate(ctx);
}
}
/**
* Do any cache updates needed for this modification. This method is called just after
* the successful execution of the database statement.
*/
public void cacheUpdate (PersistenceContext ctx)
{
}
protected CacheInvalidator _invalidator;
}
@@ -1,155 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.util.Collection;
import java.util.HashMap;
import java.util.Map;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
* A special form of {@link Where} clause that specifies an explicit range of database rows. It
* does not implement {@link CacheKey} but it does implement {@link CacheInvalidator} which means
* it can be sent into e.g. {@link DepotRepository#deleteAll} and have it clean up after itself.
*/
public class MultiKey<T extends PersistentRecord> extends WhereClause
implements ValidatingCacheInvalidator
{
/**
* Constructs a new single-column {@code MultiKey} with the given value range.
*/
public MultiKey (Class<T> pClass, String ix, Comparable<?>... val)
{
this(pClass, new String[0], new Comparable[0], ix, val);
}
/**
* Constructs a new two-column {@code MultiKey} with the given value range.
*/
public MultiKey (Class<T> pClass, String ix1, Comparable<?> val1, String ix2,
Comparable<?>... val2)
{
this(pClass, new String[] { ix1 }, new Comparable[] { val1 }, ix2, val2);
}
/**
* Constructs a new three-column {@code MultiKey} with the given value range.
*/
public MultiKey (Class<T> pClass, String ix1, Comparable<?> val1,
String ix2, Comparable<?> val2, String ix3, Comparable<?>... val3)
{
this(pClass, new String[] { ix1, ix2 }, new Comparable[] { val1, val2 }, ix3, val3);
}
/**
* Constructs a new multi-column {@code MultiKey} with the given value range.
* See {@link Key#Key(Class,String[],Comparable[])} for somewhat relevant comments.
*/
public MultiKey (Class<T> pClass, String[] sFields, Comparable<?>[] sValues,
String mField, Comparable<?>[] mValues)
{
if (sFields.length != sValues.length) {
throw new IllegalArgumentException(
"Key field and values arrays must be of equal length.");
}
_pClass = pClass;
_mField = mField;
_mValues = mValues;
_map = new HashMap<String, Comparable<?>>();
for (int i = 0; i < sFields.length; i ++) {
_map.put(sFields[i], sValues[i]);
}
}
public Class<T> getPersistentClass ()
{
return _pClass;
}
public Map<String, Comparable<?>> getSingleFieldsMap ()
{
return _map;
}
public String getMultiField ()
{
return _mField;
}
public Comparable<?>[] getMultiValues ()
{
return _mValues;
}
// from WhereClause
public SQLExpression getWhereExpression ()
{
throw new RuntimeException("Not used");
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
// nothing to add
}
// from ValidatingCacheInvalidator
public void validateFlushType (Class<?> pClass)
{
if (!pClass.equals(_pClass)) {
throw new IllegalArgumentException(
"Class mismatch between persistent record and cache invalidator " +
"[record=" + pClass.getSimpleName() +
", invtype=" + _pClass.getSimpleName() + "].");
}
}
// from CacheInvalidator
public void invalidate (PersistenceContext ctx)
{
HashMap<String, Comparable<?>> newMap = new HashMap<String, Comparable<?>>(_map);
for (int i = 0; i < _mValues.length; i ++) {
newMap.put(_mField, _mValues[i]);
ctx.cacheInvalidate(new SimpleCacheKey(_pClass, newMap));
}
}
@Override // from WhereClause
public void validateQueryType (Class<?> pClass)
{
super.validateQueryType(pClass);
validateTypesMatch(pClass, _pClass);
}
protected String _mField;
protected Comparable<?>[] _mValues;
protected Class<T> _pClass;
protected HashMap<String, Comparable<?>> _map;
}
@@ -1,276 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne, Pär Winzell
//
// 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.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Set;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.depot.FieldMarshaller.BooleanMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ByteArrayMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ByteEnumMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ByteMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.DoubleMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.FloatMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.IntArrayMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.IntMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.LongMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ObjectMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ShortMarshaller;
import com.samskivert.jdbc.depot.annotation.FullTextIndex;
import com.samskivert.jdbc.depot.clause.DeleteClause;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.EpochSeconds;
import com.samskivert.jdbc.depot.operator.Conditionals.FullTextMatch;
import static com.samskivert.Log.log;
public class MySQLBuilder
extends SQLBuilder
{
public class MSBuildVisitor extends BuildVisitor
{
@Override public void visit (FullTextMatch match)
{
_builder.append("match(");
Class<? extends PersistentRecord> pClass = match.getPersistentRecord();
String[] fields =
_types.getMarshaller(pClass).getFullTextIndex(match.getName()).fields();
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
_builder.append(", ");
}
new ColumnExp(pClass, fields[ii]).accept(this);
}
_builder.append(") against (? in boolean mode)");
}
@Override public void visit (DeleteClause<? extends PersistentRecord> deleteClause)
{
_builder.append("delete from ");
appendTableName(deleteClause.getPersistentClass());
_builder.append(" ");
// MySQL can't do DELETE FROM SomeTable AS T1, so we turn off abbreviations briefly.
boolean savedFlag = _types.getUseTableAbbreviations();
_types.setUseTableAbbreviations(false);
try {
deleteClause.getWhereClause().accept(this);
} finally {
_types.setUseTableAbbreviations(savedFlag);
}
}
public void visit (EpochSeconds epochSeconds)
{
_builder.append("unix_timestamp(");
epochSeconds.getArgument().accept(this);
_builder.append(")");
}
protected MSBuildVisitor (DepotTypes types)
{
super(types);
}
@Override protected void appendTableName (Class<? extends PersistentRecord> type)
{
_builder.append(_types.getTableName(type));
}
@Override protected void appendTableAbbreviation (Class<? extends PersistentRecord> type)
{
_builder.append(_types.getTableAbbreviation(type));
}
@Override protected void appendIdentifier (String field)
{
_builder.append(field);
}
}
public class MSBindVisitor extends BindVisitor
{
protected MSBindVisitor (DepotTypes types, Connection conn, PreparedStatement stmt) {
super(types, conn, stmt);
}
@Override public void visit (FullTextMatch match) {
try {
_stmt.setString(_argIdx++, match.getQuery());
} catch (SQLException sqe) {
throw new DatabaseException("Failed to configure full-text match column " +
"[idx=" + (_argIdx-1) + ", match=" + match + "]", sqe);
}
}
}
public MySQLBuilder (DepotTypes types)
{
super(types);
}
@Override
public void getFtsIndexes (
Iterable<String> columns, Iterable<String> indexes, Set<String> target)
{
for (String index : indexes) {
if (index.startsWith("ftsIx_")) {
target.add(index.substring("ftsIx_".length()));
}
}
}
@Override
public <T extends PersistentRecord> boolean addFullTextSearch (
Connection conn, DepotMarshaller<T> marshaller, FullTextIndex fts)
throws SQLException
{
Class<T> pClass = marshaller.getPersistentClass();
StringBuilder update = new StringBuilder("ALTER TABLE ").
append(marshaller.getTableName()).append(" ADD FULLTEXT INDEX ftsIx_").
append(fts.name()).append(" (");
String[] fields = fts.fields();
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
update.append(", ");
}
update.append(_types.getColumnName(pClass, fields[ii]));
}
update.append(")");
Statement stmt = conn.createStatement();
try {
log.info("Adding full-text search index: ftsIx_" + fts.name());
stmt.executeUpdate(update.toString());
} finally {
JDBCUtil.close(stmt);
}
return true;
}
@Override
public boolean isPrivateColumn (String column)
{
// The MySQL builder does not yet have any private columns.
return false;
}
@Override
protected String getBooleanDefault ()
{
return "0";
}
@Override
protected BuildVisitor getBuildVisitor ()
{
return new MSBuildVisitor(_types);
}
@Override
protected BindVisitor getBindVisitor (Connection conn, PreparedStatement stmt)
{
return new MSBindVisitor(_types, conn, stmt);
}
@Override
protected <T> String getColumnType (FieldMarshaller<?> fm, int length)
{
if (fm instanceof ByteMarshaller) {
return "TINYINT";
} else if (fm instanceof ShortMarshaller) {
return "SMALLINT";
} else if (fm instanceof IntMarshaller) {
return "INTEGER";
} else if (fm instanceof LongMarshaller) {
return "BIGINT";
} else if (fm instanceof FloatMarshaller) {
return "FLOAT";
} else if (fm instanceof DoubleMarshaller) {
return "DOUBLE";
} else if (fm instanceof ObjectMarshaller) {
Class<?> ftype = fm.getField().getType();
if (ftype.equals(Byte.class)) {
return "SMALLINT";
} else if (ftype.equals(Short.class)) {
return "SMALLINT";
} else if (ftype.equals(Integer.class)) {
return "INTEGER";
} else if (ftype.equals(Long.class)) {
return "BIGINT";
} else if (ftype.equals(Float.class)) {
return "FLOAT";
} else if (ftype.equals(Double.class)) {
return "DOUBLE";
} else if (ftype.equals(String.class)) {
if (length < (1 << 15)) {
return "VARCHAR(" + length + ")";
}
return "TEXT";
} else if (ftype.equals(Date.class)) {
return "DATE";
} else if (ftype.equals(Time.class)) {
return "DATETIME";
} else if (ftype.equals(Timestamp.class)) {
return "DATETIME";
} else if (ftype.equals(Blob.class)) {
return "BYTEA";
} else if (ftype.equals(Clob.class)) {
return "TEXT";
} else {
throw new IllegalArgumentException(
"Don't know how to create SQL for " + ftype + ".");
}
} else if (fm instanceof ByteArrayMarshaller ||
fm instanceof IntArrayMarshaller) {
if (fm instanceof IntArrayMarshaller) {
length *= 4;
}
// semi-arbitrarily use VARBINARY() up to 32767
if (length < (1 << 15)) {
return "VARBINARY(" + length + ")";
}
// use BLOB to 65535
if (length < (1 << 16)) {
return "BLOB";
}
if (length < (1 << 24)) {
return "MEDIUMBLOB";
}
return "LONGBLOB";
} else if (fm instanceof ByteEnumMarshaller) {
return "TINYINT";
} else if (fm instanceof BooleanMarshaller) {
return "TINYINT";
} else {
throw new IllegalArgumentException("Unknown field marshaller type: " + fm.getClass());
}
}
}
@@ -1,38 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2001-2008 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.SQLException;
import com.samskivert.jdbc.DatabaseLiaison;
/**
* An abstraction that encompasses both {@link Query} and {@link Modifier} operations.
*/
public interface Operation<T>
{
/**
* Performs the actual JDBC interactions associated with this operation.
*/
public T invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException;
}
@@ -1,607 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLException;
import com.samskivert.io.PersistenceException;
import com.samskivert.util.StringUtil;
import com.samskivert.jdbc.ConnectionProvider;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.LiaisonRegistry;
import com.samskivert.jdbc.MySQLLiaison;
import com.samskivert.jdbc.PostgreSQLLiaison;
import com.samskivert.jdbc.depot.annotation.TableGenerator;
import static com.samskivert.jdbc.depot.Log.log;
/**
* Defines a scope in which global annotations are shared.
*/
public class PersistenceContext
{
/** Allow toggling of query logging and other debug output via a system property. */
public static final boolean DEBUG = Boolean.getBoolean("com.samskivert.jdbc.depot.debug");
/** Map {@link TableGenerator} instances by name. */
public HashMap<String, TableGenerator> tableGenerators = new HashMap<String, TableGenerator>();
/**
* A cache listener is notified when cache entries change. Its purpose is typically to do
* further invalidation of dependent entries in other caches.
*/
public static interface CacheListener<T>
{
/**
* The given entry (which is never null) has just been evicted from the cache slot
* indicated by the given key.
*
* This method is most commonly used to trigger custom cache invalidation of records that
* depend on the one that was just invalidated.
*/
public void entryInvalidated (CacheKey key, T oldEntry);
/**
* The given entry, which may be an explicit null, has just been placed into the cache
* under the given key. The previous cache entry, if any, is also supplied.
*
* This method is most likely used by repositories to index entries by attribute for quick
* cache invalidation when brute force is unrealistically time consuming.
*/
public void entryCached (CacheKey key, T newEntry, T oldEntry);
}
/**
* The callback for {@link #cacheTraverse}; this is called for each entry in a given cache.
*/
public static interface CacheTraverser<T extends Serializable>
{
/**
* Performs whatever cache-related tasks need doing for this cache entry. This method
* is called for each cache entry in a full-cache enumeration.
*/
public void visitCacheEntry (
PersistenceContext ctx, String cacheId, Serializable key, T record);
}
/**
* A simple implementation of {@link CacheTraverser} that selectively deletes entries in
* a cache depending on the return value of {@link #testForEviction}.
*/
public static abstract class CacheEvictionFilter<T extends Serializable>
implements CacheTraverser<T>
{
// from CacheTraverser
public void visitCacheEntry (
PersistenceContext ctx, String cacheId, Serializable key, T record)
{
if (testForEviction(key, record)) {
ctx.cacheInvalidate(cacheId, key);
}
}
/**
* Decides whether or not this entry should be evicted and returns true if yes, false if
* no.
*/
protected abstract boolean testForEviction (Serializable key, T record);
}
/**
* Returns the cache adapter used by this context or null if caching is disabled.
*/
public CacheAdapter getCacheAdapter ()
{
return _cache;
}
/**
* Creates an uninitialized persistence context. {@link #init} must later be called on this
* context to prepare it for operation.
*/
public PersistenceContext ()
{
}
/**
* Creates and initializes a persistence context. See {@link #init}.
*/
public PersistenceContext (String ident, ConnectionProvider conprov, CacheAdapter adapter)
{
init(ident, conprov, adapter);
}
/**
* Initializes this context with its connection provider and cache adapter.
*
* @param ident the identifier to provide to the connection provider when requesting a
* connection.
* @param conprov provides JDBC {@link Connection} instances.
* @param adapter an optional adapter to a cache management system.
*/
public void init (String ident, ConnectionProvider conprov, CacheAdapter adapter)
{
_ident = ident;
_conprov = conprov;
_liaison = LiaisonRegistry.getLiaison(conprov.getURL(ident));
_cache = adapter;
}
/**
* Shuts this persistence context down, shutting down any caching system in use and shutting
* down the JDBC connection pool.
*/
public void shutdown ()
{
try {
if (_cache != null) {
_cache.shutdown();
}
} catch (Throwable t) {
log.warning("Failure shutting down Depot cache.", t);
}
_conprov.shutdown();
}
/**
* Create and return a new {@link SQLBuilder} for the appropriate dialect.
*
* TODO: At some point perhaps use a more elegant way of discerning our dialect.
*/
public SQLBuilder getSQLBuilder (DepotTypes types)
{
if (_liaison instanceof PostgreSQLLiaison) {
return new PostgreSQLBuilder(types);
}
if (_liaison instanceof MySQLLiaison) {
return new MySQLBuilder(types);
}
throw new IllegalArgumentException("Unknown liaison type: " + _liaison.getClass());
}
/**
* Registers a schema migration for the specified entity class.
*
* <p> This method must be called <b>before</b> an Entity is used by any repository. Thus you
* should register all migrations in the constructor of the repository that declares them in
* its {@link DepotRepository#getManagedRecords} method.
*
* <p> Note that the migration process is as follows:
*
* <ul><li> Note the difference between the entity's declared version and the version recorded
* in the database.
* <li> Run all registered pre-migrations
* <li> Perform all default migrations (column and index additions, index removals)
* <li> Run all registered post-migrations </ul>
*
* Thus you must either be prepared for the entity to be at <b>any</b> version prior to your
* migration target version because we may start up, find the schema at version 1 and the
* Entity class at version 8 and do all "standard" migrations in one fell swoop. So if a column
* got added in version 2 and renamed in version 6 and your migration was registered for
* version 6 to do that migration, it must be prepared for the column not to exist at all.
*
* <p> If you want a completely predictable migration process, never use the default migrations
* and register a pre-migration for every single schema migration and they will then be
* guaranteed to be run in registration order and with predictable pre- and post-conditions.
*
* <p> Note that if {@link PersistenceContext#initializeRepositories} is used, then all schema
* migrations for all known repositories will be run and then all data migrations for all known
* repositories will be run. This is recommeneded because schema migrations may fail and it is
* generally better to have not yet done the data migration rather than having schema and data
* migrations interleaved and potentially leaving the database in a strange state.
*/
public <T extends PersistentRecord> void registerMigration (
Class<T> type, SchemaMigration migration)
{
getRawMarshaller(type).registerMigration(migration);
}
/**
* Returns the marshaller for the specified persistent object class, creating and initializing
* it if necessary.
*/
public <T extends PersistentRecord> DepotMarshaller<T> getMarshaller (Class<T> type)
throws DatabaseException
{
checkAreInitialized(); // le check du sanity
DepotMarshaller<T> marshaller = getRawMarshaller(type);
try {
if (!marshaller.isInitialized()) {
// initialize the marshaller which may create or migrate the table for its
// underlying persistent object
marshaller.init(this);
if (marshaller.getTableName() != null && _warnOnLazyInit) {
log.warning("Record initialized lazily", "type", type.getName(),
new Exception());
}
}
} catch (DatabaseException pe) {
throw (DatabaseException)new DatabaseException(
"Failed to initialize marshaller [type=" + type + "].").initCause(pe);
}
return marshaller;
}
/**
* Invokes a non-modifying query and returns its result.
*/
public <T> T invoke (Query<T> query)
throws DatabaseException
{
CacheKey key = query.getCacheKey();
// if there is a cache key, check the cache
if (key != null && _cache != null) {
CacheAdapter.CachedValue<T> cacheHit = cacheLookup(key);
if (cacheHit != null) {
log.debug("cache hit [key=" + key + ", hit=" + cacheHit + "]");
T value = cacheHit.getValue();
value = query.transformCacheHit(key, value);
if (value != null) {
return value;
}
log.debug("transformCacheHit returned null; rejecting cached value.");
}
log.debug("cache miss [key=" + key + "]");
}
// otherwise, perform the query
T result = invoke(query, true);
// and let the caller figure out if it wants to cache itself somehow
query.updateCache(this, result);
return result;
}
/**
* Invokes a modifying query and returns the number of rows modified.
*/
public int invoke (Modifier modifier)
throws DatabaseException
{
modifier.cacheInvalidation(this);
int rows = invoke(modifier, true);
if (rows > 0) {
modifier.cacheUpdate(this);
}
return rows;
}
/**
* Returns true if there is a {@link CacheAdapter} configured, false otherwise.
*/
public boolean isUsingCache ()
{
return _cache != null;
}
/**
* Looks up an entry in the cache by the given key.
*/
public <T> CacheAdapter.CachedValue<T> cacheLookup (CacheKey key)
{
if (_cache == null) {
return null;
}
CacheAdapter.CacheBin<T> bin = _cache.getCache(key.getCacheId());
return bin.lookup(key.getCacheKey());
}
/**
* Stores a new entry indexed by the given key.
*/
public <T> void cacheStore (CacheKey key, T entry)
{
if (_cache == null) {
return;
}
if (key == null) {
log.warning("Cache key must not be null [entry=" + entry + "]");
Thread.dumpStack();
return;
}
log.debug("storing [key=" + key + ", value=" + entry + "]");
CacheAdapter.CacheBin<T> bin = _cache.getCache(key.getCacheId());
CacheAdapter.CachedValue<T> element = bin.lookup(key.getCacheKey());
T oldEntry = (element != null ? element.getValue() : null);
// update the cache
bin.store(key.getCacheKey(), entry);
// then do cache invalidations
Set<CacheListener<?>> listeners = _listenerSets.get(key.getCacheId());
if (listeners != null && listeners.size() > 0) {
for (CacheListener<?> listener : listeners) {
log.debug("cascading [listener=" + listener + "]");
@SuppressWarnings("unchecked")
CacheListener<T> casted = (CacheListener<T>)listener;
casted.entryCached(key, entry, oldEntry);
}
}
}
/**
* Evicts the cache entry indexed under the given key, if there is one. The eviction may
* trigger further cache invalidations.
*/
public void cacheInvalidate (CacheKey key)
{
if (key == null) {
log.warning("Cache key to invalidate must not be null.");
Thread.dumpStack();
} else {
cacheInvalidate(key.getCacheId(), key.getCacheKey());
}
}
/**
* Evicts the cache entry indexed under the given class and cache key, if there is one. The
* eviction may trigger further cache invalidations.
*/
public void cacheInvalidate (Class<? extends PersistentRecord> pClass, Serializable cacheKey)
{
cacheInvalidate(pClass.getName(), cacheKey);
}
/**
* Evicts the cache entry indexed under the given cache id and cache key, if there is one. The
* eviction may trigger further cache invalidations.
*/
public <T extends Serializable> void cacheInvalidate (String cacheId, Serializable cacheKey)
{
if (_cache == null) {
return;
}
log.debug("invalidating [cacheId=" + cacheId + ", cacheKey=" + cacheKey + "]");
CacheAdapter.CacheBin<T> bin = _cache.getCache(cacheId);
CacheAdapter.CachedValue<T> element = bin.lookup(cacheKey);
if (element != null) {
// find the old entry, if any
T oldEntry = element.getValue();
if (oldEntry != null) {
// if there was one, do (possibly cascading) cache invalidations
Set<CacheListener<?>> listeners = _listenerSets.get(cacheId);
if (listeners != null && listeners.size() > 0) {
CacheKey key = new SimpleCacheKey(cacheId, cacheKey);
for (CacheListener<?> listener : listeners) {
log.debug("cascading [listener=" + listener + "]");
@SuppressWarnings("unchecked") CacheListener<T> casted =
(CacheListener<T>)listener;
casted.entryInvalidated(key, oldEntry);
}
}
}
}
// then remove the keyed entry from the cache system
bin.remove(cacheKey);
}
/**
* Brutally iterates over the entire contents of the cache associated with the given class,
* invoking the callback for each cache entry.
*/
public <T extends Serializable> void cacheTraverse (
Class<? extends PersistentRecord> pClass, CacheTraverser<T> filter)
{
cacheTraverse(pClass.getName(), filter);
}
/**
* Brutally iterates over the entire contents of the cache identified by the given cache id,
* invoking the callback for each cache entry.
*/
public <T extends Serializable> void cacheTraverse (String cacheId, CacheTraverser<T> filter)
{
if (_cache == null) {
return;
}
CacheAdapter.CacheBin<T> bin = _cache.getCache(cacheId);
if (bin != null) {
for (Object key : bin.enumerateKeys()) {
CacheAdapter.CachedValue<T> element = bin.lookup((Serializable) key);
T value;
if (element != null && (value = element.getValue()) != null) {
filter.visitCacheEntry(this, cacheId, (Serializable) key, value);
}
}
}
}
/**
* Registers a new cache listener with the cache associated with the given class.
*/
public <T extends Serializable> void addCacheListener (
Class<T> pClass, CacheListener<T> listener)
{
addCacheListener(pClass.getName(), listener);
}
/**
* Registers a new cache listener with the identified cache.
*/
public <T extends Serializable> void addCacheListener (
String cacheId, CacheListener<T> listener)
{
Set<CacheListener<?>> listenerSet = _listenerSets.get(cacheId);
if (listenerSet == null) {
listenerSet = new HashSet<CacheListener<?>>();
_listenerSets.put(cacheId, listenerSet);
}
listenerSet.add(listener);
}
/**
* Initializes all repositories that have been created and registered with this persistence
* context. Any repositories that are constructed after this call will be immediately
* initialized at the time they are constructed (which is probably undesirable). When a
* repository is initialized, schema migrations for all of its managed persistent records are
* run. It is best to do all schema migrations when the system initializes which is why lazy
* initialization of repositories is undesirable.
*
* @param warnOnLazyInit if true, any repositories are constructed after this method is called
* will result in a warning so that the application developer can restructure their code to
* ensure that those repositories are properly created prior to this call.
*/
public void initializeRepositories (boolean warnOnLazyInit)
throws DatabaseException
{
// resolve all persistent records and trigger all schema migrations
for (DepotRepository repo : _repositories) {
repo.resolveRecords();
}
// then run all repository initialization methods, triggering all data migrations
for (DepotRepository repo : _repositories) {
repo.init();
}
// now potentially issue a warning if we lazily initialize any other persistent record
_warnOnLazyInit = warnOnLazyInit;
// finally note that we've now been initialized
_repositories = null;
}
/**
* Called when a depot repository is created. We register all persistent record classes used by
* the repository so that systems that desire it can force the resolution of all database
* tables rather than allowing resolution to happen on demand.
*/
protected void repositoryCreated (DepotRepository repo)
{
if (_repositories == null) {
if (_warnOnLazyInit) {
log.warning("Repository created lazily: " + repo.getClass().getName());
}
repo.resolveRecords();
repo.init();
} else {
_repositories.add(repo);
}
}
/**
* Looks up and creates, but does not initialize, the marshaller for the specified Entity type.
*/
protected <T extends PersistentRecord> DepotMarshaller<T> getRawMarshaller (Class<T> type)
{
@SuppressWarnings("unchecked") DepotMarshaller<T> marshaller =
(DepotMarshaller<T>)_marshallers.get(type);
if (marshaller == null) {
_marshallers.put(type, marshaller = new DepotMarshaller<T>(type, this));
}
return marshaller;
}
/**
* Internal invoke method that takes care of transient retries for both queries and modifiers.
*/
protected <T> T invoke (Operation<T> op, boolean retryOnTransientFailure)
throws DatabaseException
{
checkAreInitialized(); // le check du sanity
boolean isReadOnly = !(op instanceof Modifier);
Connection conn;
try {
conn = _conprov.getConnection(_ident, isReadOnly);
} catch (PersistenceException pe) {
throw new DatabaseException(pe.getMessage(), pe.getCause());
}
// TEMP: we synchronize on the connection to cooperate with SimpleRepository when used in
// conjunction with a StaticConnectionProvider; at some point we'll switch to standard JDBC
// connection pooling which will block in getConnection() instead of returning a connection
// that someone else may be using
synchronized (conn) {
try {
// if this becomes more complex than this single statement, then this should turn
// into a method call that contains the complexity
return op.invoke(conn, _liaison);
} catch (SQLException sqe) {
if (!isReadOnly) {
// convert this exception to a DuplicateKeyException if appropriate
if (_liaison.isDuplicateRowException(sqe)) {
throw new DuplicateKeyException(sqe.getMessage());
}
}
// let the provider know that the connection failed
_conprov.connectionFailed(_ident, isReadOnly, conn, sqe);
conn = null;
if (retryOnTransientFailure && _liaison.isTransientException(sqe)) {
// the MySQL JDBC driver has the annoying habit of including the embedded
// exception stack trace in the message of their outer exception; if I want a
// fucking stack trace, I'll call printStackTrace() thanksverymuch
String msg = StringUtil.split(String.valueOf(sqe), "\n")[0];
log.info("Transient failure executing op, retrying [error=" + msg + "].");
} else {
throw new DatabaseException("Operation failure " + op, sqe);
}
} finally {
_conprov.releaseConnection(_ident, isReadOnly, conn);
}
}
// if we got here, we want to retry a transient failure
return invoke(op, false);
}
protected void checkAreInitialized ()
{
if (_conprov == null) {
throw new IllegalStateException(
"This persistence context has not yet been initialized. You are probably " +
"doing something too early, like in a repository's constructor. Don't do that.");
}
}
protected String _ident;
protected ConnectionProvider _conprov;
protected DatabaseLiaison _liaison;
protected boolean _warnOnLazyInit;
/** The object through which all our caching is relayed, or null, for no caching. */
protected CacheAdapter _cache;
/** Tracks repositories during the pre-initialization phase. */
protected List<DepotRepository> _repositories = new ArrayList<DepotRepository>();
/** A mapping from persistent record class to resolved marshaller. */
protected Map<Class<?>, DepotMarshaller<?>> _marshallers =
new HashMap<Class<?>, DepotMarshaller<?>>();
/** A mapping of cache listeners by cache id. */
protected Map<String, Set<CacheListener<?>>> _listenerSets =
new HashMap<String, Set<CacheListener<?>>>();
}
@@ -1,41 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.io.Serializable;
/**
* The base class for all persistent records used in Depot. Persistent records must be cloneable
* and serializable; this class is used to enforce those requirements.
*/
public class PersistentRecord
implements Cloneable, Serializable
{
@Override // from Object
public PersistentRecord clone ()
{
try {
return (PersistentRecord) super.clone();
} catch (CloneNotSupportedException cnse) {
throw new RuntimeException(cnse); // this should never happen
}
}
}
@@ -1,297 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne, Pär Winzell
//
// 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.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Set;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.LiaisonRegistry;
import com.samskivert.jdbc.depot.FieldMarshaller.BooleanMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ByteArrayMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ByteEnumMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ByteMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.DoubleMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.FloatMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.IntArrayMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.IntMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.LongMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ObjectMarshaller;
import com.samskivert.jdbc.depot.FieldMarshaller.ShortMarshaller;
import com.samskivert.jdbc.depot.annotation.FullTextIndex;
import com.samskivert.jdbc.depot.expression.EpochSeconds;
import com.samskivert.jdbc.depot.operator.Conditionals.FullTextMatch;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.StringUtil;
import static com.samskivert.Log.log;
public class PostgreSQLBuilder
extends SQLBuilder
{
public class PGBuildVisitor extends BuildVisitor
{
@Override public void visit (FullTextMatch match) {
appendIdentifier("ftsCol_" + match.getName());
_builder.append(" @@ TO_TSQUERY('default', ?)");
}
@Override public void visit (EpochSeconds epochSeconds) {
_builder.append("date_part('epoch', ");
epochSeconds.getArgument().accept(this);
_builder.append(")");
}
// TODO: enable when we can require 1.6 support
// @Override public void visit (In in) {
// in.getColumn().accept(this);
// _builder.append(" = any (?)");
// }
protected PGBuildVisitor (DepotTypes types) {
super(types);
}
@Override protected void appendIdentifier (String field) {
_builder.append("\"").append(field).append("\"");
}
}
public class PGBindVisitor extends BindVisitor
{
@Override public void visit (FullTextMatch match) {
// The tsearch2 engine takes queries on the form
// (foo&bar)|goop
// so in this first simple implementation, we just take the user query, chop it into
// words by space/punctuation and 'or' those together like so:
// 'ho! who goes there?' -> 'ho|who|goes|there'
String[] searchTerms = match.getQuery().toLowerCase().split("\\W+");
if (searchTerms.length > 0 && searchTerms[0].length() == 0) {
searchTerms = ArrayUtil.splice(searchTerms, 0, 1);
}
String query = StringUtil.join(searchTerms, "|");
try {
_stmt.setString(_argIdx++, query);
} catch (SQLException sqe) {
throw new DatabaseException("Failed to configure full-text match column " +
"[idx=" + (_argIdx-1) + ", query=" + query + "]", sqe);
}
}
// TODO: enable when we can require 1.6 support
// @Override public void visit (In in) {
// Comparable<?>[] values = in.getValues();
// try {
// _stmt.setObject(
// _argIdx++, _conn.createArrayOf(getElementType(values), (Object[])values));
// } catch (SQLException sqe) {
// throw new DatabaseException(
// "Failed to write value to statement [idx=" + (_argIdx-1) +
// ", values=" + StringUtil.safeToString(values) + "]", sqe);
// }
// }
// protected String getElementType (Comparable<?>[] values) {
// if (values instanceof Integer[]) {
// return "integer";
// } else if (values instanceof String[]) {
// return "character varying";
// } else {
// throw new DatabaseException(
// "Don't know how to make Postgres array for " + values.getClass());
// }
// }
protected PGBindVisitor (DepotTypes types, Connection conn, PreparedStatement stmt) {
super(types, conn, stmt);
}
}
public PostgreSQLBuilder (DepotTypes types)
{
super(types);
}
@Override
public void getFtsIndexes (
Iterable<String> columns, Iterable<String> indexes, Set<String> target)
{
for (String column : columns) {
if (column.startsWith("ftsCol_")) {
target.add(column.substring("ftsCol_".length()));
}
}
}
@Override
public <T extends PersistentRecord> boolean addFullTextSearch (
Connection conn, DepotMarshaller<T> marshaller, FullTextIndex fts)
throws SQLException
{
Class<T> pClass = marshaller.getPersistentClass();
DatabaseLiaison liaison = LiaisonRegistry.getLiaison(conn);
String[] fields = fts.fields();
String table = marshaller.getTableName();
String column = "ftsCol_" + fts.name();
String index = table + "_ftsIx_" + fts.name();
String trigger = table + "_ftsTrig_" + fts.name();
// build the UPDATE
StringBuilder initColumn = new StringBuilder("UPDATE ").
append(liaison.tableSQL(table)).append(" SET ").append(liaison.columnSQL(column)).
append(" = TO_TSVECTOR('default', ");
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
initColumn.append(" || ' ' || ");
}
initColumn.append("COALESCE(").
append(liaison.columnSQL(_types.getColumnName(pClass, fields[ii]))).
append(", '')");
}
initColumn.append(")");
// build the CREATE TRIGGER
StringBuilder createTrigger = new StringBuilder("CREATE TRIGGER ").
append(liaison.columnSQL(trigger)).append(" BEFORE UPDATE OR INSERT ON ").
append(liaison.tableSQL(table)).append(" FOR EACH ROW EXECUTE PROCEDURE tsearch2(").
append(liaison.columnSQL(column)).append(", ");
for (int ii = 0; ii < fields.length; ii ++) {
if (ii > 0) {
createTrigger.append(", ");
}
createTrigger.append(liaison.columnSQL(_types.getColumnName(pClass, fields[ii])));
}
createTrigger.append(")");
// build the CREATE INDEX
StringBuilder createIndex = new StringBuilder("CREATE INDEX ").
append(liaison.columnSQL(index)).append(" ON " ).append(liaison.tableSQL(table)).
append(" USING GIST(").append(liaison.columnSQL(column)).append(")");
Statement stmt = conn.createStatement();
try {
log.info(
"Adding full-text search column, index and trigger: " + column + ", " +
index + ", " + trigger);
liaison.addColumn(conn, table, column, "TSVECTOR", true);
stmt.executeUpdate(initColumn.toString());
stmt.executeUpdate(createIndex.toString());
stmt.executeUpdate(createTrigger.toString());
} finally {
JDBCUtil.close(stmt);
}
return true;
}
@Override
public boolean isPrivateColumn (String column)
{
// filter out any column that we created as part of FTS support
return column.startsWith("ftsCol_");
}
@Override
protected BuildVisitor getBuildVisitor ()
{
return new PGBuildVisitor(_types);
}
@Override
protected BindVisitor getBindVisitor (Connection conn, PreparedStatement stmt)
{
return new PGBindVisitor(_types, conn, stmt);
}
@Override
protected <T> String getColumnType (FieldMarshaller<?> fm, int length)
{
if (fm instanceof ByteMarshaller) {
return "SMALLINT";
} else if (fm instanceof ShortMarshaller) {
return "SMALLINT";
} else if (fm instanceof IntMarshaller) {
return "INTEGER";
} else if (fm instanceof LongMarshaller) {
return "BIGINT";
} else if (fm instanceof FloatMarshaller) {
return "REAL";
} else if (fm instanceof DoubleMarshaller) {
return "DOUBLE PRECISION";
} else if (fm instanceof ObjectMarshaller) {
Class<?> ftype = fm.getField().getType();
if (ftype.equals(Byte.class)) {
return "SMALLINT";
} else if (ftype.equals(Short.class)) {
return "SMALLINT";
} else if (ftype.equals(Integer.class)) {
return "INTEGER";
} else if (ftype.equals(Long.class)) {
return "BIGINT";
} else if (ftype.equals(Float.class)) {
return "FLOAT";
} else if (ftype.equals(Double.class)) {
return "DOUBLE";
} else if (ftype.equals(String.class)) {
if (length < (1 << 15)) {
return "VARCHAR(" + length + ")";
}
return "TEXT";
} else if (ftype.equals(Date.class)) {
return "DATE";
} else if (ftype.equals(Time.class)) {
return "TIME";
} else if (ftype.equals(Timestamp.class)) {
return "TIMESTAMP";
} else if (ftype.equals(Blob.class)) {
return "BYTEA";
} else if (ftype.equals(Clob.class)) {
return "TEXT";
} else {
throw new IllegalArgumentException(
"Don't know how to create SQL for " + ftype + ".");
}
} else if (fm instanceof ByteArrayMarshaller) {
return "BYTEA";
} else if (fm instanceof IntArrayMarshaller) {
return "BYTEA";
} else if (fm instanceof ByteEnumMarshaller) {
return "SMALLINT";
} else if (fm instanceof BooleanMarshaller) {
return "BOOLEAN";
} else {
throw new IllegalArgumentException("Unknown field marshaller type: " + fm.getClass());
}
}
}
@@ -1,73 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.SQLException;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.depot.PersistenceContext.CacheListener;
/**
* The base of all read-only queries.
*/
public interface Query<T> extends Operation<T>
{
/** A simple base class for non-complex queries. */
public abstract class Trivial<T> implements Query<T>
{
public abstract T invoke (Connection conn, DatabaseLiaison liaison) throws SQLException;
public CacheKey getCacheKey () {
return null;
}
public T transformCacheHit (CacheKey key, T value) {
return value;
}
public void updateCache (PersistenceContext ctx, T result) {
}
}
/**
* Any query may elect to utilize the built-in cache by returning a non-null {@link CacheKey}
* in this method. This is done automatically by the {@link DepotRepository} when looking up
* single entities by primary key, but even entire collections can be cached under a single
* key.
*
* Great care must be taken to invalidate such cached collections when their constituent
* entities are invalidated. This is generally done using {@link CacheListener} and
* {@link CacheInvalidator}.
*/
public CacheKey getCacheKey ();
/**
* Overriden by subclasses to perform special operations when the query would return a cache
* hit. The value may be mutated, modified, or null may be return to force a database hit.
*/
public T transformCacheHit (CacheKey key, T value);
/**
* Overriden by subclasses to perform case-by-case cache updates.
*/
public void updateCache (PersistenceContext ctx, T result);
}
@@ -1,230 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.lang.reflect.Field;
import java.util.Set;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.jdbc.depot.annotation.Column;
import com.samskivert.jdbc.depot.annotation.FullTextIndex;
import com.samskivert.jdbc.depot.annotation.GeneratedValue;
import com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import static com.samskivert.jdbc.depot.Log.log;
/**
* At the heart of Depot's SQL generation, this object constructs two {@link ExpressionVisitor}
* objects and executes them, one after another; the first one constructs SQL as it recurses, the
* other binds arguments in the {@link PreparedStatement}. This class must be subclassed by the
* database dialects we wish to support.
*/
public abstract class SQLBuilder
{
public SQLBuilder (DepotTypes types)
{
_types = types;
}
/**
* Construct an entirely new SQL query relative to our configured {@link DepotTypes} data.
* This method may be called multiple times, each time beginning a new query, and should be
* followed up by a call to {@link #prepare(Connection)} which creates, configures and
* returns the actual {@link PreparedStatement} to execute.
*/
public void newQuery (QueryClause clause)
{
_clause = clause;
_buildVisitor = getBuildVisitor();
_clause.accept(_buildVisitor);
}
/**
* After {@link #newQuery(QueryClause)} has been executed, this method is run to recurse
* through the {@link QueryClause} structure, setting the {@link PreparedStatement} arguments
* that were defined in the generated SQL.
*
* This method throws {@link SQLException} and is thus meant to be called from within
* {@link Query#invoke} and {@link Modifier#invoke}.
*/
public PreparedStatement prepare (Connection conn)
throws SQLException
{
if (_buildVisitor == null) {
throw new IllegalArgumentException("Cannot prepare query until it's been built.");
}
PreparedStatement stmt = conn.prepareStatement(_buildVisitor.getQuery());
_bindVisitor = getBindVisitor(conn, stmt);
_clause.accept(_bindVisitor);
if (PersistenceContext.DEBUG) {
log.info("SQL: " + stmt.toString());
}
return stmt;
}
protected String nullify (String str) {
return (str != null && str.length() > 0) ? str : null;
}
/**
* Generates the SQL needed to construct a database column for field represented by the given
* {@link FieldMarshaller}.
*
* TODO: This method should be split into several parts that are more easily overridden on a
* case-by-case basis in the dialectal subclasses.
*/
public ColumnDefinition buildColumnDefinition (FieldMarshaller<?> fm)
{
// if this field is @Computed, it has no SQL definition
if (fm.getComputed() != null) {
return null;
}
Field field = fm.getField();
String type = null;
boolean nullable = false;
boolean unique = false;
String defaultValue = null;
int length = 255;
Column column = field.getAnnotation(Column.class);
if (column != null) {
type = nullify(column.type());
nullable = column.nullable();
unique = column.unique();
defaultValue = nullify(column.defaultValue());
length = column.length();
}
// handle primary keyness
GeneratedValue genValue = fm.getGeneratedValue();
if (genValue != null) {
switch (genValue.strategy()) {
case AUTO:
case IDENTITY:
type = "SERIAL";
unique = true;
break;
case SEQUENCE: // TODO
throw new IllegalArgumentException(
"SEQUENCE key generation strategy not yet supported.");
case TABLE:
// nothing to do here, it'll be handled later
break;
}
}
if (type == null) {
type = getColumnType(fm, length);
}
// sanity check nullability
if (nullable && field.getType().isPrimitive()) {
throw new IllegalArgumentException(
"Primitive Java type cannot be nullable [field=" + field.getName() + "]");
}
// Java primitive types cannot be null, so we provide a default value for these columns
// that matches Java's default for primitive types; however, if the column has a generated
// value, don't provide a default because that will anger the database Gods
if (defaultValue == null && genValue == null) {
if (field.getType().equals(Byte.TYPE) ||
field.getType().equals(Short.TYPE) ||
field.getType().equals(Integer.TYPE) ||
field.getType().equals(Long.TYPE) ||
field.getType().equals(Float.TYPE) ||
field.getType().equals(Double.TYPE) ||
ByteEnum.class.isAssignableFrom(field.getType())) {
defaultValue = "0";
} else if (field.getType().equals(Boolean.TYPE)) {
defaultValue = getBooleanDefault();
}
}
return new ColumnDefinition(type, nullable, unique, defaultValue);
}
/**
* Add full-text search capabilities, as defined by the provided {@link FullTextIndex}, on
* the table associated with the given {@link DepotMarshaller}. This is a highly database
* specific operation and must thus be implemented by each dialect subclass.
*
* @see FullTextIndex
*/
public abstract <T extends PersistentRecord> boolean addFullTextSearch (
Connection conn, DepotMarshaller<T> marshaller, FullTextIndex fts)
throws SQLException;
/**
* Return true if the supplied column is an internal consideration of this {@link SQLBuilder},
* e.g. PostgreSQL's full text search data is stored in a table column that should otherwise
* not be visible to Depot; this method helps mask it.
*/
public abstract boolean isPrivateColumn (String column);
/**
* Figure out what full text search indexes already exist on this table and add the names of
* those indexes to the supplied target set.
*/
public abstract void getFtsIndexes (
Iterable<String> columns, Iterable<String> indexes, Set<String> target);
/**
* Returns the boolean literal that corresponds to false.
*/
protected String getBooleanDefault ()
{
return "false";
}
/**
* Overridden by subclasses to create a dialect-specific {@link BuildVisitor}.
*/
protected abstract BuildVisitor getBuildVisitor ();
/**
* Overridden by subclasses to create a dialect-specific {@link BindVisitor}.
*/
protected abstract BindVisitor getBindVisitor (Connection conn, PreparedStatement stmt);
/**
* Overridden by subclasses to figure the dialect-specific SQL type of the given field.
* @param length
*/
protected abstract <T> String getColumnType (FieldMarshaller<?> fm, int length);
/** The class that maps persistent classes to marshallers. */
protected DepotTypes _types;
protected QueryClause _clause;
protected BuildVisitor _buildVisitor;
protected BindVisitor _bindVisitor;
}
@@ -1,246 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.SQLException;
import java.util.Map;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.depot.annotation.Column;
import static com.samskivert.jdbc.depot.Log.log;
/**
* Encapsulates the migration of a persistent record's database schema. These can be registered
* with the {@link PersistenceContext} to effect hand-coded migrations between entity versions. The
* modifier should override {@link #invoke} to perform its migrations. See {@link
* PersistenceContext#registerMigration} for details on the migration process.
*
* <p> Note: these should only be used for actual schema changes (column additions, removals,
* renames, retypes, etc.). It should not be used for data migration, use {@link DataMigration} for
* that.
*/
public abstract class SchemaMigration extends Modifier
{
/**
* A convenient migration for dropping a column from an entity.
*/
public static class Drop extends SchemaMigration
{
public Drop (int targetVersion, String columnName) {
super(targetVersion);
_columnName = columnName;
}
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
if (!liaison.tableContainsColumn(conn, _tableName, _columnName)) {
// we'll accept this inconsistency
log.warning(_tableName + "." + _columnName + " already dropped.");
return 0;
}
log.info("Dropping '" + _columnName + "' from " + _tableName);
return liaison.dropColumn(conn, _tableName, _columnName) ? 1 : 0;
}
protected String _columnName;
}
/**
* A convenient migration for renaming a column in an entity.
*/
public static class Rename extends SchemaMigration
{
public Rename (int targetVersion, String oldColumnName, String newColumnName) {
super(targetVersion);
_oldColumnName = oldColumnName;
_newColumnName = newColumnName;
}
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
if (!liaison.tableContainsColumn(conn, _tableName, _oldColumnName)) {
if (liaison.tableContainsColumn(conn, _tableName, _newColumnName)) {
// we'll accept this inconsistency
log.warning(_tableName + "." + _oldColumnName + " already renamed to " +
_newColumnName + ".");
return 0;
}
// but this is not OK
throw new IllegalArgumentException(
_tableName + " does not contain '" + _oldColumnName + "'");
}
// nor is this
if (liaison.tableContainsColumn(conn, _tableName, _newColumnName)) {
throw new IllegalArgumentException(
_tableName + " already contains '" + _newColumnName + "'");
}
log.info("Renaming '" + _oldColumnName + "' to '" + _newColumnName + "' in: " +
_tableName);
return liaison.renameColumn(
conn, _tableName, _oldColumnName, _newColumnName, _newColumnDef) ? 1 : 0;
}
@Override public boolean runBeforeDefault () {
return true;
}
@Override
protected void init (String tableName, Map<String, FieldMarshaller<?>> marshallers) {
super.init(tableName, marshallers);
FieldMarshaller<?> fm = marshallers.get(_newColumnName);
if (fm == null) {
throw new IllegalArgumentException(
_tableName + " does not contain '" + _newColumnName + "' field.");
}
_newColumnDef = fm.getColumnDefinition();
}
protected String _oldColumnName, _newColumnName;
protected ColumnDefinition _newColumnDef;
}
/**
* A convenient migration for changing the type of an existing field. NOTE: This object is
* instantiated with the name of a persistent field, not the name of a database column. These
* can be very different things for classes that use @Column annotations.
*/
public static class Retype extends SchemaMigration
{
public Retype (int targetVersion, String fieldName) {
super(targetVersion);
_fieldName = fieldName;
}
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
log.info("Updating type of '" + _fieldName + "' in " + _tableName);
return liaison.changeColumn(conn, _tableName, _fieldName, _newColumnDef.getType(),
_newColumnDef.isNullable(), _newColumnDef.isUnique(),
_newColumnDef.getDefaultValue()) ? 1 : 0;
}
@Override public boolean runBeforeDefault () {
return false;
}
@Override
protected void init (String tableName, Map<String, FieldMarshaller<?>> marshallers) {
super.init(tableName, marshallers);
_columnName = marshallers.get(_fieldName).getColumnName();
_newColumnDef = marshallers.get(_fieldName).getColumnDefinition();
}
protected String _fieldName, _columnName;
protected ColumnDefinition _newColumnDef;
}
/**
* A convenient migration for adding a new column that requires a default value to be specified
* during the addition. Normally Depot will automatically handle column addition, but if you
* have a column that normally does not have a default value but needs one when it is added to
* a table with existing rows, you can use this migration.
*
* @see Column#defaultValue
*/
public static class Add extends SchemaMigration
{
public Add (int targetVersion, String fieldName, String defaultValue) {
super(targetVersion);
_fieldName = fieldName;
_defaultValue = defaultValue;
}
@Override
public Integer invoke (Connection conn, DatabaseLiaison liaison) throws SQLException {
// override the default value in the column definition with the one provided
ColumnDefinition defColumnDef = new ColumnDefinition(
_newColumnDef.getType(), _newColumnDef.isNullable(),
_newColumnDef.isUnique(), _defaultValue);
// first add the column with the overridden default value
if (liaison.addColumn(conn, _tableName, _fieldName, defColumnDef, true)) {
// then change the column to the permanent default value
liaison.changeColumn(conn, _tableName, _fieldName, _newColumnDef.getType(),
null, null, _newColumnDef.getDefaultValue());
return 1;
}
return 0;
}
@Override public boolean runBeforeDefault () {
return true;
}
@Override
protected void init (String tableName, Map<String, FieldMarshaller<?>> marshallers) {
super.init(tableName, marshallers);
_columnName = marshallers.get(_fieldName).getColumnName();
_newColumnDef = marshallers.get(_fieldName).getColumnDefinition();
}
protected String _fieldName, _columnName, _defaultValue;
protected ColumnDefinition _newColumnDef;
}
/**
* If this method returns true, this migration will be run <b>before</b> the default
* migrations, if false it will be run after.
*/
public boolean runBeforeDefault ()
{
return true;
}
/**
* When an Entity is being migrated, this method will be called to check whether this migration
* should be run. The default implementation runs as long as the currentVersion is less than
* the target version supplied to the migration at construct time.
*/
public boolean shouldRunMigration (int currentVersion, int targetVersion)
{
return (currentVersion < _targetVersion);
}
protected SchemaMigration (int targetVersion)
{
super();
_targetVersion = targetVersion;
}
/**
* This is called to provide the migration with the name of the entity table and access to its
* field marshallers prior to being invoked. This will <em>only</em> be called after this
* migration has been determined to be runnable so one cannot rely on this method having been
* called in {@link #shouldRunMigration}.
*/
protected void init (String tableName, Map<String, FieldMarshaller<?>> marshallers)
{
_tableName = tableName;
}
protected int _targetVersion;
protected String _tableName;
}
@@ -1,113 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.io.Serializable;
/**
* Convenience class that implements {@link CacheKey} as simply as possibly. This class is
* typically used when the caller wants to cache a non-obvious query such as a collection,
* and needs to specify their own cache key and file it under a hand-picked cache id.
*/
public class SimpleCacheKey
implements CacheKey
{
/**
* Construct a {@link SimpleCacheKey} for a query that has no parameters whatsoever.
*/
public SimpleCacheKey (String cacheId)
{
this(cacheId, Boolean.TRUE);
}
/**
* Construct a {@link SimpleCacheKey} associated with the given persistent class with
* the given cache key.
*/
public SimpleCacheKey (Class<?> cacheClass, Serializable cacheKey)
{
this(cacheClass.getName(), cacheKey);
}
/**
* Construct a {@link SimpleCacheKey} for the given cache id with the given cache key.
*/
public SimpleCacheKey (String cacheId, Serializable value)
{
_cacheId = cacheId;
_cacheKey = value;
}
// from CacheKey
public String getCacheId ()
{
return _cacheId;
}
// from CacheKey
public Serializable getCacheKey ()
{
return _cacheKey;
}
@Override
public int hashCode ()
{
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((_cacheId == null) ? 0 : _cacheId.hashCode());
result = PRIME * result + ((_cacheKey == null) ? 0 : _cacheKey.hashCode());
return result;
}
@Override
public boolean equals (Object obj)
{
if (obj == null || obj.getClass() != getClass()) {
return false;
}
SimpleCacheKey other = (SimpleCacheKey) obj;
if (_cacheId == null) {
if (other._cacheId != null) {
return false;
}
} else if (!_cacheId.equals(other._cacheId)) {
return false;
}
if (_cacheKey == null) {
if (other._cacheKey != null) {
return false;
}
} else if (!_cacheKey.equals(other._cacheKey)) {
return false;
}
return true;
}
@Override
public String toString ()
{
return "[cacheId=" + _cacheId + ", value=" + _cacheKey + "]";
}
protected String _cacheId;
protected Serializable _cacheKey;
}
@@ -1,174 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.ResultSet;
import java.sql.SQLException;
import com.samskivert.jdbc.depot.annotation.GeneratedValue;
import com.samskivert.jdbc.depot.annotation.TableGenerator;
import com.samskivert.jdbc.ColumnDefinition;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
/**
* Generates primary keys using an external table .
*/
public class TableValueGenerator extends ValueGenerator
{
public TableValueGenerator (
TableGenerator tg, GeneratedValue gv, DepotMarshaller<?> dm, FieldMarshaller<?> fm)
{
super(gv, dm, fm);
_valueTable = defStr(tg.table(), "IdSequences");
_pkColumnName = defStr(tg.pkColumnName(), "sequence");
_pkColumnValue = defStr(tg.pkColumnValue(), "default");
_valueColumnName = defStr(tg.valueColumnName(), "value");
}
@Override // from ValueGenerator
public boolean isPostFactum ()
{
return false;
}
@Override // from ValueGenerator
public void create (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
// make sure our table exists
liaison.createTableIfMissing(
conn, _valueTable,
new String[] { _pkColumnName, _valueColumnName },
new ColumnDefinition[] {
new ColumnDefinition("VARCHAR(255)", true, false, null),
new ColumnDefinition("INTEGER")
},
null,
new String[] { _pkColumnName });
// and also that there's a row in it for us
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(
" SELECT * FROM " + liaison.tableSQL(_valueTable) +
" WHERE " + liaison.columnSQL(_pkColumnName) + " = ?");
stmt.setString(1, _pkColumnValue);
if (stmt.executeQuery().next()) {
return;
}
JDBCUtil.close(stmt);
stmt = null;
int initialValue = _initialValue;
if (_migrateIfExists) {
Integer max = getFieldMaximum(conn, liaison);
if (max != null) {
initialValue = 1 + max.intValue();
}
}
stmt = conn.prepareStatement(
" INSERT INTO " + liaison.tableSQL(_valueTable) + " (" +
liaison.columnSQL(_pkColumnName) + ", " + liaison.columnSQL(_valueColumnName) +
") VALUES (?, ?)");
stmt.setString(1, _pkColumnValue);
stmt.setInt(2, initialValue);
stmt.executeUpdate();
} finally {
JDBCUtil.close(stmt);
}
}
@Override // from ValueGenerator
public void delete (Connection conn, DatabaseLiaison liaison) throws SQLException
{
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(
" DELETE FROM " + liaison.tableSQL(_valueTable) +
" WHERE " + liaison.columnSQL(_pkColumnName) + " = ?");
stmt.setString(1, _pkColumnValue);
stmt.executeUpdate();
} finally {
JDBCUtil.close(stmt);
}
}
@Override // from ValueGenerator
public int nextGeneratedValue (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
PreparedStatement stmt = null;
try {
// TODO: Make this lockless!
String query =
" SELECT " + liaison.columnSQL(_valueColumnName) +
" FROM " + liaison.tableSQL(_valueTable) +
" WHERE " + liaison.columnSQL(_pkColumnName) + " = ? FOR UPDATE";
stmt = conn.prepareStatement(query);
stmt.setString(1, _pkColumnValue);
ResultSet rs = stmt.executeQuery();
if (!rs.next()) {
throw new SQLException("Failed to find next primary key value " +
"[table=" + _valueTable + ", column=" + _valueColumnName +
", where=" + _pkColumnName + "=" + _pkColumnValue + "]");
}
int val = rs.getInt(1);
JDBCUtil.close(stmt);
stmt = conn.prepareStatement(
" UPDATE " + liaison.tableSQL(_valueTable) +
" SET " + liaison.columnSQL(_valueColumnName) + " = ? " +
" WHERE " + liaison.columnSQL(_pkColumnName) + " = ?");
stmt.setInt(1, val + _allocationSize);
stmt.setString(2, _pkColumnValue);
stmt.executeUpdate();
return val;
} finally {
JDBCUtil.close(stmt);
}
}
/**
* Convenience function to return a value or a default fallback.
*/
protected static String defStr (String value, String def)
{
if (value == null || value.trim().length() == 0) {
return def;
}
return value;
}
protected String _valueTable;
protected String _pkColumnName;
protected String _pkColumnValue;
protected String _valueColumnName;
}
@@ -1,38 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2001-2007 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;
/**
* An augmented cache invalidator interface for invalidators that can ensure that they are
* operating on the proper persistent record class.
*/
public interface ValidatingCacheInvalidator extends CacheInvalidator
{
/**
* Validates that this invalidator operates on the supplied persistent record class. This helps
* to catch programmer errors where one record type is used for a query clause and another is
* used for the cache invalidator.
*
* @exception IllegalArgumentException thrown if the supplied persistent record class does not
* match the class that this invalidator will flush from the cache.
*/
public void validateFlushType (Class<?> pClass);
}
@@ -1,124 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.depot.annotation.GeneratedValue;
import static com.samskivert.jdbc.depot.Log.log;
/**
* Defines the interface to our value generators.
*/
public abstract class ValueGenerator
{
public ValueGenerator (GeneratedValue gv, DepotMarshaller<?> dm, FieldMarshaller<?> fm)
{
_allocationSize = gv.allocationSize();
_initialValue = gv.initialValue();
_migrateIfExists = gv.migrateIfExists();
_dm = dm;
_fm = fm;
}
/**
* If true, this key generator will be run after the insert statement, if false, it will be run
* before.
*/
public abstract boolean isPostFactum ();
/**
* Ensures the generator is prepared for operation, creating it if necessary. Care is taken to
* only run this the first time a column is created. However, if a column with a value
* generator is renamed, we can't distinguish that from a newly created column and we call this
* method again on the renamed column. If it's possible to not fail in that circumstance, try
* to avoid doing so.
*/
public abstract void create (Connection conn, DatabaseLiaison liaison)
throws SQLException;
/**
* Fetch/generate the next primary key value.
*/
public abstract int nextGeneratedValue (Connection conn, DatabaseLiaison liaison)
throws SQLException;
/**
* Delete all database entities associated with this value generator.
*/
public abstract void delete (Connection conn, DatabaseLiaison liaison)
throws SQLException;
/**
* Scans the table associated with this {@link ValueGenerator} and returns either null, if
* there are no rows, or an {@link Integer} containing the largest numerical value our
* field attains.
*/
protected Integer getFieldMaximum (Connection conn, DatabaseLiaison liaison)
throws SQLException
{
String column = _fm.getColumnName();
String table = _dm.getTableName();
Statement stmt = conn.createStatement();
try {
ResultSet rs = stmt.executeQuery(
" SELECT COUNT(*), MAX(" + liaison.columnSQL(column) + ") " +
" FROM " + liaison.tableSQL(table));
if (!rs.next()) {
log.warning("Query on count()/max() bizarrely returned no rows.");
return null;
}
int cnt = rs.getInt(1);
if (cnt > 0) {
return Integer.valueOf(rs.getInt(2));
}
return null;
} finally {
JDBCUtil.close(stmt);
}
}
public DepotMarshaller<?> getDepotMarshaller ()
{
return _dm;
}
public FieldMarshaller<?> getFieldMarshaller ()
{
return _fm;
}
protected int _initialValue;
protected int _allocationSize;
protected boolean _migrateIfExists;
protected DepotMarshaller<?> _dm;
protected FieldMarshaller<?> _fm;
}
@@ -1,60 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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 com.samskivert.jdbc.depot.clause.QueryClause;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
* Currently only exists as a type without any functionality of its own.
*/
public abstract class WhereClause extends QueryClause
{
/**
* Returns the condition associated with this where clause.
*/
public abstract SQLExpression getWhereExpression ();
/**
* Validates that the supplied persistent record type is the type matched by this where clause.
* Not all clauses will be able to perform this validation, but those that can, should do so to
* help alleviate programmer error.
*
* @exception IllegalArgumentException thrown if the supplied class is known not to by the type
* matched by this where clause.
*/
public void validateQueryType (Class<?> pClass)
{
// nothing by default
}
/**
* A helper function for implementing {@link #validateQueryType}.
*/
protected void validateTypesMatch (Class<?> qClass, Class<?> kClass)
{
if (!qClass.equals(kClass)) {
throw new IllegalArgumentException(
"Class mismatch between persistent record and key in query " +
"[qtype=" + qClass.getSimpleName() + ", ktype=" + kClass.getSimpleName() + "].");
}
}
}
@@ -1,71 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Is used to specify a mapped column for a persistent property or field. If no Column annotation
* is specified, the default values are applied.
*/
@Target(value=ElementType.FIELD)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Column
{
/**
* The name of the column. Defaults to the field name.
*/
String name () default "";
/**
* Whether the property is a unique key. This is a shortcut for the UniqueConstraint annotation
* at the table level and is useful for when the unique key constraint is only a single
* field. This constraint applies in addition to any constraint entailed by primary key mapping
* and to constraints specified at the table level.
*/
boolean unique () default false;
/**
* Whether the database column is nullable. <em>Note:</em> this default differs from the value
* used by the EJB3 persistence framework.
*/
boolean nullable () default false;
/**
* The SQL type that is used when generating the DDL for the column.
*/
String type() default "";
/**
* The column length. (Applies to String and byte[] columns.)
*/
int length () default 255;
/**
* The SQL literal value to be used when defining this column's default value. The value must
* be quoted and escaped if it is not a SQL primitive datatype. For example:
* <code>'2006-01-01'</code> or <code>25</code> or <code>NULL</code>.
*/
String defaultValue () default "";
}
@@ -1,64 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.clause.FieldOverride;
import com.samskivert.jdbc.depot.clause.FromOverride;
import com.samskivert.jdbc.depot.clause.Join;
/**
* Marks a field as computed, meaning it is ignored for schema purposes and it does not directly
* correspond to a column in a table.
*/
@Retention(value=RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE })
public @interface Computed
{
/** If this value is false, the field is not populated at all. */
boolean required () default true;
/** A non-empty value here is taken as literal SQL and used to populate the computed field. */
String fieldDefinition () default "";
/**
* A computed record can shadow a concrete record, which causes any field the former has in
* common with the latter and which is not otherwise overriden to inherit its definition. The
* shadowed class may also be given at the field level.
*
* The purpose of shadowing is largely to avoid having to supply a {@link FieldOverride} when
* querying for objects that that contain large subsets of some other persistent object's
* fields -- in other words, when you use a computed entity to query only some of the columns
* from a table.
*
* The shadowed class must have been brought into the query using e.g. {@link FromOverride}
* or {@link Join} clauses. The referenced fields must be simple concrete columns in a table;
* they must themselves be computed or overridden or shadowing.
*
* TODO: Do in fact let the shadowed field be computed, overriden or shadowing.
*/
Class<? extends PersistentRecord> shadowOf () default PersistentRecord.class;
}
@@ -1,48 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies the primary field(s) of an entity.
*/
@Target(value=ElementType.TYPE)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Entity
{
/** The name of an entity. Defaults to the unqualified name of the entity class. */
String name () default "";
/** Unique constraints that are to be placed on this entity's table. These constraints apply in
* addition to any constraints specified by the Column annotation and constraints entailed by
* primary key mappings. Defaults to no additional constraints. */
UniqueConstraint[] uniqueConstraints () default {};
/** Indices to add to this entity's table. */
Index[] indices () default {};
/** Full-text search indexes defined on this entity, if any. Defaults to none. */
FullTextIndex[] fullTextIndices () default {};
}
@@ -1,44 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2007 Michael Bayne, Pär Winzell
//
// 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.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation is used to specify that a full text index is to be included in the generated DDL
* for a table.
*/
@Target(value={})
@Retention(value=RetentionPolicy.RUNTIME)
public @interface FullTextIndex
{
/**
* An identifier for this index, unique with the scope of the record.
*/
public String name ();
/**
* An array of the field names that should be indexed.
*/
public String[] fields ();
}
@@ -1,64 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Provides for the specification of generation strategies for the values of primary keys. The
* GeneratedValue annotation may be applied to a primary field in conjunction with the {@link Id}
* annotation.
*/
@Target(value=ElementType.FIELD)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface GeneratedValue
{
/** Identifies which generator should be used to generate this value when using a table or
* sequence generator. */
String generator () default "";
/** Identifies the strategy to be used to generate this value. */
GenerationType strategy () default GenerationType.AUTO;
/**
* The initial value to be used when allocating id numbers from the generator. The default
* initial value is 1. <em>Note:</em> this default differs from the value used by the EJB3
* persistence framework.
*/
int initialValue () default 1;
/**
* If there are rows in our corresponding table, this boolean determines whether or not to
* attempt to initialize the generator to the maximum value of our associated field over
* those rows. This attribute does not exist in the EJB3 framework.
*/
boolean migrateIfExists () default true;
/**
* The amount to increment by when allocating id numbers from the generator. The default
* allocation size is 1. <em>Note:</em> this default differs from the value used by the EJB3
* persistence framework.
*/
int allocationSize () default 1;
}
@@ -1,51 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.annotation;
/**
* Defines the types of primary key generation.
*/
public enum GenerationType
{
/**
* Indicates that the persistence provider must assign primary keys for the entity using an
* underlying database table to ensure uniqueness.
*/
TABLE,
/**
* Indicates that the persistence provider must assign primary keys for the entity using
* database sequences.
*/
SEQUENCE,
/**
* Indicates that the persistence provider must assign primary keys for the entity using
* database identity column.
*/
IDENTITY,
/**
* Indicates that the persistence provider should pick an appropriate strategy for the
* particular database.
*/
AUTO;
}
@@ -1,35 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies the primary key property or field of an entity.
*/
@Target(value=ElementType.FIELD)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Id
{
}
@@ -1,42 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Defines an index on an entity table.
*/
@Target(value={})
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Index
{
/** Defines the name of the index. */
String name ();
/** Does this index enforce a uniqueness constraint? */
boolean unique () default false;
/** Defines the fields on which the index operates. */
String[] fields () default {};
}
@@ -1,66 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation defines a primary key generator that may be referenced by name when a generator
* element is specified for the GeneratedValue annotation. A table generator may be specified on
* the entity class or on the primary key field. The scope of the generator name is global to the
* persistence unit (across all generator types).
*/
@Target(value={ElementType.TYPE, ElementType.FIELD})
@Retention(value=RetentionPolicy.RUNTIME)
public @interface TableGenerator
{
/**
* A unique generator name that can be referenced by one or more classes to be the
* generator for id values.
*/
String name ();
/**
* Name of table that stores the generated id values. Defaults to a name chosen by persistence
* provider.
*/
String table () default "";
/**
* Name of the primary key column in the table Defaults to a provider-chosen name.
*/
String pkColumnName () default "";
/**
* Name of the column that stores the last value generated Defaults to a provider-chosen name.
*/
String valueColumnName () default "";
/**
* The primary key value in the generator table that distinguishes this set of generated values
* from others that may be stored in the table Defaults to a provider-chosen value to store in
* the primary key column of the generator table
*/
String pkColumnValue () default "";
}
@@ -1,36 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation specifies that the property or field is not persistent. It is used to annotate
* a property or field of an entity class, mapped superclass, or embeddable class.
*/
@Target(value=ElementType.FIELD)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Transient
{
}
@@ -1,39 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation is used to specify that a unique constraint is to be included in the
* generated DDL for a table.
*/
@Target(value={})
@Retention(value=RetentionPolicy.RUNTIME)
public @interface UniqueConstraint
{
/**
* An array of the field names that make up the constraint
*/
public String[] fieldNames () default {};
}
@@ -1,67 +0,0 @@
//
// $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.clause;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.WhereClause;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
/**
* Builds actual SQL given a main persistent type and some {@link QueryClause} objects.
*/
public class DeleteClause<T extends PersistentRecord> extends QueryClause
{
public DeleteClause (Class<? extends PersistentRecord> pClass, WhereClause where)
{
_pClass = pClass;
_where = where;
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
public WhereClause getWhereClause ()
{
return _where;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
/** The type of persistent record on which we operate. */
protected Class<? extends PersistentRecord> _pClass;
/** The where clause. */
protected WhereClause _where;
}
@@ -1,89 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.clause;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.LiteralExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
* Supplies a definition for a computed field of the persistent object we're creating.
*
* Thus the select portion of a query can include a reference to a different column in a different
* table through a {@link ColumnExp}, or a literal expression such as COUNT(*) through a
* {@link LiteralExp}.
*
* @see FieldOverride
*/
public class FieldDefinition extends QueryClause
{
public FieldDefinition (String field, String str)
{
this(field, new LiteralExp(str));
}
public FieldDefinition (String field, Class<? extends PersistentRecord> pClass, String pCol)
{
this(field, new ColumnExp(pClass, pCol));
}
public FieldDefinition (String field, SQLExpression override)
{
_field = field;
_definition = override;
}
/**
* The field we're defining. The Query object uses this for indexing.
*/
public String getField ()
{
return _field;
}
public SQLExpression getDefinition ()
{
return _definition;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_definition.addClasses(classSet);
}
// from SQLExpression
public void accept (ExpressionVisitor visitor)
{
visitor.visit(this);
}
/** The name of the field on the persistent object to override. */
protected String _field;
/** The defining expression. */
protected SQLExpression _definition;
}
@@ -1,52 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.clause;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.LiteralExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
* Redirects one field of the persistent object we're creating from its default associated column
* to a general {@link SQLExpression}.
*
* Thus the select portion of a query can include a reference to a different column in a different
* table through a {@link ColumnExp}, or a literal expression such as COUNT(*) through a
* {@link LiteralExp}.
*/
public class FieldOverride extends FieldDefinition
{
public FieldOverride (String field, String str)
{
super(field, str);
}
public FieldOverride (String field, Class<? extends PersistentRecord> pClass, String pCol)
{
super(field, pClass, pCol);
}
public FieldOverride (String field, SQLExpression override)
{
super(field, override);
}
}
@@ -1,43 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.clause;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
/**
* Represents a FOR UPDATE clause.
*/
public class ForUpdate extends QueryClause
{
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
}
@@ -1,72 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.clause;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
/**
* Completely overrides the FROM clause, if it exists.
*/
public class FromOverride extends QueryClause
{
public FromOverride (Class<? extends PersistentRecord> fromClass)
{
_fromClasses.add(fromClass);
}
public FromOverride (Class<? extends PersistentRecord> fromClass1,
Class<? extends PersistentRecord> fromClass2)
{
_fromClasses.add(fromClass1);
_fromClasses.add(fromClass2);
}
public FromOverride (Collection<Class<? extends PersistentRecord>> fromClasses)
{
_fromClasses.addAll(fromClasses);
}
public List<Class<? extends PersistentRecord>> getFromClasses ()
{
return _fromClasses;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.addAll(getFromClasses());
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
/** The classes of the tables we're selecting from. */
protected List<Class<? extends PersistentRecord>> _fromClasses =
new ArrayList<Class<? extends PersistentRecord>>();
}
@@ -1,59 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.clause;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
* Represents a GROUP BY clause.
*/
public class GroupBy extends QueryClause
{
public GroupBy (SQLExpression... values)
{
_values = values;
}
public SQLExpression[] getValues ()
{
return _values;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
// I can't imagine a GROUP BY clause bringing in new tables... ?
}
/** The expressions that are generated for the clause. */
protected SQLExpression[] _values;
}
@@ -1,76 +0,0 @@
//
// $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.clause;
import java.util.Collection;
import java.util.Set;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
/**
* Builds actual SQL given a main persistent type and some {@link QueryClause} objects.
*/
public class InsertClause<T extends PersistentRecord> extends QueryClause
{
public InsertClause (
Class<? extends PersistentRecord> pClass, Object pojo, Set<String> identityFields)
{
_pClass = pClass;
_pojo = pojo;
_idFields = identityFields;
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
public Object getPojo ()
{
return _pojo;
}
public Set<String> getIdentityFields ()
{
return _idFields;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
// If we add SQLExpression[] values INSERT, remember to recurse into them here.
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
protected Class<? extends PersistentRecord> _pClass;
/** The object from which to fetch values, or null. */
protected Object _pojo;
protected Set<String> _idFields;
}
@@ -1,103 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.clause;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.operator.Conditionals.Equals;
/**
* Represents a JOIN.
*/
public class Join extends QueryClause
{
/** Indicates the join type to be used. The default is INNER. */
public static enum Type { INNER, LEFT_OUTER, RIGHT_OUTER };
public Join (Class<? extends PersistentRecord> pClass, String pCol,
Class<? extends PersistentRecord> joinClass, String jCol)
{
_joinClass = joinClass;
_joinCondition = new Equals(new ColumnExp(joinClass, jCol), new ColumnExp(pClass, pCol));
}
public Join (ColumnExp primary, ColumnExp join)
{
_joinClass = join.getPersistentClass();
_joinCondition = new Equals(primary, join);
}
public Join (Class<? extends PersistentRecord> joinClass, SQLExpression joinCondition)
{
_joinClass = joinClass;
_joinCondition = joinCondition;
}
/**
* Configures the type of join to be performed.
*/
public Join setType (Type type)
{
_type = type;
return this;
}
public Type getType ()
{
return _type;
}
public Class<? extends PersistentRecord> getJoinClass ()
{
return _joinClass;
}
public SQLExpression getJoinCondition ()
{
return _joinCondition;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_joinClass);
_joinCondition.addClasses(classSet);
}
/** Indicates the type of join to be performed. */
protected Type _type = Type.INNER;
/** The class of the table we're to join against. */
protected Class<? extends PersistentRecord> _joinClass;
/** The condition used to join in the new table. */
protected SQLExpression _joinCondition;
}
@@ -1,65 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.clause;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
/**
* Represents a LIMIT/OFFSET clause, for pagination.
*/
public class Limit extends QueryClause
{
public Limit (int offset, int count)
{
_offset = offset;
_count = count;
}
public int getOffset ()
{
return _offset;
}
public int getCount ()
{
return _count;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
/** The first row of the result set to return. */
protected int _offset;
/** The number of rows, at most, to return. */
protected int _count;
}
@@ -1,98 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.clause;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.LiteralExp;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
* Represents an ORDER BY clause.
*/
public class OrderBy extends QueryClause
{
/** Indicates the order of the clause. */
public enum Order { ASC, DESC };
/**
* Creates and returns a random order by clause.
*/
public static OrderBy random ()
{
return ascending(new LiteralExp("rand()"));
}
/**
* Creates and returns an ascending order by clause on the supplied expression.
*/
public static OrderBy ascending (SQLExpression value)
{
return new OrderBy(new SQLExpression[] { value } , new Order[] { Order.ASC });
}
/**
* Creates and returns a descending order by clause on the supplied expression.
*/
public static OrderBy descending (SQLExpression value)
{
return new OrderBy(new SQLExpression[] { value }, new Order[] { Order.DESC });
}
public OrderBy (SQLExpression[] values, Order[] orders)
{
_values = values;
_orders = orders;
}
public SQLExpression[] getValues ()
{
return _values;
}
public Order[] getOrders ()
{
return _orders;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
for (SQLExpression expression : _values) {
expression.addClasses(classSet);
}
}
/** The expressions that are generated for the clause. */
protected SQLExpression[] _values;
/** Whether the ordering is to be ascending or descending. */
protected Order[] _orders;
}
@@ -1,30 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.clause;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
* Represents a piece or modifier of an SQL query.
*/
public abstract class QueryClause implements SQLExpression
{
}
@@ -1,226 +0,0 @@
//
// $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.clause;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.WhereClause;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
/**
* Builds actual SQL given a main persistent type and some {@link QueryClause} objects.
*/
public class SelectClause<T extends PersistentRecord> extends QueryClause
{
/**
* 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.
*/
public SelectClause (Class<T> pClass, String[] fields, Collection<? extends QueryClause> clauses)
{
_pClass = pClass;
_fields = fields;
// iterate over the clauses and sort them into the different types we understand
for (QueryClause clause : clauses) {
if (clause == null) {
continue;
}
if (clause instanceof WhereClause) {
if (_where != null) {
throw new IllegalArgumentException(
"Query can't contain multiple Where clauses.");
}
_where = (WhereClause) clause;
} else if (clause instanceof FromOverride) {
if (_fromOverride != null) {
throw new IllegalArgumentException(
"Query can't contain multiple FromOverride clauses.");
}
_fromOverride = (FromOverride) clause;
} else if (clause instanceof Join) {
_joinClauses.add((Join) clause);
} else if (clause instanceof FieldDefinition) {
_disMap.put(((FieldDefinition) clause).getField(), ((FieldDefinition) 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;
} else {
throw new IllegalArgumentException(
"Unknown clause provided in select " + clause + ".");
}
}
}
/**
* A varargs version of the constructor.
*/
public SelectClause (Class<T> pClass, String[] fields, QueryClause... clauses)
{
this(pClass, fields, Arrays.asList(clauses));
}
public FieldDefinition lookupDefinition (String field)
{
return _disMap.get(field);
}
public Collection<FieldDefinition> getFieldDefinitions ()
{
return _disMap.values();
}
public Class<T> getPersistentClass ()
{
return _pClass;
}
public String[] getFields ()
{
return _fields;
}
public FromOverride getFromOverride ()
{
return _fromOverride;
}
public WhereClause getWhereClause ()
{
return _where;
}
public List<Join> getJoinClauses ()
{
return _joinClauses;
}
public OrderBy getOrderBy ()
{
return _orderBy;
}
public GroupBy getGroupBy ()
{
return _groupBy;
}
public Limit getLimit ()
{
return _limit;
}
public ForUpdate getForUpdate ()
{
return _forUpdate;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
if (_fromOverride != null) {
_fromOverride.addClasses(classSet);
}
if (_where != null) {
_where.addClasses(classSet);
}
for (Join join : _joinClauses) {
join.addClasses(classSet);
}
for (FieldDefinition override : _disMap.values()) {
override.addClasses(classSet);
}
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
/** Persistent class fields mapped to field override clauses. */
protected Map<String, FieldDefinition> _disMap = new HashMap<String, FieldDefinition>();
/** The persistent class this select defines. */
protected Class<T> _pClass;
/** The persistent fields to select. */
protected String[] _fields;
/** The from override clause, if any. */
protected FromOverride _fromOverride;
/** The where clause. */
protected WhereClause _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;
}
@@ -1,114 +0,0 @@
//
// $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.clause;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.WhereClause;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
* Builds actual SQL given a main persistent type and some {@link QueryClause} objects.
*/
public class UpdateClause<T extends PersistentRecord> extends QueryClause
{
public UpdateClause (Class<? extends PersistentRecord> pClass, WhereClause where,
String[] fields, T pojo)
{
_pClass = pClass;
_where = where;
_fields = fields;
_values = null;
_pojo = pojo;
}
public UpdateClause (Class<? extends PersistentRecord> pClass, WhereClause where,
String[] fields, SQLExpression[] values)
{
_pClass = pClass;
_fields = fields;
_where = where;
_values = values;
_pojo = null;
}
public WhereClause getWhereClause ()
{
return _where;
}
public String[] getFields ()
{
return _fields;
}
public SQLExpression[] getValues ()
{
return _values;
}
public Object getPojo ()
{
return _pojo;
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
if (_where != null) {
_where.addClasses(classSet);
}
if (_values != null) {
for (int ii = 0; ii < _values.length; ii ++) {
_values[ii].addClasses(classSet);
}
}
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
/** The class we're updating. */
protected Class<? extends PersistentRecord> _pClass;
/** The where clause. */
protected WhereClause _where;
/** The persistent fields to update. */
protected String[] _fields;
/** The field values, or null. */
protected SQLExpression[] _values;
/** The object from which to fetch values, or null. */
protected Object _pojo;
}
@@ -1,99 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.clause;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.WhereClause;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.expression.ValueExp;
import com.samskivert.jdbc.depot.operator.Conditionals.Equals;
import com.samskivert.jdbc.depot.operator.Conditionals.IsNull;
import com.samskivert.jdbc.depot.operator.Logic.And;
/**
* Represents a where clause: the condition can be any comparison operator or logical combination
* thereof.
*/
public class Where extends WhereClause
{
public Where (ColumnExp column, Comparable<?> value)
{
this(new ColumnExp[] { column }, new Comparable<?>[] { value });
}
public Where (ColumnExp index1, Comparable<?> value1,
ColumnExp index2, Comparable<?> value2)
{
this(new ColumnExp[] { index1, index2 }, new Comparable<?>[] { value1, value2 });
}
public Where (ColumnExp index1, Comparable<?> value1,
ColumnExp index2, Comparable<?> value2,
ColumnExp index3, Comparable<?> value3)
{
this(new ColumnExp[] { index1, index2, index3 },
new Comparable<?>[] { value1, value2, value3 });
}
public Where (ColumnExp[] columns, Comparable<?>[] values)
{
this(toCondition(columns, values));
}
public Where (SQLExpression condition)
{
_condition = condition;
}
// from WhereClause
public SQLExpression getWhereExpression ()
{
return _condition;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_condition.addClasses(classSet);
}
protected static SQLExpression toCondition (ColumnExp[] columns, Comparable<?>[] values)
{
SQLExpression[] comparisons = new SQLExpression[columns.length];
for (int ii = 0; ii < columns.length; ii ++) {
comparisons[ii] = (values[ii] == null) ? new IsNull(columns[ii]) :
new Equals(columns[ii], new ValueExp(values[ii]));
}
return new And(comparisons);
}
protected SQLExpression _condition;
}
@@ -1,67 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.expression;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
/**
* An expression that unambiguously identifies a field of a class, e.g. GameRecord.itemId.
*/
public class ColumnExp
implements SQLExpression
{
public ColumnExp (Class<? extends PersistentRecord> pClass, String field)
{
super();
_pClass = pClass;
_pField = field;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
classSet.add(_pClass);
}
public Class<? extends PersistentRecord> getPersistentClass ()
{
return _pClass;
}
public String getField ()
{
return _pField;
}
/** The table that hosts the column we reference, or null. */
protected final Class<? extends PersistentRecord> _pClass;
/** The name of the column we reference. */
protected final String _pField;
}
@@ -1,59 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.expression;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
/**
* An expression for extracting the seconds since the epoch from a date expression.
*/
public class EpochSeconds implements SQLExpression
{
/**
* Create a new EpochSeconds with the given argument.
*/
public EpochSeconds (SQLExpression arg)
{
_arg = arg;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_arg.addClasses(classSet);
}
public SQLExpression getArgument ()
{
return _arg;
}
/** The argument. */
protected SQLExpression _arg;
}
@@ -1,79 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006 Michael Bayne, Pär Winzell
//
// 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.expression;
import com.samskivert.jdbc.depot.Key;
import com.samskivert.jdbc.depot.MultiKey;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.WhereClause;
import com.samskivert.jdbc.depot.clause.DeleteClause;
import com.samskivert.jdbc.depot.clause.FieldDefinition;
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.InsertClause;
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.SelectClause;
import com.samskivert.jdbc.depot.clause.UpdateClause;
import com.samskivert.jdbc.depot.operator.Conditionals.Exists;
import com.samskivert.jdbc.depot.operator.Conditionals.In;
import com.samskivert.jdbc.depot.operator.Conditionals.IsNull;
import com.samskivert.jdbc.depot.operator.Conditionals.FullTextMatch;
import com.samskivert.jdbc.depot.operator.Logic.Not;
import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator;
import com.samskivert.jdbc.depot.operator.SQLOperator.MultiOperator;
/**
* Enumerates visitation methods for every possible SQL expression type.
*/
public interface ExpressionVisitor
{
public void visit (FieldDefinition fieldOverride);
public void visit (FunctionExp functionExp);
public void visit (EpochSeconds epochSeconds);
public void visit (FromOverride fromOverride);
public void visit (MultiOperator multiOperator);
public void visit (BinaryOperator binaryOperator);
public void visit (IsNull isNull);
public void visit (In in);
public void visit (FullTextMatch match);
public void visit (ColumnExp columnExp);
public void visit (Not not);
public void visit (GroupBy groupBy);
public void visit (ForUpdate forUpdate);
public void visit (OrderBy orderBy);
public void visit (Join join);
public void visit (Limit limit);
public void visit (LiteralExp literalExp);
public void visit (ValueExp valueExp);
public void visit (WhereClause where);
public void visit (Key.Expression<? extends PersistentRecord> key);
public void visit (MultiKey<? extends PersistentRecord> key);
public void visit (Exists<? extends PersistentRecord> exists);
public void visit (SelectClause<? extends PersistentRecord> selectClause);
public void visit (UpdateClause<? extends PersistentRecord> updateClause);
public void visit (DeleteClause<? extends PersistentRecord> deleteClause);
public void visit (InsertClause<? extends PersistentRecord> insertClause);
}
@@ -1,70 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.expression;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
/**
* An expression for a function, e.g. FLOOR(blah).
*/
public class FunctionExp implements SQLExpression
{
/**
* Create a new FunctionExp with the given function and arguments.
*/
public FunctionExp (String function, SQLExpression... arguments)
{
_function = function;
_arguments = arguments;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
for (int ii = 0; ii < _arguments.length; ii ++) {
_arguments[ii].addClasses(classSet);
}
}
public String getFunction ()
{
return _function;
}
public SQLExpression[] getArguments ()
{
return _arguments;
}
/** The literal name of this function, e.g. FLOOR */
protected String _function;
/** The arguments to this function */
protected SQLExpression[] _arguments;
}
@@ -1,57 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.expression;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
/**
* An expression for things we don't support natively, e.g. COUNT(*).
*/
public class LiteralExp
implements SQLExpression
{
public LiteralExp (String text)
{
super();
_text = text;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
public String getText ()
{
return _text;
}
/** The literal text of this expression, e.g. COUNT(*) */
protected String _text;
}
@@ -1,48 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.expression;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.SQLBuilder;
/**
* Represents an SQL expression, e.g. column name, function, or constant.
*/
public interface SQLExpression
{
/**
* Most uses of this class have been implemented with a visitor pattern. Create your own
* {@link ExpressionVisitor} and call this method with it.
*
* @see SQLBuilder
*/
public void accept (ExpressionVisitor builder);
/**
* Adds all persistent classes that are brought into the SQL context by this clause: FROM
* clauses, JOINs, UPDATEs, anything that could create a new table abbreviation. This method
* should recurse into any subordinate state that may in turn bring in new classes so that
* sub-queries work correctly.
*/
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet);
}
@@ -1,56 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.expression;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
/**
* A Java value that is bound as a parameter to the query, e.g. 1 or 'abc'.
*/
public class ValueExp
implements SQLExpression
{
public ValueExp (Object _value)
{
this._value = _value;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
public Object getValue ()
{
return _value;
}
/** The value to be bound to the SQL parameters. */
protected Object _value;
}
@@ -1,145 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.operator;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.operator.SQLOperator.BinaryOperator;
/**
* A convenient container for implementations of arithmetic operators. Classes that value brevity
* that feel otherwise will use Arithmetic.Add() and Arithmetic.Sub().
*/
public abstract class Arithmetic
{
/** The SQL '+' operator. */
public static class Add extends BinaryOperator
{
public Add (SQLExpression column, Comparable<?> value)
{
super(column, value);
}
public Add (SQLExpression column, SQLExpression value)
{
super(column, value);
}
@Override public String operator()
{
return "+";
}
}
/** The SQL '-' operator. */
public static class Sub extends BinaryOperator
{
public Sub (SQLExpression column, Comparable<?> value)
{
super(column, value);
}
public Sub (SQLExpression column, SQLExpression value)
{
super(column, value);
}
@Override public String operator()
{
return "-";
}
}
/** The SQL '*' operator. */
public static class Mul extends BinaryOperator
{
public Mul (SQLExpression column, Comparable<?> value)
{
super(column, value);
}
public Mul (SQLExpression column, SQLExpression value)
{
super(column, value);
}
@Override public String operator()
{
return "*";
}
}
/** The SQL '/' operator. */
public static class Div extends BinaryOperator
{
public Div (SQLExpression column, Comparable<?> value)
{
super(column, value);
}
public Div (SQLExpression column, SQLExpression value)
{
super(column, value);
}
@Override public String operator()
{
return " / "; // Pad with spaces to work-around a MySQL bug.
}
}
/** The SQL '&' operator. */
public static class BitAnd extends BinaryOperator
{
public BitAnd (SQLExpression column, Comparable<?> value)
{
super(column, value);
}
public BitAnd (SQLExpression column, SQLExpression value)
{
super(column, value);
}
@Override public String operator()
{
return "&";
}
}
/** The SQL '|' operator. */
public static class BitOr extends BinaryOperator
{
public BitOr (SQLExpression column, Comparable<?> value)
{
super(column, value);
}
public BitOr (SQLExpression column, SQLExpression value)
{
super(column, value);
}
@Override public String operator()
{
return "|";
}
}
}
@@ -1,336 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.operator;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.clause.SelectClause;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
* A convenient container for implementations of conditional operators. Classes that value brevity
* classes that feel otherwise will use Conditionals.Equals() and Conditionals.In().
*/
public abstract class Conditionals
{
/** The SQL 'is null' operator. */
public static class IsNull
implements SQLOperator
{
public IsNull (String pColumn)
{
this(new ColumnExp(null, pColumn));
}
public IsNull (Class<? extends PersistentRecord> pClass, String pColumn)
{
this(new ColumnExp(pClass, pColumn));
}
public IsNull (ColumnExp column)
{
_column = column;
}
public ColumnExp getColumn()
{
return _column;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
protected ColumnExp _column;
}
/** The SQL '=' operator. */
public static class Equals extends SQLOperator.BinaryOperator
{
public Equals (SQLExpression column, Comparable<?> value)
{
super(column, value);
}
public Equals (SQLExpression column, SQLExpression value)
{
super(column, value);
}
@Override public String operator()
{
return "=";
}
}
/** The SQL '!=' operator. */
public static class NotEquals extends SQLOperator.BinaryOperator
{
public NotEquals (SQLExpression column, Comparable<?> value)
{
super(column, value);
}
public NotEquals (SQLExpression column, SQLExpression value)
{
super(column, value);
}
@Override public String operator()
{
return "!=";
}
}
/** The SQL '<' operator. */
public static class LessThan extends SQLOperator.BinaryOperator
{
public LessThan (SQLExpression column, Comparable<?> value)
{
super(column, value);
}
public LessThan (SQLExpression column, SQLExpression value)
{
super(column, value);
}
@Override public String operator()
{
return "<";
}
}
/** The SQL '<=' operator. */
public static class LessThanEquals extends SQLOperator.BinaryOperator
{
public LessThanEquals (SQLExpression column, Comparable<?> value)
{
super(column, value);
}
public LessThanEquals (SQLExpression column, SQLExpression value)
{
super(column, value);
}
@Override public String operator()
{
return "<=";
}
}
/** The SQL '>' operator. */
public static class GreaterThan extends SQLOperator.BinaryOperator
{
public GreaterThan (SQLExpression column, Comparable<?> value)
{
super(column, value);
}
public GreaterThan (SQLExpression column, SQLExpression value)
{
super(column, value);
}
@Override public String operator()
{
return ">";
}
}
/** The SQL '>=' operator. */
public static class GreaterThanEquals extends SQLOperator.BinaryOperator
{
public GreaterThanEquals (SQLExpression column, Comparable<?> value)
{
super(column, value);
}
public GreaterThanEquals (SQLExpression column, SQLExpression value)
{
super(column, value);
}
@Override public String operator()
{
return ">=";
}
}
/** The SQL 'in (...)' operator. */
public static class In
implements SQLOperator
{
/** The maximum number of keys allowed in an IN() clause. */
public static final int MAX_KEYS = Short.MAX_VALUE;
public In (Class<? extends PersistentRecord> pClass, String pColumn, Comparable<?>... values)
{
this(new ColumnExp(pClass, pColumn), values);
}
public In (Class<? extends PersistentRecord> pClass, String pColumn,
Collection<? extends Comparable<?>> values)
{
this(new ColumnExp(pClass, pColumn), values.toArray(new Comparable<?>[values.size()]));
}
public In (ColumnExp column, Comparable<?>... values)
{
if (values.length == 0) {
throw new IllegalArgumentException("In() condition needs at least one value.");
}
_column = column;
_values = values;
}
public In (ColumnExp pColumn, Collection<? extends Comparable<?>> values)
{
this(pColumn, values.toArray(new Comparable<?>[values.size()]));
}
public ColumnExp getColumn ()
{
return _column;
}
public Comparable<?>[] getValues ()
{
return _values;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_column.addClasses(classSet);
}
protected ColumnExp _column;
protected Comparable<?>[] _values;
}
/** The SQL ' like ' operator. */
public static class Like extends SQLOperator.BinaryOperator
{
public Like (SQLExpression column, Comparable<?> value)
{
super(column, value);
}
public Like (SQLExpression column, SQLExpression value)
{
super(column, value);
}
@Override public String operator()
{
return " like ";
}
}
/** The SQL ' exists' operator. */
public static class Exists<T extends PersistentRecord> implements SQLOperator
{
public Exists (SelectClause<T> clause)
{
_clause = clause;
}
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_clause.addClasses(classSet);
}
public SelectClause<T> getSubClause ()
{
return _clause;
}
protected SelectClause<T> _clause;
}
/**
* An attempt at a dialect-agnostic full-text search condition, such as MySQL's MATCH() and
* PostgreSQL's @@ TO_TSQUERY(...) abilities.
*/
public static class FullTextMatch
implements SQLOperator
{
public FullTextMatch (Class<? extends PersistentRecord> pClass, String name, String query)
{
_pClass = pClass;
_name = name;
_query = query;
}
public Class<? extends PersistentRecord> getPersistentRecord ()
{
return _pClass;
}
public String getQuery ()
{
return _query;
}
public String getName ()
{
return _name;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
}
protected Class<? extends PersistentRecord> _pClass;
protected String _name;
protected String _query;
}
}
@@ -1,107 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.operator;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
/**
* A convenient container for implementations of logical operators. Classes that value brevity
* feel otherwise will use Logic.And() and Logic.Not().
*/
public abstract class Logic
{
/**
* Represents a condition that is false iff all its subconditions are false.
*/
public static class Or extends SQLOperator.MultiOperator
{
public Or (Collection<? extends SQLExpression> conditions)
{
super(conditions.toArray(new SQLExpression[conditions.size()]));
}
public Or (SQLExpression... conditions)
{
super(conditions);
}
@Override public String operator()
{
return "or";
}
}
/**
* Represents a condition that is true iff all its subconditions are true.
*/
public static class And extends SQLOperator.MultiOperator
{
public And (Collection<? extends SQLExpression> conditions)
{
super(conditions.toArray(new SQLExpression[conditions.size()]));
}
public And (SQLExpression... conditions)
{
super(conditions);
}
@Override public String operator()
{
return "and";
}
}
/**
* Represents the truth negation of another conditon.
*/
public static class Not
implements SQLOperator
{
public Not (SQLExpression condition)
{
_condition = condition;
}
public SQLExpression getCondition ()
{
return _condition;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_condition.addClasses(classSet);
}
protected SQLExpression _condition;
}
}
@@ -1,122 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.operator;
import java.util.Collection;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.expression.ExpressionVisitor;
import com.samskivert.jdbc.depot.expression.SQLExpression;
import com.samskivert.jdbc.depot.expression.ValueExp;
/**
* A common interface for operator hierarchies in SQL. The main purpose of breaking this out from
* SQLExpression is to capture the recursive nature of e.g. the logical operators, which work on
* other SQLOperators but not general SQLExpressions.
*/
public interface SQLOperator extends SQLExpression
{
/**
* Represents an operator with any number of operands.
*/
public abstract static class MultiOperator
implements SQLOperator
{
public MultiOperator (SQLExpression ... conditions)
{
_conditions = conditions;
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
for (int ii = 0; ii < _conditions.length; ii ++) {
_conditions[ii].addClasses(classSet);
}
}
public SQLExpression[] getConditions ()
{
return _conditions;
}
/**
* Returns the text infix to be used to join expressions together.
*/
public abstract String operator ();
protected SQLExpression[] _conditions;
}
/**
* Does the real work for simple binary operators such as Equals.
*/
public abstract static class BinaryOperator implements SQLOperator
{
public BinaryOperator (SQLExpression lhs, SQLExpression rhs)
{
_lhs = lhs;
_rhs = rhs;
}
public BinaryOperator (SQLExpression lhs, Comparable<?> rhs)
{
this(lhs, new ValueExp(rhs));
}
// from SQLExpression
public void accept (ExpressionVisitor builder)
{
builder.visit(this);
}
// from SQLExpression
public void addClasses (Collection<Class<? extends PersistentRecord>> classSet)
{
_lhs.addClasses(classSet);
_rhs.addClasses(classSet);
}
/**
* Returns the string representation of the operator.
*/
public abstract String operator();
public SQLExpression getLeftHandSide ()
{
return _lhs;
}
public SQLExpression getRightHandSide ()
{
return _rhs;
}
protected SQLExpression _lhs;
protected SQLExpression _rhs;
}
}
@@ -1,128 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.tests;
import java.sql.Date;
import java.sql.Timestamp;
import com.samskivert.jdbc.depot.Key;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.annotation.Entity;
import com.samskivert.jdbc.depot.annotation.Id;
import com.samskivert.jdbc.depot.annotation.Index;
import com.samskivert.jdbc.depot.expression.ColumnExp;
import com.samskivert.util.StringUtil;
/**
* A test persistent object.
*/
@Entity(indices={ @Index(name="createdIndex", fields={"created"}) })
public class TestRecord extends PersistentRecord
{
// AUTO-GENERATED: FIELDS START
/** The column identifier for the {@link #recordId} field. */
public static final String RECORD_ID = "recordId";
/** The qualified column identifier for the {@link #recordId} field. */
public static final ColumnExp RECORD_ID_C =
new ColumnExp(TestRecord.class, RECORD_ID);
/** The column identifier for the {@link #name} field. */
public static final String NAME = "name";
/** The qualified column identifier for the {@link #name} field. */
public static final ColumnExp NAME_C =
new ColumnExp(TestRecord.class, NAME);
/** The column identifier for the {@link #age} field. */
public static final String AGE = "age";
/** The qualified column identifier for the {@link #age} field. */
public static final ColumnExp AGE_C =
new ColumnExp(TestRecord.class, AGE);
/** The column identifier for the {@link #homeTown} field. */
public static final String HOME_TOWN = "homeTown";
/** The qualified column identifier for the {@link #homeTown} field. */
public static final ColumnExp HOME_TOWN_C =
new ColumnExp(TestRecord.class, HOME_TOWN);
/** The column identifier for the {@link #created} field. */
public static final String CREATED = "created";
/** The qualified column identifier for the {@link #created} field. */
public static final ColumnExp CREATED_C =
new ColumnExp(TestRecord.class, CREATED);
/** The column identifier for the {@link #lastModified} field. */
public static final String LAST_MODIFIED = "lastModified";
/** The qualified column identifier for the {@link #lastModified} field. */
public static final ColumnExp LAST_MODIFIED_C =
new ColumnExp(TestRecord.class, LAST_MODIFIED);
/** The column identifier for the {@link #numbers} field. */
public static final String NUMBERS = "numbers";
/** The qualified column identifier for the {@link #numbers} field. */
public static final ColumnExp NUMBERS_C =
new ColumnExp(TestRecord.class, NUMBERS);
// AUTO-GENERATED: FIELDS END
public static final int SCHEMA_VERSION = 3;
@Id
public int recordId;
public String name;
public int age;
public String homeTown;
public Date created;
public Timestamp lastModified;
public int[] numbers;
@Override
public String toString ()
{
return StringUtil.fieldsToString(this);
}
// AUTO-GENERATED: METHODS START
/**
* Create and return a primary {@link Key} to identify a {@link TestRecord}
* with the supplied key values.
*/
public static Key<TestRecord> getKey (int recordId)
{
return new Key<TestRecord>(
TestRecord.class,
new String[] { RECORD_ID },
new Comparable[] { recordId });
}
// AUTO-GENERATED: METHODS END
}
@@ -1,126 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.tests;
import java.sql.Date;
import java.sql.Timestamp;
// import java.util.HashSet;
import java.util.Set;
import com.samskivert.util.RandomUtil;
import com.samskivert.jdbc.StaticConnectionProvider;
import com.samskivert.jdbc.depot.DepotRepository;
// import com.samskivert.jdbc.depot.Key;
// import com.samskivert.jdbc.depot.KeySet;
import com.samskivert.jdbc.depot.PersistenceContext;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.SchemaMigration;
import com.samskivert.jdbc.depot.clause.Where;
import com.samskivert.jdbc.depot.expression.LiteralExp;
import com.samskivert.jdbc.depot.operator.Conditionals;
/**
* A test tool for the Depot repository services.
*/
public class TestRepository extends DepotRepository
{
public static void main (String[] args)
throws Exception
{
PersistenceContext perCtx = new PersistenceContext();
perCtx.init("test", new StaticConnectionProvider("depot.properties"), null);
// tests a bogus rename migration
// perCtx.registerMigration(TestRecord.class, new SchemaMigration.Rename(1, "foo", "bar"));
// tests a custom add column migration
perCtx.registerMigration(TestRecord.class,
new SchemaMigration.Add(2, TestRecord.HOME_TOWN, "'Anytown USA'"));
TestRepository repo = new TestRepository(perCtx);
repo.delete(TestRecord.class, 1);
Date now = new Date(System.currentTimeMillis());
Timestamp tnow = new Timestamp(System.currentTimeMillis());
TestRecord record = new TestRecord();
record.recordId = 1;
record.name = "Elvis";
record.age = 99;
record.created = now;
record.homeTown = "Right here";
record.lastModified = tnow;
record.numbers = new int[] { 9, 0, 2, 1, 0 };
repo.insert(record);
System.out.println(repo.load(TestRecord.class, record.recordId));
// record.age = 25;
// record.name = "Bob";
// record.numbers = new int[] { 1, 2, 3, 4, 5 };
// repo.update(record, TestRecord.AGE, TestRecord.NAME, TestRecord.NUMBERS);
repo.updatePartial(TestRecord.class, record.recordId,
TestRecord.AGE, 25, TestRecord.NAME, "Bob",
TestRecord.NUMBERS, new int[] { 1, 2, 3, 4, 5 });
System.out.println(repo.load(TestRecord.class, record.recordId));
for (int ii = 2; ii < CREATE_RECORDS; ii++) {
record = new TestRecord();
record.recordId = ii;
record.name = "Spam!";
record.age = RandomUtil.getInt(150);
record.homeTown = "Over there";
record.numbers = new int[] { 5, 4, 3, 2, 1 };
record.created = now;
record.lastModified = tnow;
repo.insert(record);
}
System.out.println("Have " + repo.findAll(TestRecord.class).size() + " records.");
repo.deleteAll(TestRecord.class, new Where(new Conditionals.LessThan(
TestRecord.RECORD_ID_C, CREATE_RECORDS/2)));
System.out.println("Now have " + repo.findAll(TestRecord.class).size() + " records.");
repo.deleteAll(TestRecord.class, new Where(new LiteralExp("true")));
// // TODO: try to break our In() clause
// Set<Key<TestRecord>> ids = new HashSet<Key<TestRecord>>();
// for (int ii = 1; ii <= Conditionals.In.MAX_KEYS*2+3; ii++) {
// ids.add(TestRecord.getKey(ii));
// }
// repo.deleteAll(TestRecord.class, new KeySet<TestRecord>(TestRecord.class, ids));
System.out.println("Now have " + repo.findAll(TestRecord.class).size() + " records.");
}
public TestRepository (PersistenceContext perCtx)
{
super(perCtx);
}
@Override // from DepotRepository
protected void getManagedRecords (Set<Class<? extends PersistentRecord>> classes)
{
classes.add(TestRecord.class);
}
protected static final int CREATE_RECORDS = 150;
}
@@ -1,493 +0,0 @@
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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.tools;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.util.ClasspathUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import com.samskivert.jdbc.depot.PersistentRecord;
import com.samskivert.jdbc.depot.annotation.Id;
import com.samskivert.jdbc.depot.annotation.Transient;
import com.samskivert.util.ClassUtil;
import com.samskivert.util.GenUtil;
import com.samskivert.util.StringUtil;
import com.samskivert.velocity.VelocityUtil;
/**
* An ant task that updates the column constants for a persistent record.
*/
public class GenRecordTask extends Task
{
/**
* Adds a nested fileset element which enumerates record source files.
*/
public void addFileset (FileSet set)
{
_filesets.add(set);
}
/**
* Configures that classpath that we'll use to load record classes.
*/
public void setClasspathref (Reference pathref)
{
_cloader = ClasspathUtils.getClassLoaderForPath(getProject(), pathref);
}
@Override
public void execute () throws BuildException
{
if (_cloader == null) {
String errmsg = "This task requires a 'classpathref' attribute " +
"to be set to the project's classpath.";
throw new BuildException(errmsg);
}
try {
_velocity = VelocityUtil.createEngine();
} catch (Exception e) {
throw new BuildException("Failure initializing Velocity", e);
}
// resolve the PersistentRecord class using our classloader
try {
_prclass = _cloader.loadClass(PersistentRecord.class.getName());
} catch (Exception e) {
throw new BuildException("Can't resolve InvocationListener", e);
}
for (FileSet fs : _filesets) {
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
File fromDir = fs.getDir(getProject());
String[] srcFiles = ds.getIncludedFiles();
for (int f = 0; f < srcFiles.length; f++) {
processRecord(new File(fromDir, srcFiles[f]));
}
}
}
/**
* Processes a distributed object source file.
*/
protected void processRecord (File source)
{
// System.err.println("Processing " + source + "...");
// load up the file and determine it's package and classname
String name = null;
try {
name = readClassName(source);
} catch (Exception e) {
System.err.println("Failed to parse " + source + ": " + e.getMessage());
}
try {
processRecord(source, _cloader.loadClass(name));
} catch (ClassNotFoundException cnfe) {
System.err.println("Failed to load " + name + ".\n" +
"Missing class: " + cnfe.getMessage());
System.err.println("Be sure to set the 'classpathref' attribute to a classpath\n" +
"that contains your projects invocation service classes.");
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
/** Processes a resolved persistent record class instance. */
protected void processRecord (File source, Class<?> rclass)
{
// make sure we extend persistent record
if (!_prclass.isAssignableFrom(rclass)) {
// System.err.println("Skipping " + rclass.getName() + "...");
return;
}
boolean isAbstract = Modifier.isAbstract(rclass.getModifiers());
// determine our primary key fields for getKey() generation (if we're not an abstract)
List<Field> kflist = new ArrayList<Field>();
if (!isAbstract) {
// determine which fields make up our primary key; we'd just use Class.getFields() but
// that returns things in a random order whereas ClassUtil returns fields in
// declaration order starting from the top-most class and going down the line
for (Field field : ClassUtil.getFields(rclass)) {
if (hasAnnotation(field, Id.class)) {
kflist.add(field);
continue;
}
}
}
// determine which fields we need to generate constants for
List<Field> flist = new ArrayList<Field>();
for (Field field : rclass.getFields()) {
if (isPersistentField(field)) {
flist.add(field);
}
}
Set<Field> declared = new HashSet<Field>();
for (Field field : rclass.getDeclaredFields()) {
if (isPersistentField(field)) {
declared.add(field);
}
}
// slurp our source file into newline separated strings
String[] lines = null;
try {
BufferedReader bin = new BufferedReader(new FileReader(source));
ArrayList<String> llist = new ArrayList<String>();
String line = null;
while ((line = bin.readLine()) != null) {
llist.add(line);
}
lines = llist.toArray(new String[llist.size()]);
bin.close();
} catch (IOException ioe) {
System.err.println("Error reading '" + source + "': " + ioe);
return;
}
// now determine where to insert our static field declarations
int bstart = -1, bend = -1;
int nstart = -1, nend = -1;
int mstart = -1, mend = -1;
for (int ii = 0; ii < lines.length; ii++) {
String line = lines[ii].trim();
// look for the start of the class body
if (NAME_PATTERN.matcher(line).find()) {
if (line.endsWith("{")) {
bstart = ii+1;
} else {
// search down a few lines for the open brace
for (int oo = 1; oo < 10; oo++) {
if (get(lines, ii+oo).trim().endsWith("{")) {
bstart = ii+oo+1;
break;
}
}
}
// track the last } on a line by itself and we'll call that the end of the class body
} else if (line.equals("}")) {
bend = ii;
// look for our field and method markers
} else if (line.equals(FIELDS_START)) {
nstart = ii;
} else if (line.equals(FIELDS_END)) {
nend = ii+1;
} else if (line.equals(METHODS_START)) {
mstart = ii;
} else if (line.equals(METHODS_END)) {
mend = ii+1;
}
}
// sanity check the markers
if (check(source, "fields start", nstart, "fields end", nend) ||
check(source, "fields end", nend, "fields start", nstart) ||
check(source, "methods start", mstart, "methods end", mend) ||
check(source, "methods end", mend, "methods start", mstart)) {
return;
}
// we have no previous markers then stuff the fields at the top of the class body and the
// methods at the bottom
if (nstart == -1) {
nstart = bstart;
nend = bstart;
}
if (mstart == -1) {
mstart = bend;
mend = bend;
}
// get the unqualified class name
String rname = rclass.getName();
rname = rname.substring(rname.lastIndexOf(".")+1);
// generate our fields section
StringBuilder fsection = new StringBuilder();
for (int ii = 0; ii < flist.size(); ii++) {
Field f = flist.get(ii);
String fname = f.getName();
// create our velocity context
VelocityContext ctx = new VelocityContext();
ctx.put("record", rname);
ctx.put("field", fname);
ctx.put("capfield", StringUtil.unStudlyName(fname).toUpperCase());
// now generate our bits
if (declared.contains(f)) {
if (fsection.length() > 0) {
fsection.append("\n");
}
fsection.append(mergeTemplate(NAME_TMPL, ctx));
}
if (!isAbstract) {
if (fsection.length() > 0) {
fsection.append("\n");
}
fsection.append(mergeTemplate(COL_TMPL, ctx));
}
}
// generate our methods section
StringBuilder msection = new StringBuilder();
// add a getKey() method, if applicable
if (kflist.size() > 0) {
// create our velocity context
VelocityContext ctx = new VelocityContext();
ctx.put("record", rname);
StringBuilder argList = new StringBuilder();
StringBuilder argNameList = new StringBuilder();
StringBuilder fieldNameList = new StringBuilder();
for (Field keyField : kflist) {
if (argList.length() > 0) {
argList.append(", ");
argNameList.append(", ");
fieldNameList.append(", ");
}
String name = keyField.getName();
argList.append(GenUtil.simpleName(keyField)).append(" ").append(name);
argNameList.append(name);
fieldNameList.append(StringUtil.unStudlyName(name));
}
ctx.put("argList", argList.toString());
ctx.put("argNameList", argNameList.toString());
ctx.put("fieldNameList", fieldNameList.toString());
// generate our bits and append them as appropriate to the string buffers
msection.append(mergeTemplate(KEY_TMPL, ctx));
}
// now bolt everything back together into a class declaration
try {
BufferedWriter bout = new BufferedWriter(new FileWriter(source));
for (int ii = 0; ii < nstart; ii++) {
writeln(bout, lines[ii]);
}
if (fsection.length() > 0) {
String prev = get(lines, nstart-1);
if (!StringUtil.isBlank(prev) && !prev.equals("{")) {
bout.newLine();
}
writeln(bout, " " + FIELDS_START);
bout.write(fsection.toString());
writeln(bout, " " + FIELDS_END);
if (!StringUtil.isBlank(get(lines, nend))) {
bout.newLine();
}
}
for (int ii = nend; ii < mstart; ii++) {
writeln(bout, lines[ii]);
}
if (msection.length() > 0) {
if (!StringUtil.isBlank(get(lines, mstart-1))) {
bout.newLine();
}
writeln(bout, " " + METHODS_START);
bout.write(msection.toString());
writeln(bout, " " + METHODS_END);
String next = get(lines, mend);
if (!StringUtil.isBlank(next) && !next.equals("}")) {
bout.newLine();
}
}
for (int ii = mend; ii < lines.length; ii++) {
writeln(bout, lines[ii]);
}
bout.close();
} catch (IOException ioe) {
System.err.println("Error writing to '" + source + "': " + ioe);
}
}
/**
* Returns true if the supplied field is part of a persistent record (is a public, non-static,
* non-transient field).
*/
protected boolean isPersistentField (Field field)
{
int mods = field.getModifiers();
return Modifier.isPublic(mods) && !Modifier.isStatic(mods) &&
!Modifier.isTransient(mods) && !hasAnnotation(field, Transient.class);
}
/**
* Safely gets the <code>index</code>th line, returning the empty string if we exceed the
* length of the array.
*/
protected String get (String[] lines, int index)
{
return (index < lines.length) ? lines[index] : "";
}
/** Helper function for sanity checking marker existence. */
protected boolean check (File source, String mname, int mline, String fname, int fline)
{
if (mline == -1 && fline != -1) {
System.err.println("Found " + fname + " marker (at line " + (fline+1) + ") but no " +
mname + " marker in '" + source + "'.");
return true;
}
return false;
}
/** Helper function for writing a string and a newline to a writer. */
protected void writeln (BufferedWriter bout, String line)
throws IOException
{
bout.write(line);
bout.newLine();
}
/** Helper function for generating our boilerplate code. */
protected String mergeTemplate (String tmpl, VelocityContext ctx)
{
StringWriter writer = new StringWriter();
try {
_velocity.mergeTemplate(tmpl, "UTF-8", ctx, writer);
} catch (Exception e) {
System.err.println("Failed processing template [tmpl=" + tmpl + "]");
e.printStackTrace(System.err);
}
return writer.toString();
}
protected static boolean hasAnnotation (Field field, Class<?> annotation)
{
// iterate becase getAnnotation() fails if we're dealing with multiple classloaders
for (Annotation a : field.getAnnotations()) {
if (annotation.getName().equals(a.annotationType().getName())) {
return true;
}
}
return false;
}
/**
* Reads in the supplied source file and locates the package and class or interface name and
* returns a fully qualified class name.
*/
protected static String readClassName (File source)
throws IOException
{
// load up the file and determine it's package and classname
String pkgname = null, name = null;
BufferedReader bin = new BufferedReader(new FileReader(source));
String line;
while ((line = bin.readLine()) != null) {
Matcher pm = PACKAGE_PATTERN.matcher(line);
if (pm.find()) {
pkgname = pm.group(1);
}
Matcher nm = NAME_PATTERN.matcher(line);
if (nm.find()) {
name = nm.group(2);
break;
}
}
bin.close();
// make sure we found something
if (name == null) {
throw new IOException("Unable to locate class or interface name in " + source + ".");
}
// prepend the package name to get a name we can Class.forName()
if (pkgname != null) {
name = pkgname + "." + name;
}
return name;
}
/** A list of filesets that contain tile images. */
protected ArrayList<FileSet> _filesets = new ArrayList<FileSet>();
/** Used to do our own classpath business. */
protected ClassLoader _cloader;
/** Used to generate source files from templates. */
protected VelocityEngine _velocity;
/** {@link PersistentRecord} resolved with the proper classloader so that we can compare it to
* loaded derived classes. */
protected Class<?> _prclass;
/** Specifies the path to the name code template. */
protected static final String NAME_TMPL = "com/samskivert/jdbc/depot/tools/record_name.tmpl";
/** Specifies the path to the column code template. */
protected static final String COL_TMPL = "com/samskivert/jdbc/depot/tools/record_column.tmpl";
/** Specifies the path to the key code template. */
protected static final String KEY_TMPL = "com/samskivert/jdbc/depot/tools/record_key.tmpl";
// markers
protected static final String MARKER = "// AUTO-GENERATED: ";
protected static final String FIELDS_START = MARKER + "FIELDS START";
protected static final String FIELDS_END = MARKER + "FIELDS END";
protected static final String METHODS_START = MARKER + "METHODS START";
protected static final String METHODS_END = MARKER + "METHODS END";
/** A regular expression for matching the package declaration. */
protected static final Pattern PACKAGE_PATTERN = Pattern.compile("^\\s*package\\s+(\\S+)\\W");
/** A regular expression for matching the class or interface declaration. */
protected static final Pattern NAME_PATTERN = Pattern.compile(
"^\\s*public\\s+(?:abstract\\s+)?(interface|class)\\s+(\\w+)(\\W|$)");
}
@@ -1,3 +0,0 @@
/** The qualified column identifier for the {@link #$field} field. */
public static final ColumnExp ${capfield}_C =
new ColumnExp(${record}.class, $capfield);
@@ -1,11 +0,0 @@
/**
* Create and return a primary {@link Key} to identify a {@link $record}
* with the supplied key values.
*/
public static Key<${record}> getKey (${argList})
{
return new Key<${record}>(
${record}.class,
new String[] { $fieldNameList },
new Comparable[] { $argNameList });
}
@@ -1,2 +0,0 @@
/** The column identifier for the {@link #$field} field. */
public static final String $capfield = "$field";