001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. 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: * $Header:$
018: */
019:
020: package org.apache.beehive.controls.system.jdbc;
021:
022: import org.apache.beehive.controls.api.ControlException;
023:
024: import java.lang.reflect.AccessibleObject;
025: import java.lang.reflect.Field;
026: import java.lang.reflect.InvocationTargetException;
027: import java.lang.reflect.Method;
028: import java.lang.reflect.Modifier;
029: import java.sql.ResultSet;
030: import java.sql.ResultSetMetaData;
031: import java.sql.SQLException;
032: import java.util.Calendar;
033: import java.util.HashMap;
034:
035: /**
036: * Map a ResultSet row to an Object. This mapper uses Java reflection to perform the mapping. The Class being mapped
037: * to must have setter methods which match the ResultSet column names. For example, if a column in the ResultSet
038: * named USERID, the object must have a setter method named setUserid(). If a setter method cannot be class fields
039: * are also checked, the same naming conventions applies, USERID -> userid.
040: */
041: public class RowToObjectMapper extends RowMapper {
042:
043: private final int _columnCount;
044:
045: private AccessibleObject[] _fields;
046: private int[] _fieldTypes;
047:
048: private final Object[] _args = new Object[1];
049:
050: /**
051: * Create a new RowToObjectMapper.
052: *
053: * @param resultSet ResultSet to map
054: * @param returnTypeClass Class to map to.
055: * @param cal Calendar instance for date/time mappings.
056: */
057: RowToObjectMapper(ResultSet resultSet, Class returnTypeClass,
058: Calendar cal) {
059: super (resultSet, returnTypeClass, cal);
060:
061: _fields = null;
062:
063: try {
064: _columnCount = resultSet.getMetaData().getColumnCount();
065: } catch (SQLException e) {
066: throw new ControlException(
067: "RowToObjectMapper: SQLException: "
068: + e.getMessage(), e);
069: }
070: }
071:
072: /**
073: * Do the mapping.
074: *
075: * @return An object instance.
076: */
077: public Object mapRowToReturnType() {
078:
079: Object resultObject = null;
080:
081: // if the ResultSet only contains a single column we may be able to map directly
082: // to the return type -- if so we don't need to build any structures to support
083: // mapping
084: if (_columnCount == 1) {
085:
086: final int typeId = _tmf.getTypeId(_returnTypeClass);
087:
088: try {
089: if (typeId != TypeMappingsFactory.TYPE_UNKNOWN) {
090: return extractColumnValue(1, typeId);
091: } else {
092: // we still might want a single value (i.e. java.util.Date)
093: Object val = extractColumnValue(1, typeId);
094: if (_returnTypeClass.isAssignableFrom(val
095: .getClass())) {
096: return val;
097: }
098: }
099: } catch (SQLException e) {
100: throw new ControlException(e.getMessage(), e);
101: }
102: }
103:
104: if (_fields == null) {
105: try {
106: getFieldMappings();
107: } catch (SQLException e) {
108: throw new ControlException(e.getMessage(), e);
109: }
110: }
111:
112: try {
113: resultObject = _returnTypeClass.newInstance();
114: } catch (InstantiationException e) {
115: throw new ControlException(
116: "InstantiationException when trying to create instance of : "
117: + _returnTypeClass.getName(), e);
118: } catch (IllegalAccessException e) {
119: throw new ControlException(
120: "IllegalAccessException when trying to create instance of : "
121: + _returnTypeClass.getName(), e);
122: }
123:
124: for (int i = 1; i < _fields.length; i++) {
125: AccessibleObject f = _fields[i];
126: Object resultValue = null;
127:
128: try {
129: resultValue = extractColumnValue(i, _fieldTypes[i]);
130: if (f instanceof Field) {
131: ((Field) f).set(resultObject, resultValue);
132: } else {
133: _args[0] = resultValue;
134: ((Method) f).invoke(resultObject, _args);
135: }
136: } catch (SQLException e) {
137: throw new ControlException(e.getMessage(), e);
138: } catch (IllegalArgumentException iae) {
139:
140: try {
141: ResultSetMetaData md = _resultSet.getMetaData();
142: if (f instanceof Field) {
143: throw new ControlException(
144: "The declared Java type for field "
145: + ((Field) f).getName()
146: + ((Field) f).getType()
147: .toString()
148: + " is incompatible with the SQL format of column "
149: + md.getColumnName(i)
150: .toString()
151: + md.getColumnTypeName(i)
152: .toString()
153: + " which returns objects of type "
154: + resultValue.getClass()
155: .getName());
156: } else {
157: throw new ControlException(
158: "The declared Java type for method "
159: + ((Method) f).getName()
160: + ((Method) f)
161: .getParameterTypes()[0]
162: .toString()
163: + " is incompatible with the SQL format of column "
164: + md.getColumnName(i)
165: .toString()
166: + md.getColumnTypeName(i)
167: .toString()
168: + " which returns objects of type "
169: + resultValue.getClass()
170: .getName());
171: }
172: } catch (SQLException e) {
173: throw new ControlException(e.getMessage(), e);
174: }
175:
176: } catch (IllegalAccessException e) {
177: if (f instanceof Field) {
178: throw new ControlException(
179: "IllegalAccessException when trying to access field "
180: + ((Field) f).getName(), e);
181: } else {
182: throw new ControlException(
183: "IllegalAccessException when trying to access method "
184: + ((Method) f).getName(), e);
185: }
186: } catch (InvocationTargetException e) {
187: if (f instanceof Field) {
188: throw new ControlException(
189: "InvocationTargetException when trying to access field "
190: + ((Field) f).getName(), e);
191: } else {
192: throw new ControlException(
193: "InvocationTargetException when trying to access method "
194: + ((Method) f).getName(), e);
195: }
196: }
197: }
198: return resultObject;
199: }
200:
201: /**
202: * Build the structures necessary to do the mapping
203: *
204: * @throws SQLException on error.
205: */
206: protected void getFieldMappings() throws SQLException {
207:
208: final String[] keys = getKeysFromResultSet();
209:
210: //
211: // find fields or setters for return class
212: //
213: HashMap<String, AccessibleObject> mapFields = new HashMap<String, AccessibleObject>(
214: _columnCount * 2);
215: for (int i = 1; i <= _columnCount; i++) {
216: mapFields.put(keys[i], null);
217: }
218:
219: // public methods
220: Method[] classMethods = _returnTypeClass.getMethods();
221: for (Method m : classMethods) {
222:
223: if (isSetterMethod(m)) {
224: final String fieldName = m.getName().substring(3)
225: .toUpperCase();
226: if (mapFields.containsKey(fieldName)) {
227:
228: // check for overloads
229: Object field = mapFields.get(fieldName);
230: if (field == null) {
231: mapFields.put(fieldName, m);
232: } else {
233: throw new ControlException(
234: "Unable to choose between overloaded methods "
235: + m.getName()
236: + " on the "
237: + _returnTypeClass.getName()
238: + " class. Mapping is done using "
239: + "a case insensitive comparision of SQL ResultSet columns to field "
240: + "names and public setter methods on the return class.");
241: }
242: }
243: }
244: }
245:
246: // fix for 8813: include inherited and non-public fields
247: for (Class clazz = _returnTypeClass; clazz != null
248: && clazz != Object.class; clazz = clazz.getSuperclass()) {
249: Field[] classFields = clazz.getDeclaredFields();
250: for (Field f : classFields) {
251: if (Modifier.isStatic(f.getModifiers()))
252: continue;
253: if (!Modifier.isPublic(f.getModifiers()))
254: continue;
255: String fieldName = f.getName().toUpperCase();
256: if (!mapFields.containsKey(fieldName))
257: continue;
258:
259: Object field = mapFields.get(fieldName);
260: if (field == null) {
261: mapFields.put(fieldName, f);
262: }
263: }
264: }
265:
266: // finally actually init the fields array
267: _fields = new AccessibleObject[_columnCount + 1];
268: _fieldTypes = new int[_columnCount + 1];
269:
270: for (int i = 1; i < _fields.length; i++) {
271: AccessibleObject f = mapFields.get(keys[i]);
272: if (f == null) {
273: throw new ControlException(
274: "Unable to map the SQL column "
275: + keys[i]
276: + " to a field on the "
277: + _returnTypeClass.getName()
278: + " class. Mapping is done using a case insensitive comparision of SQL ResultSet "
279: + "columns to field names and public setter methods on the return class.");
280: }
281:
282: _fields[i] = f;
283: if (f instanceof Field) {
284: _fieldTypes[i] = _tmf.getTypeId(((Field) f).getType());
285: } else {
286: _fieldTypes[i] = _tmf.getTypeId(((Method) f)
287: .getParameterTypes()[0]);
288: }
289: }
290: }
291: }
|