package com.fitbank.common;

import com.fitbank.common.conectivity.HbSession;
import com.fitbank.common.conectivity.HbSessionAuxiliar;
import com.fitbank.common.exception.FitbankException;
import com.fitbank.common.hb.AbstractExpire;
import com.fitbank.common.hb.AbstractId;
import com.fitbank.common.hb.ManejaHistory;
import com.fitbank.common.helper.FormatDates;
import com.fitbank.common.logger.CallerFinder;
import com.fitbank.common.logger.FitbankLogger;
import com.fitbank.dto.management.Detail;
import com.fitbank.common.properties.PropertiesHandler;
import com.fitbank.hb.persistence.sec.Tsequencebase;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.io.Serializable;
import java.util.List;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.NonUniqueObjectException;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.internal.SessionImpl;
import org.hibernate.transform.Transformers;

/**
 * Helper que almacena Threads de session, transaccion, y FinancialSession
 * utilizadas en el proceso de transacciones
 * financieras.
 *
 * @author Fitbank
 * @version 2.0
 */
public final class Helper {

    /**
     * Almacena una session de Hibernate.
     */
    private static ThreadLocal<Session> threadSession =
            new ThreadLocal<Session>();

    /**
     * Almacena una session de Hibernate auxiliar.
     */
    private static ThreadLocal<Session> threadAuxiliarSession =
            new ThreadLocal<Session>();

    /**
     * Almacena una transaccion de Hibernate.
     */
    private static ThreadLocal<Transaction> threadTransaction =
            new ThreadLocal<Transaction>();

    private static ThreadLocal<Object> context = new ThreadLocal<Object>();

    /**
     * Notifica si la información de la sesión actual ya fue rastreada o no.
     */
    private static ThreadLocal<Boolean> threadTraced = new ThreadLocal<Boolean>();

    private static final Logger LOGGER = FitbankLogger.getLogger();

    private static final String FDESDE = "fdesde";

    private static final String FHASTA = "pk.fhasta";

    private static Exception commonException = new Exception("Excepción genérica Helper");

    private static final Configuration CONFIG = PropertiesHandler.getConfig("persistence");

    /**
     * Permite establecer los datos que se asignaran a la sesion
     */
    private static boolean developerMode = CONFIG.getBoolean("com.fitbank.common.DEVELOPER_MODE", false);

    private Helper() {
    }

    /**
     * Inicia el proceso de una transaccion de hibernate..
     */
    public static void beginTransaction() {
        Transaction tx = Helper.threadTransaction.get();
        if (tx == null) {
            tx = Helper.getSession().beginTransaction();
            Helper.threadTransaction.set(tx);
            LOGGER.debug("BeginTransaction ejecutado desde " + CallerFinder.getCallerName());
        }
    }

    /**
     * Cierra una session de base de datos.
     */
    public static void closeAuxiliarSession() {
        Session s = Helper.threadAuxiliarSession.get();
        if (s != null) {
            try {
                s.clear();
                s.close();

                LOGGER.debug("CloseAuxiliarSession ejecutado desde " + CallerFinder.getCallerName());
            } catch (Exception ex) {
                LOGGER.debug("No se pudo cerrar la sesion", ex);
            } finally {
                Helper.threadAuxiliarSession.set(null);
            }
        }
    }

    /**
     * Cierra una session de base de datos.
     */
    public static void closeSession() {
        Session s = Helper.threadSession.get();
        if (s != null) {
            try {
                s.clear();
                s.close();

                HbSession.getInstance().updateStatsValue(HbSession.CLOSED);
                HbSession.getInstance().traceStatistics("Sesion cerrada");
                LOGGER.debug("CloseSession ejecutado desde " + CallerFinder.getCallerName());
            } catch (Exception ex) {
                LOGGER.debug("No se pudo cerrar la sesion", ex);
            } finally {
                Helper.threadTransaction.set(null);
                Helper.threadSession.set(null);
            }
        }
    }

    /**
     * Graba los dml que estan en la transaccion.
     */
    public static void commitTransaction() {
        Transaction tx = Helper.threadTransaction.get();
        if (tx == null) {
            tx = getSession().getTransaction();
        }

        boolean successfulCommit = false;
        try {
            if (tx == null) {
                throw new FitbankException("HB003", "NO EXISTE UNA TRANSACCION INICIADA");
            } else if(tx.wasCommitted()) {
                throw new FitbankException("HB003", "TRANSACCION YA APLICADA");
            } else if(tx.wasRolledBack()) {
                throw new FitbankException("HB003", "TRANSACCION YA APLICADO ROLLBACK");
            } else {
                if (tx.isActive() || tx.isParticipating()) {
                    tx.commit();
                }

                Helper.getSession().clear();

                successfulCommit = true;
                LOGGER.debug("Commit ejecutado desde " + CallerFinder.getCallerName());
            }
        } finally {
            if (successfulCommit) {
                Helper.threadTransaction.set(null);
            }
        }
    }

