001: package org.apache.ojb.broker.accesslayer;
002:
003: /* Copyright 2002-2005 The Apache Software Foundation
004: *
005: * Licensed under the Apache License, Version 2.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: import java.lang.reflect.Method;
019: import java.sql.ResultSet;
020: import java.sql.SQLException;
021: import java.util.Map;
022:
023: import org.apache.ojb.broker.PersistenceBrokerException;
024: import org.apache.ojb.broker.metadata.ClassDescriptor;
025: import org.apache.ojb.broker.metadata.FieldDescriptor;
026: import org.apache.ojb.broker.util.ClassHelper;
027: import org.apache.ojb.broker.util.SqlHelper;
028:
029: /**
030: * Default implementation of the {@link RowReader} interface.
031: *
032: * @version $Id: RowReaderDefaultImpl.java,v 1.30.2.11 2005/12/21 22:22:58 tomdz Exp $
033: */
034:
035: public class RowReaderDefaultImpl implements RowReader {
036: /**
037: * Used as key in result set row map.
038: */
039: private static final String OJB_CONCRETE_CLASS_KEY = "ojbTemporaryNoneColumnKey";
040: /**
041: * represents a zero sized parameter array
042: */
043: private static final Object[] NO_ARGS = {};
044:
045: private ClassDescriptor m_cld;
046:
047: public RowReaderDefaultImpl(ClassDescriptor cld) {
048: this .m_cld = cld;
049: }
050:
051: /**
052: * materialize a single object, described by cld,
053: * from the first row of the ResultSet rs.
054: * There are two possible strategies:
055: * 1. The persistent class defines a public constructor with arguments matching the persistent
056: * primitive attributes of the class. In this case we build an array args of arguments from rs
057: * and call Constructor.newInstance(args) to build an object.
058: * 2. The persistent class does not provide such a constructor, but only a public default
059: * constructor. In this case we create an empty instance with Class.newInstance().
060: * This empty instance is then filled by calling Field::set(obj,getObject(matchingColumn))
061: * for each attribute.
062: * The second strategy needs n calls to Field::set() which are much more expensive
063: * than the filling of the args array in the first strategy.
064: * client applications should therefore define adequate constructors to benefit from
065: * performance gain of the first strategy.
066: *
067: * MBAIRD: The rowreader is told what type of object to materialize, so we have to trust
068: * it is asked for the right type. It is possible someone marked an extent in the repository,
069: * but not in java, or vice versa and this could cause problems in what is returned.
070: *
071: * we *have* to be able to materialize an object from a row that has a objConcreteClass, as we
072: * retrieve ALL rows belonging to that table. The objects using the rowReader will make sure they
073: * know what they are asking for, so we don't have to make sure a descriptor is assignable from the
074: * selectClassDescriptor. This allows us to map both inherited classes and unrelated classes to the
075: * same table.
076: *
077: */
078: public Object readObjectFrom(Map row)
079: throws PersistenceBrokerException {
080: // allow to select a specific classdescriptor
081: ClassDescriptor cld = selectClassDescriptor(row);
082: return buildOrRefreshObject(row, cld, null);
083: }
084:
085: /**
086: * @see org.apache.ojb.broker.accesslayer.RowReader#refreshObject(Object, Map)
087: */
088: public void refreshObject(Object instance, Map row) {
089: // 1. select target ClassDescriptor
090: ClassDescriptor targetClassDescriptor = selectClassDescriptor(row);
091: // 2. fill all scalar attributes of the existing object
092: buildOrRefreshObject(row, targetClassDescriptor, instance);
093: }
094:
095: /**
096: * Creates an object instance according to clb, and fills its fileds width data provided by row.
097: * @param row A {@link Map} contain the Object/Row mapping for the object.
098: * @param targetClassDescriptor If the "ojbConcreteClass" feature was used, the target
099: * {@link org.apache.ojb.broker.metadata.ClassDescriptor} could differ from the descriptor
100: * this class was associated - see {@link #selectClassDescriptor}.
101: * @param targetObject If 'null' a new object instance is build, else fields of object will
102: * be refreshed.
103: * @throws PersistenceBrokerException if there ewas an error creating the new object
104: */
105: protected Object buildOrRefreshObject(Map row,
106: ClassDescriptor targetClassDescriptor, Object targetObject) {
107: Object result = targetObject;
108: FieldDescriptor fmd;
109: FieldDescriptor[] fields = targetClassDescriptor
110: .getFieldDescriptor(true);
111:
112: if (targetObject == null) {
113: // 1. create new object instance if needed
114: result = ClassHelper
115: .buildNewObjectInstance(targetClassDescriptor);
116: }
117:
118: // 2. fill all scalar attributes of the new object
119: for (int i = 0; i < fields.length; i++) {
120: fmd = fields[i];
121: fmd.getPersistentField().set(result,
122: row.get(fmd.getColumnName()));
123: }
124:
125: if (targetObject == null) {
126: // 3. for new build objects, invoke the initialization method for the class if one is provided
127: Method initializationMethod = targetClassDescriptor
128: .getInitializationMethod();
129: if (initializationMethod != null) {
130: try {
131: initializationMethod.invoke(result, NO_ARGS);
132: } catch (Exception ex) {
133: throw new PersistenceBrokerException(
134: "Unable to invoke initialization method:"
135: + initializationMethod.getName()
136: + " for class:"
137: + m_cld.getClassOfObject(), ex);
138: }
139: }
140: }
141: return result;
142: }
143:
144: /**
145: * materialize a single object, described by cld,
146: * from the first row of the ResultSet rs.
147: * There are two possible strategies:
148: * 1. The persistent class defines a public constructor with arguments matching the persistent
149: * primitive attributes of the class. In this case we build an array args of arguments from rs
150: * and call Constructor.newInstance(args) to build an object.
151: * 2. The persistent class does not provide such a constructor, but only a public default
152: * constructor. In this case we create an empty instance with Class.newInstance().
153: * This empty instance is then filled by calling Field::set(obj,getObject(matchingColumn))
154: * for each attribute.
155: * The second strategy needs n calls to Field::set() which are much more expensive
156: * than the filling of the args array in the first strategy.
157: * client applications should therefore define adequate constructors to benefit from
158: * performance gain of the first strategy.
159: *
160: * @throws PersistenceBrokerException if there is an error accessing the access layer
161: */
162: public void readObjectArrayFrom(ResultSetAndStatement rs_stmt,
163: Map row) {
164: FieldDescriptor[] fields;
165: /*
166: arminw:
167: TODO: this feature doesn't work, so remove this in future
168: */
169: if (m_cld.getSuperClass() != null) {
170: /**
171: * treeder
172: * append super class fields if exist
173: */
174: fields = m_cld.getFieldDescriptorsInHeirarchy();
175: } else {
176: String ojbConcreteClass = extractOjbConcreteClass(m_cld,
177: rs_stmt.m_rs, row);
178: /*
179: arminw:
180: if multiple classes were mapped to the same table, lookup the concrete
181: class and use these fields, attach ojbConcreteClass in row map for later use
182: */
183: if (ojbConcreteClass != null) {
184: ClassDescriptor cld = m_cld.getRepository()
185: .getDescriptorFor(ojbConcreteClass);
186: row.put(OJB_CONCRETE_CLASS_KEY, cld.getClassOfObject());
187: fields = cld.getFieldDescriptor(true);
188: } else {
189: String ojbClass = SqlHelper
190: .getOjbClassName(rs_stmt.m_rs);
191: if (ojbClass != null) {
192: ClassDescriptor cld = m_cld.getRepository()
193: .getDescriptorFor(ojbClass);
194: row.put(OJB_CONCRETE_CLASS_KEY, cld
195: .getClassOfObject());
196: fields = cld.getFieldDescriptor(true);
197: } else {
198: fields = m_cld.getFieldDescriptor(true);
199: }
200: }
201: }
202: readValuesFrom(rs_stmt, row, fields);
203: }
204:
205: /*
206: * @see RowReader#readPkValuesFrom(ResultSet, ClassDescriptor, Map)
207: * @throws PersistenceBrokerException if there is an error accessing the access layer
208: */
209: public void readPkValuesFrom(ResultSetAndStatement rs_stmt, Map row) {
210: String ojbClass = SqlHelper.getOjbClassName(rs_stmt.m_rs);
211: ClassDescriptor cld;
212:
213: if (ojbClass != null) {
214: cld = m_cld.getRepository().getDescriptorFor(ojbClass);
215: } else {
216: cld = m_cld;
217: }
218:
219: FieldDescriptor[] pkFields = cld.getPkFields();
220: readValuesFrom(rs_stmt, row, pkFields);
221: }
222:
223: protected void readValuesFrom(ResultSetAndStatement rs_stmt,
224: Map row, FieldDescriptor[] fields) {
225: int size = fields.length;
226: Object val;
227: FieldDescriptor fld = null;
228: try {
229: for (int j = 0; j < size; j++) {
230: fld = fields[j];
231: if (!row.containsKey(fld.getColumnName())) {
232: int idx = rs_stmt.m_sql.getColumnIndex(fld);
233: val = fld.getJdbcType().getObjectFromColumn(
234: rs_stmt.m_rs, null, fld.getColumnName(),
235: idx);
236: row.put(fld.getColumnName(), fld
237: .getFieldConversion().sqlToJava(val));
238: }
239: }
240: } catch (SQLException t) {
241: throw new PersistenceBrokerException(
242: "Error reading class '"
243: + (fld != null ? fld.getClassDescriptor()
244: .getClassNameOfObject() : m_cld
245: .getClassNameOfObject())
246: + "' from result set, current read field was '"
247: + (fld != null ? fld.getPersistentField()
248: .getName()
249: + "'" : null), t);
250: }
251: }
252:
253: protected String extractOjbConcreteClass(ClassDescriptor cld,
254: ResultSet rs, Map row) {
255: FieldDescriptor fld = m_cld.getOjbConcreteClassField();
256: if (fld == null) {
257: return null;
258: }
259: try {
260: Object tmp = fld.getJdbcType().getObjectFromColumn(rs,
261: fld.getColumnName());
262: // allow field-conversion for discriminator column too
263: String result = (String) fld.getFieldConversion()
264: .sqlToJava(tmp);
265: result = result != null ? result.trim() : null;
266: if (result == null || result.length() == 0) {
267: throw new PersistenceBrokerException(
268: "ojbConcreteClass field for class "
269: + cld.getClassNameOfObject()
270: + " returned null or 0-length string");
271: } else {
272: /*
273: arminw: Make sure that we don't read discriminator field twice from the ResultSet.
274: */
275: row.put(fld.getColumnName(), result);
276: return result;
277: }
278: } catch (SQLException e) {
279: throw new PersistenceBrokerException(
280: "Unexpected error while try to read 'ojbConcretClass'"
281: + " field from result set using column name "
282: + fld.getColumnName() + " main class"
283: + " was " + m_cld.getClassNameOfObject(), e);
284: }
285: }
286:
287: /**
288: * Check if there is an attribute which tells us which concrete class is to be instantiated.
289: */
290: protected ClassDescriptor selectClassDescriptor(Map row)
291: throws PersistenceBrokerException {
292: ClassDescriptor result = m_cld;
293: Class ojbConcreteClass = (Class) row
294: .get(OJB_CONCRETE_CLASS_KEY);
295: if (ojbConcreteClass != null) {
296: result = m_cld.getRepository().getDescriptorFor(
297: ojbConcreteClass);
298: // if we can't find class-descriptor for concrete class, something wrong with mapping
299: if (result == null) {
300: throw new PersistenceBrokerException(
301: "Can't find class-descriptor for ojbConcreteClass '"
302: + ojbConcreteClass
303: + "', the main class was "
304: + m_cld.getClassNameOfObject());
305: }
306: }
307: return result;
308: }
309:
310: public void setClassDescriptor(ClassDescriptor cld) {
311: this .m_cld = cld;
312: }
313:
314: public ClassDescriptor getClassDescriptor() {
315: return m_cld;
316: }
317: }
|