/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.venky.swf.db.table;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
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.venky.cache.Cache;
import com.venky.core.collections.IgnoreCaseList;
import com.venky.core.collections.LowerCaseStringCache;
import com.venky.core.collections.SequenceMap;
import com.venky.core.collections.SequenceSet;
import com.venky.core.log.SWFLogger;
import com.venky.core.log.TimerStatistics.Timer;
import com.venky.core.string.StringUtil;
import com.venky.core.util.ObjectUtil;
import com.venky.extension.Registry;
import com.venky.swf.db.Database;
import com.venky.swf.db.JdbcTypeHelper.TypeConverter;
import com.venky.swf.db.JdbcTypeHelper.TypeRef;
import com.venky.swf.db.annotations.column.COLUMN_DEF;
import com.venky.swf.db.annotations.column.IS_VIRTUAL;
import com.venky.swf.db.annotations.column.defaulting.StandardDefaulter;
import com.venky.swf.db.annotations.column.relationship.CONNECTED_VIA;
import com.venky.swf.db.annotations.column.validations.processors.DateFormatValidator;
import com.venky.swf.db.annotations.column.validations.processors.EnumerationValidator;
import com.venky.swf.db.annotations.column.validations.processors.ExactLengthValidator;
import com.venky.swf.db.annotations.column.validations.processors.FieldValidator;
import com.venky.swf.db.annotations.column.validations.processors.IntegerRangeValidator;
import com.venky.swf.db.annotations.column.validations.processors.MaxLengthValidator;
import com.venky.swf.db.annotations.column.validations.processors.MinLengthValidator;
import com.venky.swf.db.annotations.column.validations.processors.NotNullValidator;
import com.venky.swf.db.annotations.column.validations.processors.NumericRangeValidator;
import com.venky.swf.db.annotations.column.validations.processors.RegExValidator;
import com.venky.swf.db.annotations.model.CONFIGURATION;
import com.venky.swf.db.annotations.model.validations.ModelValidator;
import com.venky.swf.db.annotations.model.validations.UniqueKeyValidator;
import com.venky.swf.db.model.Model;
import com.venky.swf.db.model.User;
import com.venky.swf.db.model.reflection.ModelReflector;
import com.venky.swf.db.table.Table.ColumnDescriptor;
import com.venky.swf.exceptions.AccessDeniedException;
import com.venky.swf.exceptions.MultiException;
import com.venky.swf.routing.Config;
import com.venky.swf.sql.Conjunction;
import com.venky.swf.sql.Delete;
import com.venky.swf.sql.Expression;
import com.venky.swf.sql.Insert;
import com.venky.swf.sql.Operator;
import com.venky.swf.sql.Select;
import com.venky.swf.sql.Update;
import com.venky.swf.sql.parser.SQLExpressionParser;
/**
*
* @author venky
*/
public class ModelInvocationHandler implements InvocationHandler {
private Record record = null;
private Model proxy = null;
private ModelReflector<? extends Model> reflector = null;
private List<String> virtualFields = new IgnoreCaseList(false);
private String modelName = null;
@SuppressWarnings("unchecked")
public <M extends Model> ModelReflector<M> getReflector() {
return (ModelReflector<M>) reflector;
}
public String getModelName(){
return modelName;
}
public String getPool(){
return getReflector().getPool();
}
public ModelInvocationHandler(Class<? extends Model> modelClass, Record record) {
this.record = record;
this.reflector = ModelReflector.instance(modelClass);
this.modelName = Table.getSimpleModelClassName(reflector.getTableName());
this.virtualFields = reflector.getVirtualFields();
record.startTracking();
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Not Required. setProxy(getModelClass().cast(proxy));
String mName = method.getName();
Class<?> retType = method.getReturnType();
Class<?>[] parameters = method.getParameterTypes();
if (getReflector().getFieldGetterSignatures().contains(getReflector().getSignature(method))) {
String fieldName = getReflector().getFieldName(method);
if (!virtualFields.contains(fieldName)){
ColumnDescriptor cd = getReflector().getColumnDescriptor(fieldName);
String columnName = cd.getName();
Object value = record.get(columnName);
TypeRef<?> ref =Database.getJdbcTypeHelper(getPool()).getTypeRef(retType);
TypeConverter<?> converter = ref.getTypeConverter();
if (value == null) {
Object defaultValue = null;
COLUMN_DEF colDef = getReflector().getAnnotation(method,COLUMN_DEF.class);
if (colDef != null){
defaultValue = StandardDefaulter.getDefaultValue(colDef.value(),colDef.args());
}
if (retType.isPrimitive()){
return converter.valueOf(defaultValue);
}else {
return defaultValue;
}
} else if (retType.isInstance(value) && !ref.isLOB()) {
return value;
} else {
return converter.valueOf(value);
}
}
} else if (getReflector().getFieldSetters().contains(method) ) {
String fieldName = StringUtil.underscorize(mName.substring(3));
if (!virtualFields.contains(fieldName)){
String columnName = getReflector().getColumnDescriptor(fieldName).getName();
return record.put(columnName, args[0]);
}
} else if (getReflector().getReferredModelGetters().contains(method)) {
if (!getReflector().isAnnotationPresent(method,IS_VIRTUAL.class)){
return getParent(method);
}
} else if (getReflector().getChildGetters().contains(method)) {
if (!getReflector().isAnnotationPresent(method,IS_VIRTUAL.class)){
CONNECTED_VIA join = reflector.getAnnotation(method,CONNECTED_VIA.class);
if (join != null){
return getChildren(getReflector().getChildModelClass(method),join.value(),join.additional_join());
}else {
return getChildren(getReflector().getChildModelClass(method));
}
}
}
/* Optimization
for (Object impl: modelImplObjects){
try {
Method inModelImplClass = impl.getClass().getMethod(mName, parameters);
if (retType.isAssignableFrom(inModelImplClass.getReturnType())){
Timer timer = startTimer(inModelImplClass.toString());
try {
return inModelImplClass.invoke(impl, args);
}catch(InvocationTargetException ex){
throw ex.getCause();
}finally{
timer.stop();
}
}
}catch(NoSuchMethodException ex){
//
}
}
Method inCurrentClass = this.getClass().getMethod(mName, parameters);
if (retType.isAssignableFrom(inCurrentClass.getReturnType())) {
try {
return inCurrentClass.invoke(this, args);
}catch (InvocationTargetException ex){
throw ex.getCause();
}
} else {
throw new NoSuchMethodException("Donot know how to execute this method");
}
*/
Class<?> implClass = getMethodImplClass(method);
Object implObject = null;
if (implClass != null){
implObject = modelImplObjects.get(implClass);
}
if (implClass == null || implObject == null){
//implObject is null while constructing impls.
implClass = this.getClass();
implObject = this;
}
Method inImplClass = implClass.getMethod(mName, parameters);
if (retType.isAssignableFrom(inImplClass.getReturnType())) {
Timer timer = cat.startTimer(inImplClass.toString());
try {
return inImplClass.invoke(implObject, args);
}catch (InvocationTargetException ex){
throw ex.getCause();
}finally{
timer.stop();
}
}else {
throw new NoSuchMethodException("Donot know how to execute " + reflector.getSignature(method));
}
}
private final SWFLogger cat = Config.instance().getLogger(getClass().getName()+"."+getModelName());
@SuppressWarnings("unchecked")
public <P extends Model> P getParent(Method parentGetter) {
Class<P> parentClass = (Class<P>) parentGetter.getReturnType();
String parentIdFieldName = StringUtil.underscorize(parentGetter.getName().substring(3) +"Id");
Method parentIdGetter = this.reflector.getFieldGetter(parentIdFieldName);
Integer parentId;
try {
parentId = (Integer)parentIdGetter.invoke(proxy);
} catch (Exception e) {
throw new RuntimeException(parentIdFieldName,e);
}
P parent = null;
if (parentId != null) {
parent = Database.getTable(parentClass).get(parentId);
}
return parent;
}
public <C extends Model> List<C> getChildren(Class<C> childClass){
Class<? extends Model> modelClass = getReflector().getModelClass();
ModelReflector<?> childReflector = ModelReflector.instance(childClass);
Expression expression = new Expression(childReflector.getPool(),Conjunction.OR);
for (String fieldName: childReflector.getFields()){
if (fieldName.endsWith("_ID")){
Method fieldGetter = childReflector.getFieldGetter(fieldName);
Method referredModelGetter = childReflector.getReferredModelGetterFor(fieldGetter);
if (referredModelGetter != null && referredModelGetter.getReturnType().isAssignableFrom(modelClass)){
String columnName = childReflector.getColumnDescriptor(fieldName).getName();
expression.add(new Expression(childReflector.getPool(),columnName,Operator.EQ,proxy.getId()));
}
}
}
if (expression.isEmpty()){
throw new RuntimeException("Don;t know how to getChildren of kind " + childClass.getSimpleName() + " for " + modelClass.getSimpleName());
}
return getChildren(childClass,expression);
}
public <C extends Model> List<C> getChildren(Class<C> childClass, String parentIdFieldName){
return getChildren(childClass,parentIdFieldName,null);
}
public <C extends Model> List<C> getChildren(Class<C> childClass, String parentIdFieldName, String addnl_condition){
int parentId = proxy.getId();
ModelReflector<C> childReflector = ModelReflector.instance(childClass);
String parentIdColumnName = childReflector.getColumnDescriptor(parentIdFieldName).getName();
Expression where = new Expression(getPool(),Conjunction.AND);
where.add(new Expression(getPool(),parentIdColumnName,Operator.EQ,new BindVariable(getPool(),parentId)));
if (!ObjectUtil.isVoid(addnl_condition)){
Expression addnl = new SQLExpressionParser(childClass).parse(addnl_condition);
where.add(addnl);
}
return getChildren(childClass, where);
}
public <C extends Model> List<C> getChildren(Class<C> childClass, Expression expression){
Select q = new Select();
q.from(childClass);
q.where(expression);
q.orderBy(ModelReflector.instance(childClass).getOrderBy());
return q.execute(childClass);
}
public <M extends Model> void setProxy(M proxy) {
this.proxy = proxy;
}
@SuppressWarnings("unchecked")
public <M extends Model> M getProxy() {
return (M)proxy;
}
public boolean isAccessibleBy(User user){
return isAccessibleBy(user, getReflector().getModelClass());
}
public Set<String> getParticipatingRoles(User user){
return getParticipatingRoles(user,getReflector().getModelClass());
}
public Set<String> getParticipatingRoles(User user,Class<? extends Model> asModel){
if (!getReflector().reflects(asModel)){
throw new AccessDeniedException();
}
return getParticipatingRoles(user, user.getParticipationOptions(asModel));
}
private Set<String> getParticipatingRoles(User user,Cache<String,Map<String,List<Integer>>> pGroupOptions){
Timer timer = cat.startTimer();
try {
ModelReflector<? extends Model> reflector = getReflector();
Set<String> participantingRoles = new HashSet<String>();
for (String participantRoleGroup : pGroupOptions.keySet()){
Map<String,List<Integer>> pOptions = pGroupOptions.get(participantRoleGroup);
for (String referencedModelIdFieldName :pOptions.keySet()){
Integer referenceValue = reflector.get(getRawRecord(),referencedModelIdFieldName);
if (pOptions.get(referencedModelIdFieldName).contains(referenceValue)){
participantingRoles.add(reflector.getParticipatingRole(referencedModelIdFieldName));
}
}
if (!pOptions.isEmpty() && participantingRoles.isEmpty()){
throw new AccessDeniedException(); // User is not a participant on the model.
}
}
return participantingRoles;
}finally{
timer.stop();
}
}
public boolean isAccessibleBy(User user,Class<? extends Model> asModel){
Timer timer = cat.startTimer(null,Config.instance().isTimerAdditive());
try {
if (!getReflector().reflects(asModel)){
return false;
}
Set<String> pRoles = getParticipatingRoles(user,asModel);
return (pRoles != null);// It is always true. returning false depends on AccessDeniedException being thrown.
}catch(AccessDeniedException ex){
return false;
}finally{
timer.stop();
}
}
public Record getRawRecord(){
return record;
}
public static void dispose(){
modelImplClassesCache.clear();
methodImplClassCache.clear();
}
public static <M extends Model> M getProxy(Class<M> modelClass, Record record) {
ModelReflector<M> ref = ModelReflector.instance(modelClass);
List<Class<?>> modelImplClasses = getModelImplClasses(modelClass);
try {
ModelInvocationHandler mImpl = new ModelInvocationHandler(modelClass, record);
M m = modelClass.cast(Proxy.newProxyInstance(modelClass.getClassLoader(), ref.getClassHierarchies().toArray(new Class<?>[]{}), mImpl));
mImpl.setProxy(m);
for (Class<?> implClass: modelImplClasses){
mImpl.addModelImplObject(constructImpl(implClass, m));
}
return m;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
private static <M extends Model> Object constructImpl(Class<?> implClass, M m){
if (ModelImpl.class.isAssignableFrom(implClass)){
if (ModelImpl.class.equals(implClass)) {
return new ModelImpl<M>(m);
}else {
ParameterizedType pt = (ParameterizedType)implClass.getGenericSuperclass();
Class<? extends Model> modelClass = (Class<? extends Model>) pt.getActualTypeArguments()[0];
try {
return implClass.getConstructor(modelClass).newInstance(m);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
throw new RuntimeException("Don't know how to instantiate " + implClass.getName());
}
private SequenceMap<Class<?>,Object> modelImplObjects = new SequenceMap<Class<?>,Object>();
private void addModelImplObject(Object o){
modelImplObjects.put(o.getClass(),o);
}
private Class<?> getMethodImplClass(Method m){
return methodImplClassCache.get(reflector.getModelClass()).get(m);
}
private static Cache<Class<? extends Model>,Cache<Method,Class<?>>> methodImplClassCache = new Cache<Class<? extends Model>, Cache<Method,Class<?>>>() {
/**
*
*/
private static final long serialVersionUID = -8303755398345923039L;
@Override
protected Cache<Method, Class<?>> getValue(final Class<? extends Model> modelClass) {
return new Cache<Method, Class<?>>() {
/**
*
*/
private static final long serialVersionUID = 1322249489351360016L;
@Override
protected Class<?> getValue(Method method) {
String mName = method.getName();
Class<?> retType = method.getReturnType();
Class<?>[] parameters = method.getParameterTypes();
for (Class<?> implClass: getModelImplClasses(modelClass)){
try {
Method inModelImplClass = implClass.getMethod(mName, parameters);
if (retType.isAssignableFrom(inModelImplClass.getReturnType())){
return implClass;
}
}catch (NoSuchMethodException ex){
//
}
}
return null;
}
};
}
};
private static Cache<Class<? extends Model>,List<Class<?>>> modelImplClassesCache = new Cache<Class<? extends Model>, List<Class<?>>>() {
/**
*
*/
private static final long serialVersionUID = 7544606584634901930L;
@Override
protected List<Class<?>> getValue(Class<? extends Model> modelClass) {
SequenceSet<Class<? extends Model>> modelClasses = ModelReflector.instance(modelClass).getClassHierarchies();
List<Class<?>> modelImplClasses = new ArrayList<Class<?>>();
for (Class<?> c : modelClasses){
String modelImplClassName = c.getName()+"Impl";
try {
Class<?> modelImplClass = Class.forName(modelImplClassName);
if (ModelImpl.class.isAssignableFrom(modelImplClass)){
modelImplClasses.add(modelImplClass);
}else {
throw new ClassCastException(modelImplClassName + " does not extend " + ModelImpl.class.getName());
}
}catch(ClassNotFoundException ex){
// Nothing
}
}
return modelImplClasses;
}
};
private static <M extends Model> List<Class<?>> getModelImplClasses(Class<M> modelClass){
return modelImplClassesCache.get(modelClass);
}
public void save() {
save(true);
}
public void save(boolean validate) {
if (!isDirty()) {
return;
}
if (validate){
validate();
}
beforeSave();
if (record.isNewRecord()) {
callExtensions("before.create");
create();
callExtensions("after.create");
} else {
callExtensions("before.update");
update();
callExtensions("after.update");
}
afterSave();
}
public void init(){
}
private static final Cache<String,List<FieldValidator<? extends Annotation>>> _fieldValidators = new Cache<String, List<FieldValidator<? extends Annotation>>>() {
/**
*
*/
private static final long serialVersionUID = -8174150221673158116L;
@Override
protected List<FieldValidator<? extends Annotation>> getValue(String pool) {
List<FieldValidator<? extends Annotation>> fieldValidators = new ArrayList<FieldValidator<? extends Annotation>>();
fieldValidators.add(new ExactLengthValidator(pool));
fieldValidators.add(new MaxLengthValidator(pool));
fieldValidators.add(new MinLengthValidator(pool));
fieldValidators.add(new NotNullValidator(pool));
fieldValidators.add(new RegExValidator(pool));
fieldValidators.add(new EnumerationValidator(pool));
fieldValidators.add(new DateFormatValidator(pool));
fieldValidators.add(new NumericRangeValidator(pool));
fieldValidators.add(new IntegerRangeValidator(pool));
return fieldValidators;
}
};
private static final List<ModelValidator> modelValidators = new ArrayList<ModelValidator>();
static{
modelValidators.add(new UniqueKeyValidator());
}
protected boolean isModelValid(MultiException ex) {
List<String> fields = reflector.getEditableFields();
boolean ret = true;
for (String field : fields) {
MultiException fieldException = new MultiException();
if (!reflector.isHouseKeepingField(field) && !isFieldValid(field,fieldException)) {
ex.add(fieldException);
ret = false;
}
}
if (ret){
for (ModelValidator v : modelValidators){
ret = v.isValid(getProxy(),ex) && ret;
}
}
return ret;
}
protected boolean isFieldValid(String field, MultiException fieldException) {
boolean ret = true;
Iterator<FieldValidator<? extends Annotation>> i = _fieldValidators.get(getPool()).iterator();
while (i.hasNext()) {
FieldValidator<? extends Annotation> v = i.next();
ret = v.isValid(getProxy(), field, fieldException) && ret;
}
return ret;
}
protected void validate(){
beforeValidate();
MultiException me = new MultiException();
if (!isModelValid(me)) {
throw me;
}
afterValidate();
}
private <R extends Model> SequenceSet<String> getExtensionPoints(Class<R> modelClass, String extnPointNameSuffix){
SequenceSet<String> extnPoints = new SequenceSet<String>();
ModelReflector<R> ref = ModelReflector.instance(modelClass);
for (Class<? extends Model> inHierarchy : ref.getClassHierarchies()){
String extnPoint = inHierarchy.getSimpleName() + "." + extnPointNameSuffix;
extnPoints.add(extnPoint);
}
return extnPoints;
}
private <R extends Model> void callExtensions(String extnPointNameSuffix){
for (String extnPoint: getExtensionPoints(reflector.getModelClass(), extnPointNameSuffix)){
Registry.instance().callExtensions(extnPoint, getProxy());
}
}
protected void beforeValidate(){
defaultFields();
callExtensions("before.validate");
}
public void defaultFields(){
if (!record.isNewRecord()){
proxy.setUpdatedAt(null);
proxy.setUpdaterUserId(null);
}
for (String field:reflector.getRealFields()){
String columnName = reflector.getColumnDescriptor(field).getName();
if (record.get(columnName) == null){
Method fieldGetter = reflector.getFieldGetter(field);
COLUMN_DEF cdef = reflector.getAnnotation(fieldGetter,COLUMN_DEF.class);
if (cdef != null){
Object defaultValue = StandardDefaulter.getDefaultValue(cdef.value(),cdef.args());
record.put(columnName,defaultValue);
}
}
}
}
protected void afterValidate(){
callExtensions("after.validate");
}
protected void beforeSave() {
callExtensions("before.save");
}
protected void afterSave() {
callExtensions("after.save");
}
protected void beforeDestory(){
callExtensions("before.destroy");
}
protected void afterDestroy(){
callExtensions("after.destroy");
}
public boolean isBeingDestroyed(){
return beingDestroyed;
}
private boolean beingDestroyed = false;
private void destroyCascade(){
ModelReflector<? extends Model> ref = getReflector();
for (Method childrenGetter : ref.getChildGetters()){
Class<? extends Model> childModelClass = ref.getChildModelClass(childrenGetter);
ModelReflector<? extends Model> childReflector = ModelReflector.instance(childModelClass);
List<String> referenceFields = childReflector.getReferenceFields(ref.getModelClass());
for (String referenceField: referenceFields){
try {
if (childReflector.getRealModelClass() == null){
continue;
}
@SuppressWarnings("unchecked")
List<Model> children = (List<Model>)childrenGetter.invoke(getProxy());
for (Model child : children){
if (childReflector.isFieldMandatory(referenceField)){
child.destroy();
}else {
childReflector.set(child,referenceField,null);
child.save();
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
public void destroy() {
if (isBeingDestroyed()){
return;
}
try {
beingDestroyed = true;
beforeDestory();
destroyCascade();
Delete q = new Delete(getReflector());
Expression condition = new Expression(getPool(),Conjunction.AND);
condition.add(new Expression(getPool(),getReflector().getColumnDescriptor("id").getName(),Operator.EQ,new BindVariable(getPool(),proxy.getId())));
condition.add(new Expression(getPool(),getReflector().getColumnDescriptor("lock_id").getName(),Operator.EQ,new BindVariable(getPool(),proxy.getLockId())));
q.where(condition);
if (q.executeUpdate() <= 0){
throw new RecordNotFoundException();
}
Database.getInstance().getCache(getReflector()).registerDestroy((Model)getProxy());
Database.getInstance().getCurrentTransaction().registerTableDataChanged(getReflector().getTableName());
afterDestroy();
}finally{
beingDestroyed = false;
}
}
private void update() {
int oldLockId = proxy.getLockId();
int newLockId = oldLockId + 1;
Update q = new Update(getReflector());
Iterator<String> fI = record.getDirtyFields().iterator();
while (fI.hasNext()) {
String columnName = fI.next();
String fieldName = getReflector().getFieldName(columnName);
TypeRef<?> ref = Database.getJdbcTypeHelper(getPool()).getTypeRef(getReflector().getFieldGetter(fieldName).getReturnType());
q.set(columnName,new BindVariable(getPool(),record.get(columnName), ref));
}
String idColumn = getReflector().getColumnDescriptor("id").getName();
String lockidColumn = getReflector().getColumnDescriptor("lock_id").getName();
q.set(lockidColumn,new BindVariable(getPool(),newLockId));
Expression condition = new Expression(getPool(),Conjunction.AND);
condition.add(new Expression(getPool(),idColumn,Operator.EQ,new BindVariable(getPool(),proxy.getId())));
condition.add(new Expression(getPool(),lockidColumn,Operator.EQ,new BindVariable(getPool(),oldLockId)));
q.where(condition);
if (q.executeUpdate() <= 0){
throw new RecordNotFoundException();
}
proxy.setLockId(newLockId);
record.startTracking();
if (!getReflector().isAnnotationPresent(CONFIGURATION.class)){
record.setLocked(true);
//Do only for transaction tables as config cache would need to be reset to false after commit. This is just to avoid that unwanted loop over config records cached.
}
Database.getInstance().getCache(getReflector()).registerUpdate((Model)getProxy());
Database.getInstance().getCurrentTransaction().registerTableDataChanged(getReflector().getTableName());
}
private void create() {
proxy.setLockId(0);
//Table<? extends Model> table = Database.getTable(getReflector().getTableName());
Insert insertSQL = new Insert(getReflector());
Map<String,BindVariable> values = new HashMap<String, BindVariable>();
Iterator<String> columnIterator = record.getDirtyFields().iterator();
while (columnIterator.hasNext()) {
String columnName = columnIterator.next();
String fieldName = getReflector().getFieldName(columnName);
if (fieldName == null){
continue;
}
TypeRef<?> ref = Database.getJdbcTypeHelper(getPool()).getTypeRef(getReflector().getFieldGetter(fieldName).getReturnType());
values.put(columnName,new BindVariable(getPool(),record.get(columnName), ref));
}
insertSQL.values(values);
Record generatedValues = new Record(getPool());
Set<String> autoIncrementColumns = reflector.getAutoIncrementColumns();
assert (autoIncrementColumns.size() <= 1); // atmost one auto increment id column
List<String> generatedKeys = new ArrayList<String>();
for (String anAutoIncrementColumn:autoIncrementColumns){
if ( Database.getJdbcTypeHelper(getPool()).isColumnNameAutoLowerCasedInDB() ){
generatedKeys.add(LowerCaseStringCache.instance().get(anAutoIncrementColumn));
}else {
generatedKeys.add(anAutoIncrementColumn);
}
}
insertSQL.executeUpdate(generatedValues, generatedKeys.toArray(new String[]{}));
if (generatedKeys.size() == 1){
assert (generatedValues.getDirtyFields().size() == 1);
String fieldName = generatedKeys.get(0);
String virtualFieldName = generatedValues.getDirtyFields().iterator().next();
int id = ((Number)generatedValues.get(virtualFieldName)).intValue();
record.put(fieldName, id);
}
record.setNewRecord(false);
record.startTracking();
if (!getReflector().isAnnotationPresent(CONFIGURATION.class)){
record.setLocked(true);
}
Database.getInstance().getCache(getReflector()).registerInsert((Model)getProxy());
Database.getInstance().getCurrentTransaction().registerTableDataChanged(getReflector().getTableName());
}
@Override
public boolean equals(Object o){
if (o == null){
return false;
}
if (!(o instanceof ModelInvocationHandler) && !getReflector().canReflect(o)){
return false;
}
if (o instanceof ModelInvocationHandler){
return equalImpl((ModelInvocationHandler)o);
}else {
return equalsProxy((Model)o);
}
}
public int hashCode(){
return (getModelName() + ":" + getProxy().getId()).hashCode() ;
}
protected boolean equalImpl(ModelInvocationHandler anotherImpl){
return (getProxy().getId() == anotherImpl.getProxy().getId()) && getReflector().getTableName().equals(anotherImpl.getReflector().getTableName());
}
protected boolean equalsProxy(Model anotherProxy){
boolean ret = false;
if (anotherProxy != null){
ret = getProxy().getId() == anotherProxy.getId();
}
return ret;
}
@SuppressWarnings("unchecked")
public <M extends Model> M cloneProxy(){
return (M)getRawRecord().clone().getAsProxy(getReflector().getModelClass());
}
private Map<String,Object> txnProperties = new HashMap<String, Object>();
public Object getTxnProperty(String name) {
return txnProperties.get(name);
}
public void setTxnPropery(String name,Object value) {
txnProperties.put(name, value);
}
public Object removeTxnProperty(String name) {
return txnProperties.remove(name);
}
public boolean isDirty(){
return !getProxy().getRawRecord().getDirtyFields().isEmpty();
}
}