    /**
     * Completa la compañía para los beans que al hacer update o save el valor
     * de la compañía llega en null desde el formulario.
     *
     * @param pBean Bean a verificar el valor de la compañía.
     */
    public static void completeCompany(TransportBean pBean) {
        try {
            Object cia = BeanManager.getBeanAttributeValue(pBean,
                    "pk.cpersona_compania");
            if (cia == null) {
                BeanManager.setBeanAttributeValue(pBean, "pk.cpersona_compania",
                        RequestData.getDetail().getCompany());
            }
        } catch (Exception ex) {
            LOGGER.debug("Bean " + pBean.getClass().getName() + " no tiene compañía");
        }
    }

    public static SQLQuery createSQLQuery(String pSQL) {
        return Helper.createSQLQuery(Helper.getSession(), pSQL);
    }

    /**
     * @param s
     * @param pSQL
     *
     * @return
     */
    public static SQLQuery createSQLQuery(Session s, String pSQL) {
        SessionFactoryImpl imp = (SessionFactoryImpl) s.getSessionFactory();
        Dialect dialect = imp.getDialect();
        if (dialect instanceof SQLServerDialect) {
            pSQL = pSQL.replaceAll("\\|\\|", "+");
        }
        return s.createSQLQuery(pSQL);
    }

    /**
     * Elimina un registro en la base de datos.
     *
     * @param pClassName Nombre de la clase que maneja la persistencia de una
     * tabla.
     * @param pBean Bean a actualiar.
     */
    public static void delete(String pClassName, TransportBean pBean) {
        Session s = Helper.getSession();
        s.delete(pClassName, pBean);
        LOGGER.debug("Delete ejecutado desde " + CallerFinder.getCallerName());
    }

    /**
     * Elimina un registro en la base de datos.
     *
     * @param pBean Bean a actualiar.
     */
    public static void delete(TransportBean pBean) {
        Session s = Helper.getSession();
        s.delete(pBean);
        LOGGER.debug("Delete ejecutado desde " + CallerFinder.getCallerName());
    }

    /**
     * Caduca registro persistente.
     *
     * @param currentBean Bean a caducar.
     *
     * @throws Exception
     */
    public static void expire(TransportBean currentBean) throws Exception {
        Session s = Helper.getSession();
        if (Helper.hasChildRecords(currentBean)) {
            throw new FitbankException("HB016", "EXISTEN REGISTROS DEPENDIENTES. "
                    + "NO SE PUEDE CADUCAR", commonException);
        }

        Object pk = BeanManager.getBeanAttributeValue(currentBean, "pk");
        if (pk instanceof ManejaHistory 
                && currentBean instanceof AbstractExpire) {
            AbstractExpire expiredBean = (AbstractExpire) currentBean.cloneMe();
            Timestamp expireTimestamp = ApplicationDates.getDBTimestamp();
            BeanManager.setBeanAttributeValue(expiredBean, FHASTA, expireTimestamp);

            try {
                BeanManager.setBeanAttributeValue(expiredBean, "pk.fparticion",
                        FormatDates.formatFPartition(expireTimestamp));
            } catch (Exception e1) {
                //Si no tiene el campo fparticion, no hacer nada.
            }

            Helper.saveHistory(expiredBean);
        }

        s.delete(currentBean);
        LOGGER.debug("Expire ejecutado desde " + CallerFinder.getCallerName());
    }

    /**
     * @param s Contenido del clob
     * @return Un clob
     */
    public static Clob createClob(String s) {
        return Hibernate.getLobCreator(getSession()).createClob(s);
    }

    /**
     * @param bytes Contenido del blob
     * @return un Blob
     */
    public static Blob createBlob(byte[] bytes) {
        return Hibernate.getLobCreator(getSession()).createBlob(bytes);
    }

