com.xpn.xwiki.store.XWikiHibernateBaseStore.java Source code

Java tutorial

Introduction

Here is the source code for com.xpn.xwiki.store.XWikiHibernateBaseStore.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package com.xpn.xwiki.store;

import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.inject.Inject;
import javax.inject.Named;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.connection.ConnectionProvider;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.jdbc.BorrowedConnectionProxy;
import org.hibernate.jdbc.ConnectionManager;
import org.hibernate.jdbc.Work;
import org.hibernate.mapping.Table;
import org.hibernate.tool.hbm2ddl.DatabaseMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.component.phase.Initializable;
import org.xwiki.component.phase.InitializationException;
import org.xwiki.context.Execution;
import org.xwiki.logging.LoggerManager;

import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.monitor.api.MonitorPlugin;
import com.xpn.xwiki.objects.classes.BaseClass;
import com.xpn.xwiki.store.hibernate.HibernateSessionFactory;
import com.xpn.xwiki.store.migration.DataMigrationManager;
import com.xpn.xwiki.util.Util;
import com.xpn.xwiki.web.Utils;
import com.xpn.xwiki.objects.PropertyInterface;
import com.xpn.xwiki.objects.ListProperty;

public class XWikiHibernateBaseStore implements Initializable {
    private static final Logger LOGGER = LoggerFactory.getLogger(XWikiHibernateBaseStore.class);

    private Map<String, String> connections = new ConcurrentHashMap<String, String>();

    private int nbConnections = 0;

    /** LoggerManager to suspend logging during normal faulty SQL operation. */
    @Inject
    protected LoggerManager loggerManager;

    @Inject
    private HibernateSessionFactory sessionFactory;

    @Inject
    @Named("hibernate")
    private DataMigrationManager dataMigrationManager;

    /** Need to get the xcontext to get the path tho the hibernate.cfg.xml. */
    @Inject
    private Execution execution;

    private String hibpath = "/WEB-INF/hibernate.cfg.xml";

    /**
     * Key in XWikiContext for access to current hibernate database name.
     */
    private static String currentDatabaseKey = "hibcurrentdatabase";

    private DatabaseProduct databaseProduct = DatabaseProduct.UNKNOWN;

    /**
     * THis allows to initialize our storage engine. The hibernate config file path is taken from xwiki.cfg or directly
     * in the WEB-INF directory.
     * 
     * @param xwiki
     * @param context
     * @deprecated 1.6M1. Use ComponentManager.lookup(String) instead.
     */
    @Deprecated
    public XWikiHibernateBaseStore(XWiki xwiki, XWikiContext context) {
        String path = xwiki.Param("xwiki.store.hibernate.path", "/WEB-INF/hibernate.cfg.xml");
        LOGGER.debug("Hibernate configuration file: [" + path + "]");
        setPath(path);
    }

    /**
     * Initialize the storage engine with a specific path This is used for tests.
     * 
     * @param hibpath
     * @deprecated 1.6M1. Use ComponentManager.lookup(String) instead.
     */
    @Deprecated
    public XWikiHibernateBaseStore(String hibpath) {
        setPath(hibpath);
    }

    /**
     * Empty constructor needed for component manager.
     */
    public XWikiHibernateBaseStore() {
    }

    @Override
    public void initialize() throws InitializationException {
        XWikiContext context = (XWikiContext) this.execution.getContext().getProperty("xwikicontext");
        setPath(context.getWiki().Param("xwiki.store.hibernate.path", getPath()));
    }

    /**
     * Allows to get the current hibernate config file path
     */
    public String getPath() {
        return this.hibpath;
    }

    /**
     * Allows to set the current hibernate config file path
     * 
     * @param hibpath
     */
    public void setPath(String hibpath) {
        this.hibpath = hibpath;
    }

    /**
     * Retrieve the current database product name. If no current session is available, obtains a connection from the
     * Hibernate connection provider attached to the current Session Factory.
     *
     * @return the database product name, see {@link DatabaseProduct}
     * @since 4.0M1
     */
    public DatabaseProduct getDatabaseProductName() {
        Connection connection = null;
        DatabaseProduct product = this.databaseProduct;

        if (product == DatabaseProduct.UNKNOWN) {
            // Note that we need to do the cast because this is how Hibernate suggests to get the Connection Provider.
            // See http://bit.ly/QAJXlr
            ConnectionProvider connectionProvider = ((SessionFactoryImplementor) getSessionFactory())
                    .getConnectionProvider();
            try {
                connection = connectionProvider.getConnection();
                product = DatabaseProduct.toProduct(connection.getMetaData().getDatabaseProductName());
            } catch (SQLException ignored) {
                // do not care, return UNKNOWN
            } finally {
                if (connection != null) {
                    try {
                        connectionProvider.closeConnection(connection);
                    } catch (SQLException ignored) {
                        // do not care, return UNKNOWN
                    }
                }
            }
        }

        return product;
    }

