com.nway.spring.jdbc.bean.JavassistBeanProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.nway.spring.jdbc.bean.JavassistBeanProcessor.java

Source

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

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.util.ClassUtils;

import com.nway.spring.classwork.DynamicObjectException;
import com.nway.spring.jdbc.annotation.Column;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;

/**
 * <p>
 * <code>BeanProcessor</code> matches column names to bean property names and converts
 * <code>ResultSet</code> columns into objects for those bean properties. Subclasses should override
 * the methods in the processing chain to customize behavior.
 * </p>
 *
 * <p>
 * This class is thread-safe.
 * </p>
 *
 * apache-dbutilsbean,
 * <p>
 *
 * @since DbUtils 1.1
 */
class JavassistBeanProcessor implements BeanProcessor {

    private static final Map<String, DbBeanFactory> DBBEANFACTORY_CACHE = new HashMap<String, DbBeanFactory>();

    /**
     * Special array value used by <code>mapColumnsToProperties</code> that indicates there is no
     * bean property that matches a column from a <code>ResultSet</code>.
     */
    private static final int PROPERTY_NOT_FOUND = -1;

    /**
     * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans. This implementation
     * uses reflection and <code>BeanInfo</code> classes to match column names to bean property
     * names. Properties are matched to columns based on several factors: <br/>
     * <ol>
     * <li>
     * The class has a writable property with the same name as a column. The name comparison is case
     * insensitive.</li>
     *
     * <li>
     * The column type can be converted to the property's set method parameter type with a
     * ResultSet.get* method. If the conversion fails (ie. the property was an int and the column
     * was a Timestamp) an SQLException is thrown.</li>
     * </ol>
     *
     * <p>
     * Primitive bean properties are set to their defaults when SQL NULL is returned from the
     * <code>ResultSet</code>. Numeric fields are set to 0 and booleans are set to false. Object
     * bean properties are set to <code>null</code> when SQL NULL is returned. This is the same
     * behavior as the <code>ResultSet</code> get* methods.
     * </p>
     *
     * @param <T> The type of bean to create
     * @param rs ResultSet that supplies the bean data
     * @param type Class from which to create the bean instance
     * @throws SQLException if a database access error occurs
     * @return the newly created List of beans
     */
    public <T> List<T> toBeanList(ResultSet rs, Class<T> type, String cacheKey) throws SQLException {

        if (!rs.next()) {

            return Collections.emptyList();
        }

        final List<T> results = new ArrayList<T>();

        //String cacheKey = DynamicClassUtils.makeCacheKey(rs, type.getName());

        do {

            results.add(toBean(rs, type, cacheKey));
        } while (rs.next());

        return results;
    }

    /**
     * beanbean
     * <p>
     *
     * <b>ASMbeanjavassistbean{@link
     * this#createBeanByJavassist(ResultSet, Class, String)}</b>
     *
     * @param <T>
     * @param rs {@link ResultSet}
     * @param type bean
     * @param querying sql
     * @return bean
     *
     * @throws SQLException
     */
    public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {

        return toBean(rs, type, null);
    }

    public <T> T toBean(ResultSet rs, Class<T> type, String cacheKey) throws SQLException {

        if (cacheKey == null) {

            cacheKey = DynamicClassUtils.makeCacheKey(rs, type.getName());
        }

        /*
         * 
         * DbBeanFactory
         * type
         */
        //      synchronized (type) {

        return createBeanByJavassist(rs, type, cacheKey);

        //      }

    }

    private <T> T createBeanByJavassist(ResultSet rs, Class<T> mappedClass, String key) throws SQLException {

        DbBeanFactory dynamicRse = DBBEANFACTORY_CACHE.get(key);

        // 
        if (dynamicRse != null) {

            return dynamicRse.createBean(rs, mappedClass);
        }

        T bean = this.newInstance(mappedClass);

        ResultSetMetaData rsmd = rs.getMetaData();

        PropertyDescriptor[] props = BeanUtils.getPropertyDescriptors(mappedClass);

        int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);

        StringBuilder handlerScript = new StringBuilder();

        handlerScript.append("{").append(mappedClass.getName()).append(" bean = new ").append(mappedClass.getName())
                .append("();\n");

        PropertyDescriptor desc = null;
        for (int i = 1; i < columnToProperty.length; i++) {

            if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
                continue;
            }

            desc = props[columnToProperty[i]];
            Class<?> propType = desc.getPropertyType();

            Object value = processColumn(rs, i, propType, desc.getWriteMethod().getName(), handlerScript);