    /**
     * Metodo que verifica si un registro tiene registros secundarios
     * 
     * @param pBean Bean a verificar
     * @return true si tienes childRecords false caso contrario.
     */
    public static boolean hasChildRecords(TransportBean pBean) {
        if (!(pBean instanceof AbstractId)) {
            return false;
        }

        boolean doControl = CONFIG.getBoolean(Helper.class.getName() + ".CHILD_RECORDS_CONTROL");
        if (!doControl) {
            return false;
        }

        List tablesToIgnore = CONFIG.getList(Helper.class.getName() + ".CHILD_RECORDS_TABLE_TO_IGNORE");
        String tableName = HbSession.getInstance().getTableName(pBean.getClass());

        if (!tablesToIgnore.isEmpty() && tablesToIgnore.contains(tableName)) {
            LOGGER.debug("Ignorando tabla " + tableName + " para el control de registros secundarios");
            return false;
        }

        boolean hasChildRecords = false;
        Session s = HbSession.getInstance().forceOpenSession();
        Transaction tr = s.beginTransaction();

        LOGGER.debug("Verificando existencia de registros secundarios antes de caducar...");
        Helper.completeCompany(pBean);

        try {
            //Carga la clase ID
            Class<?> idClass = Class.forName(pBean.getClass().getName() + "id");
            TransportBean tb = (TransportBean) idClass.newInstance();

            //Armar el objeto de la clase ID con los valores PK del bean
            Object id = tb.createInstance();
            List<String> l = HbSession.getInstance().getFieldsPrimaryKey(idClass);

            if (l.size() == 1) {
                Object value = BeanManager.getBeanAttributeValue(pBean,
                        "pk." + l.get(0).toLowerCase());
                BeanManager.setBeanAttributeValue(id, "pk", value);
            } else {
                for (String pkField : l) {
                    Object value = BeanManager.getBeanAttributeValue(pBean, "pk."
                            + pkField.toLowerCase());
                    BeanManager.setBeanAttributeValue(id, "pk." + pkField.toLowerCase(),
                            value);
                }
            }

            //Carga el beanID de la sesion
            Object obj = null;
            try {
                obj = s.get(idClass, (Serializable) BeanManager.
                        getBeanAttributeValue(id, "pk"));
            } catch (Exception ex) {
                LOGGER.error("Error obteniendo objeto de la clase ID", ex);
            }

            //Si el beanID existe, intentar eliminarlo para la verificacion
            if (obj != null) {
                //Eliminar primero los valores de la tabla principal
                String delHql = "delete from " + pBean.getClass().getName()
                        + " o where";
                boolean first = true;
                for (String pkField : l) {
                    pkField = pkField.toLowerCase();
                    if ("fhasta".equals(pkField)) {
                        continue;
                    }

                    Object value = BeanManager.getBeanAttributeValue(pBean, "pk." + pkField);

                    if (first) {
                        delHql = delHql.concat(" o.pk." + pkField + "=\'" + value + "\'");
                        first = false;
                    } else {
                        delHql = delHql.concat(" and o.pk." + pkField + "=\'" + value + "\'");
                    }
                }

                s.createQuery(delHql).executeUpdate();

                //Intentar eliminar el PK del beanID
                s.delete(obj);

                //Esta sentencia deberia lanzar una excepcion, si el PK tiene registros secundarios
                s.flush();
            }
        } catch (ClassNotFoundException e) {
            LOGGER.debug("El bean " + pBean.getClass().getName() + " no posee un BeanId");
        } catch (IllegalAccessException e) {
            LOGGER.error("Errores al crear el objeto BeanId", e);
        } catch (Exception e) {
            hasChildRecords = doControl;
            commonException = e;
            LOGGER.error("Registros secundarios encontrados en " + pBean.getClass().getName(), e);
        } finally {
            tr.rollback();
            s.clear();
            s.close();
        }

        return hasChildRecords;
    }

    /**
     * Graba los dml que estan en la transaccion.
     */
    public static void flushTransaction() {
        Helper.getSession().flush();
        LOGGER.debug("FlushTransaction ejecutado desde " + CallerFinder.getCallerName());
    }

    public static Session getAuxiliarSession() {
        LOGGER.debug("Obteniendo sesión auxiliar, llamado desde " + CallerFinder.getCallerName());
        Session s = Helper.threadAuxiliarSession.get();
        if (s == null) {
            throw new FitbankException("HB002",
                    "NO EXISTE UNA SESION AUXILIAR ABIERTA PARA EL THREAD ACTUAL");
        }

        //Si la sesion no esta abierta para el thread actual, abrir una nueva
        if (!s.isOpen()) {
            try {
                Helper.threadAuxiliarSession.set(null);
                s = HbSessionAuxiliar.getInstance().openSession();
                Helper.threadAuxiliarSession.set(s);
            } catch (Exception e) {
            throw new FitbankException("HB002",
                    "NO EXISTE UNA SESION AUXILIAR ABIERTA PARA EL THREAD ACTUAL", e);
            }
        }

        return s;
    }

    /**
     * Obtiene una referencia a un determinado Bean dado su PK
     *
     * @param <T> Tipo de bean a devolver
     * @param pType tipo de Bean
     * @param pPk Referencia a la clave Primaria.
     * @param proyecciones Campos que se consultarán en forma parcial, en caso de no especificar se consultarán todos.
     *
     * @return Bean Requerido
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(Class<T> pType, Serializable pPk, String... proyecciones) {
        if (proyecciones.length > 0) {
            Criteria criteria = Helper.getSession().createCriteria(pType).add(Restrictions.eq("pk", pPk));

            ProjectionList projectionList = Projections.projectionList();
            for (String proyeccion : proyecciones) {
                // Para evitar mantener beanes parciales
                if ("versioncontrol".equalsIgnoreCase(proyeccion)) {
                    throw new FitbankException("HB009", "NO SE PUEDE CONSULTAR VERSIONCONTROL");
                }
                projectionList.add(Projections.property(proyeccion), proyeccion);
            }
            criteria.setProjection(projectionList).setResultTransformer(Transformers.aliasToBean(pType));

            return (T) criteria.uniqueResult();
        } else {
            return (T) Helper.getSession().get(pType, pPk);
        }
    }

    /**
     * Obtiene una referencia a un determinado Bean dado su PK
     *
     * @param <T> Tipo de bean a devolver
     * @param pType tipo de Bean
     * @param pPk Referencia a la clave Primaria.
     *
     * @return Bean Requerido
     */
    public static <T> T getBean(Class<T> pType, Serializable pPk) {
        return (T) Helper.getSession().get(pType, pPk);
    }

