public class BeanTransformerAdapterimplementsResultTransformer {
/** Logger available to subclasses */
protected finalLoglogger= LogFactory.getLog(getClass());
/** The class we are mapping to */
privateClassmappedClass;
/** Whether we're strictly validating */
private booleancheckFullyPopulated=false;
/** Whether we're defaulting primitives when mapping a null value */
private booleanprimitivesDefaultedForNullValue=false;
/** Map of the fields we provide mapping for */
privateMapmappedFields;
/** Set of bean properties we provide mapping for */
privateSetmappedProperties;
/**
* Create a newBeanPropertyRowMapperfor bean-style configuration.
*@see#setMappedClass
*@see#setCheckFullyPopulated
*/
publicBeanTransformerAdapter() {
}
/**
* Create a newBeanPropertyRowMapper, acceptingunpopulatedproperties
* in the target bean.
*
Consider using the{@link #newInstance}factory method instead,
* which allows for specifying the mapped type once only.
*@parammappedClass the class that each row should be mapped to
*/
publicBeanTransformerAdapter(ClassmappedClass) {
initialize(mappedClass);
}
/**
* Create a newBeanPropertyRowMapper.
*@parammappedClass the class that each row should be mapped to
*@paramcheckFullyPopulated whether we're strictly validating that
* all bean properties have been mapped from corresponding database fields
*/
publicBeanTransformerAdapter(ClassmappedClass,boolean checkFullyPopulated) {
initialize(mappedClass);
this.checkFullyPopulated=checkFullyPopulated;
}
/**
* Set the class that each row should be mapped to.
*/
public voidsetMappedClass(ClassmappedClass) {
if(this.mappedClass==null) {
initialize(mappedClass);
}else{
if(!this.mappedClass.equals(mappedClass)) {
throw newInvalidDataAccessApiUsageException("The mapped class can not be reassigned to map to "+mappedClass+" since it is already providing mapping for "+this.mappedClass);
}
}
}
/**
* Initialize the mappingmetadatafor the given class.
*@parammappedClass the mapped class.
*/
protected voidinitialize(ClassmappedClass) {
this.mappedClass=mappedClass;
this.mappedFields=newHashMap();
this.mappedProperties=newHashSet();
PropertyDescriptor[]pds= BeanUtils.getPropertyDescriptors(mappedClass);
for(PropertyDescriptorpd:pds) {
if(pd.getWriteMethod() !=null) {
this.mappedFields.put(pd.getName().toLowerCase(),pd);
StringunderscoredName= underscoreName(pd.getName());
if(!pd.getName().toLowerCase().equals(underscoredName)) {
this.mappedFields.put(underscoredName,pd);
}
this.mappedProperties.add(pd.getName());
}
}
}
/**
* Convert a name in camelCase to an underscored name in lower case.
* Any upper case letters are converted to lower case with a preceding underscore.
*@paramname the string containing original name
*@returnthe converted name
*/
privateString underscoreName(Stringname) {
if(!StringUtils.hasLength(name)) {
return "";
}
StringBuilderresult=newStringBuilder();
result.append(name.substring(0, 1).toLowerCase());
for(int i= 1;i
Strings=name.substring(i,i+ 1);
Stringslc=s.toLowerCase();
if(!s.equals(slc)) {
result.append("_").append(slc);
}else{
result.append(s);
}
}
return result.toString();
}
/**
* Get the class that we are mapping to.
*/
public finalClass getMappedClass() {
return this.mappedClass;
}
/**
* Set whether we're strictly validating that all bean properties have been
* mapped from corresponding database fields.
*
Default is {@code false}, acceptingunpopulatedproperties in the
* target bean.
*/
public voidsetCheckFullyPopulated(boolean checkFullyPopulated) {
this.checkFullyPopulated=checkFullyPopulated;
}
/**
* Return whether we're strictly validating that all bean properties have been
* mapped from corresponding database fields.
*/
public booleanisCheckFullyPopulated() {
return this.checkFullyPopulated;
}
/**
* Set whether we're defaulting Java primitives in the case of mapping a null value
* from corresponding database fields.
*
Default is {@code false}, throwing an exception when nulls are mapped to Java primitives.
*/
public voidsetPrimitivesDefaultedForNullValue(boolean primitivesDefaultedForNullValue) {
this.primitivesDefaultedForNullValue=primitivesDefaultedForNullValue;
}
/**
* Return whether we're defaulting Java primitives in the case of mapping a null value
* from corresponding database fields.
*/
public booleanisPrimitivesDefaultedForNullValue() {
return primitivesDefaultedForNullValue;
}
/**
* Initialize the given BeanWrapper to be used for row mapping.
* To be called for each row.
*
The default implementation is empty. Can be overridden in subclasses.
*@parambw the BeanWrapper to initialize
*/
protected voidinitBeanWrapper(BeanWrapperbw) {
}
/**
* Retrieve a JDBC object value for the specified column.
*
The default implementation calls
*{@link JdbcUtils#getResultSetValue(java.sql.ResultSet,int, Class)}.
* Subclasses may override this to check specific value typesupfront,
* or to post-process values return from {@code getResultSetValue}.
*@paramrs is the ResultSet holding the data
*@paramindex is the column index
*@parampd the bean property that each result object is expected to match
* (or {@code null} if none specified)
*@returnthe Object value
*@throwsSQLException in case of extraction failure
*@seeorg.springframework.jdbc.support.JdbcUtils#getResultSetValue(java.sql.ResultSet, int, Class)
*/
protectedObject getColumnValue(ResultSetrs,int index, PropertyDescriptorpd)throwsSQLException {
returnJdbcUtils.getResultSetValue(rs,index,pd.getPropertyType());
}
/**
* Static factory method to create a newBeanPropertyRowMapper
* (with the mapped class specified only once).
*@parammappedClass the class that each row should be mapped to
*/
public staticBeanPropertyRowMapper newInstance(ClassmappedClass) {
BeanPropertyRowMappernewInstance=new BeanPropertyRowMapper();
newInstance.setMappedClass(mappedClass);
return newInstance;
}
@Override
publicObject transformTuple(Object[]tuple, String[]aliases) {
TmappedObject= BeanUtils.instantiate(this.mappedClass);
BeanWrapperbw= PropertyAccessorFactory.forBeanPropertyAccess(mappedObject);
initBeanWrapper(bw);
SetpopulatedProperties= (isCheckFullyPopulated() ?newHashSet() :null);
for(int i= 0;i
Stringcolumn=aliases[i];
PropertyDescriptorpd=this.mappedFields.get(column.replaceAll(" ","").toLowerCase());
if(pd!=null) {
try{
Objectvalue=tuple[i];
try{
bw.setPropertyValue(pd.getName(),value);
}catch(TypeMismatchExceptione) {
if(value==null&&primitivesDefaultedForNullValue) {
logger.debug("Intercepted TypeMismatchException for column "+column+" and column '"+column+"' with value "+value+" when setting property '"+pd.getName() +"' of type "+pd.getPropertyType()
+" on object: "+mappedObject);
}else{
throw e;
}
}
if(populatedProperties!=null) {
populatedProperties.add(pd.getName());
}
}catch(NotWritablePropertyExceptionex) {
throw newDataRetrievalFailureException("Unable to map column "+column+" to property "+pd.getName(),ex);
}
}
}
if(populatedProperties!=null&& !populatedProperties.equals(this.mappedProperties)) {
throw newInvalidDataAccessApiUsageException("Given ResultSet does not contain all fields "+"necessary to populate object of class ["+this.mappedClass+"]: "+this.mappedProperties);
}
return mappedObject;
}
@Override
public ListtransformList(List list) {
return list;
}