All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.noorm.jdbc.BeanMetaDataUtil Maven / Gradle / Ivy

There is a newer version: 1.5.0
Show newest version
package org.noorm.jdbc;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * Convenience class to provide access to Bean metadata. Other classes and functionalities
 * like the BeanMapper require metadata information about the Bean, in particular information
 * about the non-transient fields, which are annotated with the JDBCColumn annotation.
 *
 * Since declared fields and annotations are determined at compile time, they are cached here to
 * improve performance.
 *
 * @author Ulf Pietruschka / [email protected]
 *         Date: 26.07.11
 *         Time: 11:37
 */
public class BeanMetaDataUtil {

	private static final Logger log = LoggerFactory.getLogger(BeanMetaDataUtil.class);

	public static final String SERIAL_VERSION_UID = "serialVersionUID";

    private static Map jdbcColumnAnnotationCache = new HashMap();
    private static Map declaredFieldInclParentCache = new HashMap();

    public static Map getJavaNames2ColumnNames(final Class pClass) {

    	final HashMap javaNames2ColumnNames = new HashMap<>();
		final Field[] fields = getDeclaredFieldsInclParent(pClass);
		for (final Field field : fields) {
			final JDBCColumn jdbcColumn = getJDBCColumnAnnotation(field);
			if (jdbcColumn != null) {
				final String javaName = field.getName();
				final String columnName = jdbcColumn.name();
				javaNames2ColumnNames.put(javaName, columnName);
			}
		}
		return javaNames2ColumnNames;
	}

	/**
	 * Using Class.getDeclaredFields does not return fields declared in a potentially existing super-class.
	 * This method extends the retrieval of declared fields to the super-class, if any. Note that there is
	 * no recursive mechanism to detect declarations of the super-class of the super-class.
	 * @param pClass the class
	 * @return all declared fields of the provided class and its super-class, if any.
	 */
	public static Field[] getDeclaredFieldsInclParent(final Class pClass) {

        if (log.isTraceEnabled()) {
            log.trace("Retrieving declared fields by reflection for class ".concat(pClass.getName()));
        }

        Field[] allFields = declaredFieldInclParentCache.get(pClass);
        if (allFields == null) {
            final Field[] fields = pClass.getDeclaredFields();
            Field[] sFields = new Field[0];
            final Class superClass = pClass.getSuperclass();
            if (superClass != null) {
                sFields = superClass.getDeclaredFields();
            }
            allFields = Arrays.copyOf(fields, fields.length + sFields.length);
            System.arraycopy(sFields, 0, allFields, fields.length, sFields.length);

            if (log.isTraceEnabled()) {
                int i = 0;
                for (final Field field : allFields) {
                    final StringBuilder logMessage = new StringBuilder();
                    logMessage.append("Field ");
                    logMessage.append(i++);
                    logMessage.append(" : ");
                    logMessage.append(field.getName());
                    logMessage.append(":");
                    logMessage.append(field.getType().getName());
                    log.trace(logMessage.toString());
                }
            }
            declaredFieldInclParentCache.put(pClass, allFields);
        }

		return allFields;
	}

    /**
     * Returns the annotations for a given field.
     * The results are stored in a local cache, since method Field.getDeclaredAnnotations has a VERY BAD
     * performance.
     *
     * @param pField the field
     * @return the annotations for the given field
     */
    public static JDBCColumn getJDBCColumnAnnotation(final Field pField) {

        JDBCColumn jdbcColumn = jdbcColumnAnnotationCache.get(pField);
        if (jdbcColumn == null) {
            final Annotation[] annotations = pField.getDeclaredAnnotations();
            if (annotations != null && annotations.length > 0) {
                if (annotations[0].annotationType() == JDBCColumn.class) {
                    jdbcColumn = (JDBCColumn) annotations[0];
                }
                jdbcColumnAnnotationCache.put(pField, jdbcColumn);
            }
        }
        return jdbcColumn;
    }