    /**
     * Obtiene una referencia a un determinado Bean dado su PK Permitiendo la
     * Opcion de bloquear la cuenta
     *
     * @param <T> Tipo de bean a devolver
     * @param pType tipo de Bean
     * @param pPk Referencia a la clave Primaria
     * @param pLockMode tipo de bloqueo del Registro
     *
     * @return Bean Requerido
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(Class<T> pType, Serializable pPk,
            LockMode pLockMode) {
        return (T) Helper.getSession().get(pType, pPk, new LockOptions(pLockMode));
    }

    public static Object getContext() {
        return Helper.context.get();
    }

    /**
     * Retorna una session de hibernate.
     *
     * @return s
     */
    public static Session getSession() {
        LOGGER.debug("Obteniendo sesión, llamado desde " + CallerFinder.getCallerName());
        Session s = Helper.threadSession.get();
        if (s == null) {
            throw new FitbankException("HB002",
                    "NO EXISTE UNA SESION ABIERTA PARA EL THREAD ACTUAL");
        }

        //Si la sesion no esta abierta para el thread actual, abrir una nueva
        if (!s.isOpen()) {
            Helper.threadSession.set(null);
            s = HbSession.getInstance().openSession();
            Helper.threadSession.set(s);
        }

        return s;
    }

    /**
     * Retorna la conexion JDBC asociada a una sesion.
     * @param pSession
     * @return 
     */
    public static Connection getConnection(Session pSession) {
        LOGGER.debug("Obteniendo conexión mediante una sesión, llamado desde " + CallerFinder.getCallerName());
        return ((SessionImpl) pSession).connection();
    }

    /**
     * Retorna la conexion JDBC asociada a la sesion actual.
     * @return 
     */
    public static Connection getConnection() {
        LOGGER.debug("Obteniendo conexión, llamado desde " + CallerFinder.getCallerName());
        return ((SessionImpl) Helper.getSession()).connection();
    }

    /**
     * Retorna verdadero si a la transacci�n se dio un commit;
     *
     * @return boolean
     */
    public static boolean isCommitted() {
        Transaction tx = Helper.threadTransaction.get();
        return tx != null && tx.wasCommitted();
    }

    /**
     * Retorna verdadero si a la transacci�n se dio un rollback;
     *
     * @return boolean
     */
    public static boolean isRolledBack() {
        Transaction tx = Helper.threadTransaction.get();
        return tx != null && tx.wasRolledBack();
    }

    /**
     * Retorna verdadero si la sesion aun tiene una transaccion activa
     *
     * @return boolean
     */
    public static boolean isTransactionActive() {
        Transaction tx = Helper.threadTransaction.get();
        if (tx == null) {
            tx = Helper.getSession().getTransaction();
        }

        return tx != null && (tx.isActive() || tx.isParticipating());
    }

    /**
     * Retorna verdadero si existe una sesion abierta en el thread actual
     * 
     * @return boolean
     */
    public static boolean isSessionOpened() {
        boolean isSessionOpened;
        try {
            isSessionOpened = Helper.getSession().isOpen() 
                    && Helper.getSession().isConnected();
        } catch (Exception e) {
            //Sesion no abierta en el thread actual
            isSessionOpened = false;
        }

        return isSessionOpened;
    }

    /**
     * Manejo de historia de registros, inserta un registro con los valores
     * anteriores y actualiza el registro vigente
     * con los valores nuevos.
     *
     * @param pBean Bean a crear historia.
     * @param caducar Indica si se caduca un registro o se maneja como historia.
     *
     * @return bean a insertar, actualizar o eliminar.
     *
     * @throws Exception
     */
    private static TransportBean manageHistory(TransportBean pBean) throws Exception {
        return manageHistory(pBean, null);
    }

