/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package com.venky.swf.db.table; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Types; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import com.venky.cache.Cache; import com.venky.core.checkpoint.Mergeable; import com.venky.core.collections.IgnoreCaseMap; import com.venky.core.util.ChangeListener; import com.venky.core.util.ChangeObservable; import com.venky.core.util.ObjectUtil; import com.venky.swf.db.Database; import com.venky.swf.db.JdbcTypeHelper; import com.venky.swf.db.JdbcTypeHelper.TypeRef; import com.venky.swf.db.model.Model; import com.venky.swf.db.model.reflection.ModelReflector; /** * * @author venky */ public class Record implements Comparable<Record>, Cloneable , Mergeable<Record>{ private IgnoreCaseMap<Object> fieldValues = new IgnoreCaseMap<Object>(); private IgnoreCaseMap<Object> dirtyFields = new IgnoreCaseMap<Object>(); private String pool; public Record(){ } public void setPool(String pool){ this.pool = pool; } public Record(String pool){ this.pool = pool; } public String getPool(){ return pool; } @Override public Record clone(){ Record r; try { r = (Record)super.clone(); r.fieldValues = fieldValues.clone(); r.dirtyFields = dirtyFields.clone(); r.proxyCache = new ProxyCache(r); } catch (CloneNotSupportedException e) { throw new RuntimeException("Should have not happened",e); } return r; } public String toString(){ return fieldValues.toString(); } public Set<String> getFieldNames(){ return fieldValues.keySet(); } private boolean equals(Object o1,Object o2){ boolean ret = false ; if (o1 == o2){ ret = true; }else if (o1 != null){ if (o1.equals(o2)){ ret = true; }else if (o2 != null){ if (o1.getClass() != o2.getClass()){ BindVariable b1 = new BindVariable(getPool(),o1); BindVariable b2 = new BindVariable(getPool(),o2); ret = b1.getValue().equals(b2.getValue()); // May be they are equal in db terms as the underlying db types for the 2 classes are same. } } } return ret ; } private void markDirty(String fieldName, Object oldValue, Object newValue){ if (isFieldDirty(fieldName)){//if already dirty.. Object oldestValue = dirtyFields.get(fieldName); if (equals(oldestValue,newValue)){ // Value is rolled back. dirtyFields.remove(fieldName); } }else { dirtyFields.put(fieldName,oldValue); } } /** * * @param fieldName * @param value * @return previous value of the field. */ public Object put( String fieldName, Object value){ Object oldValue = get(fieldName); if (!equals(oldValue, value)){ markDirty(fieldName, oldValue, value); if (value != null && value instanceof ChangeObservable){ ((ChangeObservable)value).registerChangeListener(new ChangeListener() { @Override public void hasChanged(Object oldValue, Object newValue) { markDirty(fieldName, oldValue, newValue); } }); } } return fieldValues.put(fieldName, value); } public Object get(String fieldName){ return fieldValues.get(fieldName); } public Object remove(String fieldName){ return fieldValues.remove(fieldName); } public void merge(Record record){ if (ObjectUtil.equals(getId(),record.getId())){ this.fieldValues.clear(); this.fieldValues.putAll(record.fieldValues); this.dirtyFields.clear(); this.dirtyFields.putAll(record.dirtyFields); this.locked = record.locked; this.newRecord = record.newRecord; }else { throw new MergeFailedException("Cannot merge with a different record"); } //this.proxyCache left out intentionally as it is only a cache and the cache on the current object is what we want anyway //and not the one on passed record. } public void load(ResultSet rs) throws SQLException{ load(rs,null); } public void load(ResultSet rs,ModelReflector<? extends Model> reflector) throws SQLException{ ResultSetMetaData meta = rs.getMetaData(); for (int i = 1 ; i <= meta.getColumnCount() ; i ++ ){ Object columnValue = rs.getObject(i); String columnName = meta.getColumnLabel(i); int type = meta.getColumnType(i); if (JdbcTypeHelper.isLOB(type)){ columnValue = Database.getJdbcTypeHelper(getPool()).getTypeRef(type).getTypeConverter().valueOf(columnValue); }else if (columnValue != null && reflector != null && type != Types.VARCHAR){ //SQLParser has similar code. List<TypeRef<?>> refs = Database.getJdbcTypeHelper(getPool()).getTypeRefs(type); TypeRef<?> ref = null; if (refs.size() == 1){ ref = refs.get(0); }else { String fieldName = reflector.getFieldName(columnName); if (fieldName == null) { continue; } ref = Database.getJdbcTypeHelper(getPool()).getTypeRef(reflector.getFieldGetter(fieldName).getReturnType()); } columnValue = ref.getTypeConverter().valueOf(columnValue); } put(columnName,columnValue); } } boolean newRecord = false; void startTracking(){ dirtyFields.clear(); newRecord = fieldValues.isEmpty(); } public boolean isNewRecord() { return newRecord; } public void setNewRecord(boolean newRecord) { this.newRecord = newRecord; } public Set<String> getDirtyFields() { return dirtyFields.keySet(); } public boolean isFieldDirty(String field){ return dirtyFields.containsKey(field); } public Object getOldValue(String field){ return dirtyFields.get(field); } public Integer getId(){ if (fieldValues.containsKey("ID")){ return Integer.valueOf(String.valueOf(fieldValues.get("ID"))); }else { return fakeId.addAndGet(1); } } private static AtomicInteger fakeId = new AtomicInteger(); @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + getId().hashCode(); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Record other = (Record) obj; if (fieldValues == null) { if (other.fieldValues != null) return false; } else if (!getId().equals(other.getId())) return false; return true; } public int compareTo(Record o) { return this.getId().compareTo(o.getId()); } private static class ProxyCache extends Cache<Class<? extends Model>,Model> { /** * */ private static final long serialVersionUID = -1436596145516567205L; Record record; ProxyCache(Record record){ this.record = record; } @Override protected Model getValue(Class<? extends Model> k) { return ModelInvocationHandler.getProxy(k, record); } } private transient ProxyCache proxyCache = new ProxyCache(this); @SuppressWarnings("unchecked") public <M extends Model> M getAsProxy(Class<M> modelClass){ return (M)proxyCache.get(modelClass); } private boolean locked = false; public boolean isLocked(){ return locked; } public void setLocked(boolean locked){ this.locked = locked; } }