package com.casic.PgInterface.core.hibernate;

import com.casic.PgInterface.core.util.BeanUtils;
import com.casic.PgInterface.core.util.ReflectUtils;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Order;
import org.hibernate.metadata.ClassMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.transaction.Synchronization;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.List;

/**
 * 封装session和jdbcTemplate的基础类，不涉及泛型和CRUD操作.
 * 
 * @author Lingo
 */
@Transactional(rollbackFor = Exception.class)
public class HibernateBasicDao implements ApplicationContextAware {
    /** logger. */
    private static Logger logger = LoggerFactory
            .getLogger(HibernateBasicDao.class);

    /** application context. */
    private ApplicationContext applicationContext;

    /** sessionFactory. */
    private SessionFactory sessionFactory;

    /** jdbcTemplate. */
    private JdbcTemplate jdbcTemplate;

    /** default constructor. */
    public HibernateBasicDao() {
    }

    /**
     * constructor.
     * 
     * @param sessionFactory
     *            SessionFactory
     */
    public HibernateBasicDao(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    /** @return SessionFactory. */
    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    /** @return jdbcTemplate. */
    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    /** @return session. */
    public Session getSession() {
        return this.sessionFactory.getCurrentSession();
    }

    // ============================================================================================
    // autowired
    // ============================================================================================
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        logger.debug("Autowired applicationContext");
        this.applicationContext = applicationContext;
    }

    /**
     * @param sessionFactory
     *            SessionFactory.
     */
    @Resource
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    /**
     * @param jdbcTemplate
     *            JdbcTemplate.
     */
    @Resource
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    // ============================================================================================
    // get, load, getAll, save, remove, removeById, removeAll
    // ============================================================================================

    /**
     * 获得一个实体类型的一条记录.
     * 
     * @param <T>
     *            实体类型
     * @param entityClass
     *            实体类型
     * @param id
     *            主键
     * @return 实例
     */
    @Transactional(readOnly = true)
    public <T> T get(Class<T> entityClass, Serializable id) {
        Assert.notNull(id, "Id can not be null.");

        return (T) this.getSession().get(entityClass, id);
    }

    /**
     * load一个实例，如果id不存在，会返回一个proxy，在调用proxy的时候出现问题. 使用这个方法，可以利用缓存，但是如果实例不存在，会出现不容易预计的错误
     * 
     * @param <T>
     *            实体类型
     * @param entityClass
     *            实体类型
     * @param id
     *            主键
     * @return 实例
     */
    @Transactional(readOnly = true)
    public <T> T load(Class<T> entityClass, Serializable id) {
        Assert.notNull(id, "Id can not be null.");

        return (T) this.getSession().load(entityClass, id);
    }

    /**
     * 获得一个实体类型的所有记录.
     * 
     * @param <T>
     *            实体类型
     * @param entityClass
     *            实体类型
     * @return 所有实例列表
     */
    @Transactional(readOnly = true)
    public <T> List<T> getAll(Class<T> entityClass) {
        return this.getSession().createCriteria(entityClass).list();
    }

    /**
     * 获得所有记录，带排序参数.
     * 
     * @param <T>
     *            实体类型
     * @param entityClass
     *            实体类型
     * @param orderBy
     *            排序字段名
     * @param isAsc
     *            是否正序排列
     * @return 返回结果列表
     */
    @Transactional(readOnly = true)
    public <T> List<T> getAll(Class<T> entityClass, String orderBy,
            boolean isAsc) {
        if (StringUtils.hasText(orderBy)) {
            Criteria criteria = this.getSession().createCriteria(entityClass);

            if (isAsc) {
                criteria.addOrder(Order.asc(orderBy));
            } else {
                criteria.addOrder(Order.desc(orderBy));
            }

            return criteria.list();
        } else {
            return this.getAll(entityClass);
        }
    }

    /**
     * 添加或更新. 相关的操作包括：save, update, saveOrUpdate, merge, persist, refresh
     * 
     * @param entity
     *            实例
     */
    @Transactional
    public void save(Object entity) {
        Assert.notNull(entity, "Entity can not be null.");
        this.getSession().saveOrUpdate(entity);
        logger.debug("save entity: {}", entity);
    }

