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    }