            this.callSetter(bean, desc, value);

        }

        handlerScript.append("return bean;");

        handlerScript.append("}");

        try {

            ClassPool classPool = ClassPool.getDefault();

            classPool.appendClassPath(new LoaderClassPath(ClassUtils.getDefaultClassLoader()));

            CtClass ctHandler = classPool.makeClass(DynamicClassUtils.getBeanProcessorName(mappedClass));
            ctHandler.setSuperclass(classPool.get("com.nway.spring.jdbc.bean.DbBeanFactory"));

            CtMethod mapRow = CtNewMethod.make(
                    "public Object createBean(java.sql.ResultSet rs, Class type) throws java.sql.SQLException{return null;}",
                    ctHandler);

            mapRow.setGenericSignature("<T:Ljava/lang/Object;>(Ljava/sql/ResultSet;Ljava/lang/Class<TT;>;)TT;");

            mapRow.setBody(handlerScript.toString());

            ctHandler.addMethod(mapRow);

            DBBEANFACTORY_CACHE.put(key, (DbBeanFactory) ctHandler.toClass().newInstance());

        } catch (Exception e) {

            throw new DynamicObjectException("javassist [ " + mappedClass.getName() + " ] ", e);
        }

        return bean;
    }

    /**
     * Calls the setter method on the target object for the given property. If no setter method
     * exists for the property, this method does nothing.
     *
     * @param target The object to set the property on.
     * @param prop The property to set.
     * @param value The value to pass into the setter.
     * @throws SQLException if an error occurs setting the property.
     */
    private void callSetter(Object target, PropertyDescriptor prop, Object value) throws SQLException {

        Method setter = prop.getWriteMethod();

        if (setter == null) {

            return;
        }

        try {

            // Don't call setter if the value object isn't the right type
            // if (this.isCompatibleType(value, params[0])) {
            setter.invoke(target, new Object[] { value });

        } catch (Exception e) {

            throw new SQLException("Cannot set " + prop.getName() + ": " + e.toString(), e);
        }
    }

    /**
     * Factory method that returns a new instance of the given Class. This is called at the start of
     * the bean creation process and may be overridden to provide custom behavior like returning a
     * cached bean instance.
     *
     * @param <T> The type of object to create
     * @param c The Class to create an object from.
     * @return A newly created object of the Class.
     * @throws SQLException if creation failed.
     */
    private <T> T newInstance(Class<T> c) throws SQLException {

        try {

            return c.newInstance();
        } catch (InstantiationException e) {

            throw new SQLException("Cannot create " + c.getName() + ": " + e.toString(), e);
        } catch (IllegalAccessException e) {

            throw new SQLException("Cannot create " + c.getName() + ": " + e.toString(), e);
        }
    }

    /**
     * The positions in the returned array represent column numbers. The values stored at each
     * position represent the index in the <code>PropertyDescriptor[]</code> for the bean property
     * that matches the column name. If no bean property was found for a column, the position is set
     * to <code>PROPERTY_NOT_FOUND</code>.
     *
     * @param rsmd The <code>ResultSetMetaData</code> containing column information.
     *
     * @param props The bean property descriptors.
     *
     * @throws SQLException if a database access error occurs
     *
     * @return An int[] with column index to property index mappings. The 0th element is meaningless
     * because JDBC column indexing starts at 1.
     */
    private int[] mapColumnsToProperties(ResultSetMetaData rsmd, PropertyDescriptor[] props) throws SQLException {

        int cols = rsmd.getColumnCount();
        int[] columnToProperty = new int[cols + 1];

        Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);

        for (int col = 1; col <= cols; col++) {

            String columnName = rsmd.getColumnLabel(col);

            for (int i = 0; i < props.length; i++) {

                Column columnAnnotation = props[i].getReadMethod().getAnnotation(Column.class);

                if (columnAnnotation == null) {

                    //'_'
                    if (columnName.replace("_", "").equalsIgnoreCase(props[i].getName())) {

                        columnToProperty[col] = i;
                        break;
                    }
                } else if (columnName.equalsIgnoreCase(columnAnnotation.value())
                        || columnName.equalsIgnoreCase(columnAnnotation.name())) {

                    columnToProperty[col] = i;
                    break;
                }

            }
        }

        return columnToProperty;
    }

    private Object processColumn(ResultSet rs, int index, Class<?> propType, String writer, StringBuilder handler)
            throws SQLException {
        if (propType.equals(String.class)) {
            handler.append("bean.").append(writer).append("(").append("$1.getString(").append(index).append("));");
            return rs.getString(index);
        } else if (propType.equals(Integer.TYPE)) {
            handler.append("bean.").append(writer).append("(").append("$1.getInt(").append(index).append("));");
            return rs.getInt(index);
        } else if (propType.equals(Integer.class)) {
            handler.append("bean.").append(writer).append("(").append("integerValue($1.getInt(").append(index)
                    .append("),$1.wasNull()));");
            return JdbcUtils.getResultSetValue(rs, index, Integer.class);
        } else if (propType.equals(Long.TYPE)) {
            handler.append("bean.").append(writer).append("(").append("$1.getLong(").append(index).append("));");
            return rs.getLong(index);
        } else if (propType.equals(Long.class)) {
            handler.append("bean.").append(writer).append("(").append("longValue($1.getLong(").append(index)
                    .append("),$1.wasNull()));");
            return JdbcUtils.getResultSetValue(rs, index, Long.class);
        } else if (propType.equals(java.sql.Date.class)) {
            handler.append("bean.").append(writer).append("(").append("$1.getDate(").append(index).append("));");
            return rs.getDate(index);
        } else if (propType.equals(java.util.Date.class) || propType.equals(Timestamp.class)) {
            handler.append("bean.").append(writer).append("(").append("$1.getTimestamp(").append(index)
                    .append("));");
            return rs.getTimestamp(index);
        } else if (propType.equals(Double.TYPE)) {
            handler.append("bean.").append(writer).append("(").append("$1.getDouble(").append(index).append("));");
            return rs.getDouble(index);
        } else if (propType.equals(Double.class)) {
            handler.append("bean.").append(writer).append("(").append("doubleValue($1.getDouble(").append(index)
                    .append("),$1.wasNull()));");
            return JdbcUtils.getResultSetValue(rs, index, Double.class);
        } else if (propType.equals(Float.TYPE)) {
            handler.append("bean.").append(writer).append("(").append("$1.getFloat(").append(index).append("));");
            return rs.getFloat(index);
        } else if (propType.equals(Float.class)) {
            handler.append("bean.").append(writer).append("(").append("floatValue($1.getFloat(").append(index)
                    .append("),$1.wasNull()));");
            return JdbcUtils.getResultSetValue(rs, index, Float.class);
        } else if (propType.equals(Time.class)) {
            handler.append("bean.").append(writer).append("(").append("$1.getTime(").append(index).append("));");
            return rs.getTime(index);
        } else if (propType.equals(Boolean.TYPE)) {
            handler.append("bean.").append(writer).append("(").append("$1.getBoolean(").append(index).append("));");
            return rs.getBoolean(index);
        } else if (propType.equals(Boolean.class)) {
            handler.append("bean.").append(writer).append("(").append("booleanValue($1.getBoolean(").append(index)
                    .append("),$1.wasNull()));");
            return JdbcUtils.getResultSetValue(rs, index, Boolean.class);
        } else if (propType.equals(byte[].class)) {
            handler.append("bean.").append(writer).append("(").append("$1.getBytes(").append(index).append("));");
            return rs.getBytes(index);
        } else if (BigDecimal.class.equals(propType)) {
            handler.append("bean.").append(writer).append("(").append("$1.getBigDecimal(").append(index)
                    .append("));");
            return rs.getBigDecimal(index);
        } else if (Blob.class.equals(propType)) {
            handler.append("bean.").append(writer).append("(").append("$1.getBlob(").append(index).append("));");
            return rs.getBlob(index);
        } else if (Clob.class.equals(propType)) {
            handler.append("bean.").append(writer).append("(").append("$1.getClob(").append(index).append("));");
            return rs.getClob(index);
        } else if (propType.equals(Short.TYPE)) {
            handler.append("bean.").append(writer).append("(").append("$1.getShort(").append(index).append("));");
            return rs.getShort(index);
        } else if (propType.equals(Short.class)) {
            handler.append("bean.").append(writer).append("(").append("shortValue($1.getShort(").append(index)
                    .append("),$1.wasNull()));");
            return JdbcUtils.getResultSetValue(rs, index, Short.class);
        } else if (propType.equals(Byte.TYPE)) {
            handler.append("bean.").append(writer).append("(").append("$1.getByte(").append(index).append("));");
            return rs.getByte(index);
        } else if (propType.equals(Byte.class)) {
            handler.append("bean.").append(writer).append("(").append("byteValue($1.getByte(").append(index)
                    .append("),$1.wasNull()));");
            return JdbcUtils.getResultSetValue(rs, index, Byte.class);
        } else {
            handler.append("bean.").append(writer).append("(").append("(").append(propType.getName()).append(")")
                    .append("$1.getObject(").append(index).append("));");
            return rs.getObject(index);
        }
    }
}