    /**
     * 删除一条记录.
     * 
     * @param entity
     *            实例
     */
    @Transactional
    public void remove(Object entity) {
        Assert.notNull(entity, "Entity can not be null.");
        this.getSession().delete(entity);
        logger.debug("remove entity: {}", entity);
    }

    /**
     * 根据主键删除记录.
     * 
     * @param <T>
     *            实体类型
     * @param entityClass
     *            实体类型
     * @param id
     *            主键
     */
    @Transactional
    public <T> void removeById(Class<T> entityClass, Serializable id) {
        Assert.notNull(id, "Id can not be null.");
        this.remove(this.load(entityClass, id));
        logger.debug("remove entity by id: {}", id);
    }

    /**
     * 删除集合里的所有记录.
     * 
     * @param list
     *            需要删除的集合
     */
    @Transactional
    public void removeAll(Collection list) {
        Assert.notNull(list, "List can not be null.");

        for (Object obj : list) {
            this.remove(obj);
        }
    }

    /**
     * 删除所有记录.
     * 
     * @param <T>
     *            实体类型
     * @param entityClass
     *            实体类型
     */
    @Transactional
    public <T> void removeAll(Class<T> entityClass) {
        this.removeAll(this.getAll(entityClass));
    }

    // ============================================================================================
    // flush, clear, evict, initialize
    // ============================================================================================
    /** 把session中的数据flush到数据库里. */
    public void flush() {
        this.getSession().flush();
    }

    /** 清空session. */
    public void clear() {
        this.getSession().clear();
    }

    /**
     * 把实体类对象从session中删除.
     * 
     * @param entity
     *            实体类
     */
    public void evict(Object entity) {
        Assert.notNull(entity, "Entity cannot be null");
        this.getSession().evict(entity);
    }

    /**
     * 直接初始化数据，避免出现lazy load错误的一个方法.
     * 
     * @param object
     *            entity
     */
    public void initialize(Object object) {
        Assert.notNull(object, "Object cannot be null");
        Hibernate.initialize(object);
    }

    // ============================================================================================
    // getId, getIdName
    // ============================================================================================
    /**
     * 取得对象的主键值，辅助函数.
     * 
     * @param entityClass
     *            实体类型
     * @param entity
     *            实体对象
     * @param <T>
     *            generic
     * @return 主键
     * @throws NoSuchMethodException
     *             找不到方法
     * @throws IllegalAccessException
     *             没有访问权限
     * @throws java.lang.reflect.InvocationTargetException
     *             反射异常
     */
    public Serializable getId(Class entityClass, Object entity)
            throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException {
        Assert.notNull(entity);

        String idName = getIdName(entityClass);
        String getterName = ReflectUtils.getGetterMethodName(entity, idName);

        return (Serializable) BeanUtils.invokeMethod(entity, getterName);
    }

    /**
     * 取得对象的主键名,辅助函数.
     * 
     * @param entityClass
     *            实体类型
     * @return 主键名称
     */
    public String getIdName(Class entityClass) {
        Assert.notNull(entityClass);
        entityClass = ReflectUtils.getOriginalClass(entityClass);

        ClassMetadata meta = this.getSessionFactory().getClassMetadata(
                entityClass);
        Assert.notNull(meta, "Class " + entityClass
                + " not define in hibernate session factory.");

        String idName = meta.getIdentifierPropertyName();
        Assert.hasText(idName, entityClass.getSimpleName()
                + " has no identifier property define.");

        return idName;
    }

    // ============================================================================================
    // publish event
    // ============================================================================================
    public void registerSynchronization(Synchronization synchronization) {
        SynchronizationNotification synchronizationNotification = new SynchronizationNotification(
                synchronization);
        TransactionSynchronizationManager
                .registerSynchronization(synchronizationNotification);
    }

    public void publishEvent(ApplicationEvent applicationEvent) {
        this.applicationContext.publishEvent(applicationEvent);
    }

    public static class SynchronizationNotification extends
            TransactionSynchronizationAdapter {
        private Synchronization synchronization;

        public SynchronizationNotification(Synchronization synchronization) {
            this.synchronization = synchronization;
        }

        public void afterCompletion(int status) {
            synchronization.afterCompletion(status);
        }

        public void beforeCompletion() {
            synchronization.beforeCompletion();
        }
    }
}
