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

org.onetwo.dbm.utils.DbmUtils Maven / Gradle / Ivy

package org.onetwo.dbm.utils;

import java.lang.annotation.Annotation;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.sql.DataSource;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.onetwo.common.date.DateUtils;
import org.onetwo.common.db.dquery.condition.directive.DynamicConditionDirective;
import org.onetwo.common.db.filequery.directive.SetDirective;
import org.onetwo.common.db.filequery.directive.WhereDirective;
import org.onetwo.common.log.JFishLoggerFactory;
import org.onetwo.common.reflect.ReflectUtils;
import org.onetwo.common.spring.SpringUtils;
import org.onetwo.common.spring.Springs;
import org.onetwo.common.spring.ftl.AbstractFreemarkerTemplateConfigurer;
import org.onetwo.common.spring.ftl.DateRangeDirective;
import org.onetwo.common.spring.ftl.ForeachDirective;
import org.onetwo.common.spring.ftl.StrDirective;
import org.onetwo.common.utils.CUtils;
import org.onetwo.common.utils.LangUtils;
import org.onetwo.dbm.annotation.DbmFieldListeners;
import org.onetwo.dbm.core.spi.DbmTransaction;
import org.onetwo.dbm.exception.DbmException;
import org.onetwo.dbm.exception.UpdateCountException;
import org.onetwo.dbm.jdbc.JdbcUtils;
import org.onetwo.dbm.jdbc.method.JdbcOperationMethod;
import org.onetwo.dbm.jdbc.spi.SqlParametersProvider;
import org.onetwo.dbm.mapping.DbmEntityFieldListener;
import org.onetwo.dbm.mapping.DbmEnumValueMapping;
import org.onetwo.dbm.mapping.DbmMappedField;
import org.onetwo.dbm.spring.EnableDbm;
import org.slf4j.Logger;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.transaction.ChainedTransactionManager;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.jdbc.core.SqlProvider;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterUtils;
import org.springframework.jdbc.core.namedparam.ParsedSql;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

final public class DbmUtils {
	
	private static final Logger logger = JFishLoggerFactory.getLogger(DbmUtils.class);
	
	public static final int MAX_PRINTABLE_ARG_SIZE = 50;
	
	public final static ConversionService CONVERSION_SERVICE = new DefaultConversionService();
	
	private static final String CHAINED_TRANSACTION_MANAGER = "org.springframework.data.transaction.ChainedTransactionManager";

	public static final int INVALID_VALUE_FIRST_RECORD = -1;
	public static final int INVALID_VALUE_MAX_RESULTS = 0;

	public static final String FIRST_RESULT_NAME = "DbmQueryFirstResult";
	public static final String MAX_RESULT_NAME = "DbmQueryMaxResult";

	/****
	 * 是否分页
	 * @author weishao zeng
	 * @param first
	 * @param maxResults
	 * @return
	 */
	public static boolean isLimitedQuery(Integer first, Integer maxResults){
		return first>INVALID_VALUE_FIRST_RECORD && maxResults>INVALID_VALUE_MAX_RESULTS;
	}
	
	public static boolean isChanedTransactionManagerPresent(){
		return ClassUtils.isPresent(CHAINED_TRANSACTION_MANAGER, ClassUtils.getDefaultClassLoader());
	}
	public static void throwIfEffectiveCountError(String errorMsg, int expectCount, int effectiveCount){
		if(effectiveCount!=expectCount)
			throw new UpdateCountException(errorMsg, expectCount, effectiveCount);
	}
	
	public static List initDbmEntityFieldListeners(DbmFieldListeners listenersAnntation){
		Assert.notNull(listenersAnntation, "listenersAnntationn can not be null");
		Class[] flClasses = listenersAnntation.value();
		List fieldListeners = Lists.newArrayList();
		for(Class flClass : flClasses){
			DbmEntityFieldListener fl = DbmUtils.createDbmBean(flClass); // ReflectUtils.newInstance(flClass);
			fieldListeners.add(fl);
		}
		return fieldListeners;
	}

	/*public static Object getActualSqlValue(Object value){
		if(value!=null && Enum.class.isAssignableFrom(value.getClass())){
//			return Types.asValue(value.toString(), value.getClass());
			return ((Enum)value).name();
		}
		return value;
	}*/


	public static Map lookupColumnNames(SqlRowSetMetaData resultSetMetaData) throws SQLException {
		int columnCount = resultSetMetaData.getColumnCount();
		Map names = new HashMap();
		for (int index = 1; index <= columnCount; index++) {
			String columName = lookupColumnName(resultSetMetaData, index);
			names.put(JdbcUtils.lowerCaseName(columName), index);
			names.put(columName, index);
		}
		return names;
	}

