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: import org.apache.xmlbeans.SchemaProperty;
024: import org.apache.xmlbeans.SchemaType;
025: import org.apache.xmlbeans.XmlObject;
026: import org.apache.xmlbeans.XmlOptions;
027:
028: import java.lang.reflect.Field;
029: import java.lang.reflect.InvocationTargetException;
030: import java.lang.reflect.Method;
031: import java.lang.reflect.Modifier;
032: import java.sql.ResultSet;
033: import java.sql.ResultSetMetaData;
034: import java.sql.SQLException;
035: import java.util.Calendar;
036: import java.util.HashMap;
037:
038: /**
039: * Maps a ResultSet row to an XmlObject.
040: */
041: public class RowToXmlObjectMapper extends RowMapper {
042:
043: private final int _columnCount;
044: private final SchemaType _schemaType;
045:
046: private SetterMethod[] _setterMethods;
047: private final Object[] _args = new Object[1];
048:
049: /**
050: * Create a new RowToXmlObjectMapper.
051: *
052: * @param resultSet ResultSet to map
053: * @param returnTypeClass Class to map to.
054: * @param cal Calendar instance for date/time mappings.
055: * @throws SQLException on error.
056: */
057: RowToXmlObjectMapper(ResultSet resultSet, Class returnTypeClass,
058: Calendar cal) throws SQLException {
059: super (resultSet, returnTypeClass, cal);
060:
061: _columnCount = resultSet.getMetaData().getColumnCount();
062: _schemaType = getSchemaType(_returnTypeClass);
063: _setterMethods = null;
064: }
065:
066: /**
067: * map a row from the ResultSet to an XmlObject instance
068: *
069: * @return An XmlObject instance.
070: */
071: public Object mapRowToReturnType() {
072:
073: Object resultObject = null;
074: if (_columnCount == 1) {
075:
076: final int typeId = _tmf.getTypeId(_returnTypeClass);
077:
078: try {
079: if (typeId != TypeMappingsFactory.TYPE_UNKNOWN) {
080: return extractColumnValue(1, typeId);
081: } else {
082: // we still might want a single value (i.e. java.util.Date)
083: Object val = extractColumnValue(1, typeId);
084: if (_returnTypeClass.isAssignableFrom(val
085: .getClass())) {
086: return val;
087: }
088: }
089: } catch (SQLException e) {
090: throw new ControlException(e.getMessage(), e);
091: }
092: }
093:
094: if (_setterMethods == null) {
095: try {
096: getResultSetMappings();
097: } catch (SQLException e) {
098: throw new ControlException(e.getMessage(), e);
099: }
100: }
101:
102: resultObject = XmlObject.Factory.newInstance(new XmlOptions()
103: .setDocumentType(_schemaType));
104:
105: for (int i = 1; i < _setterMethods.length; i++) {
106: Method setterMethod = _setterMethods[i].getSetter();
107: Object resultValue = null;
108:
109: try {
110: resultValue = extractColumnValue(i, _setterMethods[i]
111: .getParameterType());
112:
113: // if the setter is for an xmlbean enum type, convert the extracted resultset column
114: // value to the proper xmlbean enum type. All xmlbean enums are derived from the class
115: // StringEnumAbstractBase
116: if (_setterMethods[i].getParameterType() == TypeMappingsFactory.TYPE_XMLBEAN_ENUM) {
117: Class parameterClass = _setterMethods[i]
118: .getParameterClass();
119: Method m = parameterClass.getMethod("forString",
120: new Class[] { String.class });
121: resultValue = m.invoke(null,
122: new Object[] { resultValue });
123: }
124:
125: _args[0] = resultValue;
126: setterMethod.invoke(resultObject, _args);
127:
128: if (_setterMethods[i].getNilable() != null) {
129: if (_resultSet.wasNull()) {
130: _setterMethods[i].getNilable().invoke(
131: resultObject, (Object[]) null);
132: }
133: }
134: } catch (SQLException se) {
135: throw new ControlException(se.getMessage(), se);
136: } catch (IllegalArgumentException iae) {
137: try {
138: ResultSetMetaData md = _resultSet.getMetaData();
139: throw new ControlException(
140: "The declared Java type for method "
141: + setterMethod.getName()
142: + setterMethod.getParameterTypes()[0]
143: .toString()
144: + " is incompatible with the SQL format of column "
145: + md.getColumnName(i).toString()
146: + md.getColumnTypeName(i)
147: .toString()
148: + " which returns objects of type "
149: + resultValue.getClass().getName());
150: } catch (SQLException e) {
151: throw new ControlException(e.getMessage(), e);
152: }
153: } catch (IllegalAccessException e) {
154: throw new ControlException(
155: "IllegalAccessException when trying to access method "
156: + setterMethod.getName(), e);
157: } catch (NoSuchMethodException e) {
158: throw new ControlException(
159: "NoSuchMethodException when trying to map schema enum value using Enum.forString().",
160: e);
161: } catch (InvocationTargetException e) {
162: throw new ControlException(
163: "IllegalInvocationException when trying to access method "
164: + setterMethod.getName(), e);
165: }
166: }
167: return resultObject;
168: }
169:
170: // ///////////////////////////////////////////////// private methods /////////////////////////////////////////////////
171:
172: /**
173: * Build the necessary structures to do the mapping
174: *
175: * @throws SQLException
176: */
177: private void getResultSetMappings() throws SQLException {
178:
179: //
180: // special case for XmlObject, find factory class
181: //
182: if (_schemaType.isDocumentType()) {
183: return;
184: }
185:
186: final String[] keys = getKeysFromResultSet();
187:
188: //
189: // find setters for return class
190: //
191: HashMap<String, Method> mapFields = new HashMap<String, Method>(
192: _columnCount * 2);
193: for (int i = 1; i <= _columnCount; i++) {
194: mapFields.put(keys[i], null);
195: }
196:
197: // public methods
198: Method[] classMethods = _returnTypeClass.getMethods();
199: for (Method method : classMethods) {
200:
201: if (isSetterMethod(method)) {
202: final String fieldName = method.getName().substring(3)
203: .toUpperCase();
204: if (mapFields.containsKey(fieldName)) {
205: mapFields.put(fieldName, method);
206: }
207: }
208: }
209:
210: // finally actually init the fields array
211: _setterMethods = new SetterMethod[_columnCount + 1];
212:
213: for (int i = 1; i < _setterMethods.length; i++) {
214: Method setterMethod = mapFields.get(keys[i]);
215: if (setterMethod == null) {
216: throw new ControlException(
217: "Unable to map the SQL column "
218: + keys[i]
219: + " to a field on the "
220: + _returnTypeClass.getName()
221: + " class. Mapping is done using a case insensitive comparision of SQL ResultSet "
222: + "columns to public setter methods on the return class.");
223: }
224:
225: _setterMethods[i] = new SetterMethod(setterMethod);
226: }
227: }
228:
229: /**
230: * Build a String array of column names from the ResultSet.
231: *
232: * @return A String array containing the column names contained within the ResultSet.
233: * @throws SQLException on error
234: */
235: protected String[] getKeysFromResultSet() throws SQLException {
236:
237: String[] keys = super .getKeysFromResultSet();
238:
239: // check schemaProperty mapping names for more accurate column->field mapping
240: SchemaProperty[] props = _schemaType.getElementProperties();
241: for (int i = 0; i < props.length; i++) {
242:
243: int col = -1;
244: try {
245: col = _resultSet.findColumn(props[i].getName()
246: .getLocalPart());
247: } catch (SQLException x) {
248: }
249:
250: if (col > 0) {
251: keys[col] = props[i].getJavaPropertyName()
252: .toUpperCase();
253: }
254: }
255: return keys;
256: }
257:
258: /**
259: * Get the SchemaType for the specified class.
260: *
261: * @param returnType Class to get the SchemaType for.
262: * @return SchemaType
263: */
264: private SchemaType getSchemaType(Class returnType) {
265: SchemaType schemaType = null;
266: if (XmlObject.class.isAssignableFrom(returnType)) {
267: try {
268: Field f = returnType.getField("type");
269: if (SchemaType.class.isAssignableFrom(f.getType())
270: && Modifier.isStatic(f.getModifiers())) {
271: schemaType = (SchemaType) f.get(null);
272: }
273: } catch (NoSuchFieldException x) {
274: } catch (IllegalAccessException x) {
275: }
276: }
277: return schemaType;
278: }
279:
280: // /////////////////////////////////////////////INNER CLASSES/////////////////////////////////////////////////
281:
282: /**
283: * Helper class which contains setter method information.
284: */
285: private final class SetterMethod {
286: private final Method _setter;
287: private final int _parameterType;
288: private final Class _parameterClass;
289: private final Method _nilable;
290:
291: /**
292: * Create a new setter method.
293: *
294: * @param setter Method instance.
295: */
296: SetterMethod(Method setter) {
297: _setter = setter;
298: _parameterClass = _setter.getParameterTypes()[0];
299: _parameterType = _tmf.getTypeId(_parameterClass);
300: _nilable = isNilable();
301: }
302:
303: /**
304: * Return the setter method.
305: *
306: * @return Method
307: */
308: Method getSetter() {
309: return _setter;
310: }
311:
312: /**
313: * Return the class of the setter method's paramater.
314: *
315: * @return Class of the setter method's param.
316: */
317: Class getParameterClass() {
318: return _parameterClass;
319: }
320:
321: /**
322: * Get the type of the methods paramter.
323: * Type is defined by the TypeMappingsFactory, prefixed by TYPE_.
324: *
325: * @return int type.
326: */
327: int getParameterType() {
328: return _parameterType;
329: }
330:
331: /**
332: * Get the nilable method for this setter.
333: *
334: * @return Method.
335: */
336: Method getNilable() {
337: return _nilable;
338: }
339:
340: /**
341: * This takes care of the special case for xml beans return types.
342: * since they return primitive types even if they are nillable, we
343: * must explicitly call setNil after getting the column out of the resultSet.
344: * for that, we need to keep track of each field's setNil method.
345: * if the field is not nillable, it will not have a setNil method, and
346: * that array index will be null.
347: *
348: * @return Method
349: */
350: private Method isNilable() {
351: try {
352: return _returnTypeClass.getMethod("setNil"
353: + _setter.getName().substring(3),
354: new Class[] {});
355: } catch (NoSuchMethodException e) {
356: // NOOP - just means there is no setNil
357: }
358: return null;
359: }
360: }
361: }
|