    /**
     * Manejo de historia de registros, inserta un registro con los valores
     * anteriores y actualiza el registro vigente
     * con los valores nuevos.
     *
     * @param pBean Bean a crear historia.
     * @param old El bean antiguo o null para consultarlo de la base de datos.
     *
     * @return bean a insertar, actualizar o eliminar.
     *
     * @throws Exception
     */
    private static TransportBean manageHistory(TransportBean pBean, AbstractExpire old) throws Exception {
        Session s = Helper.getSession();
        Object id = BeanManager.getBeanAttributeValue(pBean, "pk");

        if (!(id instanceof ManejaHistory) || !(pBean instanceof AbstractExpire)) {
            return pBean;
        }

        s.evict(pBean);

        TransportBean bean = (TransportBean) pBean.cloneMe();
        AbstractExpire cad = (AbstractExpire) pBean.cloneMe();

        if (s.contains(cad)) {
            s.evict(cad);
        }

        if (old == null) {
            old = (AbstractExpire) s.get(pBean.getClass(), (Serializable) ((AbstractExpire) pBean).getId());
        }

        if (old != null) {
            s.evict(old);
        }

        Timestamp t = ApplicationDates.getDBTimestamp();
        BeanManager.setBeanAttributeValue(bean, Helper.FDESDE, t);
        if (old == null) {
            BeanManager.setBeanAttributeValue(bean, Helper.FHASTA,
                    ApplicationDates.DEFAULT_EXPIRY_TIMESTAMP);
        } else if (!BeanManager.compareObject(bean, old)) {
            ManejaHistory pk = (ManejaHistory) old.getId();
            pk.setFhasta(t);
            try {
                BeanManager.setBeanAttributeValue(pk, "fparticion",
                        FormatDates.formatFPartition(t));
            } catch (Exception e1) {
                //Si no existe el campo fparticion, no hacer nada.
            }

            try {
                Helper.saveHistory(old);
            } catch (NonUniqueObjectException e) {
                t = ApplicationDates.getDBTimestamp();
                pk = (ManejaHistory) old.getId();
                pk.setFhasta(t);
                Helper.saveHistory(old);
            }
        } else {
            return null;
        }

        return bean;
    }

    private static void saveHistory(AbstractExpire transport) throws Exception {
        Session s = Helper.getSession();
        if (Helper.getContext() == null) {
            s.save(transport);
        } else {
            s.persist(transport);
        }
    }

    private static void manageIdTables(TransportBean pBean) throws Exception {
        LOGGER.debug(pBean.getClass());
        if (pBean instanceof AbstractId) {
            Class<?> idClass = Class.forName(pBean.getClass().getName() + "id");
            TransportBean tb = (TransportBean) idClass.newInstance();
            Object id = tb.createInstance();
            List<String> l =
                    HbSession.getInstance().getFieldsPrimaryKey(idClass);
            if (l.size() == 1) {
                BeanManager.setBeanAttributeValue(id, "pk",
                        BeanManager.getBeanAttributeValue(pBean, "pk."
                        + l.get(0).toLowerCase()));
            } else {
                for (String pkField : l) {
                    pkField = pkField.toLowerCase();
                    BeanManager.setBeanAttributeValue(id, "pk." + pkField,
                            BeanManager.getBeanAttributeValue(pBean, "pk."
                            + pkField));
                }
            }
            Session s = Helper.getSession();
            Object obj = s.get(idClass, (Serializable) BeanManager.
                    getBeanAttributeValue(id, "pk"));
            if (obj == null) {
                if (Helper.getContext() == null) {
                    s.save(id);
                } else {
                    s.persist(id);
                }
            }
        }
    }

    /**
     * @param pSecuence
     *
     * @return
     *
     * @throws Exception
     */
    public static Integer nextValue(String pSecuence) throws Exception {
        return Helper.nextValue(pSecuence, null).intValue();
    }

    /**
     * @param pSecuence
     *
     * @return
     *
     * @throws Exception
     */
    public static Long nextLongValue(String pSecuence) throws Exception {
        return Helper.nextValue(pSecuence, null);
    }

    /**
     * Mï¿½todo que obtiene el valor siguiente de una secuencia
     *
     * @param pSecuence - Nombre de la Secuencia
     *
     * @return Valor de la secuencia
     *
     * @throws java.lang.Exception
     */
    private static Long nextValue(String pSecuence, Session pSession) throws
            Exception {
        HbSession hb = HbSession.getInstance();
        Dialect dialect = hb.getDialect();
        if (dialect instanceof SQLServerDialect) {
            return Helper.sqlServerSequence(pSecuence);
        }
        String sSql = dialect.getSequenceNextValString(pSecuence);
        if ((sSql.indexOf("nextval") > -1) && (sSql.indexOf('.') < 0)) {
            sSql = "select nextval for " + pSecuence + " from sysibm.sysdummy1";
        }
        Session session = pSession != null ? pSession : Helper.getSession();
        PreparedStatement sql = Helper.getConnection(session).prepareStatement(sSql);
        ResultSet rst = null;
        long val = 0l;
        try {
            rst = sql.executeQuery();
            if (rst.next()) {
                val = rst.getLong(1);
            }
        } finally {
            if (rst != null) {
                rst.close();
            }
            sql.close();
        }

        return val;
    }