    /**
     * @return the database product name
     * @deprecated since 4.0M1 use {@link #getDatabaseProductName()}
     */
    @Deprecated
    public DatabaseProduct getDatabaseProductName(XWikiContext context) {
        return getDatabaseProductName();
    }

    /**
     * Allows to init the hibernate configuration
     * 
     * @throws org.hibernate.HibernateException
     */
    private synchronized void initHibernate(XWikiContext context) throws HibernateException {
        getConfiguration().configure(getPath());

        XWiki wiki = context.getWiki();
        if (wiki != null && wiki.Param("xwiki.db") != null && !wiki.isVirtualMode()) {
            // substitute default db name to configured.
            // note, that we can't call getSchemaFromWikiName() here,
            // because it ask getDatabaseProduct() which use connection
            // which must be opened. But here (before connection init)
            // we have no opened connections yet.
            String schemaName = getSchemaFromWikiName(context.getDatabase(), null, context);

            String dialect = getConfiguration().getProperty(Environment.DIALECT);
            if ("org.hibernate.dialect.MySQLDialect".equals(dialect)) {
                getConfiguration().setProperty(Environment.DEFAULT_CATALOG, schemaName);
            } else {
                getConfiguration().setProperty(Environment.DEFAULT_SCHEMA, schemaName);
            }
        }
        if (this.sessionFactory == null) {
            this.sessionFactory = Utils.getComponent(HibernateSessionFactory.class);
        }

        setSessionFactory(getConfiguration().buildSessionFactory());
    }

    /**
     * This get's the current session. This is set in beginTransaction
     * 
     * @param context
     */
    public Session getSession(XWikiContext context) {
        Session session = (Session) context.get("hibsession");
        // Make sure we are in this mode
        try {
            if (session != null) {
                session.setFlushMode(FlushMode.COMMIT);
            }
        } catch (org.hibernate.SessionException ex) {
            session = null;
        }

        return session;
    }

    /**
     * Allows to set the current session in the context This is set in beginTransaction
     * 
     * @param session
     * @param context
     */
    public void setSession(Session session, XWikiContext context) {
        if (session == null) {
            context.remove("hibsession");
        } else {
            context.put("hibsession", session);
        }
    }

    /**
     * Allows to get the current transaction from the context This is set in beginTransaction
     * 
     * @param context
     */
    public Transaction getTransaction(XWikiContext context) {
        Transaction transaction = (Transaction) context.get("hibtransaction");
        return transaction;
    }

    /**
     * Allows to set the current transaction This is set in beginTransaction
     * 
     * @param transaction
     * @param context
     */
    public void setTransaction(Transaction transaction, XWikiContext context) {
        if (transaction == null) {
            context.remove("hibtransaction");
        } else {
            context.put("hibtransaction", transaction);
        }
    }

    /**
     * Allows to shut down the hibernate configuration Closing all pools and connections
     * 
     * @param context
     * @throws HibernateException
     */
    public void shutdownHibernate(XWikiContext context) throws HibernateException {
        Session session = getSession(context);
        preCloseSession(session);
        closeSession(session);

        // Close all connections
        if (getSessionFactory() != null) {
            // Note that we need to do the cast because this is how Hibernate suggests to get the Connection Provider.
            // See http://bit.ly/QAJXlr
            ConnectionProvider connectionProvider = ((SessionFactoryImplementor) getSessionFactory())
                    .getConnectionProvider();
            connectionProvider.close();
        }
    }

    /**
     * Allows to update the schema to match the hibernate mapping
     * 
     * @param context
     * @throws HibernateException
     */
    public void updateSchema(XWikiContext context) throws HibernateException {
        updateSchema(context, false);
    }

