AbstractJdbcDynaClass.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.beanutils2.sql;

  18. import java.sql.Date;
  19. import java.sql.ResultSet;
  20. import java.sql.ResultSetMetaData;
  21. import java.sql.SQLException;
  22. import java.sql.Time;
  23. import java.sql.Timestamp;
  24. import java.util.ArrayList;
  25. import java.util.HashMap;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.Objects;

  29. import org.apache.commons.beanutils2.DynaBean;
  30. import org.apache.commons.beanutils2.DynaClass;
  31. import org.apache.commons.beanutils2.DynaProperty;

  32. /**
  33.  * <p>
  34.  * Provides common logic for JDBC implementations of {@link DynaClass}.
  35.  * </p>
  36.  */
  37. abstract class AbstractJdbcDynaClass implements DynaClass {

  38.     /**
  39.      * Cross Reference for column name --> dyna property name (needed when lowerCase option is true)
  40.      */
  41.     private Map<String, String> columnNameXref;

  42.     /**
  43.      * <p>
  44.      * Flag defining whether column names should be lower cased when converted to property names.
  45.      * </p>
  46.      */
  47.     protected boolean lowerCase = true;

  48.     /**
  49.      * <p>
  50.      * The set of dynamic properties that are part of this {@link DynaClass}.
  51.      * </p>
  52.      */
  53.     protected DynaProperty[] properties;

  54.     /**
  55.      * <p>
  56.      * The set of dynamic properties that are part of this {@link DynaClass}, keyed by the property name. Individual descriptor instances will be the same
  57.      * instances as those in the {@code properties} list.
  58.      * </p>
  59.      */
  60.     protected Map<String, DynaProperty> propertiesMap = new HashMap<>();

  61.     /**
  62.      * <p>
  63.      * Flag defining whether column names or labels should be used.
  64.      */
  65.     private boolean useColumnLabel;

  66.     /**
  67.      * <p>
  68.      * Factory method to create a new DynaProperty for the given index into the result set metadata.
  69.      * </p>
  70.      *
  71.      * @param metadata is the result set metadata
  72.      * @param i        is the column index in the metadata
  73.      * @return the newly created DynaProperty instance
  74.      * @throws SQLException If an error occurs accessing the SQL metadata
  75.      */
  76.     protected DynaProperty createDynaProperty(final ResultSetMetaData metadata, final int i) throws SQLException {
  77.         String columnName = null;
  78.         if (useColumnLabel) {
  79.             columnName = metadata.getColumnLabel(i);
  80.         }
  81.         if (columnName == null || columnName.trim().isEmpty()) {
  82.             columnName = metadata.getColumnName(i);
  83.         }
  84.         final String name = lowerCase ? columnName.toLowerCase() : columnName;
  85.         if (!name.equals(columnName)) {
  86.             if (columnNameXref == null) {
  87.                 columnNameXref = new HashMap<>();
  88.             }
  89.             columnNameXref.put(name, columnName);
  90.         }
  91.         String className = null;
  92.         try {
  93.             final int sqlType = metadata.getColumnType(i);
  94.             switch (sqlType) {
  95.             case java.sql.Types.DATE:
  96.                 return new DynaProperty(name, java.sql.Date.class);
  97.             case java.sql.Types.TIMESTAMP:
  98.                 return new DynaProperty(name, java.sql.Timestamp.class);
  99.             case java.sql.Types.TIME:
  100.                 return new DynaProperty(name, java.sql.Time.class);
  101.             default:
  102.                 className = metadata.getColumnClassName(i);
  103.             }
  104.         } catch (final SQLException e) {
  105.             // this is a patch for HsqlDb to ignore exceptions
  106.             // thrown by its metadata implementation
  107.         }

  108.         // Default to Object type if no class name could be retrieved
  109.         // from the metadata
  110.         Class<?> clazz = Object.class;
  111.         if (className != null) {
  112.             clazz = loadClass(className);
  113.         }
  114.         return new DynaProperty(name, clazz);
  115.     }

  116.     /**
  117.      * Gets the table column name for the specified property name.
  118.      *
  119.      * @param name The property name
  120.      * @return The column name (which can be different if the <em>lowerCase</em> option is used).
  121.      */
  122.     protected String getColumnName(final String name) {
  123.         if (columnNameXref != null && columnNameXref.containsKey(name)) {
  124.             return columnNameXref.get(name);
  125.         }
  126.         return name;
  127.     }

  128.     /**
  129.      * <p>
  130.      * Gets an array of {@code PropertyDescriptor} for the properties currently defined in this DynaClass. If no properties are defined, a zero-length array
  131.      * will be returned.
  132.      * </p>
  133.      */
  134.     @Override
  135.     public DynaProperty[] getDynaProperties() {
  136.         return properties;
  137.     }

  138.     /**
  139.      * <p>
  140.      * Gets a property descriptor for the specified property, if it exists; otherwise, return {@code null}.
  141.      * </p>
  142.      *
  143.      * @param name Name of the dynamic property for which a descriptor is requested
  144.      * @throws IllegalArgumentException if no property name is specified
  145.      */
  146.     @Override
  147.     public DynaProperty getDynaProperty(final String name) {
  148.         return propertiesMap.get(Objects.requireNonNull(name, "name"));

  149.     }

  150.     /**
  151.      * <p>
  152.      * Gets the name of this DynaClass (analogous to the {@code getName()</code> method of <code>java.lang.Class}, which allows the same {@code DynaClass}
  153.      * implementation class to support different dynamic classes, with different sets of properties.
  154.      * </p>
  155.      */
  156.     @Override
  157.     public String getName() {
  158.         return this.getClass().getName();

  159.     }

  160.     /**
  161.      * Gets a column value from a {@link ResultSet} for the specified name.
  162.      *
  163.      * @param resultSet The result set
  164.      * @param name      The property name
  165.      * @return The value
  166.      * @throws SQLException if an error occurs
  167.      */
  168.     protected Object getObject(final ResultSet resultSet, final String name) throws SQLException {
  169.         final DynaProperty property = getDynaProperty(name);
  170.         if (property == null) {
  171.             throw new IllegalArgumentException("Invalid name '" + name + "'");
  172.         }
  173.         final String columnName = getColumnName(name);
  174.         final Class<?> type = property.getType();

  175.         // java.sql.Date
  176.         if (type.equals(Date.class)) {
  177.             return resultSet.getDate(columnName);
  178.         }

  179.         // java.sql.Timestamp
  180.         if (type.equals(Timestamp.class)) {
  181.             return resultSet.getTimestamp(columnName);
  182.         }

  183.         // java.sql.Time
  184.         if (type.equals(Time.class)) {
  185.             return resultSet.getTime(columnName);
  186.         }

  187.         return resultSet.getObject(columnName);
  188.     }

  189.     /**
  190.      * <p>
  191.      * Introspect the metadata associated with our result set, and populate the {@code properties</code> and <code>propertiesMap} instance variables.
  192.      * </p>
  193.      *
  194.      * @param resultSet The {@code resultSet} whose metadata is to be introspected
  195.      * @throws SQLException if an error is encountered processing the result set metadata
  196.      */
  197.     protected void introspect(final ResultSet resultSet) throws SQLException {
  198.         // Accumulate an ordered list of DynaProperties
  199.         final List<DynaProperty> list = new ArrayList<>();
  200.         final ResultSetMetaData metadata = resultSet.getMetaData();
  201.         final int n = metadata.getColumnCount();
  202.         for (int i = 1; i <= n; i++) { // JDBC is one-relative!
  203.             final DynaProperty dynaProperty = createDynaProperty(metadata, i);
  204.             if (dynaProperty != null) {
  205.                 list.add(dynaProperty);
  206.             }
  207.         }

  208.         // Convert this list into the internal data structures we need
  209.         properties = list.toArray(DynaProperty.EMPTY_ARRAY);
  210.         for (final DynaProperty property : properties) {
  211.             propertiesMap.put(property.getName(), property);
  212.         }
  213.     }

  214.     /**
  215.      * <p>
  216.      * Loads and returns the {@code Class} of the given name. By default, a load from the thread context class loader is attempted. If there is no such class
  217.      * loader, the class loader used to load this class will be utilized.
  218.      * </p>
  219.      *
  220.      * @param className The name of the class to load
  221.      * @return The loaded class
  222.      * @throws SQLException if an exception was thrown trying to load the specified class
  223.      */
  224.     protected Class<?> loadClass(final String className) throws SQLException {
  225.         try {
  226.             ClassLoader cl = Thread.currentThread().getContextClassLoader();
  227.             if (cl == null) {
  228.                 cl = this.getClass().getClassLoader();
  229.             }
  230.             // use Class.forName() - see BEANUTILS-327
  231.             return Class.forName(className, false, cl);
  232.         } catch (final Exception e) {
  233.             throw new SQLException("Cannot load column class '" + className + "': " + e);
  234.         }
  235.     }

  236.     /**
  237.      * <p>
  238.      * Instantiate and return a new DynaBean instance, associated with this DynaClass. <strong>NOTE</strong> - This operation is not supported, and throws an
  239.      * exception.
  240.      * </p>
  241.      *
  242.      * @throws IllegalAccessException if the Class or the appropriate constructor is not accessible
  243.      * @throws InstantiationException if this Class represents an abstract class, an array class, a primitive type, or void; or if instantiation fails for some
  244.      *                                other reason
  245.      */
  246.     @Override
  247.     public DynaBean newInstance() throws IllegalAccessException, InstantiationException {
  248.         throw new UnsupportedOperationException("newInstance() not supported");
  249.     }

  250.     /**
  251.      * Sets whether the column label or name should be used for the property name.
  252.      *
  253.      * @param useColumnLabel true if the column label should be used, otherwise false
  254.      */
  255.     public void setUseColumnLabel(final boolean useColumnLabel) {
  256.         this.useColumnLabel = useColumnLabel;
  257.     }

  258. }