    /**
     * M�todo que obtiene informaci�n de la tabla TSECUENCIASBASE para obetener
     * el valor de una secuencia
     *
     * @param pSequence - Nombre de la secuencia
     *
     * @return - Valor de la Secuencia
     *
     * @throws java.lang.Exception
     */
    private static Long sqlServerSequence(String pSequence) throws Exception {
        Session s = HbSession.getInstance().openSession();
        try {
            Transaction trn = s.beginTransaction();
            Tsequencebase sequence = (Tsequencebase) s.get(Tsequencebase.class,
                    pSequence, LockOptions.UPGRADE);
            if (sequence == null) {
                throw new FitbankException("BDD", "LA SECUENCIA {0} NO EXISTE",
                        pSequence);
            }
            Long value = sequence.getConsecutivo();
            if (value == sequence.getNumeromaximo()) {
                if (sequence.getCiclica().compareTo("1") == 0) {
                    value = sequence.getNumeroinicial();
                } else {
                    throw new FitbankException("BDD",
                            "LA SECUENCIA {0} HA ALCANZADO SU VALOR MAXIMO",
                            pSequence);
                }
            }
            sequence.setConsecutivo(value + sequence.getIncremento());
            s.saveOrUpdate(sequence);
            trn.commit();
            LOGGER.debug("Commit ejecutado desde " + CallerFinder.getCallerName());
            return value.longValue();
        } finally {
            s.close();
        }

    }

    /**
     * Obtiene el bean correspondiente al anterior al registro vigente, siempre
     * y cuando el bean maneje historia o sea
     * un bean de identificaci�n
     *
     * @param pBean TransportBean
     *
     * @return TransportBean
     *
     * @throws Exception
     */
    public static TransportBean previousBean(TransportBean pBean) throws
            Exception {
        TransportBean cloneBean = (TransportBean) pBean.cloneMe();
        TransportBean previousBean = null;
        Object id = BeanManager.getBeanAttributeValue(cloneBean, "pk");
        if ((id instanceof ManejaHistory)
                && (cloneBean instanceof AbstractExpire)) {
            try {
                BeanManager.setBeanAttributeValue(cloneBean, Helper.FHASTA,
                        BeanManager.getBeanAttributeValue(cloneBean,
                        Helper.FDESDE));
                Session s = Helper.getSession();
                previousBean = (TransportBean) s.get(cloneBean.getClass(),
                        (Serializable) id);
            } catch (Exception e) {
                LOGGER.debug("Errores al obtener el registro anterior de un bean", e);
            }
        }
        return previousBean;
    }

    /**
     * Rollback de los dml que estan dentro de la transaccion.
     */
    public static void rollbackTransaction() {
        Transaction tx = Helper.threadTransaction.get();
        if (tx == null) {
            tx = getSession().getTransaction();
        }

        boolean rollbackSuccessful = false;
        try {
            if (tx == null) {
                throw new FitbankException("HB003", "NO EXISTE UNA TRANSACCION INICIADA");
            } else if(tx.wasCommitted()) {
                throw new FitbankException("HB003", "TRANSACCION YA APLICADA");
            } else if(tx.wasRolledBack()) {
                throw new FitbankException("HB003", "TRANSACCION YA APLICADO ROLLBACK");
            } else {
                if (tx.isActive() || tx.isParticipating()) {
                    tx.rollback();
                }

                Helper.getSession().clear();

                rollbackSuccessful = true;
                LOGGER.debug("Rollback ejecutado desde " + CallerFinder.getCallerName());
            }
        } finally {
            if (rollbackSuccessful) {
                Helper.threadTransaction.set(null);
            }
        }
    }

    /**
     * Inserta un registro en la base de datos.
     *
     * @param pClassName Nombre de la clase que maneja la persistencia de una
     * tabla.
     * @param pBean Bean a insertar.
     *
     * @throws Exception
     */
    public static void save(String pClassName, TransportBean pBean) throws
            Exception {
        Session s = Helper.getSession();
        Object id = BeanManager.getBeanAttributeValue(pBean, "pk");
        if ((id instanceof ManejaHistory) && (pBean instanceof AbstractExpire)) {
            s.evict(pBean);
            Timestamp t = ApplicationDates.getDBTimestamp();
            BeanManager.setBeanAttributeValue(pBean, Helper.FDESDE, t);
            t = ApplicationDates.DEFAULT_EXPIRY_TIMESTAMP;
            BeanManager.setBeanAttributeValue(pBean, Helper.FHASTA, t);
        }
        Helper.completeCompany(pBean);
        Helper.manageIdTables(pBean);
        if (Helper.getContext() == null) {
            s.save(pClassName, pBean);
        } else {
            s.persist(pClassName, pBean);
        }
        LOGGER.debug("Save ejecutado desde "
                + CallerFinder.getCallerName()
                + " " + pClassName + " " + pBean);
    }

    public static void save(TransportBean pBean) throws Exception {
        Session s = Helper.getSession();
        Object id = BeanManager.getBeanAttributeValue(pBean, "pk");
        if ((id instanceof ManejaHistory) && (pBean instanceof AbstractExpire)) {
            s.evict(pBean);
            Timestamp t = ApplicationDates.getDBTimestamp();
            BeanManager.setBeanAttributeValue(pBean, Helper.FDESDE, t);
            t = ApplicationDates.DEFAULT_EXPIRY_TIMESTAMP;
            BeanManager.setBeanAttributeValue(pBean, Helper.FHASTA, t);
        }
        Helper.completeCompany(pBean);
        Helper.manageIdTables(pBean);
        s.save(pBean);
        LOGGER.debug("Save ejecutado desde "
                + CallerFinder.getCallerName()
                + " " + pBean.getClass().getName() + " " + pBean);
    }