	public static String lookupColumnName(SqlRowSetMetaData resultSetMetaData, int columnIndex) throws SQLException {
		String name = resultSetMetaData.getColumnLabel(columnIndex);
		if (name == null || name.length() < 1) {
			name = resultSetMetaData.getColumnName(columnIndex);
		}
		return name;
	}
	
	
	public static Collection getAllDbmPackageNames(ApplicationContext applicationContext){
		ListableBeanFactory bf = (ListableBeanFactory)applicationContext.getAutowireCapableBeanFactory();
		return getAllDbmPackageNames(bf);
	}
	public static Collection getAllDbmPackageNames(ListableBeanFactory beanFactory){
		Collection packageNames = new HashSet<>();
		packageNames.addAll(scanEnableDbmPackages(beanFactory));
//		packageNames.addAll(scanDbmPackages(beanFactory));
		return packageNames;
	}
	
	public static List scanEnableDbmPackages(ApplicationContext applicationContext){
		ListableBeanFactory bf = (ListableBeanFactory)applicationContext.getAutowireCapableBeanFactory();
		return scanEnableDbmPackages(bf);
	}
	
	/***
	 * 首先找到标注了 annotationCLass 注解(比如 @EnableDbm )的bean
	 * 然后读取注解的packagesToScan属性值,有则返回此值
	 * 没有则返回标注了注解的bean所在包,作为返回值
	 * 
	 * sample:
	 *  
	 * @EnableDbm
	 * class EnableDbmBean {
	 * }
	 * 
	 * Set packageNames = SpringAnnotationUtils.scanAnnotationPackages(applicationContext, EnableDbmBean.class)
	 * packageNames = EnableDbmUIBean.class.getPackage().getName();
	 * 
	 * @author weishao zeng
	 * @param beanFactory
	 * @return
	 */
	public static List scanEnableDbmPackages(ListableBeanFactory beanFactory){
		List packageNames = new ArrayList();
		SpringUtils.scanAnnotation(beanFactory, EnableDbm.class, (beanDef, beanClass)->{
			EnableDbm enableDbm = beanClass.getAnnotation(EnableDbm.class);
			if(enableDbm==null){
				return ;
			}
			String[] modelPacks = enableDbm.packagesToScan();
			if(ArrayUtils.isNotEmpty(modelPacks)){
				for(String pack : modelPacks){
					packageNames.add(pack);
				}
			}else{
				String packageName = beanClass.getPackage().getName();
				if(!packageName.startsWith("org.onetwo.")){
					packageNames.add(packageName);
				}
			}
		});
		return packageNames;
	}
	
	/*public static List scanDbmPackages(ApplicationContext applicationContext){
		ListableBeanFactory bf = (ListableBeanFactory)applicationContext.getAutowireCapableBeanFactory();
		return scanDbmPackages(bf);
	}
	public static List scanDbmPackages(ListableBeanFactory beanFactory){
		return scanAnnotationPackages(beanFactory, DbmPackages.class, dbmPackages->dbmPackages.value());
	}*/
	public static  List scanAnnotationPackages(ListableBeanFactory beanFactory, 
																		Class annoClass,
																		Function packageExtractor){
		List packageNames = new ArrayList();
		SpringUtils.scanAnnotation(beanFactory, annoClass, (beanDef, beanClass)->{
			T annoInst = beanClass.getAnnotation(annoClass);
			if(annoInst==null){
				return ;
			}
			String[] modelPacks = packageExtractor.apply(annoInst);
			if(ArrayUtils.isNotEmpty(modelPacks)){
				for(String pack : modelPacks){
					packageNames.add(pack);
				}
			}else{
				String packageName = beanClass.getPackage().getName();
				packageNames.add(packageName);
			}
		});
		return packageNames;
	}
	

	public static void rollbackOnException(DbmTransaction transaction, Throwable ex) throws TransactionException {
		logger.debug("Initiating transaction rollback on application exception", ex);
		try {
			transaction.rollback();
		}
		catch (TransactionSystemException ex2) {
			logger.error("Application exception overridden by rollback exception", ex);
			ex2.initApplicationException(ex);
			throw ex2;
		}
		catch (RuntimeException ex2) {
			logger.error("Application exception overridden by rollback exception", ex);
			throw ex2;
		}
		catch (Error err) {
			logger.error("Application exception overridden by rollback error", ex);
			throw err;
		}
	}
	

