/*
* Copyright 2001-2008 Geert Bevin <gbevin[remove] at uwyn dot com> and
* JR Boyens <gnu-jrb[remove] at gmx dot net>
* Licensed under the Apache License, Version 2.0 (the "License")
* $Id: AbstractGenericQueryManager.java 3918 2008-04-14 17:35:35Z gbevin $
*/
package com.uwyn.rife.database.querymanagers.generic;
import com.uwyn.rife.database.*;
import com.uwyn.rife.database.queries.*;
import com.uwyn.rife.site.*;
import java.util.*;
import com.uwyn.rife.database.exceptions.DatabaseException;
import com.uwyn.rife.database.exceptions.ExecutionErrorException;
import com.uwyn.rife.database.exceptions.MissingManyToOneColumnException;
import com.uwyn.rife.database.exceptions.MissingManyToOneTableException;
import com.uwyn.rife.database.querymanagers.generic.exceptions.IncompatibleValidationTypeException;
import com.uwyn.rife.database.querymanagers.generic.exceptions.UnsupportedManyToManyValueTypeException;
import com.uwyn.rife.database.querymanagers.generic.instrument.LazyLoadAccessorsBytecodeTransformer;
import com.uwyn.rife.tools.BeanUtils;
import com.uwyn.rife.tools.ClassUtils;
import com.uwyn.rife.tools.exceptions.InnerClassException;
import com.uwyn.rife.tools.StringUtils;
import com.uwyn.rife.tools.TerracottaUtils;
import com.uwyn.rife.tools.exceptions.BeanUtilsException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import static com.uwyn.rife.database.querymanagers.generic.GenericQueryManagerRelationalUtils.ensureSupportedManyToManyPropertyValueType;
import static com.uwyn.rife.database.querymanagers.generic.GenericQueryManagerRelationalUtils.ensureSupportedManyToOneAssociationPropertyValueType;
import static com.uwyn.rife.database.querymanagers.generic.GenericQueryManagerRelationalUtils.generateManyToManyJoinColumnName;
import static com.uwyn.rife.database.querymanagers.generic.GenericQueryManagerRelationalUtils.generateManyToManyJoinTableName;
import static com.uwyn.rife.database.querymanagers.generic.GenericQueryManagerRelationalUtils.generateManyToOneJoinColumnName;
import static com.uwyn.rife.database.querymanagers.generic.GenericQueryManagerRelationalUtils.obtainManyToManyDeclarations;
import static com.uwyn.rife.database.querymanagers.generic.GenericQueryManagerRelationalUtils.obtainManyToOneAssociationDeclarations;
import static com.uwyn.rife.database.querymanagers.generic.GenericQueryManagerRelationalUtils.obtainManyToOneDeclarations;
import static com.uwyn.rife.database.querymanagers.generic.GenericQueryManagerRelationalUtils.processManyToOneJoinColumns;
import static com.uwyn.rife.database.querymanagers.generic.GenericQueryManagerRelationalUtils.restoreManyToOneProperty;
public abstract class AbstractGenericQueryManager<BeanType> extends DbQueryManager implements GenericQueryManager<BeanType>
{
protected Class<BeanType> mBaseClass = null;
protected String mPrimaryKey = null;
protected Method mGetPrimaryKeyMethod = null;
protected Method mSetPrimaryKeyMethod = null;
protected boolean mSparseIdentifier = false;
protected List<GenericQueryManagerListener> mListeners = null;
public AbstractGenericQueryManager(Datasource datasource, Class<BeanType> beanClass, String primaryKey)
throws DatabaseException
{
super(datasource);
mBaseClass = beanClass;
mPrimaryKey = primaryKey;
try
{
String capitalized_primary_key = StringUtils.capitalize(mPrimaryKey);
mGetPrimaryKeyMethod = mBaseClass.getMethod("get" + capitalized_primary_key, (Class[])null);
try
{
mSetPrimaryKeyMethod = mBaseClass.getMethod("set" + capitalized_primary_key, new Class[] {int.class});
}
catch (NoSuchMethodException e)
{
try
{
mSetPrimaryKeyMethod = mBaseClass.getMethod("set" + capitalized_primary_key, new Class[] {Integer.class});
}
catch (NoSuchMethodException e2)
{
throw e;
}
}
}
catch (Throwable e)
{
throw new DatabaseException(e);
}
Constrained constrained_bean = ConstrainedUtils.getConstrainedInstance(getBaseClass());
if (constrained_bean != null)
{
ConstrainedProperty constrained_property = constrained_bean.getConstrainedProperty(primaryKey);
if (constrained_property != null)
{
mSparseIdentifier = constrained_property.isSparse();
}
}
}
public Class getBaseClass()
{
return mBaseClass;
}
public String getIdentifierName()
{
return mPrimaryKey;
}
public int getIdentifierValue(BeanType bean)
throws DatabaseException
{
try
{
Integer id = (Integer)mGetPrimaryKeyMethod.invoke(bean, (Object[])null);
if (null == id)
{
return -1;
}
return id.intValue();
}
catch (Throwable e)
{
throw new DatabaseException(e);
}
}
public boolean isIdentifierSparse()
{
return mSparseIdentifier;
}
public void validate(Validated validated)
{
// perform validation
if (validated != null &&
!(validated.getClass() == mBaseClass))
{
throw new IncompatibleValidationTypeException(validated.getClass(), mBaseClass);
}
BeanType bean = (BeanType)validated;
// handle before callback
Callbacks callbacks = getCallbacks(bean);
if (callbacks != null &&
!callbacks.beforeValidate(bean))
{
return;
}
_validateWithoutCallbacks(validated);
// handle after callback
if (callbacks != null)
{
callbacks.afterValidate(bean);
}
}
private static int getIdentifierValue(Object bean, String propertyName)
throws DatabaseException
{
try
{
Integer id = (Integer)BeanUtils.getPropertyValue(bean, propertyName);
if (null == id)
{
return -1;
}
return id.intValue();
}
catch (Throwable e)
{
throw new DatabaseException(e);
}
}
protected void _validateWithoutCallbacks(final Validated validated)
{
if (null == validated)
{
return;
}
// handle constrained beans
final Constrained constrained = ConstrainedUtils.makeConstrainedInstance(validated);
if (constrained != null)
{
// check if the identifier exists or is still undefined (existing or
// new entry)
boolean identifier_exists = false;
int identifier_value = getIdentifierValue((BeanType)validated);
if (identifier_value >= 0)
{
identifier_exists = true;
}
// validate the many-to-one entities
processManyToOneJoinColumns(this, new ManyToOneJoinColumnProcessor() {
public boolean processJoinColumn(String columnName, String propertyName, ManyToOneDeclaration declaration)
{
Object property_value = null;
try
{
property_value = BeanUtils.getPropertyValue(constrained, propertyName);
}
catch (BeanUtilsException e)
{
throw new DatabaseException(e);
}
if (property_value != null)
{
int identifier_value = getIdentifierValue(property_value, declaration.getAssociationColumn());
if (identifier_value >= 0)
{
if (!executeHasResultRows(declaration.getAssociationManager().getRestoreQuery(identifier_value)))
{
validated.addValidationError(new ValidationError.INVALID(propertyName));
}
}
}
return true;
}
});
Map<String, ManyToOneAssociationDeclaration> manytoone_association_declarations = null;
Map<String, Object> manytoone_association_property_values = null;
boolean obtained_manytoone_association_declarations = false;
Map<String, ManyToManyDeclaration> manytomany_declarations = null;
Map<String, Object> manytomany_property_values = null;
boolean obtained_manytomany_declarations = false;
// handle invididual properties
for (ConstrainedProperty property : (Collection<ConstrainedProperty>)constrained.getConstrainedProperties())
{
// handle the uniqueness of invididual properties
if (property.isUnique())
{
Object property_value = null;
try
{
property_value = BeanUtils.getPropertyValue(constrained, property.getPropertyName());
}
catch (BeanUtilsException e)
{
throw new DatabaseException(e);
}
if (property_value != null)
{
CountQuery count_query = getCountQuery()
.where(property.getPropertyName(), "=", property_value);
if (identifier_exists)
{
count_query.whereAnd(mPrimaryKey, "!=", identifier_value);
}
if (count(count_query) > 0)
{
validated.addValidationError(new ValidationError.UNICITY(property.getPropertyName()));
}
}
}
// handle the manyToOne constraint that contain the identifier values
if (property.hasManyToOne())
{
Object property_value = null;
try
{
property_value = BeanUtils.getPropertyValue(constrained, property.getPropertyName());
}
catch (BeanUtilsException e)
{
throw new DatabaseException(e);
}
if (property_value != null &&
ClassUtils.isBasic(property_value.getClass()))
{
ConstrainedProperty.ManyToOne many_to_one = property.getManyToOne();
if (null == many_to_one.getDerivedTable())
{
throw new MissingManyToOneTableException(constrained.getClass(), property.getPropertyName());
}
if (null == many_to_one.getColumn())
{
throw new MissingManyToOneColumnException(constrained.getClass(), property.getPropertyName());
}
if (!executeHasResultRows(new Select(getDatasource())
.from(many_to_one.getDerivedTable())
.where(many_to_one.getColumn(), "=", property_value)))
{
validated.addValidationError(new ValidationError.INVALID(property.getPropertyName()));
}
}
}
// handle the manyToOneAssociation constraint
if (property.hasManyToOneAssociation())
{
// make sure that the many to one association declarations have been obtained
if (!obtained_manytoone_association_declarations)
{
manytoone_association_declarations = obtainManyToOneAssociationDeclarations(this, constrained);
if (manytoone_association_declarations != null)
{
// get the property values of those that contain many-to-one association relationships
String[] manytoone_association_property_names = new String[manytoone_association_declarations.size()];
manytoone_association_declarations.keySet().toArray(manytoone_association_property_names);
try
{
manytoone_association_property_values = BeanUtils.getPropertyValues(constrained, manytoone_association_property_names, null, null);
}
catch (BeanUtilsException e)
{
throw new DatabaseException(e);
}
}
obtained_manytoone_association_declarations = true;
}
ManyToOneAssociationDeclaration declaration = manytoone_association_declarations.get(property.getPropertyName());
if (declaration != null)
{
Object value = manytoone_association_property_values.get(property.getPropertyName());
Class type = declaration.getMainType();
try
{
checkCollectionRelationshipValidity(validated, property, value, type);
}
catch (ClassCastException e)
{
throw new UnsupportedManyToManyValueTypeException(validated.getClass(), property.getName(), e);
}
}
}
// handle the manyToMany constraint
if (property.hasManyToMany() ||
property.hasManyToManyAssociation())
{
// make sure that the many to many declarations have been obtained
if (!obtained_manytomany_declarations)
{
manytomany_declarations = obtainManyToManyDeclarations(this, constrained, true);
if (manytomany_declarations != null)
{
// get the property values of those that contain many-to-many relationships
String[] manytomany_property_names = new String[manytomany_declarations.size()];
manytomany_declarations.keySet().toArray(manytomany_property_names);
try
{
manytomany_property_values = BeanUtils.getPropertyValues(constrained, manytomany_property_names, null, null);
}
catch (BeanUtilsException e)
{
throw new DatabaseException(e);
}
}
obtained_manytomany_declarations = true;
}
ManyToManyDeclaration declaration = manytomany_declarations.get(property.getPropertyName());
if (declaration != null)
{
Object value = manytomany_property_values.get(property.getPropertyName());
try
{
checkCollectionRelationshipValidity(validated, property, value, declaration.getAssociationType());
}
catch (ClassCastException e)
{
throw new UnsupportedManyToManyValueTypeException(validated.getClass(), property.getName(), e);
}
}
}
}
// handle the bean-wide uniqueness
ConstrainedBean constrained_bean = constrained.getConstrainedBean();
if (constrained_bean != null &&
constrained_bean.hasUniques())
{
for (String[] uniques : (List<String[]>)constrained_bean.getUniques())
{
CountQuery count_query = getCountQuery();
if (identifier_exists)
{
count_query.where(mPrimaryKey, "!=", identifier_value);
}
for (String unique : uniques)
{
Object property_value = null;
try
{
property_value = BeanUtils.getPropertyValue(constrained, unique);
}
catch (BeanUtilsException e)
{
throw new DatabaseException(e);
}
if (null == property_value)
{
count_query = null;
break;
}
else
{
count_query.where(unique, "=", property_value);
}
}
if (count_query != null &&
count(count_query) > 0)
{
for (String unique : uniques)
{
validated.addValidationError(new ValidationError.UNICITY(unique));
}
}
}
}
}
}
private void checkCollectionRelationshipValidity(final Validated validated, final ConstrainedProperty property, final Object propertyValue, final Class elementType)
throws ClassCastException
{
if (elementType != null &&
propertyValue != null)
{
// cast the property value to a collection
Collection value_collection = (Collection)propertyValue;
// iterate over all the collection elements to obtain the identifier values
Set<Integer> identifiers = new HashSet<Integer>();
GenericQueryManager element_manager = createNewManager(elementType);
for (Object entity : value_collection)
{
int identifier_value = element_manager.getIdentifierValue(entity);
// only add the identifiers that have a value
if (identifier_value != -1)
{
identifiers.add(identifier_value);
}
}
// check if the many-to-one associations exist
CountQuery count_query = element_manager.getCountQuery()
.where(element_manager.getIdentifierName() + " IN (" + StringUtils.join(identifiers, ",") + ")");
int count = element_manager.count(count_query);
if (count != identifiers.size())
{
validated.addValidationError(new ValidationError.INVALID(property.getPropertyName()));
}
}
}
protected Callbacks getCallbacks(BeanType bean)
{
if (null == bean) return null;
Callbacks callbacks = null;
if (bean instanceof CallbacksProvider)
{
callbacks = ((CallbacksProvider)bean).getCallbacks();
}
else if (bean instanceof Callbacks)
{
callbacks = (Callbacks)bean;
}
return callbacks;
}
protected int _update(final Update saveUpdate, final BeanType bean)
{
// handle before callback
Callbacks callbacks = getCallbacks(bean);
if (callbacks != null &&
!callbacks.beforeUpdate(bean))
{
return -1;
}
// perform update
int result = _updateWithoutCallbacks(saveUpdate, bean);
// handle after callback
if (callbacks != null)
{
callbacks.afterUpdate(bean, result != -1);
}
return result;
}
protected int _updateWithoutCallbacks(final Update saveUpdate, final BeanType bean)
{
assert saveUpdate != null;
final int identifier_value = getIdentifierValue(bean);
int result = (Integer)inTransaction(new DbTransactionUser() {
public Integer useTransaction()
throws InnerClassException
{
int result = identifier_value;
storeManyToOne(bean);
if (0 == executeUpdate(saveUpdate, new DbPreparedStatementHandler() {
public void setParameters(final DbPreparedStatement statement)
{
statement
.setBean(bean);
setManyToOneJoinParameters(statement, bean);
}
}))
{
result = -1;
}
else
{
storeManyToOneAssociations(bean, identifier_value);
storeManyToMany(bean, identifier_value);
}
return result;
}
});
// handle listeners
if (result != -1)
{
fireUpdated(bean);
}
return result;
}
protected int _insert(final SequenceValue nextId, final Insert save, final BeanType bean)
{
// handle before callback
Callbacks callbacks = getCallbacks(bean);
if (callbacks != null &&
!callbacks.beforeInsert(bean))
{
return -1;
}
// perform insert
int result = _insertWithoutCallbacks(nextId, save, bean);
// handle after callback
if (callbacks != null)
{
callbacks.afterInsert(bean, result != -1);
}
return result;
}
protected int _insertWithoutCallbacks(final SequenceValue nextId, final Insert save, final BeanType bean)
{
assert nextId != null;
assert save != null;
int value = -1;
value = (Integer)inTransaction(new DbTransactionUser() {
public Integer useTransaction()
throws InnerClassException
{
storeManyToOne(bean);
int result = getIdentifierValue(bean);
if (!isIdentifierSparse())
{
result = executeGetFirstInt(nextId);
}
final int primary_key_id = result;
executeUpdate(save, new DbPreparedStatementHandler() {
public void setParameters(final DbPreparedStatement statement)
{
statement
.setBean(bean)
.setInt(mPrimaryKey, primary_key_id);
setManyToOneJoinParameters(statement, bean);
}
});
storeManyToOneAssociations(bean, primary_key_id);
storeManyToMany(bean, primary_key_id);
return result;
}
});
try
{
mSetPrimaryKeyMethod.invoke(bean, new Object[] {new Integer(value)});
}
catch (Throwable e)
{
throw new DatabaseException(e);
}
// handle listeners
if (value != -1)
{
fireInserted(bean);
}
return value;
}
protected void setManyToOneJoinParameters(final DbPreparedStatement statement, final BeanType bean)
{
final Constrained constrained = ConstrainedUtils.makeConstrainedInstance(bean);
// handle many-to-one join column parameters
processManyToOneJoinColumns(this, new ManyToOneJoinColumnProcessor() {
public boolean processJoinColumn(String columnName, String propertyName, ManyToOneDeclaration declaration)
{
try
{
Object join_column_property = BeanUtils.getPropertyValue(bean, propertyName);
Object identifier_value = null;
if (join_column_property != null)
{
identifier_value = BeanUtils.getPropertyValue(join_column_property, declaration.getAssociationColumn());
}
Class identifier_type = BeanUtils.getPropertyType(declaration.getAssociationType(), declaration.getAssociationColumn());
int[] indices = statement.getParameterIndices(columnName);
for (int index : indices)
{
getDatasource().getSqlConversion().setTypedParameter(statement, index, identifier_type, columnName, identifier_value, constrained);
}
}
catch (BeanUtilsException e)
{
throw new DatabaseException(e);
}
return true;
}
});
}
protected void storeManyToOne(final BeanType bean)
{
final Constrained constrained = ConstrainedUtils.makeConstrainedInstance(bean);
final Map<String, ManyToOneDeclaration> declarations = obtainManyToOneDeclarations(this, constrained, null, null);
if (declarations != null)
{
// get the property values of those that contain many-to-one relationships
String[] property_names = new String[declarations.size()];
declarations.keySet().toArray(property_names);
final Map<String, Object> values;
try
{
values = BeanUtils.getPropertyValues(bean, property_names, null, null);
}
catch (BeanUtilsException e)
{
throw new DatabaseException(e);
}
// iterate over all the many-to-one relationships that have associated classes and instance values
for (Map.Entry<String, ManyToOneDeclaration> entry : declarations.entrySet())
{
ManyToOneDeclaration declaration = entry.getValue();
if (!declaration.isBasic())
{
GenericQueryManager association_manager = declaration.getAssociationManager();
String property_name = entry.getKey();
// obtain the property value
Object value = values.get(property_name);
if (value != null)
{
int identifier_value = association_manager.getIdentifierValue(value);
// insert the collection entries that have no identifier value
if (identifier_value < 0)
{
identifier_value = association_manager.insert(value);
}
}
}
}
}
}
protected void storeManyToOneAssociations(final BeanType bean, final int objectId)
{
final Constrained constrained = ConstrainedUtils.makeConstrainedInstance(bean);
final Map<String, ManyToOneAssociationDeclaration> declarations = obtainManyToOneAssociationDeclarations(this, constrained);
if (declarations != null)
{
// get the property values of those that contain many-to-one relationships
String[] property_names = new String[declarations.size()];
declarations.keySet().toArray(property_names);
final Map<String, Object> values;
try
{
values = BeanUtils.getPropertyValues(bean, property_names, null, null);
}
catch (BeanUtilsException e)
{
throw new DatabaseException(e);
}
// iterate over all the many-to-one relationships that have associated classes and instance values
for (Map.Entry<String, ManyToOneAssociationDeclaration> entry : declarations.entrySet())
{
ManyToOneAssociationDeclaration declaration = entry.getValue();
GenericQueryManager main_manager = createNewManager(declaration.getMainType());
String property_name = entry.getKey();
// obtain the property value
Object value = values.get(property_name);
final String main_table = main_manager.getTable();
final String main_identify_column = main_manager.getIdentifierName();
final String main_join_column = generateManyToOneJoinColumnName(declaration.getMainProperty(), declaration.getMainDeclaration());
Update clear_previous_mappings = new Update(getDatasource())
.table(main_table)
.fieldCustom(main_join_column, "NULL")
.where(main_join_column, "=", objectId);
executeUpdate(clear_previous_mappings);
if (value != null)
{
ensureSupportedManyToOneAssociationPropertyValueType(mBaseClass, property_name, value);
Collection value_collection = (Collection)value;
Update update_mapping = new Update(getDatasource())
.table(main_table)
.fieldParameter(main_join_column)
.whereParameter(main_identify_column, "=");
// store the collection entries
for (Object many_to_one_entity : value_collection)
{
int identifier_value = main_manager.getIdentifierValue(many_to_one_entity);
// insert the collection entries that have no identifier value
if (identifier_value < 0)
{
identifier_value = main_manager.insert(many_to_one_entity);
}
// store the many-to-one mappings
executeUpdate(update_mapping, new DbPreparedStatementHandler<Integer>(identifier_value) {
public void setParameters(DbPreparedStatement statement)
{
statement
.setInt(main_join_column, objectId)
.setInt(main_identify_column, getData());
}
});
}
}
}
}
}
protected void storeManyToMany(final BeanType bean, final int objectId)
{
final Constrained constrained = ConstrainedUtils.makeConstrainedInstance(bean);
final Map<String, ManyToManyDeclaration> declarations = obtainManyToManyDeclarations(this, constrained, true);
if (declarations != null)
{
// get the property values of those that contain many-to-many relationships
String[] property_names = new String[declarations.size()];
declarations.keySet().toArray(property_names);
final Map<String, Object> values;
try
{
values = BeanUtils.getPropertyValues(bean, property_names, null, null);
}
catch (BeanUtilsException e)
{
throw new DatabaseException(e);
}
// iterate over all the many to many relationships
final String column1_name = generateManyToManyJoinColumnName(AbstractGenericQueryManager.this);
for (Map.Entry<String, ManyToManyDeclaration> entry : declarations.entrySet())
{
ManyToManyDeclaration declaration = entry.getValue();
GenericQueryManager association_manager = createNewManager(declaration.getAssociationType());
String join_table = generateManyToManyJoinTableName(declaration, AbstractGenericQueryManager.this, association_manager);
final String column2_name = generateManyToManyJoinColumnName(association_manager);
String property_name = entry.getKey();
// obtain the property value
Object value = values.get(property_name);
// create the delete statement to remove all the possible previous
// mappings in the join table for this primary key ID
Delete delete_previous_mappings = new Delete(getDatasource())
.from(join_table)
.where(column1_name, "=", objectId);
executeUpdate(delete_previous_mappings);
if (value != null)
{
ensureSupportedManyToManyPropertyValueType(mBaseClass, property_name, value);
Collection value_collection = (Collection)value;
Insert insert_mapping = new Insert(getDatasource())
.into(join_table)
.fieldParameter(column1_name)
.fieldParameter(column2_name);
// store the collection entries
for (Object many_to_many_entity : value_collection)
{
int identifier_value = association_manager.getIdentifierValue(many_to_many_entity);
// insert the collection entries that have no identifier value
if (identifier_value < 0)
{
identifier_value = association_manager.insert(many_to_many_entity);
}
// store the many-to_many mappings
executeUpdate(insert_mapping, new DbPreparedStatementHandler<Integer>(identifier_value) {
public void setParameters(DbPreparedStatement statement)
{
statement
.setInt(column1_name, objectId)
.setInt(column2_name, getData());
}
});
}
}
}
}
}
protected int _save(final SequenceValue nextId, final Insert save, final Update saveUpdate, final BeanType bean)
throws DatabaseException
{
assert nextId != null;
assert save != null;
assert saveUpdate != null;
int value = -1;
// handle before callback
final Callbacks callbacks = getCallbacks(bean);
if (callbacks != null &&
!callbacks.beforeSave(bean))
{
return -1;
}
// cancel indicator
final boolean[] is_cancelled = new boolean[] {false};
// perform save
value = (Integer)inTransaction(new DbTransactionUser() {
public Integer useTransaction()
throws InnerClassException
{
int result = getIdentifierValue(bean);
if (isIdentifierSparse())
{
// handle before callback
if (callbacks != null &&
!callbacks.beforeInsert(bean))
{
is_cancelled[0] = true;
return -1;
}
// try to perform the insert
try
{
result = _insertWithoutCallbacks(nextId, save, bean);
}
catch (ExecutionErrorException e)
{
result = -1;
}
// handle after callback
if (callbacks != null &&
!callbacks.afterInsert(bean, result != -1))
{
is_cancelled[0] = true;
return result;
}
// perform update if insert failed
if (-1 == result)
{
// handle before callback
if (callbacks != null &&
!callbacks.beforeUpdate(bean))
{
is_cancelled[0] = true;
return -1;
}
result = _updateWithoutCallbacks(saveUpdate, bean);
// handle after callback
if (callbacks != null &&
!callbacks.afterUpdate(bean, result != -1))
{
is_cancelled[0] = true;
return result;
}
}
}
else
{
// try to update
if (result >= 0)
{
// handle before callback
if (callbacks != null &&
!callbacks.beforeUpdate(bean))
{
is_cancelled[0] = true;
return -1;
}
result = _updateWithoutCallbacks(saveUpdate, bean);
// handle after callback
if (callbacks != null &&
!callbacks.afterUpdate(bean, result != -1))
{
is_cancelled[0] = true;
return result;
}
}
// perform insert if update failed or wasn't appropriate
if (-1 == result)
{
// handle before callback
if (callbacks != null &&
!callbacks.beforeInsert(bean))
{
is_cancelled[0] = true;
return -1;
}
result = _insertWithoutCallbacks(nextId, save, bean);
// handle after callback
if (callbacks != null &&
!callbacks.afterInsert(bean, result != -1))
{
is_cancelled[0] = true;
return result;
}
}
}
return result;
}
});
// handle after callback
if (!is_cancelled[0] &&
callbacks != null)
{
callbacks.afterSave(bean, value != -1);
}
return value;
}
protected boolean _delete(Delete delete)
throws DatabaseException
{
assert delete != null;
boolean result = true;
if (0 == executeUpdate(delete))
{
result = false;
}
return result;
}
protected boolean _delete(final Delete delete, final int objectId)
throws DatabaseException
{
assert delete != null;
// handle before callback
Callbacks callbacks = null;
if (CallbacksProvider.class.isAssignableFrom(mBaseClass))
{
try
{
callbacks = ((CallbacksProvider)mBaseClass.newInstance()).getCallbacks();
}
catch (IllegalAccessException e)
{
callbacks = null;
}
catch (InstantiationException e)
{
callbacks = null;
}
}
else if (Callbacks.class.isAssignableFrom(mBaseClass))
{
try
{
callbacks = (Callbacks)mBaseClass.newInstance();
}
catch (IllegalAccessException e)
{
callbacks = null;
}
catch (InstantiationException e)
{
callbacks = null;
}
}
if (callbacks != null &&
!callbacks.beforeDelete(objectId))
{
return false;
}
// perform delete
Boolean result = inTransaction(new DbTransactionUser() {
public Boolean useTransaction() throws InnerClassException
{
// remove all many-to-one mappings for this object ID
deleteManyToOne(objectId);
// remove all many-to-many mappings for this object ID
deleteManyToMany(objectId);
// perform the actual deletion of the object from the database
if (0 == executeUpdate(delete, new DbPreparedStatementHandler() {
public void setParameters(DbPreparedStatement statement)
{
statement
.setInt(mPrimaryKey, objectId);
}
}))
{
return false;
}
return true;
}
});
// handle listeners
if (result)
{
fireDeleted(objectId);
}
// handle after callback
if (callbacks != null)
{
callbacks.afterDelete(objectId, result);
}
return result;
}
protected void deleteManyToOne(final int objectId)
{
final Constrained constrained = ConstrainedUtils.getConstrainedInstance(getBaseClass());
final Map<String, ManyToOneAssociationDeclaration> declarations = obtainManyToOneAssociationDeclarations(this, constrained);
if (declarations != null)
{
// iterate over all the many to one assocation relationships
for (Map.Entry<String, ManyToOneAssociationDeclaration> entry : declarations.entrySet())
{
ManyToOneAssociationDeclaration declaration = entry.getValue();
String column_name = generateManyToOneJoinColumnName(declaration.getMainProperty(), declaration.getMainDeclaration());
GenericQueryManager main_manager = createNewManager(declaration.getMainType());
// create an update statement that will set all the columns that
// point to the deleted entity to NULL
Update clear_references = new Update(getDatasource())
.table(main_manager.getTable())
.fieldCustom(column_name, "NULL")
.where(column_name, "=", objectId);
executeUpdate(clear_references);
}
}
}
protected void deleteManyToMany(final int objectId)
{
final Constrained constrained = ConstrainedUtils.getConstrainedInstance(getBaseClass());
final Map<String, ManyToManyDeclaration> declarations = obtainManyToManyDeclarations(this, constrained, true);
if (declarations != null)
{
// iterate over all the many to many relationships
final String column1_name = generateManyToManyJoinColumnName(AbstractGenericQueryManager.this);
for (Map.Entry<String, ManyToManyDeclaration> entry : declarations.entrySet())
{
ManyToManyDeclaration declaration = entry.getValue();
GenericQueryManager association_manager = createNewManager(declaration.getAssociationType());
String join_table = generateManyToManyJoinTableName(declaration, AbstractGenericQueryManager.this, association_manager);
// create the delete statement to remove all the possible previous
// mappings in the join table for this primary key ID
Delete delete_previous_mappings = new Delete(getDatasource())
.from(join_table)
.where(column1_name, "=", objectId);
executeUpdate(delete_previous_mappings);
}
}
}
protected BeanType _restore(Select restore, final int objectId)
throws DatabaseException
{
assert restore != null;
BeanType result = null;
result = executeFetchFirstBean(restore, mBaseClass, new DbPreparedStatementHandler() {
public void setParameters(DbPreparedStatement statement)
{
statement
.setInt(mPrimaryKey, objectId);
}
});
// handle listeners
if (result != null)
{
restoreManyToOne(result);
restoreManyToOneAssociations(result, objectId);
restoreManyToMany(result, objectId);
fireRestored(result);
}
// handle after callback
Callbacks callbacks = getCallbacks(result);
if (callbacks != null &&
!callbacks.afterRestore(result))
{
return null;
}
return result;
}
protected BeanType _restoreFirst(Select restore)
throws DatabaseException
{
assert restore != null;
BeanType result = executeFetchFirstBean(restore, mBaseClass);
// handle listeners
if (result != null)
{
restoreManyToOne(result);
int identifier_value = getIdentifierValue(result);
restoreManyToOneAssociations(result, identifier_value);
restoreManyToMany(result, identifier_value);
fireRestored(result);
}
// handle after callback
Callbacks callbacks = getCallbacks(result);
if (callbacks != null &&
!callbacks.afterRestore(result))
{
return null;
}
return result;
}
protected List<BeanType> _restore(Select restore)
throws DatabaseException
{
assert restore != null;
DbBeanFetcher<BeanType> bean_fetcher = new DbBeanFetcher<BeanType>(getDatasource(), mBaseClass, true) {
public boolean gotBeanInstance(BeanType instance)
{
// handle listeners
if (instance != null)
{
restoreManyToOne(instance);
int identifier_value = getIdentifierValue(instance);
restoreManyToOneAssociations(instance, identifier_value);
restoreManyToMany(instance, identifier_value);
fireRestored(instance);
}
// handle after callback
Callbacks callbacks = getCallbacks(instance);
return !(callbacks != null && !callbacks.afterRestore(instance));
}
};
executeFetchAll(restore, bean_fetcher, null);
return bean_fetcher.getCollectedInstances();
}
protected boolean _restore(Select restore, DbRowProcessor rowProcessor)
throws DatabaseException
{
assert restore != null;
return executeFetchAll(restore, rowProcessor);
}
protected void restoreManyToMany(final BeanType bean, final int objectId)
{
// handle many-to-many associations
final Constrained constrained = ConstrainedUtils.makeConstrainedInstance(bean);
final Map<String, ManyToManyDeclaration> declarations = obtainManyToManyDeclarations(this, constrained, true);
if (declarations != null)
{
// iterate over all the many to many relationships
final String column1_name = generateManyToManyJoinColumnName(AbstractGenericQueryManager.this);
for (Map.Entry<String, ManyToManyDeclaration> entry : declarations.entrySet())
{
ManyToManyDeclaration declaration = entry.getValue();
// create the associations collection
Object association_collection;
if (Set.class == declaration.getCollectionType())
{
association_collection = new ManyToManySet(AbstractGenericQueryManager.this, column1_name, objectId, declaration);
}
else
{
association_collection = new ManyToManyList(AbstractGenericQueryManager.this, column1_name, objectId, declaration);
}
// set the restore mappings as the property value
try
{
BeanUtils.setPropertyValue(bean, entry.getKey(), association_collection);
}
catch (BeanUtilsException e)
{
throw new DatabaseException(e);
}
}
}
}
protected void restoreManyToOne(final BeanType bean)
{
Field gqm_field = null;
Field lazyloaded_field = null;
try
{
gqm_field = bean.getClass().getDeclaredField(LazyLoadAccessorsBytecodeTransformer.GQM_VAR_NAME);
lazyloaded_field = bean.getClass().getDeclaredField(LazyLoadAccessorsBytecodeTransformer.LAZYLOADED_VAR_NAME);
}
catch (Exception e)
{
// if the synthetic fields don't exist in the class, just set them to null
// since this means that bytecode enhancement hasn't been performed to provide
// lazy-load functionalities
gqm_field = null;
lazyloaded_field = null;
}
// if the class has been enhanced for lazy loading capabilities, add a reference
// to this GQM and create a new map for storing the already loaded property values
if (gqm_field != null &&
lazyloaded_field != null)
{
gqm_field.setAccessible(true);
lazyloaded_field.setAccessible(true);
try
{
gqm_field.set(bean, this);
if (TerracottaUtils.isTcPresent())
{
lazyloaded_field.set(bean, new HashMap());
}
else
{
lazyloaded_field.set(bean, new WeakHashMap());
}
}
catch (Exception e)
{
throw new DatabaseException(e);
}
}
// otherwise eargerly load all many-to-one properties
else
{
processManyToOneJoinColumns(this, new ManyToOneJoinColumnProcessor() {
public boolean processJoinColumn(String columnName, String propertyName, ManyToOneDeclaration declaration)
{
Object property_value = restoreManyToOneProperty(AbstractGenericQueryManager.this, bean, declaration.getAssociationManager(), columnName, declaration.getAssociationType());
// set the many-to-one mapping as the property value
try
{
BeanUtils.setPropertyValue(bean, propertyName, property_value);
}
catch (BeanUtilsException e)
{
throw new DatabaseException(e);
}
return true;
}
});
}
}
protected void restoreManyToOneAssociations(final BeanType bean, final int objectId)
{
// handle many-to-one associations
final Constrained constrained = ConstrainedUtils.makeConstrainedInstance(bean);
final Map<String, ManyToOneAssociationDeclaration> declarations = obtainManyToOneAssociationDeclarations(this, constrained);
if (declarations != null)
{
// iterate over all the many to one association relationships
for (Map.Entry<String, ManyToOneAssociationDeclaration> entry : declarations.entrySet())
{
ManyToOneAssociationDeclaration declaration = entry.getValue();
// create the associations collection
Object association_collection;
if (Set.class == declaration.getCollectionType())
{
association_collection = new ManyToOneAssociationSet(AbstractGenericQueryManager.this, objectId, declaration);
}
else
{
association_collection = new ManyToOneAssociationList(AbstractGenericQueryManager.this, objectId, declaration);
}
// set the restore mappings as the property value
try
{
BeanUtils.setPropertyValue(bean, entry.getKey(), association_collection);
}
catch (BeanUtilsException e)
{
throw new DatabaseException(e);
}
}
}
}
protected int _count(Select count) throws DatabaseException
{
return executeGetFirstInt(count);
}
protected void _install(final CreateSequence createSequence, final CreateTable createTable)
throws DatabaseException
{
assert createSequence != null;
assert createTable != null;
inTransaction(new DbTransactionUserWithoutResult() {
public void useTransactionWithoutResult()
throws InnerClassException
{
if (!isIdentifierSparse())
{
executeUpdate(createSequence);
}
executeUpdate(createTable);
installManyToMany();
}
});
fireInstalled();
}
protected void installManyToMany()
{
// create many-to-many join tables
Constrained constrained = ConstrainedUtils.getConstrainedInstance(mBaseClass);
Map<String, ManyToManyDeclaration> manytomany_declarations = obtainManyToManyDeclarations(this, constrained, false);
if (manytomany_declarations != null)
{
String column1 = generateManyToManyJoinColumnName(AbstractGenericQueryManager.this);
for (Map.Entry<String, ManyToManyDeclaration> entry : manytomany_declarations.entrySet())
{
ManyToManyDeclaration declaration = entry.getValue();
GenericQueryManager association_manager = createNewManager(declaration.getAssociationType());
String table = generateManyToManyJoinTableName(declaration, AbstractGenericQueryManager.this, association_manager);
String column2 = generateManyToManyJoinColumnName(association_manager);
// obtain the violation actions
CreateTable.ViolationAction onupdate = null;
CreateTable.ViolationAction ondelete = null;
ConstrainedProperty property = constrained.getConstrainedProperty(entry.getKey());
if (property != null &&
property.hasManyToMany())
{
onupdate = property.getManyToMany().getOnUpdate();
ondelete = property.getManyToMany().getOnDelete();
}
// build the table creation query
CreateTable create_join_table = new CreateTable(getDatasource())
.table(table)
.column(column1, int.class, CreateTable.NOTNULL)
.column(column2, int.class, CreateTable.NOTNULL)
.foreignKey(getTable(), column1, getIdentifierName(), onupdate, ondelete)
.foreignKey(association_manager.getTable(), column2, association_manager.getIdentifierName(), onupdate, ondelete);
executeUpdate(create_join_table);
}
}
}
protected void _remove(final DropSequence dropSequence, final DropTable dropTable)
throws DatabaseException
{
assert dropTable != null;
assert dropSequence != null;
inTransaction(new DbTransactionUserWithoutResult() {
public void useTransactionWithoutResult()
throws InnerClassException
{
removeManyToMany();
// drop the table itself and optionally the sequence
executeUpdate(dropTable);
if (!isIdentifierSparse())
{
executeUpdate(dropSequence);
}
}
});
fireRemoved();
}
protected void removeManyToMany()
{
// drop many-to-many join tables
Constrained constrained = ConstrainedUtils.getConstrainedInstance(mBaseClass);
Map<String, ManyToManyDeclaration> manytomany_declarations = obtainManyToManyDeclarations(this, constrained, false);
if (manytomany_declarations != null)
{
for (Map.Entry<String, ManyToManyDeclaration> entry : manytomany_declarations.entrySet())
{
ManyToManyDeclaration declaration = entry.getValue();
GenericQueryManager association_manager = createNewManager(declaration.getAssociationType());
String table = generateManyToManyJoinTableName(declaration, AbstractGenericQueryManager.this, association_manager);
// build the table removal query
DropTable drop_join_table = new DropTable(getDatasource())
.table(table);
executeUpdate(drop_join_table);
}
}
}
public void addListener(GenericQueryManagerListener listener)
{
if (null == listener)
{
return;
}
if (null == mListeners)
{
mListeners = new ArrayList<GenericQueryManagerListener>();
}
mListeners.add(listener);
}
public void removeListeners()
{
if (null == mListeners)
{
return;
}
mListeners.clear();
}
public <OtherBeanType> GenericQueryManager<OtherBeanType> createNewManager(Class<OtherBeanType> beanClass)
{
return GenericQueryManagerFactory.getInstance(getDatasource(), beanClass);
}
protected void fireInstalled()
{
if (null == mListeners)
{
return;
}
for (GenericQueryManagerListener listener : mListeners)
{
listener.installed();
}
}
protected void fireRemoved()
{
if (null == mListeners)
{
return;
}
for (GenericQueryManagerListener listener : mListeners)
{
listener.removed();
}
}
protected void fireInserted(BeanType bean)
{
if (null == mListeners)
{
return;
}
for (GenericQueryManagerListener listener : mListeners)
{
listener.inserted(bean);
}
}
protected void fireUpdated(BeanType bean)
{
if (null == mListeners)
{
return;
}
for (GenericQueryManagerListener listener : mListeners)
{
listener.updated(bean);
}
}
protected void fireRestored(BeanType bean)
{
if (null == mListeners)
{
return;
}
for (GenericQueryManagerListener listener : mListeners)
{
listener.restored(bean);
}
}
protected void fireDeleted(int objectId)
{
if (null == mListeners)
{
return;
}
for (GenericQueryManagerListener listener : mListeners)
{
listener.deleted(objectId);
}
}
}