    /**
     * Actualiza o inserta un registro en la base de datos.
     *
     * @param pClassName Nombre de la clase que maneja la persistencia de una
     * tabla.
     * @param pBean Bean a actualiar o insertar.
     *
     * @throws Exception
     */
    public static void saveOrUpdate(String pClassName, TransportBean pBean)
            throws Exception {
        Session s = Helper.getSession();
        TransportBean bean = Helper.manageHistory(pBean);
        if (bean != null) {
            Helper.completeCompany(pBean);
            Helper.manageIdTables(pBean);
            if (Helper.getContext() == null) {
                s.saveOrUpdate(pClassName, bean);
            } else {
                s.persist(pClassName, bean);
            }
            LOGGER.debug("SaveOrUpdate  ejecutado desde " + CallerFinder.
                    getCallerName() + " " + pClassName + " "
                    + pBean);
        }
    }

    /**
     * Actualiza o inserta un registro en la base de datos.
     *
     * @param pBean Bean a actualiar o insertar.
     *
     * @throws Exception
     */
    public static void saveOrUpdate(TransportBean pBean) throws Exception {
        Session s = Helper.getSession();
        TransportBean bean = Helper.manageHistory(pBean);
        if (bean != null) {
            Helper.completeCompany(pBean);
            Helper.manageIdTables(pBean);
            if (Helper.getContext() == null) {
                s.saveOrUpdate(bean);
            } else {
                s.persist(bean);
            }
            LOGGER.debug("SaveOrUpdate  ejecutado desde " + CallerFinder.
                getCallerName() + " "
                + pBean.getClass().getName() + " " + pBean);
        }
    }

    /**
     * Fija una session de hibernate.
     *
     * @param session
     */
    public static void setAuxiliarSession(Session session) {
        Session currentAuxSession = Helper.threadAuxiliarSession.get();
        if (currentAuxSession != null && currentAuxSession.isOpen()) {
            LOGGER.warn("Usando sesión auxiliar actual, cerrando la nueva sesión auxiliar");
            session.close();
            Helper.threadAuxiliarSession.set(currentAuxSession);
        } else {
            Helper.threadAuxiliarSession.set(session);
        }
    }

    public static void setContext(Object pCtx) {
        Helper.context.set(pCtx);
    }

    /**
     * Fija una session de hibernate.
     *
     * @param session
     */
    public static void setSession(Session session) {
        Session currentSession = Helper.threadSession.get();
        if (currentSession != null && currentSession.isOpen()) {
            LOGGER.warn("Usando sesión actual, cerrando la nueva sesión, "
                    + "llamado desde " + CallerFinder.getCallerName());
            session.close();
            HbSession.getInstance().updateStatsValue(HbSession.CLOSED);
            Helper.threadSession.set(currentSession);
        } else {
            Helper.threadSession.set(session);
        }
    }

    /**
     * Actualiza un registro en la base de datos.
     *
     * @param pClassName Nombre de la clase que maneja la persistencia de una
     * tabla.
     * @param pBean Bean a actualiar.
     *
     * @throws Exception
     */
    public static void update(String pClassName, TransportBean pBean) throws Exception {
        update(pClassName, pBean, null);
    }

    /**
     * Actualiza un registro en la base de datos.
     *
     * @param pClassName Nombre de la clase que maneja la persistencia de una
     * tabla.
     * @param pBean Bean a actualiar.
     *
     * @throws Exception
     */
    public static void update(String pClassName, TransportBean pBean, AbstractExpire old) throws Exception {
        Session s = Helper.getSession();
        TransportBean bean = Helper.manageHistory(pBean, old);
        if (bean != null) {
            if (Helper.getContext() == null) {
                s.update(pClassName, bean);
            } else {
                s.persist(pClassName, bean);
            }
            LOGGER.debug("Update  ejecutado desde "
                + CallerFinder.getCallerName() + " " + pClassName + " " + pBean);
        }
    }

