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

org.dbflute.s2dao.extension.TnRowCreatorExtension Maven / Gradle / Ivy

There is a newer version: 1.2.8
Show newest version
/*
 * Copyright 2014-2021 the original author or authors.
 *
 * Licensed 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 org.dbflute.s2dao.extension;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.dbflute.Entity;
import org.dbflute.bhv.core.context.ConditionBeanContext;
import org.dbflute.bhv.core.context.InternalMapContext;
import org.dbflute.bhv.core.context.ResourceContext;
import org.dbflute.cbean.ConditionBean;
import org.dbflute.cbean.chelper.HpDerivingSubQueryInfo;
import org.dbflute.cbean.sqlclause.SqlClause;
import org.dbflute.dbmeta.DBMeta;
import org.dbflute.dbmeta.accessory.ColumnNullObjectable;
import org.dbflute.dbmeta.accessory.DerivedMappable;
import org.dbflute.dbmeta.accessory.DerivedTypeHandler;
import org.dbflute.dbmeta.info.ColumnInfo;
import org.dbflute.exception.MappingClassCastException;
import org.dbflute.helper.message.ExceptionMessageBuilder;
import org.dbflute.jdbc.ValueType;
import org.dbflute.s2dao.metadata.TnBeanMetaData;
import org.dbflute.s2dao.metadata.TnPropertyMapping;
import org.dbflute.s2dao.rowcreator.impl.TnRowCreatorImpl;
import org.dbflute.s2dao.valuetype.TnValueTypes;
import org.dbflute.system.DBFluteSystem;
import org.dbflute.util.DfTypeUtil;
import org.dbflute.util.Srl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author jflute
 */
public class TnRowCreatorExtension extends TnRowCreatorImpl {

    // ===================================================================================
    //                                                                          Definition
    //                                                                          ==========
    /** The logger instance for this class. (NotNull) */
    private static final Logger _log = LoggerFactory.getLogger(TnRowCreatorExtension.class);

    /** The key of DBMeta cache. */
    protected static final String DBMETA_CACHE_KEY = "df:DBMetaCache";

    /** The prefix mark for derived mapping alias. */
    protected static final String DERIVED_MAPPABLE_ALIAS_PREFIX = DerivedMappable.MAPPING_ALIAS_PREFIX;

    // ===================================================================================
    //                                                                           Attribute
    //                                                                           =========
    protected DBMeta _fixedDBMeta;
    protected boolean _creatableByDBMeta;

    // ===================================================================================
    //                                                                         Constructor
    //                                                                         ===========
    protected TnRowCreatorExtension() {
    }

    /**
     * @param beanClass The class of target bean to find DB-meta. (NullAllowed)
     * @return The instance of internal row creator. (NotNull)
     */
    public static TnRowCreatorExtension createRowCreator(Class beanClass) {
        final TnRowCreatorExtension rowCreator = new TnRowCreatorExtension();
        if (beanClass != null) {
            final DBMeta dbmeta = findDBMetaByClass(beanClass);
            if (dbmeta != null) {
                rowCreator.setFixedDBMeta(dbmeta);
                rowCreator.setCreatableByDBMeta(isCreatableByDBMeta(beanClass, dbmeta.getEntityType()));
            }
        }
        return rowCreator;
    }

    protected static DBMeta findDBMetaByClass(Class beanClass) {
        if (!Entity.class.isAssignableFrom(beanClass)) {
            return null;
        }
        // getting from entity because the bean may be customize entity.
        final Object instance = newInstance(beanClass); // only when initialization
        return ((Entity) instance).asDBMeta();
    }