    /**
     * Allows to update the schema to match the hibernate mapping
     * 
     * @param context
     * @param force defines wether or not to force the update despite the xwiki.cfg settings
     * @throws HibernateException
     */
    public synchronized void updateSchema(XWikiContext context, boolean force) throws HibernateException {
        // We don't update the schema if the XWiki hibernate config parameter says not to update
        if ((!force) && (context.getWiki() != null)
                && ("0".equals(context.getWiki().Param("xwiki.store.hibernate.updateschema")))) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Schema update deactivated for wiki [" + context.getDatabase() + "]");
            }
            return;
        }

        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Updating schema update for wiki [" + context.getDatabase() + "]...");
        }

        try {
            String[] sql = getSchemaUpdateScript(getConfiguration(), context);
            updateSchema(sql, context);
        } finally {
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("Schema update for wiki [" + context.getDatabase() + "] done");
            }
        }
    }

    /**
     * Convert wiki name in database/schema name.
     * 
     * @param wikiName the wiki name to convert.
     * @param databaseProduct the database engine type.
     * @param context the XWiki context.
     * @return the database/schema name.
     * @since XWiki Core 1.1.2, XWiki Core 1.2M2
     */
    protected String getSchemaFromWikiName(String wikiName, DatabaseProduct databaseProduct, XWikiContext context) {
        if (wikiName == null) {
            return null;
        }

        XWiki wiki = context.getWiki();

        String schema;
        if (context.isMainWiki(wikiName)) {
            schema = wiki.Param("xwiki.db");
            if (schema == null) {
                if (databaseProduct == DatabaseProduct.DERBY) {
                    schema = "APP";
                } else if (databaseProduct == DatabaseProduct.HSQLDB) {
                    schema = "PUBLIC";
                } else {
                    schema = wikiName.replace('-', '_');
                }
            }
        } else {
            // virtual
            schema = wikiName.replace('-', '_');

            // For HSQLDB we only support uppercase schema names. This is because Hibernate doesn't properly generate
            // quotes around schema names when it qualifies the table name when it generates the update script.
            if (databaseProduct == DatabaseProduct.HSQLDB) {
                schema = StringUtils.upperCase(schema);
            }
        }

        // Apply prefix
        String prefix = wiki.Param("xwiki.db.prefix", "");
        schema = prefix + schema;

        return schema;
    }

    /**
     * Convert wiki name in database/schema name.
     * <p>
     * Need hibernate to be initialized.
     * 
     * @param wikiName the wiki name to convert.
     * @param context the XWiki context.
     * @return the database/schema name.
     * @since XWiki Core 1.1.2, XWiki Core 1.2M2
     */
    protected String getSchemaFromWikiName(String wikiName, XWikiContext context) {
        if (wikiName == null) {
            return null;
        }

        DatabaseProduct databaseProduct = getDatabaseProductName(context);

        String schema = getSchemaFromWikiName(wikiName, databaseProduct, context);

        return schema;
    }

    /**
     * Convert context's database in real database/schema name.
     * <p>
     * Need hibernate to be initialized.
     * 
     * @param context the XWiki context.
     * @return the database/schema name.
     * @since XWiki Core 1.1.2, XWiki Core 1.2M2
     */
    protected String getSchemaFromWikiName(XWikiContext context) {
        return getSchemaFromWikiName(context.getDatabase(), context);
    }

    /**
     * This function gets the schema update scripts generated by comparing the current database with the current
     * hibernate mapping config.
     * 
     * @param config
     * @param context
     * @throws HibernateException
     */
    public String[] getSchemaUpdateScript(Configuration config, XWikiContext context) throws HibernateException {
        String[] schemaSQL = null;

        Session session;
        Connection connection;
        DatabaseMetadata meta;
        Statement stmt = null;
        Dialect dialect = Dialect.getDialect(getConfiguration().getProperties());
        boolean bTransaction = true;
        String dschema = null;

        try {
            bTransaction = beginTransaction(false, context);
            session = getSession(context);
            connection = session.connection();
            setDatabase(session, context);

            String contextSchema = getSchemaFromWikiName(context);

            DatabaseProduct databaseProduct = getDatabaseProductName();
            if (databaseProduct == DatabaseProduct.ORACLE || databaseProduct == DatabaseProduct.HSQLDB
                    || databaseProduct == DatabaseProduct.DERBY || databaseProduct == DatabaseProduct.DB2) {
                dschema = config.getProperty(Environment.DEFAULT_SCHEMA);
                config.setProperty(Environment.DEFAULT_SCHEMA, contextSchema);
                Iterator iter = config.getTableMappings();
                while (iter.hasNext()) {
                    Table table = (Table) iter.next();
                    table.setSchema(contextSchema);
                }
            }

            meta = new DatabaseMetadata(connection, dialect);
            stmt = connection.createStatement();
            schemaSQL = config.generateSchemaUpdateScript(dialect, meta);
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("Failed creating schema update script", e);
            }
        } finally {
            try {
                if (stmt != null) {
                    stmt.close();
                }
                if (bTransaction) {
                    endTransaction(context, false, false);
                }
                if (dschema != null) {
                    config.setProperty(Environment.DEFAULT_SCHEMA, dschema);
                }
            } catch (Exception e) {
            }
        }

        return schemaSQL;
    }

    /**
     * Runs the update script on the current database
     * 
     * @param createSQL
     * @param context
     */
    public void updateSchema(String[] createSQL, XWikiContext context) {
        // Updating the schema for custom mappings
        Session session;
        Connection connection;
        Statement stmt = null;
        boolean bTransaction = true;
        MonitorPlugin monitor = Util.getMonitorPlugin(context);
        String sql = "";

        try {
            bTransaction = beginTransaction(context);
            session = getSession(context);
            connection = session.connection();
            setDatabase(session, context);
            stmt = connection.createStatement();

            // Start monitoring timer
            if (monitor != null) {
                monitor.startTimer("sqlupgrade");
            }
            for (int j = 0; j < createSQL.length; j++) {
                sql = createSQL[j];
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Update Schema sql: [" + sql + "]");
                }
                stmt.executeUpdate(sql);
            }
            connection.commit();
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("Failed updating schema while executing query [" + sql + "]", e);
            }
        } finally {
            try {
                if (stmt != null) {
                    stmt.close();
                }
            } catch (Exception e) {
            }
            try {
                if (bTransaction) {
                    endTransaction(context, true);
                }
            } catch (Exception e) {
            }

            // End monitoring timer
            if (monitor != null) {
                monitor.endTimer("sqlupgrade");
            }
        }
    }

    /**
     * Custom Mapping This function update the schema based on the dynamic custom mapping provided by the class
     * 
     * @param bclass
     * @param context
     * @throws com.xpn.xwiki.XWikiException
     */
    public void updateSchema(BaseClass bclass, XWikiContext context) throws XWikiException {
        String custommapping = bclass.getCustomMapping();
        if (!bclass.hasExternalCustomMapping()) {
            return;
        }

        Configuration config = getMapping(bclass.getName(), custommapping);
        /*
         * if (isValidCustomMapping(bclass.getName(), config, bclass)==false) { throw new XWikiException(
         * XWikiException.MODULE_XWIKI_STORE, XWikiException.ERROR_XWIKI_STORE_HIBERNATE_INVALID_MAPPING, "Cannot update
         * schema for class " + bclass.getName() + " because of an invalid mapping"); }
         */

        String[] sql = getSchemaUpdateScript(config, context);
        updateSchema(sql, context);
    }

    /**
     * Initializes hibernate
     * 
     * @param context
     * @throws HibernateException
     */
    public void checkHibernate(XWikiContext context) throws HibernateException {
        if (getSessionFactory() == null) {
            initHibernate(context);
        }
    }

    /**
     * Checks if this xwiki setup is virtual meaning if multiple wikis can be accessed using the same database pool
     * 
     * @param context the XWiki context.
     * @return true if multi-wiki, false otherwise.
     */
    protected boolean isVirtual(XWikiContext context) {
        if ((context == null) || (context.getWiki() == null)) {
            return true;
        }

        return context.getWiki().isVirtualMode();
    }

    /**
     * Virtual Wikis Allows to switch database connection
     * 
     * @param session
     * @param context
     * @throws XWikiException
     */
    public void setDatabase(Session session, XWikiContext context) throws XWikiException {
        try {
            if (isVirtual(context)) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Switch database to: [" + context.getDatabase() + "]");
                }

                if (context.getDatabase() != null) {
                    String schemaName = getSchemaFromWikiName(context);
                    String escapedSchemaName = escapeSchema(schemaName, context);

                    DatabaseProduct databaseProduct = getDatabaseProductName(context);
                    if (DatabaseProduct.ORACLE == databaseProduct) {
                        executeSQL("alter session set current_schema = " + escapedSchemaName, session);
                    } else if (DatabaseProduct.DERBY == databaseProduct || DatabaseProduct.HSQLDB == databaseProduct
                            || DatabaseProduct.DB2 == databaseProduct) {
                        executeSQL("SET SCHEMA " + escapedSchemaName, session);
                    } else {
                        String catalog = session.connection().getCatalog();
                        catalog = (catalog == null) ? null : catalog.replace('_', '-');
                        if (!schemaName.equals(catalog)) {
                            session.connection().setCatalog(schemaName);
                        }
                    }
                    setCurrentDatabase(context, context.getDatabase());
                }
            }

            this.dataMigrationManager.checkDatabase();
        } catch (Exception e) {
            endTransaction(context, false); // close session with rollback to avoid further usage
            Object[] args = { context.getDatabase() };
            throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
                    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_SWITCH_DATABASE,
                    "Exception while switching to database {0}", e, args);
        }
    }

    /**
     * Execute an SQL statement using Hibernate.
     *
     * @param sql the SQL statement to execute
     * @param session the Hibernate Session in which to execute the statement
     */
    private void executeSQL(final String sql, Session session) {
        session.doWork(new Work() {
            public void execute(Connection connection) throws SQLException {
                Statement stmt = null;
                try {
                    stmt = connection.createStatement();
                    stmt.execute(sql);
                } finally {
                    try {
                        if (stmt != null) {
                            stmt.close();
                        }
                    } catch (Exception e) {
                    }
                }
            }
        });
    }

    /**
     * Escape schema name depending of the database engine.
     * 
     * @param schema the schema name to escape
     * @param context the XWiki context to get database engine identifier
     * @return the escaped version
     */
    protected String escapeSchema(String schema, XWikiContext context) {
        String escapedSchema;
        Dialect dialect = Dialect.getDialect(getConfiguration().getProperties());

        // - Oracle converts user names in uppercase when no quotes is used.
        // For example: "create user xwiki identified by xwiki;" creates a user named XWIKI (uppercase)
        // - In Hibernate.cfg.xml we just specify: <property name="connection.username">xwiki</property> and Hibernate
        // seems to be passing this username as is to Oracle which converts it to uppercase.
        //
        // Thus for Oracle we don't escape the schema.
        DatabaseProduct databaseProduct = getDatabaseProductName();
        if (DatabaseProduct.ORACLE == databaseProduct) {
            escapedSchema = schema;
        } else {
            String closeQuote = String.valueOf(dialect.closeQuote());
            escapedSchema = dialect.openQuote() + schema.replace(closeQuote, closeQuote + closeQuote) + closeQuote;
        }

        return escapedSchema;
    }

    /**
     * Begins a transaction if the context does not contains any.
     * 
     * @param context the current XWikiContext
     * @return true if a new transaction has been created, false otherwise.
     * @throws XWikiException if an error occurs while retrieving or creating a new session and transaction.
     */
    public boolean beginTransaction(XWikiContext context) throws XWikiException {
        return beginTransaction(null, context);
    }

    /**
     * Begins a transaction
     * 
     * @param withTransaction this argument is unused
     * @param context the current XWikiContext
     * @return true if a new transaction has been created, false otherwise.
     * @throws XWikiException if an error occurs while retrieving or creating a new session and transaction.
     * @deprecated since 4.0M1, use {@link #beginTransaction(SessionFactory, XWikiContext)}
     */
    @Deprecated
    public boolean beginTransaction(boolean withTransaction, XWikiContext context) throws XWikiException {
        return beginTransaction(null, context);
    }

    /**
     * Begins a transaction with a specific SessionFactory.
     * 
     * @param sfactory the session factory used to begin a new session if none are available
     * @param withTransaction this argument is unused
     * @param context the current XWikiContext
     * @return true if a new transaction has been created, false otherwise.
     * @throws XWikiException if an error occurs while retrieving or creating a new session and transaction.
     * @deprecated since 4.0M1, use {@link #beginTransaction(SessionFactory, XWikiContext)}
     */
    @Deprecated
    public boolean beginTransaction(SessionFactory sfactory, boolean withTransaction, XWikiContext context)
            throws XWikiException {
        return beginTransaction(sfactory, context);
    }

    /**
     * Begins a transaction with a specific SessionFactory.
     *
     * @param sfactory the session factory used to begin a new session if none are available
     * @param context the current XWikiContext
     * @return true if a new transaction has been created, false otherwise.
     * @throws XWikiException if an error occurs while retrieving or creating a new session and transaction.
     */
    public boolean beginTransaction(SessionFactory sfactory, XWikiContext context) throws XWikiException {
        Transaction transaction = getTransaction(context);
        Session session = getSession(context);

        // XWiki uses a new Session for a new Transaction so we need to keep both in sync and thus we check if that's
        // the case. If it isn't it means some code is faulty somewhere.
        if (((session == null) && (transaction != null)) || ((transaction == null) && (session != null))) {
            if (LOGGER.isWarnEnabled()) {
                LOGGER.warn("Incompatible session (" + session + ") and transaction (" + transaction + ") status");
            }
            // TODO: Fix this problem, don't ignore it!
            return false;
        }

        if (session != null) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Taking session from context " + session);
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Taking transaction from context " + transaction);
            }
            return false;
        }

        // session is obviously null here
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Trying to get session from pool");
        }
        if (sfactory == null) {
            session = getSessionFactory().openSession();
        } else {
            session = sfactory.openSession();
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Taken session from pool " + session);
        }

        if (LOGGER.isDebugEnabled()) {
            addConnection(getRealConnection(session), context);
        }

        setSession(session, context);

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Trying to open transaction");
        }
        transaction = session.beginTransaction();
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Opened transaction " + transaction);
        }
        setTransaction(transaction, context);

        // during #setDatabase, the transaction and the session will be closed if the database could not be
        // safely accessed due to version mismatch
        setDatabase(session, context);

        return true;
    }

    /**
     * Adding a connection to the Monitor module
     * 
     * @param connection
     * @param context
     * @todo This function is temporarily deactivated because of an error that causes memory leaks.
     */
    private synchronized void addConnection(Connection connection, XWikiContext context) {
        // connection.equals(connection) = false for some strange reasons, so we're remembering the
        // toString representation of each active connection. We also remember the stack trace (if
        // debug logging is enabled) to help spotting what code causes connections to leak.
        if (connection != null) {
            try {
                // Keep some statistics about session and connections
                if (this.connections.containsKey(connection.toString())) {
                    LOGGER.info("Connection [" + connection.toString() + "] already in connection map for store "
                            + this.toString());
                } else {
                    String value = "";
                    if (LOGGER.isDebugEnabled()) {
                        // No need to fill in the logging stack trace if debug is not enabled.
                        XWikiException stackException = new XWikiException();
                        stackException.fillInStackTrace();
                        value = stackException.getStackTraceAsString();
                    }
                    this.connections.put(connection.toString(), value);
                    this.nbConnections++;
                }
            } catch (Throwable e) {
                // This should not happen
                LOGGER.warn(e.getMessage(), e);
            }
        }
    }

    /**
     * Remove a connection to the Monitor module
     * 
     * @param connection
     * @todo This function is temporarily deactivated because of an error that causes memory leaks.
     */
    private synchronized void removeConnection(Connection connection) {
        if (connection != null) {
            try {
                // Keep some statistics about session and connections
                if (this.connections.containsKey(connection.toString())) {
                    this.connections.remove(connection.toString());
                    this.nbConnections--;
                } else {
                    LOGGER.info("Connection [" + connection.toString() + "] not in connection map");
                }
            } catch (Throwable e) {
                // This should not happen
                LOGGER.warn(e.getMessage(), e);
            }
        }
    }

    /**
     * Ends a transaction and close the session.
     * 
     * @param context the current XWikiContext
     * @param commit should we commit or not
     * @param withTransaction
     * @throws HibernateException
     * @deprecated since 4.0M1, use {@link #endTransaction(XWikiContext, boolean)}
     */
    @Deprecated
    public void endTransaction(XWikiContext context, boolean commit, boolean withTransaction)
            throws HibernateException {
        endTransaction(context, commit);
    }

    /**
     * Ends a transaction and close the session.
     *
     * @param context the current XWikiContext
     * @param commit should we commit or not
     */
    public void endTransaction(XWikiContext context, boolean commit) {
        Session session = null;
        try {
            session = getSession(context);
            Transaction transaction = getTransaction(context);
            setSession(null, context);
            setTransaction(null, context);

            if (transaction != null) {
                // We need to clean up our connection map first because the connection will
                // be aggressively closed by hibernate 3.1 and more
                preCloseSession(session);

                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Releasing hibernate transaction " + transaction);
                }
                if (commit) {
                    transaction.commit();
                } else {
                    transaction.rollback();
                }
            }
        } catch (HibernateException e) {
            // Ensure the original cause will get printed.
            throw new HibernateException(
                    "Failed to commit or rollback transaction. Root cause [" + getExceptionMessage(e) + "]", e);
        } finally {
            closeSession(session);
        }
    }

    /**
     * Hibernate and JDBC will wrap the exception thrown by the trigger in another exception (the
     * java.sql.BatchUpdateException) and this exception is sometimes wrapped again. Also the
     * java.sql.BatchUpdateException stores the underlying trigger exception in the nextException and not in the cause
     * property. The following method helps you to get to the underlying trigger message.
     */
    private String getExceptionMessage(Throwable t) {
        StringBuilder sb = new StringBuilder();
        Throwable next = null;
        for (Throwable current = t; current != null; current = next) {
            next = current.getCause();
            if (next == current) {
                next = null;
            }
            if (current instanceof SQLException) {
                SQLException sx = (SQLException) current;
                while (sx.getNextException() != null) {
                    sx = sx.getNextException();
                    sb.append("\nSQL next exception = [" + sx + "]");
                }
            }
        }
        return sb.toString();
    }

    /**
     * Closes the hibernate session
     * 
     * @param session
     * @throws HibernateException
     */
    private void closeSession(Session session) throws HibernateException {
        if (session != null) {
            session.close();
        }
    }

    /**
     * Closes the hibernate session
     * 
     * @param session
     * @throws HibernateException
     */
    private void preCloseSession(Session session) throws HibernateException {
        if (session != null) {
            if (LOGGER.isDebugEnabled()) {
                // Remove the connection from the list of active connections, used for debugging.
                LOGGER.debug("Releasing hibernate session " + session);
                Connection connection = getRealConnection(session);
                if ((connection != null)) {
                    removeConnection(connection);
                }
            }
        }
    }

    /**
     * Hack to get the real JDBC connection because hibernate 3.1 wraps the connection in a proxy and this creates a
     * memory leak
     */
    private Connection getRealConnection(Session session) {
        Connection conn = session.connection();
        if (conn instanceof Proxy) {
            Object bcp = Proxy.getInvocationHandler(conn);
            if (bcp instanceof BorrowedConnectionProxy) {
                ConnectionManager cm = (ConnectionManager) XWiki.getPrivateField(bcp, "connectionManager");
                if (cm != null) {
                    return cm.getConnection();
                }
            }
        }
        return conn;
    }

    /**
     * Cleanup all sessions Used at the shutdown time
     * 
     * @param context
     */
    public void cleanUp(XWikiContext context) {
        try {
            Session session = getSession(context);
            if (session != null) {
                if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn("Cleanup of session was needed: " + session);
                }
                endTransaction(context, false);
            }
        } catch (HibernateException e) {
        }
    }

    public SessionFactory getSessionFactory() {
        return this.sessionFactory.getSessionFactory();
    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory.setSessionFactory(sessionFactory);
    }

    public Configuration getConfiguration() {
        return this.sessionFactory.getConfiguration();
    }

    public Map<String, String> getConnections() {
        return this.connections;
    }

    public int getNbConnections() {
        return this.nbConnections;
    }

    public void setNbConnections(int nbConnections) {
        this.nbConnections = nbConnections;
    }

    /**
     * Return the name generated for a dynamic mapped object.
     * 
     * @param className the classname of the object.
     * @return a name in the form xwikicustom_space_class
     * @since 4.0M1
     */
    public String dynamicMappingTableName(String className) {
        return "xwikicustom_" + className.replaceAll("\\.", "_");
    }

    /**
     * Build a {@link Configuration} containing the provide mapping.
     * Before 4.0M1, this function was called makeMapping. In 4.0M1, it enter in conflict with 
     * {@link #makeMapping(String, String)}
     * 
     * @param className the classname of the class to map.
     * @param customMapping the custom mapping
     * @return a new {@link Configuration} containing this mapping alone.
     * @since 4.0M1
     */
    protected Configuration getMapping(String className, String customMapping) {
        Configuration hibconfig = new Configuration();
        {
            hibconfig.addXML(makeMapping(className, customMapping));
        }
        hibconfig.buildMappings();
        return hibconfig;
    }

    /**
     * Build a new XML string to define the provided mapping.
     * Since 4.0M1, the ids are longs, and a confitionnal mapping is made for Oracle.
     *
     * @param className the name of the class to map.
     * @param customMapping the custom mapping
     * @return a XML definition for the given mapping, using XWO_ID column for the object id.
     */
    protected String makeMapping(String className, String customMapping) {
        DatabaseProduct databaseProduct = getDatabaseProductName();
        return new StringBuilder(2000).append("<?xml version=\"1.0\"?>\n" + "<!DOCTYPE hibernate-mapping PUBLIC\n")
                .append("\t\"-//Hibernate/Hibernate Mapping DTD//EN\"\n")
                .append("\t\"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\">\n")
                .append("<hibernate-mapping>").append("<class entity-name=\"").append(className)
                .append("\" table=\"").append(dynamicMappingTableName(className)).append("\">\n")
                .append(" <id name=\"id\" type=\"long\" unsaved-value=\"any\">\n")
                .append("   <column name=\"XWO_ID\" not-null=\"true\" ")
                .append((databaseProduct == DatabaseProduct.ORACLE) ? "sql-type=\"integer\" " : "")
                .append("/>\n   <generator class=\"assigned\" />\n").append(" </id>\n").append(customMapping)
                .append("</class>\n</hibernate-mapping>").toString();
    }

    /**
     * Callback (closure) interface for operations in hibernate. spring like.
     */
    public interface HibernateCallback<T> {
        /**
         * method executed by {@link XWikiHibernateBaseStore} and pass open session to it.
         * 
         * @param session - open hibernate session
         * @return any you need be returned by
         *         {@link XWikiHibernateBaseStore#execute(XWikiContext, boolean, boolean, HibernateCallback)}
         * @throws HibernateException if any store specific exception
         * @throws XWikiException if exception in xwiki.
         */
        T doInHibernate(Session session) throws HibernateException, XWikiException;
    }

    /**
     * Execute method for operations in hibernate. spring like.
     * 
     * @param context - used everywhere.
     * @param bTransaction - should store use old transaction(false) or create new (true)
     * @param doCommit - should store commit changes(if any), or rollback it.
     * @param cb - callback to execute
     * @return {@link HibernateCallback#doInHibernate(Session)}
     * @throws XWikiException if any error
     * @deprecated since 4.0M1, use {@link #execute(XWikiContext, boolean, HibernateCallback)} or
     *                          {@link #failSafeExecute(XWikiContext, boolean, HibernateCallback)}
     */
    @Deprecated
    public <T> T execute(XWikiContext context, boolean bTransaction, boolean doCommit, HibernateCallback<T> cb)
            throws XWikiException {
        return execute(context, doCommit, cb);
    }

    /**
     * Execute method for operations in hibernate in an independent session (but not closing the current one if any).
     * Never throw any error, but there is no warranty that the operation has been completed successfully.
     *
     * @param context - used everywhere.
     * @param doCommit - should store commit changes(if any), or rollback it.
     * @param cb - callback to execute
     * @return {@link HibernateCallback#doInHibernate(Session)}, returns null if the callback throw an error.
     */
    public <T> T failSafeExecute(XWikiContext context, boolean doCommit, HibernateCallback<T> cb) {
        final Session originalSession = getSession(context);
        final Transaction originalTransaction = getTransaction(context);
        setSession(null, context);
        setTransaction(null, context);

        this.loggerManager.pushLogListener(null);
        try {
            return execute(context, doCommit, cb);
        } catch (Exception ignored) {
            return null;
        } finally {
            this.loggerManager.popLogListener();
            setSession(originalSession, context);
            setTransaction(originalTransaction, context);
        }
    }

    /**
     * Execute method for operations in hibernate. spring like.
     *
     * @param context - used everywhere.
     * @param doCommit - should store commit changes(if any), or rollback it.
     * @param cb - callback to execute
     * @return {@link HibernateCallback#doInHibernate(Session)}
     * @throws XWikiException if any error
     */
    public <T> T execute(XWikiContext context, boolean doCommit, HibernateCallback<T> cb) throws XWikiException {
        MonitorPlugin monitor = Util.getMonitorPlugin(context);
        boolean bTransaction = false;

        try {
            // Start monitoring timer
            if (monitor != null) {
                monitor.startTimer("hibernate");
            }
            checkHibernate(context);
            bTransaction = beginTransaction(context);
            return cb.doInHibernate(getSession(context));
        } catch (Exception e) {
            doCommit = false;
            if (e instanceof XWikiException) {
                throw (XWikiException) e;
            }
            throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, XWikiException.ERROR_XWIKI_UNKNOWN,
                    "Exception while hibernate execute", e);
        } finally {
            try {
                if (bTransaction) {
                    endTransaction(context, doCommit);
                }
                if (monitor != null) {
                    monitor.endTimer("hibernate");
                }
            } catch (Exception e) {
                if (LOGGER.isErrorEnabled()) {
                    LOGGER.error("Exeption while close transaction", e);
                }
            }
        }
    }

    /**
     * Execute method for read-only operations in hibernate. spring like.
     *
     * @return {@link HibernateCallback#doInHibernate(Session)}
     * @param context the current XWikiContext
     * @param bTransaction this argument is unused
     * @param cb the callback to execute
     * @throws XWikiException if any error
     * @see #execute(XWikiContext, boolean, HibernateCallback)
     * @deprecated since 4.0M1, use {@link #executeRead(XWikiContext, HibernateCallback)} or
     *                          {@link #failSafeExecuteRead(XWikiContext, HibernateCallback)}
     */
    @Deprecated
    public <T> T executeRead(XWikiContext context, boolean bTransaction, HibernateCallback<T> cb)
            throws XWikiException {
        return execute(context, false, cb);
    }

    /**
     * Execute hibernate read-only operation in a independent session (but not closing the current one if any).
     * Never throw any error, but there is no warranty that the operation has been completed successfully.
     *
     * @param context the current XWikiContext
     * @param cb the callback to execute
     * @return {@link HibernateCallback#doInHibernate(Session)}, returns null if the callback throw an error.
     * @see #failSafeExecute(XWikiContext, boolean, HibernateCallback)
     */
    public <T> T failSafeExecuteRead(XWikiContext context, HibernateCallback<T> cb) {
        return failSafeExecute(context, false, cb);
    }

    /**
     * Execute method for read-only operations in hibernate. spring like.
     *
     * @param context - used everywhere.
     * @param cb - callback to execute
     * @return {@link HibernateCallback#doInHibernate(Session)}
     * @throws XWikiException if any error
     * @see #execute(XWikiContext, boolean, HibernateCallback)
     */
    public <T> T executeRead(XWikiContext context, HibernateCallback<T> cb) throws XWikiException {
        return execute(context, false, cb);
    }

    /**
     * Execute method for read-write operations in hibernate. spring like.
     *
     * @param context the current XWikiContext
     * @param bTransaction this argument is unused
     * @param cb the callback to execute
     * @return {@link HibernateCallback#doInHibernate(Session)}
     * @throws XWikiException if any error
     * @see #execute(XWikiContext, boolean, HibernateCallback)
     * @deprecated since 4.0M1, use {@link #executeWrite(XWikiContext, HibernateCallback)} or
     *                          {@link #failSafeExecuteWrite(XWikiContext, HibernateCallback)}
     */
    @Deprecated
    public <T> T executeWrite(XWikiContext context, boolean bTransaction, HibernateCallback<T> cb)
            throws XWikiException {
        return execute(context, true, cb);
    }

    /**
     * Execute hibernate read-only operation in a independent session (but not closing the current one if any).
     * Never throw any error, but there is no warranty that the operation has been completed successfully.
     *
     * @param context the current XWikiContext
     * @param cb the callback to execute
     * @return {@link HibernateCallback#doInHibernate(Session)}
     * @see #execute(XWikiContext, boolean, HibernateCallback)
     */
    public <T> T failSafeExecuteWrite(XWikiContext context, HibernateCallback<T> cb) {
        return failSafeExecute(context, true, cb);
    }

    /**
     * Execute method for read-write operations in hibernate. spring like.
     * 
     * @param context the current XWikiContext
     * @param cb the callback to execute
     * @return {@link HibernateCallback#doInHibernate(Session)}
     * @throws XWikiException if any error
     * @see #execute(XWikiContext, boolean, HibernateCallback)
     */
    public <T> T executeWrite(XWikiContext context, HibernateCallback<T> cb) throws XWikiException {
        return execute(context, true, cb);
    }

    /**
     * @param context XWikiContext
     * @return current hibernate database name
     */
    private String getCurrentDatabase(XWikiContext context) {
        return (String) context.get(currentDatabaseKey);
    }

    /**
     * @param context XWikiContext
     * @param database current hibernate database name to set
     */
    private void setCurrentDatabase(XWikiContext context, String database) {
        context.put(currentDatabaseKey, database);
    }
}