com.twelvemonkeys.sql.ObjectMapper Maven / Gradle / Ivy
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.sql;
import com.twelvemonkeys.lang.*;
import java.lang.reflect.*;
// Single-type import, to avoid util.Date/sql.Date confusion
import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
/**
* A class for mapping JDBC ResultSet rows to Java objects.
*
* @see ObjectReader
*
* @author Harald Kuhr ([email protected])
* @author last modified by $Author: haku $
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/sql/ObjectMapper.java#1 $
*
* @todo Use JDK logging instead of proprietary logging.
*/
public class ObjectMapper {
final static String DIRECTMAP = "direct";
final static String OBJECTMAP = "object";
final static String COLLECTIONMAP = "collection";
final static String OBJCOLLMAP = "objectcollection";
Class mInstanceClass = null;
Hashtable mMethods = null;
Hashtable mColumnMap = null;
Hashtable mPropertiesMap = null;
Hashtable mJoins = null;
private Hashtable mTables = null;
private Vector mColumns = null;
Hashtable mForeignKeys = null;
Hashtable mPrimaryKeys = null;
Hashtable mMapTypes = null;
Hashtable mClasses = null;
String mPrimaryKey = null;
String mForeignKey = null;
String mIdentityJoin = null;
Log mLog = null;
/**
* Creates a new ObjectMapper for a DatabaseReadable
*
* @param obj An object of type DatabaseReadable
*/
/*
public ObjectMapper(DatabaseReadable obj) {
this(obj.getClass(), obj.getMapping());
}
*/
/**
* Creates a new ObjectMapper for any object, given a mapping
*
* @param objClass The class of the object(s) created by this OM
* @param mapping an Hashtable containing the mapping information
* for this OM
*/
public ObjectMapper(Class pObjClass, Hashtable pMapping) {
mLog = new Log(this);
mInstanceClass = pObjClass;
mJoins = new Hashtable();
mPropertiesMap = new Hashtable();
mColumnMap = new Hashtable();
mClasses = new Hashtable();
mMapTypes = new Hashtable();
mForeignKeys = new Hashtable();
mPrimaryKeys = new Hashtable();
// Unpack and store mapping information
for (Enumeration keys = pMapping.keys(); keys.hasMoreElements();) {
String key = (String) keys.nextElement();
String value = (String) pMapping.get(key);
int dotIdx = key.indexOf(".");
if (dotIdx >= 0) {
if (key.equals(".primaryKey")) {
// Primary key
mPrimaryKey = (String) pMapping.get(value);
}
else if (key.equals(".foreignKey")) {
// Foreign key
mForeignKey = (String) pMapping.get(value);
}
else if (key.equals(".join")) {
// Identity join
mIdentityJoin = (String) pMapping.get(key);
}
else if (key.endsWith(".primaryKey")) {
// Primary key in joining table
mPrimaryKeys.put(key.substring(0, dotIdx), value);
}
else if (key.endsWith(".foreignKey")) {
// Foreign key
mForeignKeys.put(key.substring(0, dotIdx), value);
}
else if (key.endsWith(".join")) {
// Joins
mJoins.put(key.substring(0, dotIdx), value);
}
else if (key.endsWith(".mapType")) {
// Maptypes
value = value.toLowerCase();
if (value.equals(DIRECTMAP) || value.equals(OBJECTMAP) ||
value.equals(COLLECTIONMAP) ||
value.equals(OBJCOLLMAP)) {
mMapTypes.put(key.substring(0, dotIdx), value);
}
else {
mLog.logError("Illegal mapType: \"" + value + "\"! "
+ "Legal types are: direct, object, "
+ "collection and objectCollection.");
}
}
else if (key.endsWith(".class")) {
// Classes
try {
mClasses.put(key.substring(0, dotIdx),
Class.forName(value));
}
catch (ClassNotFoundException e) {
mLog.logError(e);
//e.printStackTrace();
}
}
else if (key.endsWith(".collection")) {
// TODO!!
}
}
else {
// Property to column mappings
mPropertiesMap.put(key, value);
mColumnMap.put(value.substring(value.lastIndexOf(".") + 1),
key);
}
}
mMethods = new Hashtable();
Method[] methods = mInstanceClass.getMethods();
for (int i = 0; i < methods.length; i++) {
// Two methods CAN have same name...
mMethods.put(methods[i].getName(), methods[i]);
}
}
public void setPrimaryKey(String pPrimaryKey) {
mPrimaryKey = pPrimaryKey;
}
/**
* Gets the name of the property, that acts as the unique identifier for
* this ObjectMappers type.
*
* @return The name of the primary key property
*/
public String getPrimaryKey() {
return mPrimaryKey;
}
public String getForeignKey() {
return mForeignKey;
}
/**
* Gets the join, that is needed to find this ObjectMappers type.
*
* @return The name of the primary key property
*/
public String getIdentityJoin() {
return mIdentityJoin;
}
Hashtable getPropertyMapping(String pProperty) {
Hashtable mapping = new Hashtable();
if (pProperty != null) {
// Property
if (mPropertiesMap.containsKey(pProperty))
mapping.put("object", mPropertiesMap.get(pProperty));
// Primary key
if (mPrimaryKeys.containsKey(pProperty)) {
mapping.put(".primaryKey", "id");
mapping.put("id", mPrimaryKeys.get(pProperty));
}
//Foreign key
if (mForeignKeys.containsKey(pProperty))
mapping.put(".foreignKey", mPropertiesMap.get(mForeignKeys.get(pProperty)));
// Join
if (mJoins.containsKey(pProperty))
mapping.put(".join", mJoins.get(pProperty));
// mapType
mapping.put(".mapType", "object");
}
return mapping;
}
/**
* Gets the column for a given property.
*
* @param property The property
* @return The name of the matching database column, on the form
* table.column
*/
public String getColumn(String pProperty) {
if (mPropertiesMap == null || pProperty == null)
return null;
return (String) mPropertiesMap.get(pProperty);
}
/**
* Gets the table name for a given property.
*
* @param property The property
* @return The name of the matching database table.
*/
public String getTable(String pProperty) {
String table = getColumn(pProperty);
if (table != null) {
int dotIdx = 0;
if ((dotIdx = table.lastIndexOf(".")) >= 0)
table = table.substring(0, dotIdx);
else
return null;
}
return table;
}
/**
* Gets the property for a given database column. If the column incudes
* table qualifier, the table qualifier is removed.
*
* @param column The name of the column
* @return The name of the mathcing property
*/
public String getProperty(String pColumn) {
if (mColumnMap == null || pColumn == null)
return null;
String property = (String) mColumnMap.get(pColumn);
int dotIdx = 0;
if (property == null && (dotIdx = pColumn.lastIndexOf(".")) >= 0)
property = (String) mColumnMap.get(pColumn.substring(dotIdx + 1));
return property;
}
/**
* Maps each row of the given result set to an object ot this OM's type.
*
* @param rs The ResultSet to process (map to objects)
* @return An array of objects (of this OM's class). If there are no rows
* in the ResultSet, an empty (zero-length) array will be returned.
*/
public synchronized Object[] mapObjects(ResultSet pRSet) throws SQLException {
Vector result = new Vector();
ResultSetMetaData meta = pRSet.getMetaData();
int cols = meta.getColumnCount();
// Get colum names
String[] colNames = new String[cols];
for (int i = 0; i < cols; i++) {
colNames[i] = meta.getColumnName(i + 1); // JDBC cols start at 1...
/*
System.out.println(meta.getColumnLabel(i + 1));
System.out.println(meta.getColumnName(i + 1));
System.out.println(meta.getColumnType(i + 1));
System.out.println(meta.getColumnTypeName(i + 1));
// System.out.println(meta.getTableName(i + 1));
// System.out.println(meta.getCatalogName(i + 1));
// System.out.println(meta.getSchemaName(i + 1));
// Last three NOT IMPLEMENTED!!
*/
}
// Loop through rows in resultset
while (pRSet.next()) {
Object obj = null;
try {
obj = mInstanceClass.newInstance(); // Asserts empty constructor!
}
catch (IllegalAccessException iae) {
mLog.logError(iae);
// iae.printStackTrace();
}
catch (InstantiationException ie) {
mLog.logError(ie);
// ie.printStackTrace();
}
// Read each colum from this row into object
for (int i = 0; i < cols; i++) {
String property = (String) mColumnMap.get(colNames[i]);
if (property != null) {
// This column is mapped to a property
mapColumnProperty(pRSet, i + 1, property, obj);
}
}
// Add object to the result Vector
result.addElement(obj);
}
return result.toArray((Object[]) Array.newInstance(mInstanceClass,
result.size()));
}
/**
* Maps a ResultSet column (from the current ResultSet row) to a named
* property of an object, using reflection.
*
* @param rs The JDBC ResultSet
* @param index The column index to get the value from
* @param property The name of the property to set the value of
* @param obj The object to set the property to
*/
void mapColumnProperty(ResultSet pRSet, int pIndex, String pProperty,
Object pObj) {
if (pRSet == null || pProperty == null || pObj == null)
throw new IllegalArgumentException("ResultSet, Property or Object"
+ " arguments cannot be null!");
if (pIndex <= 0)
throw new IllegalArgumentException("Index parameter must be > 0!");
String methodName = "set" + StringUtil.capitalize(pProperty);
Method setMethod = (Method) mMethods.get(methodName);
if (setMethod == null) {
// No setMethod for this property
mLog.logError("No set method for property \""
+ pProperty + "\" in " + pObj.getClass() + "!");
return;
}
// System.err.println("DEBUG: setMethod=" + setMethod);
Method getMethod = null;
String type = "";
try {
Class[] cl = {Integer.TYPE};
type = setMethod.getParameterTypes()[0].getName();
type = type.substring(type.lastIndexOf(".") + 1);
// There is no getInteger, use getInt instead
if (type.equals("Integer")) {
type = "int";
}
// System.err.println("DEBUG: type=" + type);
getMethod = pRSet.getClass().
getMethod("get" + StringUtil.capitalize(type), cl);
}
catch (Exception e) {
mLog.logError("Can't find method \"get"
+ StringUtil.capitalize(type) + "(int)\" "
+ "(for class " + StringUtil.capitalize(type)
+ ") in ResultSet", e);
return;
}
try {
// Get the data from the DB
// System.err.println("DEBUG: " + getMethod.getName() + "(" + (i + 1) + ")");
Object[] colIdx = {new Integer(pIndex)};
Object[] arg = {getMethod.invoke(pRSet, colIdx)};
// Set it to the object
// System.err.println("DEBUG: " + setMethod.getName() + "(" + arg[0] + ")");
setMethod.invoke(pObj, arg);
}
catch (InvocationTargetException ite) {
mLog.logError(ite);
// ite.printStackTrace();
}
catch (IllegalAccessException iae) {
mLog.logError(iae);
// iae.printStackTrace();
}
}
/**
* Creates a SQL query string to get the primary keys for this
* ObjectMapper.
*/
String buildIdentitySQL(String[] pKeys) {
mTables = new Hashtable();
mColumns = new Vector();
// Get columns to select
mColumns.addElement(getPrimaryKey());
// Get tables to select (and join) from and their joins
tableJoins(null, false);
for (int i = 0; i < pKeys.length; i++) {
tableJoins(getColumn(pKeys[i]), true);
}
// All data read, build SQL query string
return "SELECT " + getPrimaryKey() + " " + buildFromClause()
+ buildWhereClause();
}
/**
* Creates a SQL query string to get objects for this ObjectMapper.
*/
public String buildSQL() {
mTables = new Hashtable();
mColumns = new Vector();
String key = null;
for (Enumeration keys = mPropertiesMap.keys(); keys.hasMoreElements();) {
key = (String) keys.nextElement();
// Get columns to select
String column = (String) mPropertiesMap.get(key);
mColumns.addElement(column);
tableJoins(column, false);
}
// All data read, build SQL query string
return buildSelectClause() + buildFromClause()
+ buildWhereClause();
}
/**
* Builds a SQL SELECT clause from the columns Vector
*/
private String buildSelectClause() {
StringBuilder sqlBuf = new StringBuilder();
sqlBuf.append("SELECT ");
String column = null;
for (Enumeration select = mColumns.elements(); select.hasMoreElements();) {
column = (String) select.nextElement();
/*
String subColumn = column.substring(column.indexOf(".") + 1);
// System.err.println("DEBUG: col=" + subColumn);
String mapType = (String) mMapTypes.get(mColumnMap.get(subColumn));
*/
String mapType = (String) mMapTypes.get(getProperty(column));
if (mapType == null || mapType.equals(DIRECTMAP)) {
sqlBuf.append(column);
sqlBuf.append(select.hasMoreElements() ? ", " : " ");
}
}
return sqlBuf.toString();
}
/**
* Builds a SQL FROM clause from the tables/joins Hashtable
*/
private String buildFromClause() {
StringBuilder sqlBuf = new StringBuilder();
sqlBuf.append("FROM ");
String table = null;
String schema = null;
for (Enumeration from = mTables.keys(); from.hasMoreElements();) {
table = (String) from.nextElement();
/*
schema = (String) schemas.get(table);
if (schema != null)
sqlBuf.append(schema + ".");
*/
sqlBuf.append(table);
sqlBuf.append(from.hasMoreElements() ? ", " : " ");
}
return sqlBuf.toString();
}
/**
* Builds a SQL WHERE clause from the tables/joins Hashtable
*
* @return Currently, this metod will return "WHERE 1 = 1", if no other
* WHERE conditions are specified. This can be considered a hack.
*/
private String buildWhereClause() {
StringBuilder sqlBuf = new StringBuilder();
String join = null;
boolean first = true;
for (Enumeration where = mTables.elements(); where.hasMoreElements();) {
join = (String) where.nextElement();
if (join.length() > 0) {
if (first) {
// Skip " AND " in first iteration
first = false;
}
else {
sqlBuf.append(" AND ");
}
}
sqlBuf.append(join);
}
if (sqlBuf.length() > 0)
return "WHERE " + sqlBuf.toString();
return "WHERE 1 = 1"; // Hacky...
}
/**
* Finds tables used in mappings and joins and adds them to the tables
* Hashtable, with the table name as key, and the join as value.
*/
private void tableJoins(String pColumn, boolean pWhereJoin) {
String join = null;
String table = null;
if (pColumn == null) {
// Identity
join = getIdentityJoin();
table = getTable(getProperty(getPrimaryKey()));
}
else {
// Normal
int dotIndex = -1;
if ((dotIndex = pColumn.lastIndexOf(".")) <= 0) {
// No table qualifier
return;
}
// Get table qualifier.
table = pColumn.substring(0, dotIndex);
// Don't care about the tables that are not supposed to be selected from
String property = (String) getProperty(pColumn);
if (property != null) {
String mapType = (String) mMapTypes.get(property);
if (!pWhereJoin && mapType != null && !mapType.equals(DIRECTMAP)) {
return;
}
join = (String) mJoins.get(property);
}
}
// If table is not in the tables Hash, add it, and check for joins.
if (mTables.get(table) == null) {
if (join != null) {
mTables.put(table, join);
StringTokenizer tok = new StringTokenizer(join, "= ");
String next = null;
while(tok.hasMoreElements()) {
next = tok.nextToken();
// Don't care about SQL keywords
if (next.equals("AND") || next.equals("OR")
|| next.equals("NOT") || next.equals("IN")) {
continue;
}
// Check for new tables and joins in this join clause.
tableJoins(next, false);
}
}
else {
// No joins for this table.
join = "";
mTables.put(table, join);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy