org.datanucleus.store.rdbms.query.ResultClassROF Maven / Gradle / Ivy
/**********************************************************************
Copyright (c) 2005 Andy Jefferson and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Contributors:
...
**********************************************************************/
package org.datanucleus.store.rdbms.query;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import org.datanucleus.ExecutionContext;
import org.datanucleus.FetchPlan;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.store.query.QueryUtils;
import org.datanucleus.store.types.converters.TypeConversionHelper;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;
/**
* Take a ResultSet, and for each row retrieves an object of a specified result class type.
* Follows the rules in the JDO spec [14.6.12] regarding the result class.
*
* The resultClass will be used to create objects of that type when calling
* getObject(). The resultClass can be one of the following
*
* - Simple type - String, Long, Integer, Float, Boolean, Byte, Character, Double, Short, BigDecimal, BigInteger, java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp
* - java.util.Map - DataNucleus will choose the concrete impl of java.util.Map to use
* - Object[]
* - User defined type with either a constructor taking the result set fields, or a default constructor
* and setting the fields using a put(Object,Object) method, setXXX methods, or public fields
*
*
*
* Objects of this class are created in 2 distinct situations.
* The first is where a candidate class is available, and consequently field position mappings are available.
* The second is where no candidate class is available and so only the field names are available, and the results are taken in ResultSet order.
* These 2 modes have their own constructor.
*/
public class ResultClassROF extends AbstractROF
{
/** The result class that we should create for each row of results. */
private final Class> resultClass;
/** The index of fields position to mapping type. */
private final StatementMappingIndex[] stmtMappings;
/** Definition of results when the query has a result clause. */
private StatementResultMapping resultDefinition;
/** Names of the result field columns (in the ResultSet). */
private final String[] resultFieldNames;
/** Types of the result field columns (in the ResultSet). */
private final Class[] resultFieldTypes;
/** Map of the ResultClass Fields, keyed by the "field" names (only for user-defined result classes). */
private final Map resultClassFieldsByName = new HashMap<>();
boolean constructionDefined = false;
private Constructor resultClassArgConstructor = null;
private Constructor resultClassDefaultConstructor = null;
private ResultClassMemberSetter[] resultClassMemberSetters = null;
/**
* Constructor for a resultClass object factory where we have a result clause specified.
* @param ec ExecutionContext
* @param rs ResultSet being processed
* @param fp FetchPlan
* @param cls The result class to use (if any)
* @param resultDefinition The mapping information for the result expressions
*/
public ResultClassROF(ExecutionContext ec, ResultSet rs, FetchPlan fp, Class cls, StatementResultMapping resultDefinition)
{
super(ec, rs, fp);
// Set the result class that we convert each row into
if (cls != null && cls.getName().equals("java.util.Map"))
{
// JDO Spec 14.6.12 If user specifies java.util.Map, then impl chooses its own implementation Map class
this.resultClass = HashMap.class;
}
else if (cls == null)
{
// No result class specified so return Object/Object[] depending on number of expressions
this.resultClass = (resultDefinition != null && resultDefinition.getNumberOfResultExpressions() == 1) ? Object.class : Object[].class;
}
else
{
this.resultClass = cls;
}
this.resultDefinition = resultDefinition;
this.stmtMappings = null;
if (resultDefinition != null)
{
this.resultFieldNames = new String[resultDefinition.getNumberOfResultExpressions()];
this.resultFieldTypes = new Class[resultDefinition.getNumberOfResultExpressions()];
for (int i=0;i cls = ec.getClassLoaderResolver().classForName(classMap.getClassName());
AbstractClassMetaData acmd = ec.getMetaDataManager().getMetaDataForClass(cls, ec.getClassLoaderResolver());
PersistentClassROF rof = new PersistentClassROF<>(ec, rs, fp, classMap, acmd, cls);
rof.setIgnoreCache(ignoreCache);
resultFieldValues[i] = rof.getObject();
if (resultDefinition.getNumberOfResultExpressions() == 1)
{
if (classMap.getClassName().equals(resultClass.getName()))
{
// Special case of the result class being a persistent class so just return it
return resultFieldValues[0];
}
}
}
}
}
else if (stmtMappings != null)
{
// Field mapping information available so use it to allocate our results
resultFieldValues = new Object[stmtMappings.length];
for (int i=0; i 0)
{
str.append(",");
}
Class javaType = stmtMapping.getMapping().getJavaType();
str.append(javaType.getName());
}
NucleusLogger.QUERY.debug(Localiser.msg("021206", resultClass.getName(), str.toString()));
}
}
}
// No argumented constructor, so create using default constructor
Object obj = null;
try
{
ctr = resultClass.getDeclaredConstructor();
obj = ctr.newInstance();
}
catch (Exception e)
{
String msg = Localiser.msg("021205", resultClass.getName());
NucleusLogger.QUERY.error(msg);
throw new NucleusUserException(msg);
}
if (NucleusLogger.QUERY.isDebugEnabled())
{
NucleusLogger.QUERY.debug(Localiser.msg("021217", resultClass.getName()));
}
resultClassDefaultConstructor = ctr;
// Extract appropriate member for setting each Field
resultClassMemberSetters = new ResultClassMemberSetter[resultFieldValues.length];
for (int i=0;i ResultSetGetters by result classes */
private static Map resultSetGetters = new HashMap<>(15);
static
{
// any type specific getter from ResultSet that we can guess from the desired result class
resultSetGetters.put(Boolean.class, new ResultSetGetter()
{
public Object getValue(ResultSet rs, int i) throws SQLException
{
return Boolean.valueOf(rs.getBoolean(i));
}
});
resultSetGetters.put(Byte.class, new ResultSetGetter()
{
public Object getValue(ResultSet rs, int i) throws SQLException
{
return Byte.valueOf(rs.getByte(i));
}
});
resultSetGetters.put(Short.class, new ResultSetGetter()
{
public Object getValue(ResultSet rs, int i) throws SQLException
{
return Short.valueOf(rs.getShort(i));
}
});
resultSetGetters.put(Integer.class, new ResultSetGetter()
{
public Object getValue(ResultSet rs, int i) throws SQLException
{
return Integer.valueOf(rs.getInt(i));
}
});
resultSetGetters.put(Long.class, new ResultSetGetter()
{
public Object getValue(ResultSet rs, int i) throws SQLException
{
return Long.valueOf(rs.getLong(i));
}
});
resultSetGetters.put(Float.class, new ResultSetGetter()
{
public Object getValue(ResultSet rs, int i) throws SQLException
{
return Float.valueOf(rs.getFloat(i));
}
});
resultSetGetters.put(Double.class, new ResultSetGetter()
{
public Object getValue(ResultSet rs, int i) throws SQLException
{
return Double.valueOf(rs.getDouble(i));
}
});
resultSetGetters.put(BigDecimal.class, new ResultSetGetter()
{
public Object getValue(ResultSet rs, int i) throws SQLException
{
return rs.getBigDecimal(i);
}
});
resultSetGetters.put(String.class, new ResultSetGetter()
{
public Object getValue(ResultSet rs, int i) throws SQLException
{
return rs.getString(i);
}
});
ResultSetGetter timestampGetter = new ResultSetGetter()
{
public Object getValue(ResultSet rs, int i) throws SQLException
{
return rs.getTimestamp(i);
}
};
resultSetGetters.put(java.sql.Timestamp.class, timestampGetter);
// also use Timestamp getter for Date, so it also has time of the day e.g. with Oracle
resultSetGetters.put(java.util.Date.class, timestampGetter);
resultSetGetters.put(java.sql.Date.class, new ResultSetGetter()
{
public Object getValue(ResultSet rs, int i) throws SQLException
{
return rs.getDate(i);
}
});
resultSetGetters.put(byte[].class, new ResultSetGetter()
{
public Object getValue(ResultSet rs, int i) throws SQLException
{
return rs.getBytes(i);
}
});
resultSetGetters.put(java.io.Reader.class, new ResultSetGetter()
{
public Object getValue(ResultSet rs, int i) throws SQLException
{
return rs.getCharacterStream(i);
}
});
resultSetGetters.put(java.sql.Array.class, new ResultSetGetter()
{
public Object getValue(ResultSet rs, int i) throws SQLException
{
return rs.getArray(i);
}
});
}
/**
* Convenience method to read the value of a column out of the ResultSet.
* Uses "rs.getBoolean", "rs.getInt", etc, otherwise "rs.getObject".
* @param rs ResultSet
* @param columnNumber Number of the column (starting at 1)
* @return Value for the column for this row.
* @throws SQLException Thrown if an error occurs on reading
*/
private Object getResultObject(final ResultSet rs, int columnNumber) throws SQLException
{
// Where the result class is a basic type
ResultSetGetter getter = resultSetGetters.get(resultClass);
if (getter != null)
{
// User has specified a result type for this column so use the specific getter
return getter.getValue(rs, columnNumber);
}
// User has specified resultClass as Object/Object[] or user-type so just retrieve generically
// TODO We could pass in the "type" that we expect the column to be (whether field, or constructor arg etc), and then use getXXX here
return rs.getObject(columnNumber);
}
interface ResultClassMemberSetter
{
public boolean set(Object obj, String fieldName, Object value);
}
/**
* Class that sets a field of the ResultClass using a public field.
* This is done either using the value passed in directly, or by first converting it to the required field type.
*/
class ResultClassFieldSetter implements ResultClassMemberSetter
{
Field field;
public ResultClassFieldSetter(Field f)
{
this.field = f;
}
public boolean set(Object obj, String fieldName, Object value)
{
Object fieldValue = value;
if (value != null && !field.getType().isAssignableFrom(value.getClass()))
{
// Field is not of assignable type so try to convert it
Object convertedValue = TypeConversionHelper.convertTo(value, field.getType());
if (convertedValue != value)
{
fieldValue = convertedValue;
}
else
{
// TODO Flag the error here
}
}
try
{
field.set(obj, fieldValue);
if (NucleusLogger.QUERY.isDebugEnabled())
{
if (fieldValue != value)
{
NucleusLogger.QUERY.debug(Localiser.msg("021219", resultClass.getName(), fieldName));
}
else
{
NucleusLogger.QUERY.debug(Localiser.msg("021218", resultClass.getName(), fieldName));
}
}
return true;
}
catch (Exception e)
{
// Maybe we need to update TypeConversionHelper with further conversions
NucleusLogger.DATASTORE_RETRIEVE.warn("Unable to convert query value of type " + value.getClass().getName() + " to field of type " + field.getType().getName());
}
return false;
}
}
/**
* Class that sets a field of the ResultClass using a setter method.
* This is done either using the value passed in directly, or by first converting it to a specified argument type.
*/
class ResultClassSetMethodSetter implements ResultClassMemberSetter
{
Method setterMethod;
Class argType;
public ResultClassSetMethodSetter(Method m, Class argType)
{
this.setterMethod = m;
this.argType = argType;
}
public boolean set(Object obj, String fieldName, Object value)
{
if (argType == null)
{
// Setter takes in the precise value
try
{
setterMethod.invoke(obj, new Object[]{value});
if (NucleusLogger.QUERY.isDebugEnabled())
{
NucleusLogger.QUERY.debug(Localiser.msg("021220", resultClass.getName(), fieldName));
}
return true;
}
catch (Exception e)
{
}
}
else
{
// Setter takes in a value that needs converting first
try
{
Object convValue = TypeConversionHelper.convertTo(value, argType);
setterMethod.invoke(obj, new Object[]{convValue});
if (NucleusLogger.QUERY.isDebugEnabled())
{
NucleusLogger.QUERY.debug(Localiser.msg("021221", resultClass.getName(), fieldName));
}
return true;
}
catch (Exception e)
{
//do nothing
}
}
return false;
}
}
/**
* Class that sets a field of the ResultClass using a put() method.
*/
class ResultClassPutMethodSetter implements ResultClassMemberSetter
{
Method putMethod;
public ResultClassPutMethodSetter(Method m)
{
this.putMethod = m;
}
public boolean set(Object obj, String fieldName, Object value)
{
try
{
putMethod.invoke(obj, new Object[]{fieldName, value});
if (NucleusLogger.QUERY.isDebugEnabled())
{
NucleusLogger.QUERY.debug(Localiser.msg("021222", resultClass.getName(), fieldName));
}
return true;
}
catch (Exception e)
{
//do nothing
}
return false;
}
}
}