	/*****
	 * find the {@link PlatformTransactionManager} from {@link ApplicationContext} by {@link DataSource}
	 * @param applicationContext
	 * @param dataSource
	 * @return
	 */
	public static PlatformTransactionManager getDataSourceTransactionManager(ApplicationContext applicationContext, DataSource dataSource, Supplier notFoudCallback){
		if(DbmUtils.isChanedTransactionManagerPresent()){
			List chaineds = SpringUtils.getBeans(applicationContext, ChainedTransactionManager.class);
			for(ChainedTransactionManager chain : chaineds){
				if(isChanedTMContainDataSource(chain, dataSource)){
					return chain;
				}
			}
		}
		Map tms = SpringUtils.getBeansAsMap(applicationContext, DataSourceTransactionManager.class);
		Entry tm = null;
		for(Entry entry : tms.entrySet()){
			if(isSameDataSource(entry.getValue(), dataSource)){
				tm = entry;
				break;
			}
		}
		if(tm!=null){
			if(logger.isDebugEnabled()){
				logger.debug("auto find DataSourceTransactionManager for current dataSource: {}", tm.getKey());
			}
			return tm.getValue();
		}else{
			if(notFoudCallback!=null){
				return notFoudCallback.get();
			}
			throw new DbmException("no DataSourceTransactionManager configurate for dataSource: " + dataSource);
		}
	}
	
	public static boolean isSameDataSource(DataSourceTransactionManager tm, DataSource dataSource){
		return tm.getDataSource().equals(dataSource);
	}
	
	@SuppressWarnings("unchecked")
	public static boolean isChanedTMContainDataSource(ChainedTransactionManager chained, DataSource dataSource){
		List lists = (List)ReflectUtils.getFieldValue(chained, "transactionManagers");
		if(LangUtils.isEmpty(lists)){
			return false;
		}
		for(PlatformTransactionManager tm : lists){
			if(!DataSourceTransactionManager.class.isInstance(tm)){
				continue;
			}
			DataSourceTransactionManager dtm = (DataSourceTransactionManager)tm;
			if(isSameDataSource(dtm, dataSource)){
				return true;
			}
		}
		return false;
	}
	

	public static Pair findSqlAndParams(JdbcOperationMethod invokeMethod, Object[] args){
		String sql;
		Object sqlArgs = null;
		if (invokeMethod.getSqlParameter()!=null) {
			sql = (String)args[invokeMethod.getSqlParameter().getParameterIndex()];
			if (invokeMethod.getSqlArgsParameter()!=null) {
				sqlArgs = args[invokeMethod.getSqlArgsParameter().getParameterIndex()];
			}
		} else if (invokeMethod.getSqlProviderParameter()!=null) {
			SqlProvider sqlProvider = (SqlProvider) args[invokeMethod.getSqlProviderParameter().getParameterIndex()];
			sql = sqlProvider.getSql();
			if (sqlProvider instanceof SqlParametersProvider) {
				sqlArgs = ((SqlParametersProvider)sqlProvider).getSqlParameters();
			}
		} else {
			throw new DbmException("sql parameter not found: " + invokeMethod.getMethod().getName());
		}
		Pair sqlParams = Pair.of(sql, sqlArgs);
		return sqlParams;
	}
	
	public static Pair findSqlAndParams(Object[] args){
		String sql = null;
		Object params = null;
//		int maxArgSize = 50;
		for (int i = 0; i < args.length; i++) {
			Object arg = args[i];
			if(arg==null){
				continue;
			}
			if(arg instanceof String){
				sql = (String)arg;
			}else if(arg instanceof Date){
				params = DateUtils.formatDateTimeMillis((Date)arg);
			}else if(arg instanceof Map){
				params = arg;
			}else if(arg.getClass().isArray()){
				params = CUtils.tolist(arg, false);
			}else if(arg instanceof Collection){//batch operation...
				params = arg;
			}else{
				//if arg is SimpleArgsPreparedStatementCreator
				if(arg instanceof SqlProvider){
					sql = ((SqlProvider)arg).getSql();
				}
				if(arg instanceof SqlParametersProvider){
					params = ((SqlParametersProvider)arg).getSqlParameterList();
				}
			}
//			if (LangUtils.size(params)>maxArgSize) {
//				params = "<>";
//			}
		}
		if(sql==null){
			return null;
		}
		return Pair.of(sql, params);
	}

	public static String objectToString(Object obj){
		if (obj!=null && obj.getClass().isArray()) {
			Object[] rawArgs = (Object[]) obj;
			Object[] args = new Object[rawArgs.length];
			for (int i = 0; i < rawArgs.length; i++) {
				if (LangUtils.isMultiple(rawArgs[i]) && LangUtils.size(rawArgs[i]) > MAX_PRINTABLE_ARG_SIZE) {
					args[i] = "<>"; 
				} else {
					args[i] = rawArgs[i];
				}
			}
			return LangUtils.toString(args);
		} else {
			return LangUtils.toString(obj);
		}
	}
	
	@SuppressWarnings("unchecked")
	public static Object formatContainerValueIfNeed(Object arg) {
		if (arg instanceof Map) {
			Map temp = (Map) arg;
			Map newParams = Maps.newLinkedHashMap();
			temp.forEach((k, v)->{
				if (v instanceof Collection) {
					Collection c = (Collection) v;
					List list = c.stream().map(cv -> formatValueIfNeed(cv)).collect(Collectors.toList());
					newParams.put(k, list);
				} else {
					newParams.put(k, formatValueIfNeed(v));
				}
			});
			return newParams;
		} else if (arg!=null && arg.getClass().isArray()){
			return CUtils.tolist(arg, false).stream().map(v -> formatValueIfNeed(v)).collect(Collectors.toList());
		} else if (arg instanceof Collection){//batch operation...
			Collection c = (Collection) arg;
			return c.stream().map(v -> formatValueIfNeed(v)).collect(Collectors.toList());
		} 
		return arg;
	}
	
	public static Object formatValueIfNeed(Object arg) {
		Object val = arg;
		if (val instanceof SqlParameterValue) {
			val = ((SqlParameterValue)val).getValue();
		}
		if (val instanceof Date) {
			val = DateUtils.formatDateTime((Date)val);
		} else if (val instanceof DbmEnumValueMapping) {
			DbmEnumValueMapping dbmEnum = (DbmEnumValueMapping)val;
			val = dbmEnum.getEnumMappingValue();
		}
		if (val instanceof String) {
			String str = val.toString();
			if (str.length()>1000) {
				val = StringUtils.substring(str, 0, 1000) + "...";
			}
		}
		return val;
	}
	

	/***
	 * 
	 * @see SqlParameterSourceUtils#createBatch
	 * 
	 * @author weishao zeng
	 * @param valueMaps
	 * @return
	 */
	public static SqlParameterSource[] createBatch(List> valueMaps) {
		int size = valueMaps.size();
		MapSqlParameterSource[] batch = new MapSqlParameterSource[size];
		for (int i = 0; i < size; i++) {
			batch[i] = new MapSqlParameterSource(valueMaps.get(i));
		}
		return batch;
	}
	
	public static  T createDbmBean(Class clazz) {
		T bean;
		if (Springs.getInstance().isInitialized()) {
			bean = Springs.getInstance().getBean(clazz);
			// 如果找不到则自动创建,并注入
			if (bean==null) {
				bean = ReflectUtils.newInstance(clazz);
				Springs.getInstance().autoInject(bean);
			}
		} else {
			bean = ReflectUtils.newInstance(clazz);
		}
		return bean;
	}
	
	public static SqlParameterValue convert2SqlParameterValue(DbmMappedField field, Object value){
		return new DbmSqlParameterValue(field.getColumn().getSqlType(), value);
	}
	
	public static Object convertFromSqlParameterValue(DbmMappedField field, Object sqlParametableValue){
		Object val = sqlParametableValue;
		if (sqlParametableValue instanceof SqlParameterValue) {
			SqlParameterValue spv = (SqlParameterValue) sqlParametableValue;
			val = spv.getValue();
		}
		return val;
	}
	
	public static class DbmSqlParameterValue extends SqlParameterValue {

		public DbmSqlParameterValue(int sqlType, Object value) {
			super(sqlType, value);
		}
		
		public String toString() {
			return getValue()==null?"NULL":getValue().toString();
		}
	}
	
	public static void initSqlTemplateDirective(AbstractFreemarkerTemplateConfigurer configurer) {
		configurer.addDirective(new ForeachDirective());
		configurer.addDirective(new DateRangeDirective());
		configurer.addDirective(new StrDirective());
		configurer.addDirective(new WhereDirective());
		configurer.addDirective(new SetDirective());
		
		configurer.addDirective(new DynamicConditionDirective());
	}
	
	/***
	 * for test
	 * @author weishao zeng
	 * @param sql
	 * @param paramSource
	 * @return
	 */
	@Deprecated
	public static ParsedSql parseNamedSql(String sql, SqlParameterSource paramSource) {
		ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
//		String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
//		//Object[] params = NamedParameterUtils.buildValueArray(parsedSql, paramSource, null);
//		List declaredParameters = NamedParameterUtils.buildSqlParameterList(parsedSql, paramSource);
		return parsedSql;
	}
	
	private DbmUtils(){
	}
}