	/**
	 * Returns a Map containing a mapping from the Bean attribute names to their JDBCColumn
	 * annotations.
	 *
	 * @param pClass the Bean type
	 * @return a map containing all columns, resp. fields with their associated JDBCColumn.
	 */
	public static Map getColumnMetaData(final Class pClass) {

		final Map columnMetaDataMap = new HashMap();
		final Field[] fields = BeanMetaDataUtil.getDeclaredFieldsInclParent(pClass);
		for (final Field field : fields) {
			// Ignore serialVersionUID
			if (SERIAL_VERSION_UID.equals(field.getName())) {
				continue;
			}
			final Annotation[] annotations = field.getDeclaredAnnotations();
			if (annotations != null && annotations.length > 0) {
				if (annotations[0].annotationType() == JDBCColumn.class) {
					final JDBCColumn colAnn = (JDBCColumn) annotations[0];
					columnMetaDataMap.put(colAnn.name(), colAnn);
				}
			}
			// Ignore fields without JDBCColumn annotation (interpreted transient)
		}
		return columnMetaDataMap;
	}

	/**
	 * Retrieves the value of the (primary) key of the bean passed to this method. The data is retrieved
	 * by reflection based on the bean metadata provided by the bean itself.
	 * @param pBean the bean instance.
	 * @return the primary key value for the passed bean.
	 */
	public static Long getPrimaryKeyValue(final IBean pBean) {

		if (pBean.getPrimaryKeyColumnNames().length != 1) {
			throw new DataAccessException(DataAccessException.Type.OPERATION_NOT_SUPPORTED_WITH_COMPOSITE_PK);
		}
		final String primaryKeyJavaName = pBean.getPrimaryKeyJavaNames()[0];
		final Object property = getBeanPropertyByName(pBean, primaryKeyJavaName);
		if (!(property instanceof Long)) {
			throw new DataAccessException(DataAccessException.Type.UNSUPPORTED_DATATYPE);
		}
		return (Long) property;
	}

	public static Class getBeanPropertyType(final Object pObject, final String pPropertyName) {

        Field property = getDeclaredFieldInclParent(pObject, pPropertyName);
        return property.getType();
	}

	public static Object getBeanPropertyByName(final Object pObject, final String pPropertyName) {

        Field property = getDeclaredFieldInclParent(pObject, pPropertyName);
		try {
			return property.get(pObject);
		} catch (IllegalAccessException e) {
			throw new DataAccessException(DataAccessException.Type.COULD_NOT_ACCESS_PROPERTY_BY_REFLECTION, e);
		}
	}

	/**
	 * Sets the value of the (primary) key of the bean passed to this method. The data is set
	 * by reflection based on the bean metadata provided by the bean itself.
	 * @param pBean the bean instance.
	 * @param pPKValue the value of the primary key.
	 */
	public static void setPrimaryKeyValue(final IBean pBean, final Number pPKValue) {

		if (pBean.getPrimaryKeyColumnNames().length != 1) {
			throw new DataAccessException(DataAccessException.Type.OPERATION_NOT_SUPPORTED_WITH_COMPOSITE_PK);
		}
        log.debug("Generated key value " + pPKValue + " retrieved for table " + pBean.getTableName());
		final String primaryKeyJavaName = pBean.getPrimaryKeyJavaNames()[0];
        setBeanPropertyByName(pBean, primaryKeyJavaName, pPKValue);
	}

	/**
	 * Sets the value of the version column of the bean passed to this method. The data is set
	 * by reflection based on the bean metadata provided by the bean itself.
	 * @param pBean the bean instance.
	 * @param pVersionColumnValue the value of the version column.
	 */
	public static void setVersionColumnValue(final IBean pBean, final Object pVersionColumnValue) {

		final String versionColumnJavaName = pBean.getVersionColumnJavaName();
        setBeanPropertyByName(pBean, versionColumnJavaName, pVersionColumnValue);
	}

	private static void setBeanPropertyByName(final IBean pBean, final String pPropertyName, final Object pValue) {

        final Field property = getDeclaredFieldInclParent(pBean, pPropertyName);
		try {
			property.set(pBean, pValue);
		} catch (IllegalAccessException e) {
			throw new DataAccessException(DataAccessException.Type.COULD_NOT_ACCESS_PROPERTY_BY_REFLECTION, e);
		}
	}

    private static Field getDeclaredFieldInclParent(final Object pObject, final String pPropertyName) {

        final Field[] fields = getDeclaredFieldsInclParent(pObject.getClass());
        Field property = null;
        for (final Field field : fields) {
            if (field.getName().equals(pPropertyName)) {
                property = field;
            }
        }
        if (property == null) {
            throw new DataAccessException(DataAccessException.Type.COULD_NOT_ACCESS_PROPERTY_BY_REFLECTION,
                    pObject.getClass().getName().concat(".").concat(pPropertyName));
        }
        property.setAccessible(true);
        return property;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy