/*
|
* BaseLink - Generic object relational mapping
|
* Copyright (C) 2011 Ulrich Hilger, http://uhilger.de
|
*
|
* This program is free software: you can redistribute it and/or modify
|
* it under the terms of the GNU General Public License as published by
|
* the Free Software Foundation, either version 3 of the License, or
|
* (at your option) any later version.
|
*
|
* This program 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 General Public License for more details.
|
*
|
* You should have received a copy of the GNU General Public License
|
* along with this program. If not, see http://www.gnu.org/licenses/
|
*/
|
|
package de.uhilger.baselink;
|
|
import java.io.ByteArrayInputStream;
|
import java.lang.reflect.Method;
|
import java.sql.Blob;
|
import java.sql.Connection;
|
import java.sql.PreparedStatement;
|
import java.sql.Statement;
|
import java.sql.ResultSet;
|
import java.util.ArrayList;
|
import java.util.Enumeration;
|
import java.util.Hashtable;
|
import java.util.List;
|
import java.util.logging.Level;
|
import java.util.logging.Logger;
|
|
/**
|
* Universal mapping of objects to and from a database
|
*
|
* Any class that has annotations for <code>DBTable</code> and
|
* <code>DBColumns</code> can be persisted using <code>GenericRecord</code>.
|
*
|
* @author Copyright (c) Ulrich Hilger, http://uhilger.de
|
* @author Published under the terms and conditions of
|
* the <a href="http://www.gnu.org/licenses/" target="_blank">GNU General Public License</a>
|
*/
|
public class GenericRecord implements Record {
|
|
/** default getter method indicator */
|
public static final String GETTER_NAME = "get";
|
/** default setter method indicator */
|
public static final String SETTER_NAME = "set";
|
|
/** name of table this instance of GenericRecord references */
|
private String tableName;
|
/** list of column names that make up the primary key */
|
private List<String> primaryKeyColNames;
|
/** reference to field setters and getters */
|
private Hashtable<String,Field> columns;
|
/** reference to class that this instance of GenericRecord maps to and from a database */
|
private Class<?> recordClass;
|
|
/**
|
* Create a new object of class GenericRecord
|
* @param c the class to persist
|
*/
|
public GenericRecord(Class<?> c) {
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(c.getName());
|
this.recordClass = c;
|
|
DBTable table = c.getAnnotation(DBTable.class);
|
if(table != null) {
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(tableName);
|
tableName = table.name();
|
} else {
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).severe("missing table annotation");
|
}
|
|
primaryKeyColNames = new ArrayList<String>();
|
DBPrimaryKey primaryKey = c.getAnnotation(DBPrimaryKey.class);
|
if(primaryKey != null) {
|
String[] names = primaryKey.value();
|
for(int i = 0; i < names.length; i++) {
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(names[i]);
|
primaryKeyColNames.add(names[i]);
|
}
|
} else {
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).severe("missing primary key annotation");
|
}
|
|
columns = new Hashtable<String,Field>();
|
Method[] methods = c.getMethods();
|
for(int i = 0; i < methods.length; i++) {
|
Method method = methods[i];
|
DBColumn field = method.getAnnotation(DBColumn.class);
|
if(field != null) {
|
Field mapper = new Field();
|
String fieldName = field.name();
|
mapper.setColumnType(field.type());
|
mapper.setColumnName(fieldName);
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(fieldName);
|
String methodName = method.getName();
|
if(methodName.startsWith(GETTER_NAME)) {
|
String fieldMethod = methodName.substring(3);
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(fieldMethod);
|
Class<?> returnType = method.getReturnType();
|
try {
|
mapper.setSetter(c.getMethod(SETTER_NAME + fieldMethod, returnType));
|
} catch (Exception e) {
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.getLocalizedMessage(), e);
|
}
|
mapper.setGetter(method);
|
}
|
columns.put(fieldName, mapper);
|
}
|
}
|
}
|
|
/**
|
* Get a statement suitable to delete a given object from the database
|
* @param c the database connection to use for the delete
|
* @param record the object to delete
|
* @return the delete statement
|
* @throws Exception
|
*/
|
public PreparedStatement getDeleteStatment(Connection c, Object record) throws Exception {
|
StringBuffer sql = new StringBuffer();
|
sql.append("delete from ");
|
sql.append(tableName);
|
sql.append(" where ");
|
appendPrimaryKeyFields(sql);
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(sql.toString());
|
PreparedStatement ps = c.prepareStatement(sql.toString());
|
prepareFields(0, primaryKeyColNames, ps, record);
|
return ps;
|
}
|
|
/**
|
* Get a statement suitable to insert a given object to the database
|
* @param c the database connection to use for the insert
|
* @param record the object to insert
|
* @param autoGeneratedKeys a flag indicating whether auto-generated keys should be returned;
|
* one of Statement.RETURN_GENERATED_KEYS or Statement.NO_GENERATED_KEYS
|
* @return the insert statement
|
* @throws Exception
|
*/
|
public PreparedStatement getInsertStmt(Connection c, Object record, int autoGeneratedKeys) throws Exception {
|
StringBuffer sql = new StringBuffer();
|
sql.append("insert into ");
|
sql.append(tableName);
|
sql.append("(");
|
Enumeration<String> fieldNames = columns.keys();
|
StringBuffer fieldList = new StringBuffer();
|
StringBuffer valueList = new StringBuffer();
|
while(fieldNames.hasMoreElements()) {
|
if(fieldList.length() > 0) {
|
fieldList.append(",");
|
valueList.append(",");
|
}
|
fieldList.append(fieldNames.nextElement());
|
valueList.append("?");
|
}
|
sql.append(fieldList);
|
sql.append(")");
|
sql.append(" values (");//?, ?)");
|
sql.append(valueList);
|
sql.append(")");
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(sql.toString());
|
PreparedStatement ps = c.prepareStatement(sql.toString(), autoGeneratedKeys);
|
prepareFields(0, ps, record);
|
return ps;
|
}
|
|
/**
|
* Get a statement suitable to insert a given object to the database
|
* @param c the database connection to use for the insert
|
* @param record the object to insert
|
* @return the insert statement
|
* @throws Exception
|
*/
|
public PreparedStatement getInsertStatment(Connection c, Object record) throws Exception {
|
return getInsertStmt(c, record, Statement.NO_GENERATED_KEYS);
|
}
|
|
/**
|
* Get a statement suitable to update a given object in the database
|
* @param c the database connection to use for the update
|
* @param record the object to update
|
* @return the update statement
|
* @throws Exception
|
*/
|
public PreparedStatement getUpdateStatment(Connection c, Object record) throws Exception {
|
StringBuffer sql = new StringBuffer();
|
sql.append("update ");
|
sql.append(tableName);
|
sql.append(" set ");
|
Enumeration<String> fieldNames = columns.keys();
|
StringBuffer fieldList = new StringBuffer();
|
while(fieldNames.hasMoreElements()) {
|
if(fieldList.length() > 0) {
|
fieldList.append(", ");
|
}
|
fieldList.append(fieldNames.nextElement());
|
fieldList.append("=?");
|
}
|
sql.append(fieldList);
|
sql.append(" where ");
|
appendPrimaryKeyFields(sql);
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(sql.toString());
|
PreparedStatement ps = c.prepareStatement(sql.toString());
|
prepareFields(0, ps, record);
|
prepareFields(columns.size(), primaryKeyColNames, ps, record);
|
return ps;
|
}
|
|
/**
|
* Get contents of this record as an object
|
* @param resultSet a resultSet that points to the record to get as an object
|
* @param includeBlobs indicator whether or not to include BLOBs
|
* @return an object having the data of this record
|
* @throws Exception
|
*/
|
public Object toObject(ResultSet resultSet, boolean includeBlobs) throws Exception {
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(recordClass.getName());
|
Object o = recordClass.newInstance();
|
Enumeration<String> fieldNames = columns.keys();
|
while(fieldNames.hasMoreElements()) {
|
String columnName = fieldNames.nextElement();
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(columnName);
|
Field mapper = columns.get(columnName);
|
Method setter = mapper.getSetter();
|
if(setter != null) {
|
Object data = null;
|
if(mapper.getColumnType().equals(DBColumn.Type.BLOB)) {
|
if(includeBlobs) {
|
Blob blob = resultSet.getBlob(columnName);
|
data = new String(blob.getBytes((long) 1, (int) blob.length()));
|
setter.invoke(o, data);
|
}
|
} else {
|
data = resultSet.getObject(columnName);
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).finest(data.toString());
|
setter.invoke(o, data);
|
}
|
}
|
}
|
return o;
|
}
|
|
/**
|
* append names of primary key columns to an SQL string
|
* @param sql the SQL string to append to
|
*/
|
private void appendPrimaryKeyFields(StringBuffer sql) {
|
for(int i = 0; i < primaryKeyColNames.size(); i++) {
|
if(i > 0) {
|
sql.append(" and ");
|
}
|
sql.append(primaryKeyColNames.get(i));
|
sql.append("=?");
|
}
|
}
|
|
/**
|
* Prepare fields of a PreparedStatement
|
* @param offset number of field to start with
|
* @param ps the statement to prepare
|
* @param record object that has the data for the statement
|
* @throws Exception
|
*/
|
private void prepareFields(int offset, PreparedStatement ps, Object record) throws Exception {
|
Enumeration<String> fieldNames = columns.keys();
|
int i = 1;
|
while(fieldNames.hasMoreElements()) {
|
Field mapper = columns.get(fieldNames.nextElement());
|
Method getter = mapper.getGetter();
|
Object o = getter.invoke(record, (Object[]) null);
|
if(mapper.getColumnType().equals(DBColumn.Type.BLOB)) {
|
String content = o.toString();
|
ps.setBinaryStream(i+offset, new ByteArrayInputStream(content.getBytes()), content.length());
|
} else {
|
ps.setObject(i+offset, o);
|
}
|
i++;
|
}
|
}
|
|
/**
|
* Prepare fields of a PreparedStatement
|
* @param offset number of field to start with
|
* @param source list of field names
|
* @param ps the statement to prepare
|
* @param record object that has the data for the statement
|
* @throws Exception
|
*/
|
private void prepareFields(int offset, List<String> source, PreparedStatement ps, Object record) throws Exception {
|
for(int i = 0; i < source.size(); i++) {
|
Field mapper = columns.get(source.get(i));
|
Method getter = mapper.getGetter();
|
Object o = getter.invoke(record, (Object[]) null);
|
ps.setObject(i+offset+1, o);
|
}
|
}
|
|
/**
|
* Get the name of the database table this record references
|
* @return the table name
|
*/
|
public String getTableName() {
|
return tableName;
|
}
|
|
}
|