jdao.JDAO.java Source code

Java tutorial

Introduction

Here is the source code for jdao.JDAO.java

Source

/*
 * Copyright (c) 2017. terefang@gmail.com
 *
 * 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 jdao;

import org.apache.commons.beanutils.BeanUtils;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;

import org.apache.commons.dbutils.*;
import org.apache.commons.dbutils.handlers.*;

import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.naming.*;

import javax.sql.DataSource;
import java.beans.PropertyDescriptor;
import java.io.*;
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.*;

public class JDAO implements Closeable {
    public static Log LOG = LogFactory.getLog(JDAO.class);

    public void checkReadOnly() throws Exception {
        if (this.isReadOnly()) {
            throw new IllegalAccessException("JDAO is read-only.");
        }
    }

    public boolean isReadOnly() {
        return readOnly;
    }

    public void setReadOnly(boolean readOnly) {
        this.readOnly = readOnly;
    }

    private boolean readOnly = false;

    public static Map<String, Object> toMap(Object... values) {
        Map<String, Object> ret = new HashMap();
        for (int i = 0; i < values.length; i += 2) {
            ret.put(String.valueOf(values[i]), values[i + 1]);
        }
        return ret;
    }

    public static Map<String, String> toMapString(String... values) {
        Map<String, String> ret = new HashMap();
        for (int i = 0; i < values.length; i += 2) {
            ret.put(values[i], values[i + 1]);
        }
        return ret;
    }

    public static Collection toCollection(Object... values) {
        ArrayList ret = new ArrayList(values.length);
        for (int i = 0; i < values.length; i++) {
            ret.add(values[i]);
        }
        return ret;
    }

    public static final String PROP_DATASOURCE_CONFIG_SUFFIX = ".ds.properties";

    public static final String DEFAULT_JNDI_PATH = "java:ds";

    public static Map<String, DataSource> registerJndiDsFromDirectories(List<String> configDirs, String filesuffix,
            Context env) {
        if (filesuffix == null) {
            filesuffix = PROP_DATASOURCE_CONFIG_SUFFIX;
        }

        Map<String, DataSource> dsources = JDAO.scanRecursiveDatasourceDirectories(configDirs, filesuffix);

        registerJNDI(env, dsources);

        return dsources;
    }

    public static void registerJNDI(Context env, Map<String, DataSource> dsources) {
        for (Map.Entry<String, DataSource> entry : dsources.entrySet()) {
            String dsName = entry.getKey();
            try {
                DataSource dataSource = entry.getValue();

                bind(env, dsName, dataSource);

                log("registered ds='" + dsName + "'");
            } catch (Exception xe) {
                log("Error processing datasource: " + dsName, xe);
            }
        }
    }

    public static void unregisterJNDI(Context env, Map<String, DataSource> dsources) {
        for (Map.Entry<String, DataSource> entry : dsources.entrySet()) {
            String dsName = entry.getKey();
            try {

                unbind(env, dsName);

                log("unregistered ds='" + dsName + "'");
            } catch (Exception xe) {
                log("Error unregister datasource: " + dsName, xe);
            }
        }
    }

    public static Context retrieveContext(String jndi_path) {
        InitialContext jndiContext = null;
        Context env = null;
        try {
            log("INFO: resolving " + jndi_path);

            env = jndiContext = new InitialContext();
            env = (Context) jndiContext.lookup(jndi_path);
        } catch (Exception xe) {
            try {
                Name jname = jndiContext.getNameParser(jndi_path).parse(jndi_path);
                Enumeration<String> en = jname.getAll();
                while (en.hasMoreElements()) {
                    String name = en.nextElement();
                    Context tmp = null;
                    try {
                        tmp = (Context) env.lookup(name);
                        env = (Context) env.lookup(name);
                    } catch (NameNotFoundException nnf) {
                        log("INFO: creating " + name);
                        env = env.createSubcontext(name);
                    }
                }
            } catch (Exception xe2) {
                log("ERROR: resolving " + jndi_path, xe2);
            }
        }
        return env;
    }

    public static Context unbind(Context ctx, String nameStr) throws NamingException {
        log("unbinding " + nameStr);

        Name name = ctx.getNameParser("").parse(nameStr);

        //no name, nothing to do
        if (name.size() == 0)
            return null;

        Context subCtx = ctx;

        for (int i = 0; i < name.size() - 1; i++) {
            try {
                subCtx = (Context) subCtx.lookup(name.get(i));
            } catch (NameNotFoundException e) {
                log("Subcontext " + name.get(i) + " undefined", e);
                return null;
            }
        }

        subCtx.unbind(name.get(name.size() - 1));
        log("unbound object " + nameStr);
        return subCtx;
    }

    public static Context bind(Context ctx, String nameStr, Object obj) throws NamingException {
        log("binding " + nameStr);

        Name name = ctx.getNameParser("").parse(nameStr);

        //no name, nothing to do
        if (name.size() == 0)
            return null;

        Context subCtx = ctx;

        //last component of the name will be the name to bind
        for (int i = 0; i < name.size() - 1; i++) {
            try {
                subCtx = (Context) subCtx.lookup(name.get(i));
                log("Subcontext " + name.get(i) + " already exists");
            } catch (NameNotFoundException e) {
                subCtx = subCtx.createSubcontext(name.get(i));
                log("Subcontext " + name.get(i) + " created");
            }
        }

        subCtx.rebind(name.get(name.size() - 1), obj);
        log("Bound object to " + name.get(name.size() - 1));
        return subCtx;
    }

    public static Map<String, DataSource> scanRecursiveDatasourceDirectories(List<String> configDirs,
            String suffix) {
        Map<String, DataSource> ret = new HashMap();
        try {
            for (String scanDirs : configDirs) {
                for (String scanDir : scanDirs.split(";")) {
                    log("INFO: scanning: " + scanDir);
                    try {
                        processDatasourceDirectory(scanDir.trim(), suffix, ret);
                    } catch (Exception xe) {
                        log("ERROR: scanning: " + scanDir, xe);
                    }
                }
            }
        } catch (Exception xe) {
            log("ERROR: scanning ds dirs", xe);
        }
        return ret;
    }

    public static void processDatasourceDirectory(String scanDir, final String suffix,
            Map<String, DataSource> reg) {
        try {
            File workDir = new File(scanDir);

            if (workDir.isDirectory()) {
                File[] files = workDir.listFiles(new FileFilter() {
                    @Override
                    public boolean accept(File pathname) {
                        return pathname.isFile() && pathname.getName().endsWith(suffix);
                    }
                });

                for (File file : files) {
                    String dsName = file.getName();
                    dsName = dsName.substring(0, dsName.length() - suffix.length());
                    DataSource ds = createDatasourceFromFile(file);
                    reg.put(dsName, ds);
                }
            }
        } catch (Exception xe) {
            log("Error processing directory: " + scanDir, xe);
        }
    }

    public static DataSource createDatasourceFromFile(File file) {
        try {
            Properties properties = new Properties();
            FileReader fh = new FileReader(file);
            properties.load(fh);
            fh.close();

            DataSource dataSource = null;
            dataSource = createDataSourceByProperties(file, dataSource, properties);

            return dataSource;
        } catch (Exception xe) {
            log("Error processing datasource: " + file, xe);
        }
        return null;
    }

    public static DataSource createDataSourceByProperties(File file, DataSource dataSource, Properties properties) {
        return createDataSourceByProperties(file.toString(), dataSource, properties);
    }

    public static void adjustPropertiesForEnvParameters(Properties properties) {
        for (String key : properties.stringPropertyNames()) {
            String property = properties.getProperty(key);
            int ofs = 0;
            while ((ofs = property.indexOf("$(", ofs)) > 0) {
                int efs = property.indexOf(')', ofs);
                String lookupKey = property.substring(ofs + 2, efs);
                String lookupProp = System.getProperty(lookupKey, "$(" + lookupKey + ")");
                property = property.substring(0, ofs) + lookupProp + property.substring(efs + 1);
                ofs++;
                properties.setProperty(key, property);
            }
        }
    }

    public static DataSource createDataSourceByProperties(String file, DataSource dataSource,
            Properties properties) {
        try {
            adjustPropertiesForEnvParameters(properties);
            if (dataSource == null) {
                if (properties.containsKey("jdaoDriverClassName")) {
                    Class.forName(properties.getProperty("jdaoDriverClassName"));
                }
                if (properties.containsKey("jdaoDataSourceClassName")) {
                    dataSource = (DataSource) Thread.currentThread().getContextClassLoader()
                            .loadClass(properties.getProperty("jdaoDataSourceClassName")).newInstance();
                } else {
                    return (BasicDataSource) BasicDataSourceFactory.createDataSource(properties);
                }
            }
            BeanUtils.populate(dataSource, (Map) properties);
        } catch (Exception xe) {
            log("Error processing datasource: " + file, xe);
            return null;
        }
        return dataSource;
    }

    public static DataSource createDataSourceByProperties(Class clazz, Properties properties) {
        try {
            adjustPropertiesForEnvParameters(properties);
            DataSource dataSource = null;
            dataSource = (DataSource) clazz.newInstance();
            BeanUtils.populate(dataSource, (Map) properties);
            return dataSource;
        } catch (Exception xe) {
            log("Error processing datasource class: " + clazz.getCanonicalName(), xe);
            return null;
        }
    }

    public static DataSource createDataSourceByProperties(String clazz, Properties properties) {
        try {
            return JDAO.createDataSourceByProperties(
                    Class.forName(clazz, true, Thread.currentThread().getContextClassLoader()), properties);
        } catch (Exception xe) {
            log("Error processing datasource class: " + clazz, xe);
            return null;
        }
    }

    public static Connection createConnectionByDriverSpec(String driverclazz, String jdbcUri, String userName,
            String password) {
        try {
            if (driverclazz != null && !driverclazz.equalsIgnoreCase("")) {
                Class.forName(driverclazz, true, Thread.currentThread().getContextClassLoader());
            }
            return DriverManager.getConnection(jdbcUri, userName, password);
        } catch (Exception xe) {
            log("Error : ", xe);
            return null;
        }
    }

    public static Connection createConnectionByDataSourceSpec(String dsclazz, String jdbcUri, String userName,
            String password) {
        try {
            if (dsclazz != null && !dsclazz.equalsIgnoreCase("")) {
                DataSource ds = (DataSource) Class
                        .forName(dsclazz, true, Thread.currentThread().getContextClassLoader()).newInstance();
                BeanUtils.setProperty(ds, "url", jdbcUri);
                return ds.getConnection(userName, password);
            }
            return DriverManager.getConnection(jdbcUri, userName, password);
        } catch (Exception xe) {
            log("Error : ", xe);
            return null;
        }
    }

    public static void log(String text) {
        LOG.info(text);
    }

    public static void log(String text, Throwable thx) {
        LOG.info(text, thx);
    }

    static GenerousBeanProcessor generousBeanProcessor = new GenerousBeanProcessor();
    static BasicRowProcessor generousRowProcessor = new BasicRowProcessor(generousBeanProcessor);
    static BasicXRowProcessor basicxRowProcessor = new BasicXRowProcessor();
    static MapListHandler mapListHandler = new MapListHandler(basicxRowProcessor);
    static MapHandler mapHandler = new MapHandler(basicxRowProcessor);
    static ScalarHandler<Object> scalarHandler = new ScalarHandler<Object>();
    static ArrayListHandler arrayListHandler = new ArrayListHandler();
    static ColumnListHandler<Object> columnListHandler = new ColumnListHandler<Object>();
    static KvMapHandler kvMapHandler = new KvMapHandler();
    static KvListMapHandler kvListMapHandler = new KvListMapHandler();

    public static JDAO createDaoFromDataSource(DataSource ds, boolean pmt) throws Exception {
        return new JDAO(new QueryRunner(ds, pmt));
    }

    public static JDAO createDaoFromConnection(Connection conn, boolean pmt) throws Exception {
        return new JDAO(conn, new QueryRunner(pmt));
    }

    public static JDAO createDaoFromJndi(String jndiUri, boolean pmt) throws Exception {
        return new JDAO(JDAO.lookupDataSourceFromJndi(jndiUri).getConnection(), new QueryRunner(pmt));
    }

    /**
     * Looks for a DataSource in Jndi
     *
     * @param  name of the datasource
     * @return datasource or null
     */
    public static DataSource lookupDataSourceFromJndi(String name) throws Exception {
        InitialContext ctx = new InitialContext();
        try {
            return lookupDataSourceFromJndi(ctx, name);
        } finally {
            ctx.close();
        }
    }

    public static DataSource lookupDataSourceFromJndi(Context ctx, String name) throws Exception {
        return (DataSource) ctx.lookup(name);
    }

    public static Connection lookupConnectionFromJndi(Context ctx, String name) throws Exception {
        return lookupDataSourceFromJndi(ctx, name).getConnection();
    }

    /**
     * queries connection according to give dbType and returns data as given by resultsethandler
     *
     * @param  dbType, type of database
     * @param  rsHandler, resultsethandler
     * @param  conn, database connection
     * @param  ds, query runner
     * @param  sql, sql query
     * @param  args, sql parameters
     * @return object of type T or null
     */

    public static <T> T queryForT(int dbType, ResultSetHandler<T> rsHandler, Connection conn, QueryRunner ds,
            String sql, Object... args) throws Exception {
        if (args == null) {
            if (conn == null) {
                return ds.query(sql, rsHandler);
            } else {
                return ds.query(conn, sql, rsHandler);
            }
        } else if (args.length == 1 && args[0] instanceof Map) {
            List nArgs = new Vector();
            sql = preparseParameters(dbType, sql, nArgs, (Map) args[0]);
            if (conn == null) {
                return ds.query(sql, rsHandler, nArgs.toArray());
            } else {
                return ds.query(conn, sql, rsHandler, nArgs.toArray());
            }
        } else if (args.length > 0 && args[0] instanceof Collection) {
            if (conn == null) {
                return ds.query(sql, rsHandler, ((Collection) args[0]).toArray());
            } else {
                return ds.query(conn, sql, rsHandler, ((Collection) args[0]).toArray());
            }
        } else {
            if (conn == null) {
                return ds.query(sql, rsHandler, args);
            } else {
                return ds.query(conn, sql, rsHandler, args);
            }
        }
    }

    public static String join(Collection c, final char separator) {
        if (c == null) {
            return null;
        }

        final StringBuilder buf = new StringBuilder();
        Object[] array = c.toArray();
        int end = array.length;
        for (int i = 0; i < end; i++) {
            if (i > 0) {
                buf.append(separator);
            }
            if (array[i] != null) {
                buf.append(array[i]);
            }
        }
        return buf.toString();
    }

    public static <T> T queryTemplateForT(int dbType, ResultSetHandler<T> rsHandler, Connection conn,
            QueryRunner ds, String table, Collection cols, Map<String, Object> vm, String suffixQuery,
            int templateType, int constraintType) throws Exception {
        List param = new Vector();
        String colString = ((cols == null) ? "*" : JDAO.join(cols, ','));
        return JDAO.queryForT(dbType, rsHandler, conn, ds,
                "SELECT " + colString + " FROM " + table + " WHERE "
                        + JDAO.buildWhere(dbType, templateType, constraintType, param, vm)
                        + (suffixQuery == null ? "" : " " + suffixQuery),
                param);
    }

    public static <T> T queryTemplateForT(int dbType, ResultSetHandler<T> rsHandler, Connection conn,
            QueryRunner ds, String table, Collection cols, Map<String, Object> vm, String suffixQuery)
            throws Exception {
        return JDAO.queryTemplateForT(dbType, rsHandler, conn, ds, table, cols, vm, suffixQuery, TEMPLATE_TYPE_AUTO,
                CONSTRAINT_ALL_OF);
    }

    public static <T> T queryTemplateForT(int dbType, ResultSetHandler<T> rsHandler, Connection conn,
            QueryRunner ds, String table, Collection cols, Map<String, Object> vm) throws Exception {
        return JDAO.queryTemplateForT(dbType, rsHandler, conn, ds, table, cols, vm, null, TEMPLATE_TYPE_AUTO,
                CONSTRAINT_ALL_OF);
    }

    public static <T> T queryTemplateForT(int dbType, ResultSetHandler<T> rsHandler, Connection conn,
            QueryRunner ds, String table, Map<String, Object> vm) throws Exception {
        return JDAO.queryTemplateForT(dbType, rsHandler, conn, ds, table, null, vm, null, TEMPLATE_TYPE_AUTO,
                CONSTRAINT_ALL_OF);
    }

    /**
     * executes a query and returns a list of rows (Map)
     * <p>
     * if the only (first) argument is a map, the sql string is expected to have "?{field-name}" named parameters
     * <p>
     * if the only (first) argument is a list (collection), it is take as the list of arguments.
     *
     */
    public static List<Map<String, Object>> queryForList(int dbType, Connection conn, QueryRunner ds, String sql,
            Object... args) throws Exception {
        return queryForMapList(dbType, conn, ds, sql, args);
    }

    public static List<Map<String, Object>> queryForList(int dbType, Connection conn, QueryRunner ds, String sql)
            throws Exception {
        return queryForMapList(dbType, conn, ds, sql);
    }

    public static List<Map<String, Object>> queryForMapList(int dbType, Connection conn, QueryRunner ds, String sql,
            Object... args) throws Exception {
        return queryForT(dbType, mapListHandler, conn, ds, sql, args);
    }

    public static List<Map<String, Object>> queryForMapList(int dbType, Connection conn, QueryRunner ds, String sql)
            throws Exception {
        return queryForT(dbType, mapListHandler, conn, ds, sql);
    }

    public static List<Map<String, Object>> queryTemplateForMapList(int dbType, Connection conn, QueryRunner ds,
            String table, Collection cols, Map vm, String suffixQuery, int templateType, int constraintType)
            throws Exception {
        return queryTemplateForT(dbType, mapListHandler, conn, ds, table, cols, vm, suffixQuery, templateType,
                constraintType);
    }

    public static List<Map<String, Object>> queryTemplateForMapList(int dbType, Connection conn, QueryRunner ds,
            String table, Collection cols, Map vm, String suffixQuery) throws Exception {
        return queryTemplateForT(dbType, mapListHandler, conn, ds, table, cols, vm, suffixQuery, TEMPLATE_TYPE_AUTO,
                CONSTRAINT_ALL_OF);
    }

    public static List<Map<String, Object>> queryTemplateForMapList(int dbType, Connection conn, QueryRunner ds,
            String table, Collection cols, Map vm) throws Exception {
        return queryTemplateForT(dbType, mapListHandler, conn, ds, table, cols, vm);
    }

    public static List<Map<String, Object>> queryTemplateForMapList(int dbType, Connection conn, QueryRunner ds,
            String table, Map vm) throws Exception {
        return queryTemplateForT(dbType, mapListHandler, conn, ds, table, vm);
    }

    /**
     * executes a query and returns a list of arrays (Object[])
     * <p>
     * if the only (first) argument is a map, the sql string is expected to have "?{field-name}" named parameters
     * <p>
     * if the only (first) argument is a list (collection), it is take as the list of arguments.
     *
     */
    public static List<Object[]> queryForArrayList(int dbType, Connection conn, QueryRunner ds, String sql,
            Object... args) throws Exception {
        return queryForT(dbType, arrayListHandler, conn, ds, sql, args);
    }

    public static List<Object[]> queryForArrayList(int dbType, Connection conn, QueryRunner ds, String sql)
            throws Exception {
        return queryForT(dbType, arrayListHandler, conn, ds, sql);
    }

    public static List<Object[]> queryTemplateForArrayList(int dbType, Connection conn, QueryRunner ds,
            String table, Collection cols, Map vm, String suffixQuery, int templateType, int constraintType)
            throws Exception {
        return queryTemplateForT(dbType, arrayListHandler, conn, ds, table, cols, vm, suffixQuery, templateType,
                constraintType);
    }

    public static List<Object[]> queryTemplateForArrayList(int dbType, Connection conn, QueryRunner ds,
            String table, Collection cols, Map vm, String suffixQuery) throws Exception {
        return queryTemplateForT(dbType, arrayListHandler, conn, ds, table, cols, vm, suffixQuery,
                TEMPLATE_TYPE_AUTO, CONSTRAINT_ALL_OF);
    }

    public static List<Object[]> queryTemplateForArrayList(int dbType, Connection conn, QueryRunner ds,
            String table, Collection cols, Map vm) throws Exception {
        return queryTemplateForT(dbType, arrayListHandler, conn, ds, table, cols, vm);
    }

    public static List<Object[]> queryTemplateForArrayList(int dbType, Connection conn, QueryRunner ds,
            String table, Map vm) throws Exception {
        return queryTemplateForT(dbType, arrayListHandler, conn, ds, table, vm);
    }

    /**
     * executes a query and returns a list of scalars (Object)
     * <p>
     * if the only (first) argument is a map, the sql string is expected to have "?{field-name}" named parameters
     * <p>
     * if the only (first) argument is a list (collection), it is take as the list of arguments.
     *
     */
    public static List<Object> queryForColumnList(int dbType, Connection conn, QueryRunner ds, String sql,
            Object... args) throws Exception {
        return queryForT(dbType, columnListHandler, conn, ds, sql, args);
    }

    public static List<Object> queryForColumnList(int dbType, Connection conn, QueryRunner ds, String sql)
            throws Exception {
        return queryForT(dbType, columnListHandler, conn, ds, sql);
    }

    public static List<Object> queryTemplateForColumnList(int dbType, Connection conn, QueryRunner ds, String table,
            String col, Map vm, String suffixQuery, int templateType, int constraintType) throws Exception {
        return queryTemplateForT(dbType, columnListHandler, conn, ds, table, Collections.singletonList(col), vm,
                suffixQuery, templateType, constraintType);
    }

    public static List<Object> queryTemplateForColumnList(int dbType, Connection conn, QueryRunner ds, String table,
            String col, Map vm, String suffixQuery) throws Exception {
        return queryTemplateForT(dbType, columnListHandler, conn, ds, table, Collections.singletonList(col), vm,
                suffixQuery);
    }

    public static List<Object> queryTemplateForColumnList(int dbType, Connection conn, QueryRunner ds, String table,
            String col, Map vm) throws Exception {
        return queryTemplateForT(dbType, columnListHandler, conn, ds, table, Collections.singletonList(col), vm);
    }

    /**
     * executes a query and returns exactly one row (Map)
     * <p>
     * if the only (first) argument is a map, the sql string is expected to have "?{field-name}" named parameters
     * <p>
     * if the only (first) argument is a list (collection), it is take as the list of arguments.
     *
     */
    public static Map<String, Object> queryForMap(int dbType, Connection conn, QueryRunner ds, String sql,
            Object... args) throws Exception {
        return queryForT(dbType, mapHandler, conn, ds, sql, args);
    }

    public static Map<String, Object> queryForMap(int dbType, Connection conn, QueryRunner ds, String sql)
            throws Exception {
        return queryForT(dbType, mapHandler, conn, ds, sql);
    }

    public static Map<String, Object> queryTemplateForMap(int dbType, Connection conn, QueryRunner ds, String table,
            Collection cols, Map vm, String suffixQuery, int templateType, int constraintType) throws Exception {
        return queryTemplateForT(dbType, mapHandler, conn, ds, table, cols, vm, suffixQuery, templateType,
                constraintType);
    }

    public static Map<String, Object> queryTemplateForMap(int dbType, Connection conn, QueryRunner ds, String table,
            Collection cols, Map vm, String suffixQuery) throws Exception {
        return queryTemplateForT(dbType, mapHandler, conn, ds, table, cols, vm, suffixQuery);
    }

    public static Map<String, Object> queryTemplateForMap(int dbType, Connection conn, QueryRunner ds, String table,
            Collection cols, Map vm) throws Exception {
        return queryTemplateForT(dbType, mapHandler, conn, ds, table, cols, vm);
    }

    public static Map<String, Object> queryTemplateForMap(int dbType, Connection conn, QueryRunner ds, String table,
            Map vm) throws Exception {
        return queryTemplateForT(dbType, mapHandler, conn, ds, table, vm);
    }

    public static Map<String, String> queryForKvMap(int dbType, Connection conn, QueryRunner ds, String sql,
            Object... args) throws Exception {
        return queryForT(dbType, kvMapHandler, conn, ds, sql, args);
    }

    public static Map<String, String> queryForKvMap(int dbType, Connection conn, QueryRunner ds, String sql)
            throws Exception {
        return queryForT(dbType, kvMapHandler, conn, ds, sql);
    }

    public static Map<String, List<String>> queryForKvListMap(int dbType, Connection conn, QueryRunner ds,
            String sql, Object... args) throws Exception {
        return queryForT(dbType, kvListMapHandler, conn, ds, sql, args);
    }

    public static Map<String, List<String>> queryForKvListMap(int dbType, Connection conn, QueryRunner ds,
            String sql) throws Exception {
        return queryForT(dbType, kvListMapHandler, conn, ds, sql);
    }

    /**
     * executes a query and returns exactly one Object
     * <p>
     * if the only (first) argument is a map, the sql string is expected to have "?{field-name}" named parameters
     * <p>
     * if the only (first) argument is a list (collection), it is take as the list of arguments.
     *
     */
    public static Object queryForScalar(int dbType, Connection conn, QueryRunner ds, String sql, Object... args)
            throws Exception {
        return queryForT(dbType, scalarHandler, conn, ds, sql, args);
    }

    public static Object queryForScalar(int dbType, Connection conn, QueryRunner ds, String sql) throws Exception {
        return queryForT(dbType, scalarHandler, conn, ds, sql);
    }

    public static Object queryTemplateForScalar(int dbType, Connection conn, QueryRunner ds, String table,
            String col, Map vm, String suffixQuery, int templateType, int constraintType) throws Exception {
        return queryTemplateForT(dbType, scalarHandler, conn, ds, table, Collections.singletonList(col), vm,
                suffixQuery, templateType, constraintType);
    }

    public static Object queryTemplateForScalar(int dbType, Connection conn, QueryRunner ds, String table,
            String col, Map vm, String suffixQuery) throws Exception {
        return queryTemplateForT(dbType, scalarHandler, conn, ds, table, Collections.singletonList(col), vm,
                suffixQuery);
    }

    public static Object queryTemplateForScalar(int dbType, Connection conn, QueryRunner ds, String table,
            String col, Map vm) throws Exception {
        return queryTemplateForT(dbType, scalarHandler, conn, ds, table, Collections.singletonList(col), vm);
    }

    /**
     * executes a statment and returns the number of rows effected.
     * <p>
     * if the only (first) argument is a map, the sql string is expected to have "?{field-name}" named parameters
     * <p>
     * if the only (first) argument is a list (collection), it is take as the list of arguments.
     *
     */
    public static int update(int dbType, Connection conn, QueryRunner ds, String sql, Object... args)
            throws Exception {
        if (args == null) {
            if (conn == null) {
                return ds.update(sql);
            } else {
                return ds.update(conn, sql);
            }
        } else if (args.length > 0 && args[0] instanceof Map) {
            List nArgs = new Vector();
            sql = preparseParameters(dbType, sql, nArgs, (Map) args[0]);
            if (conn == null) {
                return ds.update(sql, nArgs.toArray());
            } else {
                return ds.update(conn, sql, nArgs.toArray());
            }
        } else if (args.length > 0 && args[0] instanceof Collection) {
            if (conn == null) {
                return ds.update(sql, ((Collection) args[0]).toArray());
            } else {
                return ds.update(conn, sql, ((Collection) args[0]).toArray());
            }
        } else {
            if (conn == null) {
                return ds.update(sql, args);
            } else {
                return ds.update(conn, sql, args);
            }
        }
    }

    /**
     * executes a statment and returns the number of rows effected.
     * <p>
     * if the only (first) argument is a map, the sql string is expected to have "?{field-name}" named parameters
     * <p>
     * if the only (first) argument is a list (collection), it is take as the list of arguments.
     *
     */
    public static int execute(int dbType, Connection conn, QueryRunner ds, String sql, Object... args)
            throws Exception {
        return update(dbType, conn, ds, sql, args);
    }

    public static int execute(int dbType, Connection conn, QueryRunner ds, String sql) throws Exception {
        return update(dbType, conn, ds, sql);
    }

    /**
     * executes an insert (mysql-insert-set) with optional on-duplicate-key-update and returns the number of rows effected.
     *
     * @return nrows
     */
    public static int insertSet(int dbType, Connection conn, QueryRunner ds, String table, Map cols,
            boolean onDuplicateKeyUpdate) throws Exception {
        return insertSet(dbType, conn, ds, table, cols, onDuplicateKeyUpdate, cols.keySet());
    }

    public static int insertSet(int dbType, Connection conn, QueryRunner ds, String table, Map cols,
            boolean onDuplicateKeyUpdate, Collection updateFields) throws Exception {
        if (dbType != JDAO.DB_TYPE_MYSQL) {
            throw new IllegalArgumentException("DB TYPE NOT MYSQL");
        }
        Vector vv = new Vector();
        String setqq = buildSet(dbType, vv, cols);
        StringBuilder qq = new StringBuilder();
        qq.append("INSERT INTO " + table + " SET ");
        qq.append(setqq);
        if (onDuplicateKeyUpdate) {
            Map um = new HashMap();
            for (Object o : updateFields) {
                um.put(o, cols.get(o));
            }
            String setuqq = buildSet(dbType, vv, um);
            qq.append(" ON DUPLICATE KEY UPDATE ");
            qq.append(setuqq);
        }

        if (conn == null) {
            return ds.update(qq.toString(), vv.toArray());
        }
        return ds.update(conn, qq.toString(), vv.toArray());
    }

    /**
     * executes an insert and returns the number of rows effected.
     *
     */
    public static int insert(int dbType, Connection conn, QueryRunner ds, String table, Map cols) throws Exception {
        return insert(dbType, conn, ds, table, cols, false);
    }

    public static int insert(int dbType, Connection conn, QueryRunner ds, String table, Map cols,
            boolean onDuplicateKeyUpdate) throws Exception {
        return insert(dbType, conn, ds, table, cols, onDuplicateKeyUpdate, cols.keySet());
    }

    public static int insert(int dbType, Connection conn, QueryRunner ds, String table, Map cols,
            boolean onDuplicateKeyUpdate, Collection updateFields) throws Exception {
        if (onDuplicateKeyUpdate && (dbType != JDAO.DB_TYPE_MYSQL) && (dbType != JDAO.DB_TYPE_CRATE)) {
            throw new IllegalArgumentException("DB TYPE NOT MYSQL");
        }
        Vector parm = new Vector();
        StringBuilder qq = new StringBuilder();
        qq.append("INSERT INTO " + table + " ( ");
        boolean op = true;
        for (Object kv : cols.entrySet()) {
            parm.add(((Map.Entry) kv).getValue());
            if (!op) {
                qq.append(",");
            }
            qq.append(((Map.Entry) kv).getKey());
            op = false;
        }
        qq.append(" ) VALUES (");
        op = true;
        for (Object v : parm) {
            if (!op) {
                qq.append(",");
            }
            qq.append("?");
            op = false;
        }
        qq.append(" ) ");

        if (onDuplicateKeyUpdate) {
            Map um = new HashMap();
            for (Object o : updateFields) {
                um.put(o, cols.get(o));
            }
            String setuqq = buildSet(dbType, parm, um);
            qq.append(" ON DUPLICATE KEY UPDATE ");
            qq.append(setuqq);
        }

        if (conn == null) {
            return ds.update(qq.toString(), parm.toArray());
        }
        return ds.update(conn, qq.toString(), parm.toArray());
    }

    public void insertList(String table, List<Map> colList) throws Exception {
        this.checkReadOnly();
        this.insertList(table, colList, null);
    }

    public void insertList(String table, List<Map> colList, String pkField) throws Exception {
        this.checkReadOnly();
        if (pkField == null) {
            JDAO.insertList(this.dbType, this.conn, this.queryRunner, table, colList, false, null);
        } else {
            for (Map row : colList) {
                Collection updateFields = row.keySet();
                updateFields.remove(pkField);
                JDAO.insert(this.dbType, this.conn, this.queryRunner, table, row, true, updateFields);
            }
        }
    }

    public static void insertList(int dbType, Connection conn, QueryRunner ds, String table, List<Map> colList,
            boolean onDuplicateKeyUpdate, Collection updateFields) throws Exception {
        for (Map row : colList) {
            JDAO.insert(dbType, conn, ds, table, row, onDuplicateKeyUpdate, updateFields);
        }
    }

    public <T> List<T> insertListWithPK(String table, List<Map> colList, Class<T> clazz) throws Exception {
        this.checkReadOnly();
        return this.insertListWithPK(table, colList, null, clazz);
    }

    public <T> List<T> insertListWithPK(String table, List<Map> colList, String pkField, Class<T> clazz)
            throws Exception {
        this.checkReadOnly();
        if (pkField == null) {
            return this.insertListWithPK(table, colList, false, null, clazz);
        } else {
            ArrayList<T> res = new ArrayList<T>();
            for (Map row : colList) {
                Collection updateFields = row.keySet();
                updateFields.remove(pkField);
                res.add(JDAO.insertWithPK(this.dbType, this.conn, this.queryRunner, table, row, true, updateFields,
                        clazz));
            }
            return res;
        }
    }

    public <T> List<T> insertListWithPK(String table, List<Map> colList, boolean onDuplicateKeyUpdate,
            Collection updateFields, Class<T> clazz) throws Exception {
        this.checkReadOnly();
        return JDAO.insertListWithPK(this.dbType, this.conn, this.queryRunner, table, colList, onDuplicateKeyUpdate,
                updateFields, clazz);
    }

    public static <T> List<T> insertListWithPK(int dbType, Connection conn, QueryRunner ds, String table,
            List<Map> colList, boolean onDuplicateKeyUpdate, Collection updateFields, Class<T> clazz)
            throws Exception {
        ArrayList<T> res = new ArrayList<T>();
        for (Map row : colList) {
            res.add(JDAO.insertWithPK(dbType, conn, ds, table, row, onDuplicateKeyUpdate, updateFields, clazz));
        }
        return res;
    }

    public static <T> T insertWithPK(int dbType, Connection conn, QueryRunner ds, String table, Map cols,
            Class<T> clazz) throws Exception {
        return insertWithPK(dbType, conn, ds, table, cols, false, clazz);
    }

    public static <T> T insertWithPK(int dbType, Connection conn, QueryRunner ds, String table, Map cols,
            boolean onDuplicateKeyUpdate, Class<T> clazz) throws Exception {
        return insertWithPK(dbType, conn, ds, table, cols, onDuplicateKeyUpdate, cols.keySet(), clazz);
    }

    public static <T> T insertWithPK(int dbType, Connection conn, QueryRunner ds, String table, Map cols,
            boolean onDuplicateKeyUpdate, Collection updateFields, Class<T> clazz) throws Exception {
        if (onDuplicateKeyUpdate && (dbType != JDAO.DB_TYPE_MYSQL) && (dbType != JDAO.DB_TYPE_CRATE)) {
            throw new IllegalArgumentException("DB TYPE NOT MYSQL");
        }
        Vector parm = new Vector();
        StringBuilder qq = new StringBuilder();
        qq.append("INSERT INTO " + table + " ( ");
        boolean op = true;
        for (Object kv : cols.entrySet()) {
            parm.add(((Map.Entry) kv).getValue());
            if (!op) {
                qq.append(",");
            }
            qq.append(((Map.Entry) kv).getKey());
            op = false;
        }
        qq.append(" ) VALUES (");
        op = true;
        for (Object v : parm) {
            if (!op) {
                qq.append(",");
            }
            qq.append("?");
            op = false;
        }
        qq.append(" ) ");

        if (onDuplicateKeyUpdate) {
            Map um = new HashMap();
            for (Object o : updateFields) {
                um.put(o, cols.get(o));
            }
            String setuqq = buildSet(dbType, parm, um);
            qq.append(" ON DUPLICATE KEY UPDATE ");
            qq.append(setuqq);
        }

        if (conn == null) {
            return ds.insert(qq.toString(), new ScalarHandler<T>(), parm.toArray());
        }
        return ds.insert(conn, qq.toString(), new ScalarHandler<T>(), parm.toArray());
    }

    public void insertKvMap(String table, String kvSF, String kvKF, String kvVF, String scopeId,
            Map<String, String> kvMap, boolean onDuplicateKeyUpdate) throws Exception {
        JDAO.insertKvMap(this.dbType, this.conn, this.queryRunner, table, kvSF, kvKF, kvVF, scopeId, kvMap,
                onDuplicateKeyUpdate);
    }

    public static void insertKvMap(int dbType, Connection conn, QueryRunner ds, String table, String kvSF,
            String kvKF, String kvVF, String scopeId, Map<String, String> kvMap, boolean onDuplicateKeyUpdate)
            throws Exception {
        for (Map.Entry<String, String> entry : kvMap.entrySet()) {
            JDAO.insert(dbType, conn, ds, table,
                    JDAO.toMap(kvSF, scopeId, kvKF, entry.getKey(), kvVF, entry.getValue()), onDuplicateKeyUpdate,
                    JDAO.toCollection(kvKF, kvVF));
        }
    }

    public <T> void queryForCallback(ResultRowCallbackHandler<T> rowHandler, String sql, Object... args)
            throws Exception {
        JDAO.queryWithCallbackT(this.dbType, rowHandler, this.conn, this.queryRunner, sql, args);
    }

    public <T> List<T> queryWithCallback(ResultRowCallbackHandler<T> rowHandler, String sql, Object... args)
            throws Exception {
        return JDAO.queryWithCallbackT(this.dbType, rowHandler, this.conn, this.queryRunner, sql, args);
    }

    public static <T> List<T> queryWithCallbackT(int dbType, ResultRowCallbackHandler<T> rowHandler,
            Connection conn, QueryRunner ds, String sql, Object... args) throws Exception {
        ResultCallbackHandler<T> rcbh = new ResultCallbackHandler<T>(rowHandler);
        return JDAO.queryForT(dbType, rcbh, conn, ds, sql, args);
    }

    public <T> void queryTemplateForCallback(ResultRowCallbackHandler<T> rowHandler, String table, Collection cols,
            Map<String, Object> vm, String suffixQuery, int templateType, int constraintType) throws Exception {
        JDAO.queryTemplateWithCallbackT(this.dbType, rowHandler, this.conn, this.queryRunner, table, cols, vm,
                suffixQuery, templateType, constraintType);
    }

    public <T> void queryTemplateForCallback(ResultRowCallbackHandler<T> rowHandler, String table, Collection cols,
            Map<String, Object> vm, String suffixQuery, int templateType) throws Exception {
        JDAO.queryTemplateWithCallbackT(this.dbType, rowHandler, this.conn, this.queryRunner, table, cols, vm,
                suffixQuery, templateType);
    }

    public <T> void queryTemplateForCallback(ResultRowCallbackHandler<T> rowHandler, String table, Collection cols,
            Map<String, Object> vm, String suffixQuery) throws Exception {
        JDAO.queryTemplateWithCallbackT(this.dbType, rowHandler, this.conn, this.queryRunner, table, cols, vm,
                suffixQuery);
    }

    public <T> void queryTemplateForCallback(ResultRowCallbackHandler<T> rowHandler, String table, Collection cols,
            Map<String, Object> vm) throws Exception {
        JDAO.queryTemplateWithCallbackT(this.dbType, rowHandler, this.conn, this.queryRunner, table, cols, vm);
    }

    public <T> List<T> queryTemplateWithCallback(ResultRowCallbackHandler<T> rowHandler, String table,
            Collection cols, Map<String, Object> vm, String suffixQuery, int templateType, int constraintType)
            throws Exception {
        return JDAO.queryTemplateWithCallbackT(this.dbType, rowHandler, this.conn, this.queryRunner, table, cols,
                vm, suffixQuery, templateType, constraintType);
    }

    public <T> List<T> queryTemplateWithCallback(ResultRowCallbackHandler<T> rowHandler, String table,
            Collection cols, Map<String, Object> vm, String suffixQuery, int templateType) throws Exception {
        return JDAO.queryTemplateWithCallbackT(this.dbType, rowHandler, this.conn, this.queryRunner, table, cols,
                vm, suffixQuery, templateType);
    }

    public <T> List<T> queryTemplateWithCallback(ResultRowCallbackHandler<T> rowHandler, String table,
            Collection cols, Map<String, Object> vm, String suffixQuery) throws Exception {
        return JDAO.queryTemplateWithCallbackT(this.dbType, rowHandler, this.conn, this.queryRunner, table, cols,
                vm, suffixQuery);
    }

    public <T> List<T> queryTemplateWithCallback(ResultRowCallbackHandler<T> rowHandler, String table,
            Collection cols, Map<String, Object> vm) throws Exception {
        return JDAO.queryTemplateWithCallbackT(this.dbType, rowHandler, this.conn, this.queryRunner, table, cols,
                vm);
    }

    public static <T> List<T> queryTemplateWithCallbackT(int dbType, ResultRowCallbackHandler<T> rowHandler,
            Connection conn, QueryRunner ds, String table, Collection cols, Map<String, Object> vm,
            String suffixQuery, int templateType, int constraintType) throws Exception {
        ResultCallbackHandler<T> rcbh = new ResultCallbackHandler<T>(rowHandler);
        return JDAO.queryTemplateForT(dbType, rcbh, conn, ds, table, cols, vm, suffixQuery, templateType,
                constraintType);
    }

    public static <T> List<T> queryTemplateWithCallbackT(int dbType, ResultRowCallbackHandler<T> rowHandler,
            Connection conn, QueryRunner ds, String table, Collection cols, Map<String, Object> vm,
            String suffixQuery, int templateType) throws Exception {
        ResultCallbackHandler<T> rcbh = new ResultCallbackHandler<T>(rowHandler);
        return JDAO.queryTemplateForT(dbType, rcbh, conn, ds, table, cols, vm, suffixQuery, templateType,
                CONSTRAINT_ALL_OF);
    }

    public static <T> List<T> queryTemplateWithCallbackT(int dbType, ResultRowCallbackHandler<T> rowHandler,
            Connection conn, QueryRunner ds, String table, Collection cols, Map<String, Object> vm,
            String suffixQuery) throws Exception {
        ResultCallbackHandler<T> rcbh = new ResultCallbackHandler<T>(rowHandler);
        return JDAO.queryTemplateForT(dbType, rcbh, conn, ds, table, cols, vm, suffixQuery, TEMPLATE_TYPE_AUTO,
                CONSTRAINT_ALL_OF);
    }

    public static <T> List<T> queryTemplateWithCallbackT(int dbType, ResultRowCallbackHandler<T> rowHandler,
            Connection conn, QueryRunner ds, String table, Collection cols, Map<String, Object> vm)
            throws Exception {
        ResultCallbackHandler<T> rcbh = new ResultCallbackHandler<T>(rowHandler);
        return JDAO.queryTemplateForT(dbType, rcbh, conn, ds, table, cols, vm, null, TEMPLATE_TYPE_AUTO,
                CONSTRAINT_ALL_OF);
    }

    /**
     * create a set statement-fragment and parameter-list from a column-map.
     *
     */
    public static String buildSet(int dbType, List parm, Map vm) {
        StringBuilder qq = new StringBuilder();
        boolean op = true;
        for (Object kv : vm.entrySet()) {
            String k = ((Map.Entry) kv).getKey().toString();
            Object v = ((Map.Entry) kv).getValue();
            if (op == true) {
                qq.append(k + "=?");
                op = false;
            } else {
                qq.append(", " + k + "=?");
            }
            parm.add(v);
        }
        return (qq.toString());
    }

    public static final int TEMPLATE_TYPE_AUTO = 0;
    public static final int TEMPLATE_TYPE_EQUAL = 1;
    public static final int TEMPLATE_TYPE_NOT_EQUAL = 2;
    public static final int TEMPLATE_TYPE_SUBSTRING = 3;
    public static final int TEMPLATE_TYPE_STARTSWITH = 4;
    public static final int TEMPLATE_TYPE_LIKE = 5;
    public static final int TEMPLATE_TYPE_REGEX = 6;

    public static final int CONSTRAINT_ANY_OF = 0;
    public static final int CONSTRAINT_ALL_OF = 1;

    /**
     * create a where statement-fragment and parameter-list from a column-map and constraint-type based on LIKE.
     *
     */
    public static String buildWhereLike(int dbType, int constraintType, List param, Map<String, Object> vm) {
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        int pNum = param.size();

        if (vm.keySet().size() > 0) {
            sb.append(" ( ");
            for (String k : vm.keySet()) {
                String v = vm.get(k).toString();
                if (v != "" && v != "*" && v != "%") {
                    if (first) {
                        sb.append(" (" + likeOpPerDbType(dbType, k, "?", false) + ")");
                    } else if (constraintType == CONSTRAINT_ALL_OF) {
                        sb.append(" AND (" + likeOpPerDbType(dbType, k, "?", false) + ")");
                    } else if (constraintType == CONSTRAINT_ANY_OF) {
                        sb.append(" OR (" + likeOpPerDbType(dbType, k, "?", false) + ")");
                    } else {
                        sb.append(" OR (" + likeOpPerDbType(dbType, k, "?", false) + ")");
                    }
                    param.add(v);
                    first = false;
                }
            }
            sb.append(" ) ");
        }

        if (pNum == param.size()) {
            return " TRUE ";
        }

        return (sb.toString());
    }

    public static String likeOpPerDbType(int dbType, String arg1, String arg2, boolean invert) {
        switch (dbType) {
        case DB_TYPE_POSTGRES: {
            return arg1 + (invert ? " NOT" : "") + " ILIKE " + arg2;
        }
        case DB_TYPE_ORACLE:
        case DB_TYPE_MSSQL: {
            return "LOWER(" + arg1 + ")" + (invert ? " NOT" : "") + " LIKE " + arg2;
        }
        case DB_TYPE_ANSI:
        case DB_TYPE_MYSQL:
        case DB_TYPE_SYBASE:
        case DB_TYPE_DB2:
        case DB_TYPE_H2:
        case DB_TYPE_SQLITE:
        case DB_TYPE_CRATE:
        default: {
            return arg1 + (invert ? " NOT" : "") + " LIKE " + arg2;
        }
        }
    }

    public static String existsOpPerDbType(int dbType, String arg1, boolean invert) {
        switch (dbType) {
        case DB_TYPE_POSTGRES:
        case DB_TYPE_ORACLE:
        case DB_TYPE_MSSQL:
        case DB_TYPE_ANSI:
        case DB_TYPE_MYSQL:
        case DB_TYPE_SYBASE:
        case DB_TYPE_DB2:
        case DB_TYPE_H2:
        case DB_TYPE_SQLITE:
        case DB_TYPE_CRATE:
        default: {
            return (invert ? " NOT" : " ") + "((" + arg1 + " IS NOT NULL) AND (" + arg1 + " !='')) ";
        }
        }
    }

    public static String regexpOpPerDbType(int dbType, String arg1, String arg2, boolean invert) {
        switch (dbType) {
        case DB_TYPE_CRATE: {
            return arg1 + (invert ? " !" : " ") + "~ " + arg2;
        }
        case DB_TYPE_POSTGRES: {
            return arg1 + (invert ? " !" : " ") + "~* " + arg2;
        }
        case DB_TYPE_ORACLE: {
            return (invert ? "NOT " : "") + "REGEXP_LIKE(" + arg1 + ", " + arg2 + ", 'i')";
        }
        case DB_TYPE_MYSQL: {
            return arg1 + (invert ? " NOT" : "") + " RLIKE " + arg2;
        }
        case DB_TYPE_MSSQL:
        case DB_TYPE_SYBASE:
        case DB_TYPE_DB2:
        case DB_TYPE_H2:
        case DB_TYPE_SQLITE:
        case DB_TYPE_ANSI:
        default: {
            return arg1 + (invert ? " NOT" : "") + " REGEXP " + arg2;
        }
        }
    }

    /**
     * create a where statement-fragment and parameter-list from a column-map and constraint-type based on REGEXP.
     *
     */
    public static String buildWhereRegexp(int dbType, int constraintType, List param, Map<String, Object> vm) {
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        int pNum = param.size();

        if (vm.keySet().size() > 0) {
            sb.append(" ( ");
            for (String k : vm.keySet()) {
                String v = vm.get(k).toString();
                if (v != "" && v != "*" && v != ".*") {
                    if (first) {
                        sb.append(" (" + regexpOpPerDbType(dbType, k, "?", false) + ")");
                    } else if (constraintType == CONSTRAINT_ALL_OF) {
                        sb.append(" AND (" + regexpOpPerDbType(dbType, k, "?", false) + ")");
                    } else if (constraintType == CONSTRAINT_ANY_OF) {
                        sb.append(" OR (" + regexpOpPerDbType(dbType, k, "?", false) + ")");
                    } else {
                        sb.append(" OR (" + regexpOpPerDbType(dbType, k, "?", false) + ")");
                    }
                    param.add(v);
                    first = false;
                }
            }
            sb.append(" ) ");
        }

        if (pNum == param.size()) {
            return " TRUE ";
        }

        return (sb.toString());
    }

    /**
     * create a where statement-fragment and parameter-list from a column-map, template-type and constraint-type.
     *
     */
    public static String buildWhere(int dbType, int templateType, int constraintType, List param,
            Map<String, Object> template) {
        switch (templateType) {
        case TEMPLATE_TYPE_AUTO:
            return buildWhereAuto(dbType, param, template);
        case TEMPLATE_TYPE_EQUAL:
            return buildWhereEqual(dbType, param, template);
        case TEMPLATE_TYPE_NOT_EQUAL:
            return buildWhereNotEqual(dbType, param, template);
        case TEMPLATE_TYPE_LIKE:
            return buildWhereLike(dbType, constraintType, param, template);
        case TEMPLATE_TYPE_REGEX:
            return buildWhereRegexp(dbType, constraintType, param, template);
        case TEMPLATE_TYPE_STARTSWITH:
            return buildWherePrefix(dbType, param, template);
        case TEMPLATE_TYPE_SUBSTRING:
            return buildWhereSubstr(dbType, param, template);
        default:
            return buildWhereLike(dbType, constraintType, param, template);
        }
    }

    public static void parseSpec(int dbType, StringBuilder sb, List param, String k, String s) {
        if (s == null || s.trim().length() == 0) {
            sb.append(" TRUE ");
            return;
        }
        boolean invert = false;

        if (s.charAt(0) == '!') {
            invert = true;
            s = s.substring(1);
        }

        if (s.charAt(0) == '+' || s.charAt(0) == '-') {
            String[] list = s.split("[,;]");

            sb.append(" (" + (invert ? " TRUE" : " FALSE"));

            for (String item : list) {
                sb.append(invert ? " AND" : " OR");
                if (s.charAt(0) == '+') {
                    parseSpec_(dbType, sb, param, k, item.substring(1), invert);
                } else if (s.charAt(0) == '-') {
                    parseSpec_(dbType, sb, param, k, item.substring(1), !invert);
                } else {
                    parseSpec_(dbType, sb, param, k, item, invert);
                }
            }

            sb.append(")");
        } else {
            parseSpec_(dbType, sb, param, k, s, invert);
        }
    }

    public static void parseSpec_(int dbType, StringBuilder sb, List param, String k, String s, boolean invert) {
        if (s.trim().length() == 0) {
            return;
        }

        if (s.charAt(0) == '~') {
            s = s.substring(1);
            sb.append(" (" + regexpOpPerDbType(dbType, k, "?", invert) + ")");
        } else if (s.charAt(0) == '^') {
            s = s.substring(1);
            sb.append(" (" + likeOpPerDbType(dbType, k, "?", invert) + ")");
            param.add(s + '%');
            return;
        } else if ("*".equals(s) || "%".equals(s)) {
            sb.append(" (" + existsOpPerDbType(dbType, k, invert) + ")");
            return;
        } else if (s.indexOf('*') >= 0) {
            s = s.replace('*', '%');
            sb.append(" (" + likeOpPerDbType(dbType, k, "?", invert) + ")");
        } else if (s.indexOf('%') >= 0) {
            sb.append(" (" + likeOpPerDbType(dbType, k, "?", invert) + ")");
        } else {
            if (invert) {
                sb.append(" (" + k + "!=?)");
            } else {
                sb.append(" (" + k + "=?)");
            }
        }
        param.add(s);
    }

    public static String buildWhereAuto(int dbType, List param, Map<String, Object> vm) {
        StringBuilder sb = new StringBuilder();

        sb.append(" TRUE ");
        for (String k : vm.keySet()) {
            Object v = vm.get(k);
            if ((v instanceof String) && v.toString() != "") {
                sb.append(" AND ");
                parseSpec(dbType, sb, param, k, v.toString());
            }
        }
        return (sb.toString());
    }

    public static String buildWhereEqual(int dbType, List param, Map<String, Object> vm) {
        StringBuilder sb = new StringBuilder();

        sb.append(" TRUE ");
        for (String k : vm.keySet()) {
            Object v = vm.get(k);
            if (v != null && !((v instanceof String) && (v.toString().equals("")))) {
                sb.append(" AND (" + k + " = ?)");
                param.add(v);
            }
        }
        return (sb.toString());
    }

    public static String buildWhereNotEqual(int dbType, List param, Map<String, Object> vm) {
        StringBuilder sb = new StringBuilder();

        sb.append(" TRUE ");
        for (String k : vm.keySet()) {
            Object v = vm.get(k);
            if (v != null && !((v instanceof String) && (v.toString().equals("")))) {
                sb.append(" AND (" + k + " != ?)");
                param.add(v);
            }
        }
        return (sb.toString());
    }

    public static String buildWhereSubstr(int dbType, List param, Map<String, Object> vm) {
        StringBuilder sb = new StringBuilder();

        sb.append(" TRUE ");
        for (String k : vm.keySet()) {
            Object v = vm.get(k);
            if (v != null && !((v instanceof String) && (v.toString().equals("")))) {
                sb.append(" AND (" + likeOpPerDbType(dbType, k, "?", false) + ")");
                param.add("%" + v + "%");
            }
        }
        return (sb.toString());
    }

    public static String buildWherePrefix(int dbType, List param, Map<String, Object> vm) {
        StringBuilder sb = new StringBuilder();

        sb.append(" TRUE ");
        for (String k : vm.keySet()) {
            Object v = vm.get(k);
            if (v != null && !((v instanceof String) && (v.toString().equals("")))) {
                sb.append(" AND (" + likeOpPerDbType(dbType, k, "?", false) + ")");
                param.add(v + "%");
            }
        }
        return (sb.toString());
    }

    public static String preparseParameters(int dbType, String format, List param, Map vars) {
        String prefix = "?{";
        String suffix = "}";
        StringBuilder sb = new StringBuilder();

        int offset = 0;
        int found = -1;
        while ((found = format.indexOf(prefix, offset)) >= offset) {
            sb.append(format.substring(offset, found));

            if (suffix.length() == 0) {
                offset = found + prefix.length() + 1;
            } else {
                offset = format.indexOf(suffix, found + prefix.length());
            }

            if (offset > found) {
                String tag = format.substring(found + prefix.length(), offset);
                offset += suffix.length();

                sb.append("?");
                if (vars.containsKey(tag)) {
                    param.add(vars.get(tag));
                } else if (vars.containsKey(tag.toLowerCase())) {
                    param.add(vars.get(tag.toLowerCase()));
                } else if (vars.containsKey(tag.toUpperCase())) {
                    param.add(vars.get(tag.toUpperCase()));
                } else {
                    param.add("{" + tag.toUpperCase() + "}");
                }
            } else {
                sb.append(prefix);
                offset = found + prefix.length();
            }
        }
        sb.append(format.substring(offset));

        return sb.toString();
    }

    public static List<String> queryFieldList(int dbType, Connection conn, QueryRunner ds, String schemaName,
            String tableName) throws Exception {
        switch (dbType) {
        case DB_TYPE_POSTGRES:
        case DB_TYPE_ORACLE:
        case DB_TYPE_MSSQL:
        case DB_TYPE_SYBASE:
        case DB_TYPE_DB2:
        case DB_TYPE_H2:
        case DB_TYPE_SQLITE:
        case DB_TYPE_CRATE: {
            return (List) queryForColumnList(dbType, conn, ds,
                    "SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME=? AND TABLE_SCHEMA=? ",
                    tableName, schemaName);
        }
        case DB_TYPE_ANSI:
        case DB_TYPE_MYSQL:
        default: {
            return (List) queryForColumnList(dbType, conn, ds,
                    "SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME=? AND TABLE_SCHEMA=? ORDER BY ORDINAL_POSITION",
                    tableName, schemaName);
        }
        }
    }

    public static Map<String, Object> filterFields(Map<String, Object> row, List<String> fieldList)
            throws Exception {
        LinkedHashMap<String, Object> returnRow = new LinkedHashMap();
        for (String key : fieldList) {
            if (row.containsKey(key)) {
                returnRow.put(key, row.get(key));
            }
        }
        return returnRow;
    }

    QueryRunner queryRunner = null;
    Connection conn = null;

    public int getDbType() {
        return dbType;
    }

    public void setDbType(int dbType) {
        this.dbType = dbType;
    }

    int dbType = 0;

    public static final int DB_TYPE_ANSI = 0;
    public static final int DB_TYPE_MYSQL = 1;
    public static final int DB_TYPE_ORACLE = 2;
    public static final int DB_TYPE_POSTGRES = 3;
    public static final int DB_TYPE_MSSQL = 4;
    public static final int DB_TYPE_SYBASE = 5;
    public static final int DB_TYPE_DB2 = 6;
    public static final int DB_TYPE_H2 = 7;
    public static final int DB_TYPE_SQLITE = 8;
    public static final int DB_TYPE_CRATE = 9;

    public JDAO() {
    }

    public JDAO(QueryRunner queryRunner) {
        this();
        this.queryRunner = queryRunner;
    }

    public JDAO(Connection conn, QueryRunner queryRunner) {
        this(queryRunner);
        this.conn = conn;
    }

    public List<Map<String, Object>> queryForList(String sql, Object... args) throws Exception {
        return JDAO.queryForList(this.dbType, this.conn, this.queryRunner, sql, args);
    }

    public List<Map<String, Object>> queryForList(String sql) throws Exception {
        return JDAO.queryForList(this.dbType, this.conn, this.queryRunner, sql);
    }

    public List<Map<String, Object>> queryForMapList(String sql, Object... args) throws Exception {
        return JDAO.queryForMapList(this.dbType, this.conn, this.queryRunner, sql, args);
    }

    public List<Map<String, Object>> queryForMapList(String sql) throws Exception {
        return JDAO.queryForMapList(this.dbType, this.conn, this.queryRunner, sql);
    }

    public List<Map<String, Object>> queryTemplateForMapList(String table, Map<String, Object> vm,
            Collection fieldList, String suffixQuery) throws Exception {
        return JDAO.queryTemplateForMapList(this.dbType, this.conn, this.queryRunner, table, fieldList, vm,
                suffixQuery);
    }

    public List<Map<String, Object>> queryTemplateForMapList(String table, Map<String, Object> vm,
            Collection fieldList) throws Exception {
        return JDAO.queryTemplateForMapList(this.dbType, this.conn, this.queryRunner, table, fieldList, vm);
    }

    public List<Map<String, Object>> queryTemplateForMapList(String table, Map<String, Object> vm)
            throws Exception {
        return JDAO.queryTemplateForMapList(this.dbType, this.conn, this.queryRunner, table, vm);
    }

    public List<Object[]> queryForArrayList(String sql, Object... args) throws Exception {
        return JDAO.queryForArrayList(this.dbType, this.conn, this.queryRunner, sql, args);
    }

    public List<Object[]> queryForArrayList(String sql) throws Exception {
        return JDAO.queryForArrayList(this.dbType, this.conn, this.queryRunner, sql);
    }

    public List<Object[]> queryTemplateForArrayList(String table, List<String> cols, Map<String, Object> vm,
            String suffixQuery) throws Exception {
        return JDAO.queryTemplateForArrayList(this.dbType, this.conn, this.queryRunner, table, cols, vm,
                suffixQuery);
    }

    public List<Object[]> queryTemplateForArrayList(String table, List<String> cols, Map<String, Object> vm)
            throws Exception {
        return JDAO.queryTemplateForArrayList(this.dbType, this.conn, this.queryRunner, table, cols, vm);
    }

    public List<Object> queryForColumnList(String sql, Object... args) throws Exception {
        return JDAO.queryForColumnList(this.dbType, this.conn, this.queryRunner, sql, args);
    }

    public List<Object> queryForColumnList(String sql) throws Exception {
        return JDAO.queryForColumnList(this.dbType, this.conn, this.queryRunner, sql);
    }

    public List<Object> queryTemplateForColumnList(String table, String col, Map<String, Object> vm,
            String suffixQuery, int templateType, int constraintType) throws Exception {
        List param = new Vector();
        return JDAO.queryTemplateForColumnList(this.dbType, this.conn, this.queryRunner, table, col, vm,
                suffixQuery, templateType, constraintType);
    }

    public List<Object> queryTemplateForColumnList(String table, String col, Map<String, Object> vm,
            String suffixQuery) throws Exception {
        List param = new Vector();
        return JDAO.queryTemplateForColumnList(this.dbType, this.conn, this.queryRunner, table, col, vm,
                suffixQuery);
    }

    public List<Object> queryTemplateForColumnList(String table, String col, Map<String, Object> vm)
            throws Exception {
        List param = new Vector();
        return JDAO.queryTemplateForColumnList(this.dbType, this.conn, this.queryRunner, table, col, vm);
    }

    public Map<String, Object> queryForMap(String sql, Object... args) throws Exception {
        return JDAO.queryForMap(this.dbType, this.conn, this.queryRunner, sql, args);
    }

    public Map<String, Object> queryForMap(String sql) throws Exception {
        return JDAO.queryForMap(this.dbType, this.conn, this.queryRunner, sql);
    }

    public Map<String, Object> queryTemplateForMap(String table, Map<String, Object> vm, List<String> fieldList,
            String suffixQuery, int templateType, int constraintType) throws Exception {
        return JDAO.queryTemplateForMap(this.dbType, this.conn, this.queryRunner, table, fieldList, vm, suffixQuery,
                templateType, constraintType);
    }

    public Map<String, Object> queryTemplateForMap(String table, Map<String, Object> vm, List<String> fieldList,
            String suffixQuery) throws Exception {
        return JDAO.queryTemplateForMap(this.dbType, this.conn, this.queryRunner, table, fieldList, vm,
                suffixQuery);
    }

    public Map<String, Object> queryTemplateForMap(String table, Map<String, Object> vm, List<String> fieldList)
            throws Exception {
        return JDAO.queryTemplateForMap(this.dbType, this.conn, this.queryRunner, table, fieldList, vm);
    }

    public Map<String, Object> queryTemplateForMap(String table, Map<String, Object> vm) throws Exception {
        return JDAO.queryTemplateForMap(this.dbType, this.conn, this.queryRunner, table, vm);
    }

    public Map<String, String> queryForKvMap(String sql, Object... args) throws Exception {
        return JDAO.queryForKvMap(this.dbType, this.conn, this.queryRunner, sql, args);
    }

    public Map<String, String> queryForKvMap(String sql) throws Exception {
        return JDAO.queryForKvMap(this.dbType, this.conn, this.queryRunner, sql);
    }

    public Map<String, String> queryTemplateForKvMap(String table, String c1, String c2, Map<String, Object> vm,
            String suffixQuery, int templateType, int constraintType) throws Exception {
        List param = new Vector();
        return JDAO.queryForKvMap(this.dbType, this.conn, this.queryRunner,
                "SELECT " + c1 + "," + c2 + " FROM " + table + " WHERE "
                        + JDAO.buildWhere(dbType, templateType, constraintType, param, vm)
                        + (suffixQuery == null ? "" : " " + suffixQuery),
                param);
    }

    public Map<String, String> queryTemplateForKvMap(String table, String c1, String c2, Map<String, Object> vm,
            String suffixQuery) throws Exception {
        return queryTemplateForKvMap(table, c1, c2, vm, suffixQuery, TEMPLATE_TYPE_AUTO, CONSTRAINT_ALL_OF);
    }

    public Map<String, String> queryTemplateForKvMap(String table, String c1, String c2, Map<String, Object> vm)
            throws Exception {
        return queryTemplateForKvMap(table, c1, c2, vm, null, TEMPLATE_TYPE_AUTO, CONSTRAINT_ALL_OF);
    }

    public Map<String, List<String>> queryForKvListMap(String sql, Object... args) throws Exception {
        return JDAO.queryForKvListMap(this.dbType, this.conn, this.queryRunner, sql, args);
    }

    public Map<String, List<String>> queryForKvListMap(String sql) throws Exception {
        return JDAO.queryForKvListMap(this.dbType, this.conn, this.queryRunner, sql);
    }

    public Map<String, String> queryScopeForKvMap(String table, String kvSF, String kvKF, String kvVF,
            String scopeId) throws Exception {
        return this.queryTemplateForKvMap(table, kvKF, kvVF, JDAO.toMap(kvSF, scopeId), null, TEMPLATE_TYPE_EQUAL,
                CONSTRAINT_ALL_OF);
    }

    public Map<String, List<String>> queryScopeForKvListMap(String table, String kvSF, String kvKF, String kvVF,
            String scopeId) throws Exception {
        return this.queryTemplateForKvListMap(table, kvKF, kvVF, JDAO.toMap(kvSF, scopeId), null,
                TEMPLATE_TYPE_EQUAL, CONSTRAINT_ALL_OF);
    }

    public Map<String, List<String>> queryTemplateForKvListMap(String table, String c1, String c2,
            Map<String, Object> vm, String suffixQuery, int templateType, int constraintType) throws Exception {
        List param = new Vector();
        return JDAO.queryForKvListMap(this.dbType, this.conn, this.queryRunner,
                "SELECT " + c1 + "," + c2 + " FROM " + table + " WHERE "
                        + JDAO.buildWhere(dbType, templateType, constraintType, param, vm)
                        + (suffixQuery == null ? "" : " " + suffixQuery),
                param);
    }

    public Map<String, List<String>> queryTemplateForKvListMap(String table, String c1, String c2,
            Map<String, Object> vm, String suffixQuery) throws Exception {
        return queryTemplateForKvListMap(table, c1, c2, vm, suffixQuery, TEMPLATE_TYPE_AUTO, CONSTRAINT_ALL_OF);
    }

    public Map<String, List<String>> queryTemplateForKvListMap(String table, String c1, String c2,
            Map<String, Object> vm) throws Exception {
        return queryTemplateForKvListMap(table, c1, c2, vm, null, TEMPLATE_TYPE_AUTO, CONSTRAINT_ALL_OF);
    }

    public Object queryForScalar(String sql, Object... args) throws Exception {
        return JDAO.queryForScalar(this.dbType, this.conn, this.queryRunner, sql, args);
    }

    public Object queryForScalar(String sql) throws Exception {
        return JDAO.queryForScalar(this.dbType, this.conn, this.queryRunner, sql);
    }

    public Object queryTemplateForScalar(String table, String col, Map<String, Object> vm, String suffixQuery,
            int templateType, int constraintType) throws Exception {
        return JDAO.queryTemplateForScalar(this.dbType, this.conn, this.queryRunner, table, col, vm, suffixQuery,
                templateType, constraintType);
    }

    public Object queryTemplateForScalar(String table, String col, Map<String, Object> vm, String suffixQuery)
            throws Exception {
        return JDAO.queryTemplateForScalar(this.dbType, this.conn, this.queryRunner, table, col, vm, suffixQuery);
    }

    public Object queryTemplateForScalar(String table, String col, Map<String, Object> vm) throws Exception {
        return JDAO.queryTemplateForScalar(this.dbType, this.conn, this.queryRunner, table, col, vm);
    }

    public int update(String sql, Object... args) throws Exception {
        this.checkReadOnly();
        return JDAO.update(this.dbType, this.conn, this.queryRunner, sql, args);
    }

    public int update(String sql) throws Exception {
        this.checkReadOnly();
        return JDAO.update(this.dbType, this.conn, this.queryRunner, sql);
    }

    public int execute(String sql, Object... args) throws Exception {
        return JDAO.execute(this.dbType, this.conn, this.queryRunner, sql, args);
    }

    public int execute(String sql) throws Exception {
        return JDAO.execute(this.dbType, this.conn, this.queryRunner, sql);
    }

    public int insertSet(String table, Map cols) throws Exception {
        this.checkReadOnly();
        return JDAO.insertSet(this.dbType, this.conn, this.queryRunner, table, cols, false);
    }

    public int insertSet(String table, Map cols, boolean onDuplicateKeyUpdate) throws Exception {
        this.checkReadOnly();
        return JDAO.insertSet(this.dbType, this.conn, this.queryRunner, table, cols, onDuplicateKeyUpdate);
    }

    public int insertSet(String table, Map cols, boolean onDuplicateKeyUpdate, Collection updateCols)
            throws Exception {
        this.checkReadOnly();
        return JDAO.insertSet(this.dbType, this.conn, this.queryRunner, table, cols, onDuplicateKeyUpdate,
                updateCols);
    }

    public int insert(String table, Map cols) throws Exception {
        this.checkReadOnly();
        return JDAO.insert(this.dbType, this.conn, this.queryRunner, table, cols);
    }

    public int insert(String table, Map cols, boolean onDuplicateKeyUpdate) throws Exception {
        this.checkReadOnly();
        return JDAO.insert(this.dbType, this.conn, this.queryRunner, table, cols, onDuplicateKeyUpdate);
    }

    public int insert(String table, Map cols, boolean onDuplicateKeyUpdate, Collection updateCols)
            throws Exception {
        this.checkReadOnly();
        return JDAO.insert(this.dbType, this.conn, this.queryRunner, table, cols, onDuplicateKeyUpdate, updateCols);
    }

    public void setAutoCommit(boolean ac) {
        try {
            if (conn != null) {
                conn.setAutoCommit(ac);
            }
        } catch (Exception xe) {
        }
    }

    public void beginTransaction() {
        try {
            if (conn != null) {
                conn.setAutoCommit(false);
            }
        } catch (Exception xe) {
        }
    }

    public void rollbackTransaction() {
        try {
            if (conn != null) {
                conn.rollback();
            }
        } catch (Exception xe) {
        }
    }

    public void commitTransaction() {
        try {
            if (conn != null) {
                conn.commit();
            }
        } catch (Exception xe) {
        }
    }

    public void close() {
        try {
            if (conn != null) {
                conn.close();
            }
        } catch (Exception xe) {
        } finally {
            conn = null;
            queryRunner = null;
        }
    }

    public Connection getConnection() {
        return this.conn;
    }

    public QueryRunner getQueryRunner() {
        return this.queryRunner;
    }

    public static class KvListMapHandler implements ResultSetHandler<Map<String, List<String>>> {
        BasicRowProcessor basicRowProcessor = new BasicRowProcessor();

        public KvListMapHandler() {
            super();
        }

        public Map<String, List<String>> handle(ResultSet rs) throws SQLException {
            Map<String, List<String>> ret = new LinkedHashMap<String, List<String>>();
            while (rs.next()) {
                Object[] row = basicRowProcessor.toArray(rs);
                if (row.length >= 2) {
                    String key = String.valueOf(row[0]);
                    if (!ret.containsKey(key)) {
                        ret.put(key, new Vector<String>());
                    }
                    ret.get(key).add(String.valueOf(row[1]));
                }
            }
            return ret;
        }
    }

    public static class KvMapHandler implements ResultSetHandler<Map<String, String>> {
        BasicRowProcessor basicRowProcessor = new BasicRowProcessor();

        public KvMapHandler() {
            super();
        }

        public Map<String, String> handle(ResultSet rs) throws SQLException {
            Map<String, String> ret = new LinkedHashMap<String, String>();
            while (rs.next()) {
                Object[] row = basicRowProcessor.toArray(rs);
                if (row.length >= 2) {
                    ret.put(String.valueOf(row[0]), String.valueOf(row[1]));
                }
            }
            return ret;
        }
    }

    public static interface ResultRowCallbackHandler<T> {
        public T handleRow(Map<String, Object> row) throws SQLException;
    }

    public static class ResultCallbackHandler<T> implements ResultSetHandler<List<T>> {
        ResultRowCallbackHandler<T> rowCallback = null;
        RowProcessor rowProcessor = new BasicXRowProcessor();

        public ResultCallbackHandler(ResultRowCallbackHandler<T> rowCallback) {
            this.rowCallback = rowCallback;
        }

        public ResultCallbackHandler(ResultRowCallbackHandler<T> rowCallback, RowProcessor rowProcessor) {
            this.rowCallback = rowCallback;
            this.rowProcessor = rowProcessor;
        }

        @Override
        public List<T> handle(ResultSet rs) throws SQLException {
            ArrayList rows = new ArrayList();

            while (rs.next()) {
                T row = this.rowCallback.handleRow(rowProcessor.toMap(rs));
                if (row != null) {
                    rows.add(row);
                }
            }

            return rows;
        }
    }

    public static class BasicXRowProcessor extends BasicRowProcessor {

        public BasicXRowProcessor() {
            super();
        }

        @Override
        public Map<String, Object> toMap(ResultSet rs) throws SQLException {
            Map result = new CaseInsensitiveHashMap();
            ResultSetMetaData rsmd = rs.getMetaData();
            int cols = rsmd.getColumnCount();

            for (int i = 1; i <= cols; i++) {
                if (rsmd.getColumnLabel(i) != null) {
                    result.put(rsmd.getColumnLabel(i), rs.getObject(i));
                } else {
                    result.put(rsmd.getColumnName(i), rs.getObject(i));
                }
            }

            return result;
        }

        private static class CaseInsensitiveHashMap extends HashMap {
            /**
             * @see Map#containsKey(Object)
             */
            public boolean containsKey(Object key) {
                return super.containsKey(key.toString().toLowerCase());
            }

            /**
             * @see Map#get(Object)
             */
            public Object get(Object key) {
                return super.get(key.toString().toLowerCase());
            }

            /**
             * @see Map#put(Object, Object)
             */
            public Object put(Object key, Object value) {
                return super.put(key.toString().toLowerCase(), value);
            }

            /**
             * @see Map#putAll(Map)
             */
            public void putAll(Map m) {
                Iterator iter = m.keySet().iterator();
                while (iter.hasNext()) {
                    Object key = iter.next();
                    Object value = m.get(key);
                    this.put(key, value);
                }
            }

            /**
             * @see Map#remove(Object)
             */
            public Object remove(Object key) {
                return super.remove(key.toString().toLowerCase());
            }
        }
    }

    public interface IBean {
    }

    @Target(value = ElementType.FIELD)
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface IBeanField {
        String value();
    }

    @Target(value = ElementType.FIELD)
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface IBeanID {
    }

    public static class IBeanProcessor<T> extends BeanProcessor {
        private Class<T> type;

        public static <T> IBeanProcessor<T> of(Class<T> type) {
            IBeanProcessor abp = new IBeanProcessor();
            abp.type = type;
            return abp;
        }

        private IBeanProcessor() {
        }

        protected int[] mapColumnsToProperties(ResultSetMetaData rsmd, PropertyDescriptor[] props)
                throws SQLException {
            int cols = rsmd.getColumnCount();
            int[] columnToProperty = new int[cols + 1];

            Arrays.fill(columnToProperty, -1);

            List<Field> fieldList = FieldUtils.getFieldsListWithAnnotation(this.type, IBeanField.class);
            for (int col = 1; col <= cols; ++col) {
                String columnName = rsmd.getColumnLabel(col);
                if (null == columnName || 0 == columnName.length()) {
                    columnName = rsmd.getColumnName(col);
                }

                for (Field f : fieldList) {
                    String fName = f.getAnnotation(IBeanField.class).value();
                    String pName = f.getName();
                    if (fName.equalsIgnoreCase(columnName)) {
                        for (int i = 0; i < props.length; ++i) {
                            String propName = props[i].getName();
                            if (pName.equalsIgnoreCase(propName)) {
                                columnToProperty[col] = i;
                                break;
                            }
                        }
                        break;
                    }
                }
            }

            return columnToProperty;
        }
    }

    public <T> Map<String, T> queryForStringBeanMap(String sql, Class<T> beanClazz, Object... args)
            throws Exception {
        return queryForBeanMap(sql, String.class, beanClazz, args);
    }

    public <T> Map<String, T> queryForStringBeanMap(String sql, Class<T> beanClazz) throws Exception {
        return queryForBeanMap(sql, String.class, beanClazz);
    }

    public <K, V> Map<K, V> queryForBeanMap(String sql, Class<K> idClazz, Class<V> beanClazz, Object... args)
            throws Exception {
        BeanMapHandler<K, V> handler = new BeanMapHandler<K, V>(beanClazz,
                IBean.class.isAssignableFrom(beanClazz) ? new BasicRowProcessor(IBeanProcessor.of(beanClazz))
                        : this.generousRowProcessor);

        return this.queryForT(this.getDbType(), handler, this.getConnection(), this.getQueryRunner(), sql, args);
    }

    public <K, V> Map<K, V> queryForBeanMap(String sql, Class<K> idClazz, Class<V> beanClazz) throws Exception {
        BeanMapHandler<K, V> handler = new BeanMapHandler<K, V>(beanClazz,
                IBean.class.isAssignableFrom(beanClazz) ? new BasicRowProcessor(IBeanProcessor.of(beanClazz))
                        : this.generousRowProcessor);

        return this.queryForT(this.getDbType(), handler, this.getConnection(), this.getQueryRunner(), sql);
    }

    public <T> List<T> queryForBeanList(String sql, Class<T> beanClazz, Object... args) throws Exception {
        ResultSetHandler<List<T>> handler = new BeanListHandler<T>(beanClazz,
                IBean.class.isAssignableFrom(beanClazz) ? new BasicRowProcessor(IBeanProcessor.of(beanClazz))
                        : this.generousRowProcessor);

        return this.queryForT(this.getDbType(), handler, this.getConnection(), this.getQueryRunner(), sql, args);
    }

    public <T> List<T> queryForBeanList(String sql, Class<T> beanClazz) throws Exception {
        ResultSetHandler<List<T>> handler = new BeanListHandler<T>(beanClazz,
                IBean.class.isAssignableFrom(beanClazz) ? new BasicRowProcessor(IBeanProcessor.of(beanClazz))
                        : this.generousRowProcessor);

        return this.queryForT(this.getDbType(), handler, this.getConnection(), this.getQueryRunner(), sql);
    }

    public <T> T queryForBean(String sql, Class<T> beanClazz, Object... args) throws Exception {
        ResultSetHandler<T> handler = new BeanHandler<T>(beanClazz,
                IBean.class.isAssignableFrom(beanClazz) ? new BasicRowProcessor(IBeanProcessor.of(beanClazz))
                        : this.generousRowProcessor);

        return this.queryForT(this.getDbType(), handler, this.getConnection(), this.getQueryRunner(), sql, args);
    }

    public <T> T queryForBean(String sql, Class<T> beanClazz) throws Exception {
        ResultSetHandler<T> handler = new BeanHandler<T>(beanClazz,
                IBean.class.isAssignableFrom(beanClazz) ? new BasicRowProcessor(IBeanProcessor.of(beanClazz))
                        : this.generousRowProcessor);

        return this.queryForT(this.getDbType(), handler, this.getConnection(), this.getQueryRunner(), sql);
    }

    public static <T> String extractIdNameFromBean(Class<T> _beanClazz) {
        List<Field> fields = FieldUtils.getFieldsListWithAnnotation(_beanClazz, IBeanID.class);
        if (fields.size() != 1) {
            throw new IllegalArgumentException("improper IBeanID annotation");
        }
        return fields.iterator().next().getAnnotation(IBeanField.class).value();
    }

    public static <T> Map<String, Object> extractIdKvFromBean(Object _bean, Class<T> _beanClazz) {
        Map<String, Object> _ret = new HashMap();
        List<Field> fields = FieldUtils.getFieldsListWithAnnotation(_beanClazz, IBeanID.class);
        if (fields.size() > 0) {
            throw new IllegalArgumentException("improper IBeanID annotation");
        }
        try {
            Iterator<Field> it = fields.iterator();
            while (it.hasNext()) {
                Field field = it.next();
                String _key = field.getAnnotation(IBeanField.class).value();
                Object _val = FieldUtils.readField(field, _bean, true);
                _ret.put(_key, _val);
            }
            return _ret;
        } catch (IllegalAccessException e) {
            // IGNORE
        }
        return null;
    }

    public static <T> Map<String, Object> extractColsFromBean(Object _bean, Class<T> _beanClazz,
            boolean _inclIdField) {
        Map<String, Object> _ret = new HashMap();
        List<Field> fields = FieldUtils.getFieldsListWithAnnotation(_beanClazz, IBeanField.class);
        for (Field field : fields) {
            try {
                if (_inclIdField || (!field.isAnnotationPresent(IBeanID.class))) {
                    String _key = field.getAnnotation(IBeanField.class).value();
                    Object _val = FieldUtils.readField(field, _bean, true);
                    _ret.put(_key, _val);
                }
            } catch (IllegalAccessException e) {
                // IGNORE
            }
        }
        return _ret;
    }

    public static <T> List<Map<String, Object>> extractColsFromBeanList(List<Object> _beans, Class<T> _beanClazz,
            boolean _inclIdField) {
        List<Map<String, Object>> _ret = new Vector();
        for (Object b : _beans) {
            _ret.add(extractColsFromBean((T) b, _beanClazz, _inclIdField));
        }
        return _ret;
    }

    public <T> int insertBean(String _table, IBean bean, Class<T> beanClazz) throws Exception {
        Map<String, Object> _col = JDAO.extractColsFromBean((T) bean, beanClazz, true);
        return this.insert(_table, _col);
    }

    public <T> int insertBeanBySQL(String _query, IBean bean, Class<T> beanClazz) throws Exception {
        Map<String, Object> _col = JDAO.extractColsFromBean((T) bean, beanClazz, true);
        return JDAO.execute(this.dbType, this.conn, this.queryRunner, _query, _col);
    }

    public <T> int updateBean(String table, Object _bean, Class<T> _beanClazz) throws Exception {
        Map<String, Object> keyCol = extractIdKvFromBean(_bean, _beanClazz);
        Map cols = extractColsFromBean(_bean, _beanClazz, false);

        Vector vv = new Vector();
        String setqq = buildSet(this.dbType, vv, cols);
        String wqq = buildWhereEqual(this.dbType, vv, keyCol);

        StringBuilder qq = new StringBuilder();

        qq.append("UPDATE " + table + " SET ");
        qq.append(setqq);
        qq.append(" WHERE ");
        qq.append(wqq);

        return this.update(qq.toString(), vv.toArray());
    }

    public <T> int updateBeans(String table, List<Object> _beans, Class<T> _beanClazz) throws Exception {
        int _ret = 0;
        for (Object _bean : _beans) {
            _ret += updateBean(table, _bean, _beanClazz);
        }
        return _ret;
    }
}