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