package org.apache.ojb.broker.accesslayer;
/* Copyright 2002-2004 The Apache Software Foundation
*
* 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.
*/
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import org.apache.ojb.broker.PBFactoryException;
import org.apache.ojb.broker.PersistenceBrokerException;
import org.apache.ojb.broker.metadata.ClassDescriptor;
import org.apache.ojb.broker.metadata.FieldDescriptor;
import org.apache.ojb.broker.util.ClassHelper;
import org.apache.ojb.broker.util.logging.LoggerFactory;
/**
* Default implementation of the {@link RowReader} interface.
*
* @version $Id: RowReaderDefaultImpl.java,v 1.30 2004/05/06 18:54:44 arminw Exp $
*/
public class RowReaderDefaultImpl implements RowReader
{
/**
* represents a zero sized parameter array
*/
private static final Object[] NO_ARGS = {};
private ClassDescriptor m_cld;
public RowReaderDefaultImpl(ClassDescriptor cld)
{
this.m_cld = cld;
}
/**
* materialize a single object, described by cld,
* from the first row of the ResultSet rs.
* There are two possible strategies:
* 1. The persistent class defines a public constructor with arguments matching the persistent
* primitive attributes of the class. In this case we build an array args of arguments from rs
* and call Constructor.newInstance(args) to build an object.
* 2. The persistent class does not provide such a constructor, but only a public default
* constructor. In this case we create an empty instance with Class.newInstance().
* This empty instance is then filled by calling Field::set(obj,getObject(matchingColumn))
* for each attribute.
* The second strategy needs n calls to Field::set() which are much more expensive
* than the filling of the args array in the first strategy.
* client applications should therefore define adequate constructors to benefit from
* performance gain of the first strategy.
*
* MBAIRD: The rowreader is told what type of object to materialize, so we have to trust
* it is asked for the right type. It is possible someone marked an extent in the repository,
* but not in java, or vice versa and this could cause problems in what is returned.
*
* we *have* to be able to materialize an object from a row that has a objConcreteClass, as we
* retrieve ALL rows belonging to that table. The objects using the rowReader will make sure they
* know what they are asking for, so we don't have to make sure a descriptor is assignable from the
* selectClassDescriptor. This allows us to map both inherited classes and unrelated classes to the
* same table.
*
*/
public Object readObjectFrom(Map row) throws PersistenceBrokerException
{
// allow to select a specific classdescriptor
ClassDescriptor cld = selectClassDescriptor(row);
return buildOrRefreshObject(row, cld, null);
}
/**
* @see org.apache.ojb.broker.accesslayer.RowReader#refreshObject(Object, Map)
*/
public void refreshObject(Object instance, Map row)
{
// 1. select target ClassDescriptor
ClassDescriptor targetClassDescriptor = selectClassDescriptor(row);
// 2. fill all scalar attributes of the existing object
buildOrRefreshObject(row, targetClassDescriptor, instance);
}
/**
* Creates an object instance according to clb, and fills its fileds width data provided by row.
* @param row A {@link Map} contain the Object/Row mapping for the object.
* @param targetClassDescriptor If the "ojbConcreteClass" feature was used, the target
* {@link org.apache.ojb.broker.metadata.ClassDescriptor} could differ from the descriptor
* this class was associated - see {@link #selectClassDescriptor}.
* @param targetObject If 'null' a new object instance is build, else fields of object will
* be refreshed.
* @throws PersistenceBrokerException if there ewas an error creating the new object
*/
protected Object buildOrRefreshObject(Map row, ClassDescriptor targetClassDescriptor, Object targetObject)
{
Object result = targetObject;
FieldDescriptor fmd = null;
if(targetObject == null)
{
// 1. create new object instance if needed
result = ClassHelper.buildNewObjectInstance(targetClassDescriptor);
}
// 2. fill all scalar attributes of the new object
FieldDescriptor[] fields = targetClassDescriptor.getFieldDescriptions();
for (int i = 0; i < fields.length; i++)
{
fmd = fields[i];
fmd.getPersistentField().set(result, row.get(fmd.getColumnName()));
}
if(targetObject == null)
{
// 3. for new build objects, invoke the initialization method for the class if one is provided
Method initializationMethod = targetClassDescriptor.getInitializationMethod();
if (initializationMethod != null)
{
try
{
initializationMethod.invoke(result, NO_ARGS);
}
catch (Exception ex)
{
throw new PersistenceBrokerException("Unable to invoke initialization method:" + initializationMethod.getName() + " for class:" + m_cld.getClassOfObject(), ex);
}
}
}
return result;
}
/**
* materialize a single object, described by cld,
* from the first row of the ResultSet rs.
* There are two possible strategies:
* 1. The persistent class defines a public constructor with arguments matching the persistent
* primitive attributes of the class. In this case we build an array args of arguments from rs
* and call Constructor.newInstance(args) to build an object.
* 2. The persistent class does not provide such a constructor, but only a public default
* constructor. In this case we create an empty instance with Class.newInstance().
* This empty instance is then filled by calling Field::set(obj,getObject(matchingColumn))
* for each attribute.
* The second strategy needs n calls to Field::set() which are much more expensive
* than the filling of the args array in the first strategy.
* client applications should therefore define adequate constructors to benefit from
* performance gain of the first strategy.
*
* @throws PersistenceBrokerException if there is an error accessing the access layer
*/
public void readObjectArrayFrom(ResultSet rs, Map row)
{
FieldDescriptor[] fields = null;
if (m_cld.getSuperClass() != null)
{
/**
* treeder
* append super class fields if exist
*/
fields = m_cld.getFieldDescriptorsInHeirarchy();
}
else
{
fields = m_cld.getRepository().getFieldDescriptorsForMultiMappedTable(m_cld);
}
readValuesFrom(rs, row, fields);
}
/*
* @see RowReader#readPkValuesFrom(ResultSet, ClassDescriptor, Map)
* @throws PersistenceBrokerException if there is an error accessing the access layer
*/
public void readPkValuesFrom(ResultSet rs, Map row)
{
FieldDescriptor[] pkFields = m_cld.getPkFields();
readValuesFrom(rs, row, pkFields);
}
protected void readValuesFrom(ResultSet rs, Map row, FieldDescriptor[] fields)
{
int size = fields.length;
Object val = null;
FieldDescriptor fld = null;
try
{
for (int j = 0; j < size; j++)
{
fld = fields[j];
val = fld.getJdbcType().getObjectFromColumn(rs, fld.getColumnName());
row.put(fld.getColumnName(), fld.getFieldConversion().sqlToJava(val));
}
}
catch (SQLException t)
{
throw new PersistenceBrokerException("Error reading class type: " + m_cld.getClassNameOfObject()
+ " from result set, current read field was " + (fld != null ? fld.getPersistentField().getName() : null), t);
}
}
/**
* Check if there is an attribute which tells us which concrete class is to be instantiated.
*/
protected ClassDescriptor selectClassDescriptor(Map row) throws PersistenceBrokerException
{
// check if there is an attribute which tells us which concrete class is to be instantiated
FieldDescriptor concreteClassFD = m_cld.getOjbConcreteClassField();
if (concreteClassFD == null)
return m_cld;
else
{
try
{
String concreteClass = (String) row.get(concreteClassFD.getColumnName());
if (concreteClass == null || concreteClass.trim().length() == 0)
{
throw new PersistenceBrokerException(
"ojbConcreteClass field returned null or 0-length string");
}
else
{
concreteClass = concreteClass.trim();
}
ClassDescriptor result = m_cld.getRepository().getDescriptorFor(concreteClass);
if (result == null)
{
LoggerFactory.getDefaultLogger().warn(
"[" + RowReaderDefaultImpl.class.getName()
+ "] Can not find class-descriptor for ojbConcreteClass " + concreteClass
+ ", use given class-descriptor '" + m_cld.getClassNameOfObject()
+ "' instead");
result = m_cld;
}
return result;
}
catch (PBFactoryException e)
{
throw new PersistenceBrokerException(e);
}
}
}
public void setClassDescriptor(ClassDescriptor cld)
{
this.m_cld = cld;
}
public ClassDescriptor getClassDescriptor()
{
return m_cld;
}
}