package zutil.db.bean;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.logging.Logger;
import zutil.db.DBConnection;
import zutil.log.LogUtil;
/**
*
* The class that extends this will be able to save its state to a DB.
* Fields that are transient will be ignored, and fields that extend
* DBBean will be replaced by the id field of that class.
*
* Supported fields:
* *Boolean
* *Integer
* *Short
* *Float
* *Double
* *String
* *Character
* *DBBean
* *List
*
*
* @author Ziver
*/
public abstract class DBBean {
public static final Logger logger = LogUtil.getLogger();
/** The id of the bean **/
private Long id;
/**
* Sets the name of the table in the database
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DBTable {
String value();
}
/**
* Sets the name of the table that links different DBBeans together
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DBLinkTable {
/** The name of the Link table */
String name();
/** The class of the linked bean */
Class extends DBBean> beanClass();
/** The name of the column that contains this objects id */
String column() default "";
}
/**
* Sets the field as the id column in the table
*/
/*@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DBTableID {}
*/
/**
* A Class that contains information about a bean
*/
protected static class DBBeanConfig{
/** The name of the table in the DB */
protected String tableName;
/** The id field */
protected Field id_field;
/** All the fields in the bean */
protected ArrayList fields;
protected DBBeanConfig(){
fields = new ArrayList();
}
}
/** This is a cache of all the initialized beans */
private static HashMap,DBBeanConfig> beanConfigs = new HashMap,DBBeanConfig>();
/** This value is for preventing recursive loops */
private boolean processing;
protected DBBean(){
if( !beanConfigs.containsKey( this.getClass() ) )
initBeanConfig( this.getClass() );
processing = false;
}
/**
* @return the ID field of the bean or null if there is non
*/
public static Field getIDField(Class extends DBBean> c){
if( !beanConfigs.containsKey( c ) )
initBeanConfig( c );
return beanConfigs.get( c ).id_field;
}
/**
* @return all the fields except the ID field
*/
public static ArrayList getFields(Class extends DBBean> c){
if( !beanConfigs.containsKey( c ) )
initBeanConfig( c );
return beanConfigs.get( c ).fields;
}
/**
* @return the configuration object for the specified class
*/
protected static DBBeanConfig getBeanConfig(Class extends DBBean> c){
if( !beanConfigs.containsKey( c ) )
initBeanConfig( c );
return beanConfigs.get( c );
}
/**
* Caches the fields
*/
private static void initBeanConfig(Class extends DBBean> c){
Field[] fields = c.getDeclaredFields();
DBBeanConfig config = new DBBeanConfig();
// Find the table name
if( c.getAnnotation(DBTable.class) != null )
config.tableName = c.getAnnotation(DBTable.class).value().replace('\"', ' ');
// Add the fields in the bean
for( Field field : fields ){
int mod = field.getModifiers();
if( !Modifier.isTransient( mod ) &&
!Modifier.isAbstract( mod ) &&
!Modifier.isFinal( mod ) &&
!Modifier.isStatic( mod ) &&
!Modifier.isInterface( mod ) &&
!Modifier.isNative( mod )){
//if(field.getAnnotation(DBBean.DBTableID.class) != null)
// config.id_field = field;
config.fields.add( field );
}
}
try {
config.id_field = DBBean.class.getDeclaredField("id");
config.id_field.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
}
beanConfigs.put(c, config);
}
/**
* Saves the Object to the DB
*/
@SuppressWarnings("unchecked")
public void save(DBConnection db) throws SQLException{
if(processing)
return;
processing = true;
Class extends DBBean> c = this.getClass();
DBBeanConfig config = beanConfigs.get(c);
try {
Object id = getFieldValue( config.id_field );
// Generate the SQL
StringBuilder query = new StringBuilder();
if( id == null )
query.append("INSERT INTO ");
else query.append("UPDATE ");
query.append( config.tableName );
StringBuilder params = new StringBuilder();
for( Field field : config.fields ){
if( !List.class.isAssignableFrom(field.getType()) ){
params.append(" ");
params.append(field.getName());
params.append("=?,");
}
}
if( params.length() > 0 ){
params.delete( params.length()-1, params.length());
query.append( " SET" );
query.append( params );
if( id != null )
query.append( " id=?" );
}
logger.fine("Save query: "+query.toString());
PreparedStatement stmt = db.getPreparedStatement( query.toString() );
// Put in the variables in the SQL
for(int i=0; i list = (List)getFieldValue(field);
if( list != null ){
DBLinkTable linkTable = field.getAnnotation( DBLinkTable.class );
String subtable = linkTable.name();
String idcol = (linkTable.column().isEmpty() ? config.tableName : linkTable.column() );
DBBeanConfig subConfig = null;
for(DBBean subobj : list){
if(subConfig == null)
subConfig = beanConfigs.get( subobj.getClass() );
// Save links in link table
PreparedStatement subStmt = db.getPreparedStatement("REPLACE INTO "+subtable+" SET ?=? ?=?");
subStmt.setString(1, idcol);
subStmt.setObject(2, config.id_field);
subStmt.setString(3, subConfig.tableName);
subStmt.setObject(4, subobj.getFieldValue(subConfig.id_field) );
DBConnection.exec(subStmt);
// Save the sub bean
subobj.save(db);
}
}
}
// Normal field
else{
Object value = getFieldValue(field);
stmt.setObject(i+1, value);
}
}
if( id != null )
stmt.setObject(config.fields.size(), id);
// Execute the SQL
DBConnection.exec(stmt);
setFieldValue( config.id_field, db.getLastInsertID() );
} catch (SQLException e) {
throw e;
} catch (Exception e) {
throw new SQLException(e);
}
}
/**
* Deletes the object from the DB, WARNING will not delete sub beans
*/
public void delete(DBConnection db){
Class extends DBBean> c = this.getClass();
DBBeanConfig config = beanConfigs.get(c);
if( config.id_field == null )
throw new NoSuchElementException("DBTableID annotation missing in bean!");
try {
String sql = "DELETE FROM "+config.tableName+" WHERE "+ config.id_field +"=?";
logger.fine("Load query: "+sql);
PreparedStatement stmt = db.getPreparedStatement( sql );
// Put in the variables in the SQL
logger.fine("Delete query: "+sql);
stmt.setObject(1, getFieldValue(config.id_field) );
// Execute the SQL
DBConnection.exec(stmt);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Loads all the rows in the table into a LinkedList
*
* @param is the class of the bean
* @param c is the class of the bean
* @return a LinkedList with all the Beans in the DB
*/
public static List load(DBConnection db, Class c) throws SQLException {
// Initiate a BeanConfig if there is non
if( !beanConfigs.containsKey( c ) )
initBeanConfig( c );
DBBeanConfig config = beanConfigs.get(c);
// Generate query
String sql = "SELECT * FROM "+config.tableName;
logger.fine("Load query: "+sql);
PreparedStatement stmt = db.getPreparedStatement( sql );
// Run query
List list = DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(c, db) );
return list;
}
/**
* Loads all the rows in the table into a LinkedList
*
* @param is the class of the bean
* @param c is the class of the bean
* @param id is the id value of the bean
* @return a DBBean Object with the specific id or null
*/
public static T load(DBConnection db, Class c, Object id) throws SQLException {
// Initiate a BeanConfig if there is non
if( !beanConfigs.containsKey( c ) )
initBeanConfig( c );
DBBeanConfig config = beanConfigs.get(c);
// Generate query
PreparedStatement stmt = db.getPreparedStatement( "SELECT * FROM "+config.tableName+" WHERE ?=? LIMIT 1" );
stmt.setString(1, config.id_field.getName());
stmt.setObject(2, id );
// Run query
T obj = DBConnection.exec(stmt, DBBeanSQLResultHandler.create(c, db) );
return obj;
}
/**
* Creates a specific table for the given Bean
*/
public static void create(DBConnection sql, Class extends DBBean> c) throws SQLException{
if( !beanConfigs.containsKey( c ) )
initBeanConfig( c );
DBBeanConfig config = beanConfigs.get(c);
// Generate the SQL
StringBuilder query = new StringBuilder();
query.append("CREATE TABLE "+config.tableName+" ( ");
for( Field field : config.fields ){
query.append(" ");
query.append( field.getName() );
query.append( classToDBName(c) );
if( config.id_field.equals( field ) )
query.append(" PRIMARY KEY");
query.append(", ");
}
query.delete( query.length()-2, query.length());
query.append(")");
PreparedStatement stmt = sql.getPreparedStatement( sql.toString() );
// Execute the SQL
DBConnection.exec(stmt);
}
private static String classToDBName(Class> c){
if( c == String.class) return "CLOB"; // TEXT
else if(c == Short.class) return "SMALLINT";
else if(c == short.class) return "SMALLINT";
else if(c == Integer.class) return "INTEGER";
else if(c == int.class) return "INTEGER";
else if(c == BigInteger.class) return "BIGINT";
else if(c == Long.class) return "DECIMAL";
else if(c == long.class) return "DECIMAL";
else if(c == Float.class) return "DOUBLE";
else if(c == float.class) return "DOUBLE";
else if(c == Double.class) return "DOUBLE";
else if(c == double.class) return "DOUBLE";
else if(c == BigDecimal.class) return "DECIMAL";
else if(c == Boolean.class) return "BOOLEAN";
else if(c == boolean.class) return "BOOLEAN";
else if(c == Byte.class) return "BINARY(1)";
else if(c == byte.class) return "BINARY(1)";
return null;
}
/**
* This is a workaround if the field is not visible to other classes
*
* @param field is the field
* @return the value of the field
*/
protected Object getFieldValue(Field field){
try {
field.setAccessible(true);
return field.get(this);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* This is a workaround if the field is not visible to other classes
*
* @param field is the field
* @return the value of the field
*/
protected void setFieldValue(Field field, Object o){
try {
field.setAccessible(true);
field.set(this, o);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @return the object id or null if the bean has not bean saved yet
*/
public Long getId(){
return id;
}
}