    /**
     * Metodo que agrega identificadores de una sesion, para procesos de trazabilidad
     *
     * @param session Sesion de base de datos
     * @param clientIdentifier Identificador del cliente
     * @param clientInfo Informacion del cliente conectado
     * @param module Modulo usado en la operacion
     * @param action Accion realizada
     */
    public static void setSessionInfo(Session session, String clientIdentifier, String clientInfo, String module,
                                      String action) {
        if (!developerMode) {
            SessionFactoryImpl imp = (SessionFactoryImpl) session.getSessionFactory();
            Dialect dialect = imp.getDialect();
            if (!dialect.getClass().getSimpleName().startsWith("Oracle")) {
                LOGGER.warn("No se pueden establecer parametros de sesion a una sesion diferente de oracle");
                return;
            }
            //Verificar que ningun campo este con valores nulos, en cuyo caso, poner un default
            //TODO: Cuando usemos commons-lang>=2.6, cambiar por StringUtils.defaultIsBlank
            clientIdentifier = StringUtils.defaultString(clientIdentifier, "Fitvank-V2.0");
            clientInfo = StringUtils.defaultString(clientInfo, Thread.currentThread().getName());
            module = StringUtils.defaultString(module, Thread.currentThread().getName());
            action = StringUtils.defaultString(action, Thread.currentThread().getName());
            try {
                Connection conn = ((SessionImpl) session).connection();
                //Informacion del cliente que genera la accion
                CallableStatement cs = conn.prepareCall("{ call dbms_session.set_identifier(?) } ");
                cs.setString(1, clientIdentifier);
                cs.execute();
                cs.close();
                //Informacion del cliente que genera la accion como extra
                cs = conn.prepareCall("{ call dbms_application_info.set_client_info(?) } ");
                cs.setString(1, clientInfo);
                cs.execute();
                cs.close();
                //Informacion del modulo y la accion usada
                cs = conn.prepareCall("{ call sys.dbms_application_info.set_module(?, ?) }");
                cs.setString(1, module);
                cs.setString(2, action);
                cs.execute();
                cs.close();
            } catch (SQLException e) {
                LOGGER.error("Problemas al agregar informacion de la sesion", e);
            }
        }
    }

    /**
     * Metodo que agrega identificadores de una sesion, para procesos de trazabilidad
     * 
     * @param clientIdentifier Identificador del cliente
     * @param clientInfo Informacion del cliente conectado
     * @param module Modulo usado en la operacion
     * @param action Accion realizada
     */
    public static void setSessionInfo(String clientIdentifier, String clientInfo, 
            String module, String action) {
        Session session = null;
        try {
            session = Helper.getSession();
        } catch (FitbankException fe) {
            //Esto indica que no existe aún una sesión abierta.
            //En este caso, aun no podemos establecer datos en la misma, se omite
        }

        if (session != null) {
            Helper.setSessionInfo(session, clientIdentifier, clientInfo, module, action);
        }
    }

    /**
     * Metodo que agrega identificadores de una sesion, para procesos de trazabilidad
     * 
     * @param clientInfo Informacion del cliente conectado
     * @param module Modulo usado en la operacion
     * @param action Accion realizada
     */
    public static void setSessionInfo(String clientInfo, String module, String action) {
        Helper.setSessionInfo(clientInfo, clientInfo, module, action);
    }

    /**
     * Metodo que agrega identificadores de una sesion, para procesos de trazabilidad
     * 
     * @param module Modulo usado en la operacion
     * @param action Accion realizada
     */
    public static void setSessionInfo(String module, String action) {
        Helper.setSessionInfo("Fitbank-V2.0", Thread.currentThread().getName(), 
                module, action);
    }

    /**
     * Metodo que agrega identificadores de una sesion, para procesos de trazabilidad.
     * Se usa el detail del RequestData, en caso de haberlo.
     * @param detail Mensaje de entrada de la sesion en curso
     */
    public static void setSessionInfo(Detail detail) {
        if (Helper.threadTraced.get() == null || !Helper.threadTraced.get()) {
            if (detail != null) {
                Helper.setSessionInfo(detail.getUser(), detail.getMessageId(),
                        detail.getIpaddress(), detail.getSubsystem()
                                .concat(detail.getTransaction()));
                Helper.threadTraced.set(Boolean.TRUE);
            } else {
                Helper.setSessionInfo("Fitbank-V2.0", Thread.currentThread().getName(),
                        Thread.currentThread().getName(), Thread.currentThread().getName());
            }
        }
    }

    /**
     * Metodo que agrega identificadores de una sesion, para procesos de trazabilidad.
     * Se usa el detail del RequestData, en caso de haberlo.
     */
    public static void setSessionInfo() {
        Detail detail = RequestData.getDetail();
        Helper.setSessionInfo(detail);
    }

    /**
     * Actualiza un registro en la base de datos.
     *
     * @param pBean Bean a actualiar.
     *
     * @throws Exception
     */
    public static void update(TransportBean pBean) throws Exception {
        update(pBean, null);
    }

    /**
     * Actualiza un registro en la base de datos.
     *
     * @param pBean Bean a actualiar.
     *
     * @throws Exception
     */
    public static void update(TransportBean pBean, AbstractExpire old) throws Exception {
        Session s = Helper.getSession();
        TransportBean bean = Helper.manageHistory(pBean, old);
        if (bean != null) {
            if (Helper.getContext() == null) {
                s.update(bean);
            } else {
                s.persist(bean);
            }
            LOGGER.debug("Update  ejecudado desde " + CallerFinder.
                getCallerName() + " " + pBean.getClass().getName() + " " + pBean);
        }
    }

}