    protected static Object newInstance(Class clazz) {
        try {
            return clazz.newInstance();
        } catch (InstantiationException e) {
            throw new IllegalStateException(e);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }

    protected static boolean isCreatableByDBMeta(Class beanClass, Class entityType) {
        // Returns false when the bean is not related to the entity or is a sub class of the entity.
        return beanClass.isAssignableFrom(entityType);
    }

    // ===================================================================================
    //                                                                                Main
    //                                                                                ====
    /** {@inheritDoc} */
    public Object createRow(ResultSet rs, Map> selectIndexMap, Map propertyCache,
            Class beanClass, ConditionBean cb) throws SQLException {
        if (propertyCache.isEmpty()) {
            String msg = "The propertyCache should not be empty: bean=" + beanClass.getName();
            throw new IllegalStateException(msg);
        }

        // temporary variable, for exception message, debug message
        String columnName = null;
        TnPropertyMapping mapping = null;
        String propertyName = null;
        Object selectedValue = null;
        ColumnInfo columnInfo = null;

        final Object row;
        final DBMeta dbmeta;
        if (_fixedDBMeta != null) {
            if (_creatableByDBMeta) { // mainly here
                final Entity entity = _fixedDBMeta.newEntity();
                reflectConditionBeanOptionToEntity(cb, entity);
                row = entity;
            } else { // e.g. manual-extended entity
                row = newBean(beanClass);
            }
            dbmeta = _fixedDBMeta;
        } else { // e.g. manual-created bean of outsideSql
            row = newBean(beanClass);
            dbmeta = findCachedDBMeta(row); // find just in case
        }
        try {
            if (dbmeta != null) { // mainly here
                final boolean isEntity = row instanceof Entity; // almost always true
                final Entity entityRow = isEntity ? (Entity) row : null;
                for (Entry entry : propertyCache.entrySet()) {
                    columnName = entry.getKey();
                    mapping = entry.getValue();
                    propertyName = mapping.getPropertyName();
                    selectedValue = getValue(rs, columnName, mapping.getValueType(), selectIndexMap);
                    columnInfo = mapping.getEntityColumnInfo();
                    if (columnInfo != null && isEntity) {
                        columnInfo.write(entityRow, selectedValue);
                    } else {
                        mapping.getPropertyAccessor().setValue(row, selectedValue);
                    }
                }
                if (canHandleDerivedMap(row)) {
                    processDerivedMap(rs, selectIndexMap, propertyCache, row);
                }
            } else { // not DBFlute entity
                for (Entry entry : propertyCache.entrySet()) {
                    columnName = entry.getKey();
                    mapping = entry.getValue();
                    propertyName = mapping.getPropertyName();
                    selectedValue = getValue(rs, columnName, mapping.getValueType(), selectIndexMap);
                    mapping.getPropertyAccessor().setValue(row, selectedValue);
                }
            }
            return row;
        } catch (ClassCastException e) {
            throwMappingClassCastException(row, dbmeta, mapping, selectedValue, e);
            return null; // unreachable
        } catch (SQLException e) {
            if (_log.isDebugEnabled()) {
                String msg = "Failed to get selected values while resultSet handling:";
                msg = msg + " target=" + DfTypeUtil.toClassTitle(beanClass) + "." + propertyName;
                _log.debug(msg);
            }
            throw e;
        }
    }

    protected void reflectConditionBeanOptionToEntity(ConditionBean cb, Entity entity) {
        // unlock access to undefined classification if allowed in condition-bean
        // this should be set before mapping values (and also relation table's creator)
        if (cb != null && cb.isUndefinedClassificationSelectAllowed()) {
            entity.myunlockUndefinedClassificationAccess();
        }
    }

    protected boolean canHandleDerivedMap(final Object row) {
        return row instanceof DerivedMappable && ConditionBeanContext.isExistConditionBeanOnThread();
    }

    protected void processDerivedMap(ResultSet rs, Map> selectIndexMap,
            Map propertyCache, Object row) throws SQLException {
        final ConditionBean cb = ConditionBeanContext.getConditionBeanOnThread();
        final SqlClause sqlClause = cb.getSqlClause();
        if (!sqlClause.hasSpecifiedDerivingSubQuery()) {
            return;
        }
        final DerivedMappable mappable = (DerivedMappable) row;
        final List derivingAliasList = sqlClause.getSpecifiedDerivingAliasList();
        DerivedTypeHandler typeHandler = null;
        for (String derivingAlias : derivingAliasList) {
            // propertyCache has alias name when derived-referrer as case-insensitive
            if (propertyCache.containsKey(derivingAlias)) { // already handled
                continue;
            }
            if (!derivingAlias.startsWith(DERIVED_MAPPABLE_ALIAS_PREFIX)) { // basically no way (just in case)
                continue; // might be exception but no need to be strict here
            }
            if (typeHandler == null) {
                typeHandler = cb.xgetDerivedTypeHandler(); // basically fixed instance returned
                if (typeHandler == null) { // no way, just in case
                    String msg = "Not found the type handler from condition-bean: " + cb.asTableDbName();
                    throw new IllegalStateException(msg);
                }
            }
            final HpDerivingSubQueryInfo derivingInfo = sqlClause.getSpecifiedDerivingInfo(derivingAlias);
            final ValueType valueType = TnValueTypes.getValueType(typeHandler.findMappingType(derivingInfo));
            final String onQueryAlias = Srl.substringFirstRear(derivingAlias, DERIVED_MAPPABLE_ALIAS_PREFIX);
            Object selectedValue = getValue(rs, onQueryAlias, valueType, selectIndexMap);
            selectedValue = typeHandler.convertToMapValue(derivingInfo, selectedValue);
            mappable.registerDerivedValue(derivingAlias, selectedValue);
        }
    }

    protected Object getValue(ResultSet rs, String columnName, ValueType valueType, Map> selectIndexMap)
            throws SQLException {
        final Object value;
        if (selectIndexMap != null) {
            value = ResourceContext.getLocalValue(rs, columnName, valueType, selectIndexMap);
        } else {
            value = valueType.getValue(rs, columnName);
        }
        return value;
    }

    protected void throwMappingClassCastException(Object entity, DBMeta dbmeta, TnPropertyMapping mapping, Object selectedValue,
            ClassCastException e) {
        final ExceptionMessageBuilder br = new ExceptionMessageBuilder();
        br.addNotice("Failed to cast a class while data mapping.");
        br.addItem("Advice");
        br.addElement("If you use Seasar(S2Container), this exception may be");
        br.addElement("from ClassLoader Headache about HotDeploy.");
        br.addElement("Add the ignore-package setting to convention.dicon like this:");
        br.addElement("    ");
        br.addElement("        ”com.example.xxx.dbflute”");
        br.addElement("    ");
        br.addElement("If you use an other DI container, this exception may be");
        br.addElement("from illegal state about your settings of DBFlute.");
        br.addElement("Confirm your settings: for example, typeMappingMap.dfprop.");
        br.addItem("Exception Message");
        br.addElement(e.getMessage());
        br.addItem("Target Entity");
        br.addElement(entity);
        br.addElement("classLoader: " + entity.getClass().getClassLoader());
        br.addItem("Target DBMeta");
        br.addElement(dbmeta);
        br.addElement("classLoader: " + dbmeta.getClass().getClassLoader());
        br.addItem("Property Mapping");
        br.addElement(mapping);
        br.addElement("type: " + (mapping != null ? mapping.getClass() : null));
        br.addItem("Selected Value");
        br.addElement(selectedValue);
        br.addElement("type: " + (selectedValue != null ? selectedValue.getClass() : null));
        final String msg = br.buildExceptionMessage();
        throw new MappingClassCastException(msg, e);
    }

    // ===================================================================================
    //                                                                        DBMeta Cache
    //                                                                        ============
    // share with relation row
    /**
     * @param row The instance of row. (NotNull)
     * @return The interface of DBMeta. (NullAllowed: If it's null, it means NotFound.)
     */
    public static DBMeta findCachedDBMeta(Object row) {
        return DBMetaCacheHandler.findDBMeta(row);
    }

    /**
     * @param rowType The type of row. (NotNull)
     * @param tableName The name of table. (NotNull)
     * @return The interface of DBMeta. (NullAllowed: If it's null, it means NotFound.)
     */
    public static DBMeta findCachedDBMeta(Class rowType, String tableName) {
        return DBMetaCacheHandler.findDBMeta(rowType, tableName);
    }

    protected static class DBMetaCacheHandler {

        /** The key of DBMeta cache. */
        protected static final String DBMETA_CACHE_KEY = "df:DBMetaCache";

        public static DBMeta findDBMeta(Object row) {
            if (!(row instanceof Entity)) {
                return null;
            }
            final Entity entity = (Entity) row;
            DBMeta dbmeta = getCachedDBMeta(entity.getClass());
            if (dbmeta != null) {
                return dbmeta;
            }
            dbmeta = entity.asDBMeta();
            cacheDBMeta(entity, dbmeta);
            return dbmeta;
        }

        public static DBMeta findDBMeta(Class rowType, String tableName) {
            DBMeta dbmeta = getCachedDBMeta(rowType);
            if (dbmeta != null) {
                return dbmeta;
            }
            // No check because the table name is not always for domain.
            dbmeta = ResourceContext.provideDBMeta(tableName);
            cacheDBMeta(rowType, dbmeta);
            return dbmeta;
        }

        protected static DBMeta getCachedDBMeta(Class rowType) {
            Map, DBMeta> contextCacheMap = getDBMetaContextCacheMap();
            if (contextCacheMap == null) {
                contextCacheMap = new HashMap, DBMeta>();
                InternalMapContext.setObject(DBMETA_CACHE_KEY, contextCacheMap);
            }
            return contextCacheMap.get(rowType);
        }

        protected static void cacheDBMeta(Entity entity, DBMeta dbmeta) {
            cacheDBMeta(entity.getClass(), dbmeta);
        }

        protected static void cacheDBMeta(Class type, DBMeta dbmeta) {
            final Map, DBMeta> dbmetaCache = getDBMetaContextCacheMap();
            dbmetaCache.put(type, dbmeta);
        }

        @SuppressWarnings("unchecked")
        protected static Map, DBMeta> getDBMetaContextCacheMap() {
            return (Map, DBMeta>) InternalMapContext.getObject(DBMETA_CACHE_KEY);
        }
    }

    // ===================================================================================
    //                                                                             Fix Row
    //                                                                             =======
    /**
     * Adjust created row. (clearing modified info, ...)
     * @param row The row of result list. (NotNull)
     * @param checkNonSp Does is use the check of access to non-specified column?
     * @param colNullObj Does is use the handling of column null object?
     * @param basePointBmd The bean meta data of the row for base-point table. (NotNull)
     * @param cb The condition-bean for the select. (NullAllowed: when not condition-bean select)
     */
    public static void adjustCreatedRow(final Object row, boolean checkNonSp, boolean colNullObj, TnBeanMetaData basePointBmd,
            ConditionBean cb) {
        // _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
        // static for handler calling
        // however no other callers now so unnecessary, exists once...
        // only for uniformity with relation
        //
        // *similar implementation for relation row exists
        // no refactoring because it needs high performance here,
        // comment only to avoid wasted calculation and determination
        // _/_/_/_/_/_/_/_/_/_/
        if (row instanceof Entity) {
            final Entity entity = (Entity) row;

            // _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
            // check access to non-specified-column
            // modified properties matches specified properties so copy it
            // so should be before clearing modified info
            // _/_/_/_/_/_/_/_/_/_/
            if (checkNonSp) { // contains enabled by CB and using SpecifyColumn
                entity.modifiedToSpecified();

                // _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
                // adjust specification for column null object handling
                // null object target is not specified but it should be able to be called
                // so add dummy to specified properties
                // e.g. A, B and cached C
                // when specifyA, specifyC => no specified-property so *set dummy property here
                // when specifyA, specifyB => C should be checked, actually checked so no action here
                // when non-SpecifyColumm  => can get so no action here
                // when specifyA, specifyC but allowed => can get so no action here
                // when specifyA, specifyB but allowed => should be no cache logically, but rare case so no action here
                // _/_/_/_/_/_/_/_/_/_/
                if (colNullObj && cb != null) { // check 'cb' just in case
                    final SqlClause sqlClause = cb.getSqlClause();
                    for (ColumnInfo columnInfo : sqlClause.getLocalSpecifiedNullObjectColumnSet()) {
                        entity.myspecifyProperty(columnInfo.getPropertyName());
                    }
                }
            }
            // enable the handling of column null object if allowed and object-able
            // only table that has null object target column is object-able (generated as it)
            if (colNullObj && entity instanceof ColumnNullObjectable) {
                ((ColumnNullObjectable) entity).enableColumnNullObject();
            }

            // clear modified properties for update process using selected entity
            entity.clearModifiedInfo();

            // mark as select to determine the entity is selected or user-created
            // basically for e.g. determine columns of batch insert
            entity.markAsSelect();
        } else { // not DBFlute entity
            // actually any bean meta data can be accepted
            // because only it gets modified properties
            basePointBmd.getModifiedPropertyNames(row).clear();
        }
    }

    // ===================================================================================
    //                                                                      General Helper
    //                                                                      ==============
    protected String ln() {
        return DBFluteSystem.ln();
    }

    // ===================================================================================
    //                                                                            Accessor
    //                                                                            ========
    public void setFixedDBMeta(DBMeta fixedDBMeta) {
        this._fixedDBMeta = fixedDBMeta;
    }

    public void setCreatableByDBMeta(boolean creatableByDBMeta) {
        this._creatableByDBMeta = creatableByDBMeta;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy