001: /*
002: * Copyright 2001-2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package com.sourcetap.sfa.util;
018:
019: import java.io.Serializable;
020: import java.util.ArrayList;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Map;
025:
026: import org.apache.commons.beanutils.BasicDynaBean;
027: import org.apache.commons.beanutils.DynaBean;
028: import org.apache.commons.beanutils.DynaClass;
029: import org.apache.commons.beanutils.DynaProperty;
030: import org.ofbiz.base.util.Debug;
031: import org.ofbiz.entity.GenericDelegator;
032: import org.ofbiz.entity.GenericEntityException;
033: import org.ofbiz.entity.GenericValue;
034: import org.ofbiz.entity.jdbc.SqlJdbcUtil;
035: import org.ofbiz.entity.model.ModelEntity;
036: import org.ofbiz.entity.model.ModelField;
037: import org.ofbiz.entity.model.ModelFieldType;
038: import org.ofbiz.entity.model.ModelViewEntity;
039:
040: /**
041: * <p>Implementation of {@link DynaClass} that creates an in-memory collection
042: * of {@link DynaBean}s representing the results of an SQL query. Once the
043: * {@link DynaClass} instance has been created, the JDBC <code>ResultSet</code>
044: * and <code>Statement</code> on which it is based can be closed, and the
045: * underlying <code>Connection</code> can be returned to its connection pool
046: * (if you are using one).</p>
047: *
048: * <p>The normal usage pattern is something like:</p>
049: * <pre>
050: * Connection conn = ...; // Acquire connection from pool
051: * Statement stmt = conn.createStatement();
052: * ResultSet rs = stmt.executeQuery("SELECT ...");
053: * RowSetDynaClass rsdc = new RowSetDynaClass(rs);
054: * rs.close();
055: * stmt.close();
056: * ...; // Return connection to pool
057: * List rows = rsdc.getRows();
058: * ...; // Process the rows as desired
059: * </pre>
060: *
061: * <p>Each column in the result set will be represented as a {@link DynaBean}
062: * property of the corresponding name (optionally forced to lower case
063: * for portability). There will be one {@link DynaBean} in the
064: * <code>List</code> returned by <code>getRows()</code> for each
065: * row in the original <code>ResultSet</code>.</p>
066: *
067: * <p>In general, instances of {@link RowSetDynaClass} can be serialized
068: * and deserialized, which will automatically include the list of
069: * {@link DynaBean}s representing the data content. The only exception
070: * to this rule would be when the underlying property values that were
071: * copied from the <code>ResultSet</code> originally cannot themselves
072: * be serialized. Therefore, a {@link RowSetDynaClass} makes a very
073: * convenient mechanism for transporting data sets to remote Java-based
074: * application components.</p>
075: *
076: * @author Craig R. McClanahan
077: */
078:
079: public class GenericValueListDynaClass implements DynaClass,
080: Serializable {
081: public static final String module = GenericValueListDynaClass.class
082: .getName();
083:
084: // ----------------------------------------------------- Instance variables
085:
086: /**
087: * <p>Limits the size of the returned list. The call to
088: * <code>getRows()</code> will return at most limit number of rows.
089: * If less than or equal to 0, does not limit the size of the result.
090: */
091: protected int limit = -1;
092:
093: /**
094: * <p>The list of {@link DynaBean}s representing the contents of
095: * the original <code>ResultSet</code> on which this
096: * {@link RowSetDynaClass} was based.</p>
097: */
098: protected List rows = new ArrayList();
099: protected List fieldsToSelect = null;
100:
101: // ----------------------------------------------------------- Constructors
102:
103: /**
104: * <p>Construct a new {@link RowSetDynaClass} for the specified
105: * <code>ResultSet</code>. The property names corresponding
106: * to column names in the result set will be not be lower cased.</p>
107: *
108: * @param resultSet The result set to be wrapped
109: *
110: * @exception NullPointerException if <code>resultSet</code>
111: * is <code>null</code>
112: * @exception GenericEntityException if the metadata for this result set
113: * cannot be introspected
114: */
115: public GenericValueListDynaClass(List genericValueList,
116: List fieldsToSelect) throws GenericEntityException {
117:
118: this (genericValueList, fieldsToSelect, false, -1);
119:
120: }
121:
122: /**
123: * <p>Construct a new {@link RowSetDynaClass} for the specified
124: * <code>ResultSet</code>. The property names corresponding
125: * to column names in the result set will not be lower cased.</p>
126: *
127: * If <code>limit</code> is not less than 0, max <code>limit</code>
128: * number of rows will be copied into the list.
129: *
130: * @param resultSet The result set to be wrapped
131: * @param limit The maximum for the size of the result.
132: *
133: * @exception NullPointerException if <code>resultSet</code>
134: * is <code>null</code>
135: * @exception GenericEntityException if the metadata for this result set
136: * cannot be introspected
137: */
138: public GenericValueListDynaClass(List genericValueList,
139: List fieldsToSelect, int limit)
140: throws GenericEntityException {
141:
142: this (genericValueList, fieldsToSelect, false, limit);
143:
144: }
145:
146: /**
147: * <p>Construct a new {@link RowSetDynaClass} for the specified
148: * <code>ResultSet</code>. The property names corresponding
149: * to the column names in the result set will be lower cased or not,
150: * depending on the specified <code>lowerCase</code> value.</p>
151: *
152: * If <code>limit</code> is not less than 0, max <code>limit</code>
153: * number of rows will be copied into the resultset.
154: *
155: *
156: * @param resultSet The result set to be wrapped
157: * @param lowerCase Should property names be lower cased?
158: *
159: * @exception NullPointerException if <code>resultSet</code>
160: * is <code>null</code>
161: * @exception GenericEntityException if the metadata for this result set
162: * cannot be introspected
163: */
164: public GenericValueListDynaClass(List genericValueList,
165: List fieldsToSelect, boolean lowerCase)
166: throws GenericEntityException {
167: this (genericValueList, fieldsToSelect, lowerCase, -1);
168:
169: }
170:
171: /**
172: * <p>Construct a new {@link RowSetDynaClass} for the specified
173: * <code>ResultSet</code>. The property names corresponding
174: * to the column names in the result set will be lower cased or not,
175: * depending on the specified <code>lowerCase</code> value.</p>
176: *
177: * <p><strong>WARNING</strong> - If you specify <code>false</code>
178: * for <code>lowerCase</code>, the returned property names will
179: * exactly match the column names returned by your JDBC driver.
180: * Because different drivers might return column names in different
181: * cases, the property names seen by your application will vary
182: * depending on which JDBC driver you are using.</p>
183: *
184: * @param resultSet The result set to be wrapped
185: * @param lowerCase Should property names be lower cased?
186: *
187: * @exception NullPointerException if <code>resultSet</code>
188: * is <code>null</code>
189: * @exception GenericEntityException if the metadata for this result set
190: * cannot be introspected
191: */
192: public GenericValueListDynaClass(List genericValueList,
193: List fieldsToSelect, boolean lowerCase, int limit)
194: throws GenericEntityException {
195:
196: if ((genericValueList == null) || (genericValueList.size() < 1)) {
197: throw new NullPointerException();
198: }
199: this .lowerCase = lowerCase;
200: this .limit = limit;
201: this .fieldsToSelect = fieldsToSelect;
202: introspect(genericValueList);
203: copy(genericValueList);
204:
205: }
206:
207: /**
208: * <p>Return a <code>List</code> containing the {@link DynaBean}s that
209: * represent the contents of each <code>Row</code> from the
210: * <code>ResultSet</code> that was the basis of this
211: * {@link RowSetDynaClass} instance. These {@link DynaBean}s are
212: * disconnected from the database itself, so there is no problem with
213: * modifying the contents of the list, or the values of the properties
214: * of these {@link DynaBean}s. However, it is the application's
215: * responsibility to persist any such changes back to the database,
216: * if it so desires.</p>
217: */
218: public List getRows() {
219:
220: return (this .rows);
221:
222: }
223:
224: // ------------------------------------------------------ Protected Methods
225:
226: /**
227: * <p>Copy the column values for each row in the specified
228: * <code>ResultSet</code> into a newly created {@link DynaBean}, and add
229: * this bean to the list of {@link DynaBean}s that will later by
230: * returned by a call to <code>getRows()</code>.</p>
231: *
232: * @param resultSet The <code>ResultSet</code> whose data is to be
233: * copied
234: *
235: * @exception GenericEntityException if an error is encountered copying the data
236: */
237: protected void copy(List genericValueList)
238: throws GenericEntityException {
239:
240: int cnt = 0;
241: Iterator gvlI = genericValueList.iterator();
242: while (gvlI.hasNext() && (limit < 0 || cnt++ < limit)) {
243: GenericValue gv = (GenericValue) gvlI.next();
244: DynaBean bean = createDynaBean();
245: for (int i = 0; i < properties.length; i++) {
246: String name = properties[i].getName();
247: bean.set(name, gv.get(name));
248: }
249: rows.add(bean);
250: }
251:
252: }
253:
254: /**
255: * <p>Create and return a new {@link DynaBean} instance to be used for
256: * representing a row in the underlying result set.</p>
257: */
258: protected DynaBean createDynaBean() {
259:
260: return (new BasicDynaBean(this ));
261:
262: }
263:
264: // ----------------------------------------------------- Instance Variables
265:
266: /**
267: * <p>Flag defining whether column names should be lower cased when
268: * converted to property names.</p>
269: */
270: protected boolean lowerCase = true;
271:
272: /**
273: * <p>The set of dynamic properties that are part of this
274: * {@link DynaClass}.</p>
275: */
276: protected DynaProperty properties[] = null;
277:
278: /**
279: * <p>The set of dynamic properties that are part of this
280: * {@link DynaClass}, keyed by the property name. Individual descriptor
281: * instances will be the same instances as those in the
282: * <code>properties</code> list.</p>
283: */
284: protected Map propertiesMap = new HashMap();
285:
286: // ------------------------------------------------------ DynaClass Methods
287:
288: /**
289: * <p>Return the name of this DynaClass (analogous to the
290: * <code>getName()</code> method of <code>java.lang.Class</code), which
291: * allows the same <code>DynaClass</code> implementation class to support
292: * different dynamic classes, with different sets of properties.</p>
293: */
294: public String getName() {
295:
296: return (this .getClass().getName());
297:
298: }
299:
300: /**
301: * <p>Return a property descriptor for the specified property, if it
302: * exists; otherwise, return <code>null</code>.</p>
303: *
304: * @param name Name of the dynamic property for which a descriptor
305: * is requested
306: *
307: * @exception IllegalArgumentException if no property name is specified
308: */
309: public DynaProperty getDynaProperty(String name) {
310:
311: if (name == null) {
312: throw new IllegalArgumentException(
313: "No property name specified");
314: }
315: return ((DynaProperty) propertiesMap.get(name));
316:
317: }
318:
319: /**
320: * <p>Return an array of <code>ProperyDescriptors</code> for the properties
321: * currently defined in this DynaClass. If no properties are defined, a
322: * zero-length array will be returned.</p>
323: */
324: public DynaProperty[] getDynaProperties() {
325:
326: return (properties);
327:
328: }
329:
330: /**
331: * <p>Instantiate and return a new DynaBean instance, associated
332: * with this DynaClass. <strong>NOTE</strong> - This operation is not
333: * supported, and throws an exception.</p>
334: *
335: * @exception IllegalAccessException if the Class or the appropriate
336: * constructor is not accessible
337: * @exception InstantiationException if this Class represents an abstract
338: * class, an array class, a primitive type, or void; or if instantiation
339: * fails for some other reason
340: */
341: public DynaBean newInstance() throws IllegalAccessException,
342: InstantiationException {
343:
344: throw new UnsupportedOperationException(
345: "newInstance() not supported");
346:
347: }
348:
349: /**
350: * <p>Loads and returns the <code>Class</code> of the given name.
351: * By default, a load from the thread context class loader is attempted.
352: * If there is no such class loader, the class loader used to load this
353: * class will be utilized.</p>
354: *
355: * @exception GenericEntityException if an exception was thrown trying to load
356: * the specified class
357: */
358: protected Class loadClass(String className)
359: throws GenericEntityException {
360:
361: try {
362: ClassLoader cl = Thread.currentThread()
363: .getContextClassLoader();
364: if (cl == null) {
365: cl = this .getClass().getClassLoader();
366: }
367: return (cl.loadClass(className));
368: } catch (Exception e) {
369: throw new GenericEntityException(
370: "Cannot load column class '" + className + "': "
371: + e);
372: }
373:
374: }
375:
376: /**
377: * <p>Factory method to create a new DynaProperty for the given index
378: * into the result set metadata.</p>
379: *
380: * @param metadata is the result set metadata
381: * @param i is the column index in the metadata
382: * @return the newly created DynaProperty instance
383: */
384: protected DynaProperty createDynaProperty(
385: GenericDelegator delegator, ModelEntity modelEntity,
386: String fieldName) throws GenericEntityException {
387:
388: String name = null;
389: ModelField modelField = modelEntity.getField(fieldName);
390:
391: ModelFieldType type = null;
392: try {
393: ModelEntity mainModelEntity = modelEntity;
394: if (modelEntity.getEntityName().equals("DynamicViewEntity"))
395: mainModelEntity = ((ModelViewEntity) modelEntity)
396: .getMemberModelEntity(((ModelViewEntity) modelEntity)
397: .getAlias(0).getEntityAlias());
398: type = delegator.getEntityFieldType(mainModelEntity,
399: modelField.getType());
400: } catch (GenericEntityException e) {
401: Debug.logWarning(e, module);
402: }
403: if (type == null)
404: throw new IllegalArgumentException("Type "
405: + modelField.getType() + " not found");
406: String fieldType = type.getJavaType();
407:
408: //modelField.get
409: if (lowerCase) {
410: name = modelField.getName().toLowerCase();
411: } else {
412: name = modelField.getName();
413: }
414: String className = null;
415:
416: className = (String) fieldTypeMap.get(new Integer(SqlJdbcUtil
417: .getType(fieldType)));
418:
419: // Default to Object type if no class name could be retrieved
420: // from the metadata
421: Class clazz = Object.class;
422: if (className != null) {
423: clazz = loadClass(className);
424: }
425: return new DynaProperty(name, clazz);
426:
427: }
428:
429: /**
430: * <p>Introspect the metadata associated with our result set, and populate
431: * the <code>properties</code> and <code>propertiesMap</code> instance
432: * variables.</p>
433: *
434: * @param resultSet The <code>resultSet</code> whose metadata is to
435: * be introspected
436: *
437: * @exception GenericEntityException if an error is encountered processing the
438: * result set metadata
439: */
440: protected void introspect(List genericValueList)
441: throws GenericEntityException {
442:
443: // Accumulate an ordered list of DynaProperties
444: ArrayList list = new ArrayList();
445:
446: GenericValue genericValue = (GenericValue) genericValueList
447: .get(0);
448: ModelEntity modelEntity = genericValue.getModelEntity();
449:
450: List fieldNames = fieldsToSelect;
451: if (fieldNames == null)
452: fieldNames = modelEntity.getAllFieldNames();
453: int n = fieldNames.size();
454: for (int i = 0; i < n; i++) {
455: DynaProperty dynaProperty = createDynaProperty(genericValue
456: .getDelegator(), modelEntity, (String) fieldNames
457: .get(i));
458: if (dynaProperty != null) {
459: list.add(dynaProperty);
460: }
461: }
462:
463: // Convert this list into the internal data structures we need
464: properties = (DynaProperty[]) list
465: .toArray(new DynaProperty[list.size()]);
466: for (int i = 0; i < properties.length; i++) {
467: propertiesMap.put(properties[i].getName(), properties[i]);
468: }
469:
470: }
471:
472: protected static Map fieldTypeMap = new HashMap();
473: static {
474: fieldTypeMap.put(new Integer(1), "java.lang.String");
475: fieldTypeMap.put(new Integer(2), "java.sql.Timestamp");
476: fieldTypeMap.put(new Integer(3), "java.sql.Time");
477: fieldTypeMap.put(new Integer(4), "java.sql.Date");
478: fieldTypeMap.put(new Integer(5), "java.lang.Integer");
479: fieldTypeMap.put(new Integer(6), "java.lang.Long");
480: fieldTypeMap.put(new Integer(7), "java.lang.Float");
481: fieldTypeMap.put(new Integer(8), "java.lang.Double");
482: fieldTypeMap.put(new Integer(9), "java.lang.Boolean");
483: fieldTypeMap.put(new Integer(10), "java.lang.Object");
484: }
485:
486: }
|