org.opencms.db.jpa.CmsSqlManager.java Source code

Java tutorial

Introduction

Here is the source code for org.opencms.db.jpa.CmsSqlManager.java

Source

/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library 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 library 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.
 *
 * For further information about Alkacon Software, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.db.jpa;

import org.opencms.configuration.CmsParameterConfiguration;
import org.opencms.configuration.CmsPersistenceUnitConfiguration;
import org.opencms.db.CmsDbContext;
import org.opencms.db.CmsDbException;
import org.opencms.db.CmsDbPool;
import org.opencms.file.CmsProject;
import org.opencms.main.CmsLog;
import org.opencms.main.CmsRuntimeException;
import org.opencms.util.CmsCollectionsGenericWrapper;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;

import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.StackObjectPool;

/**
 * JPA database server implementation of the SQL manager interface.<p>
 * 
 * @since 8.0.0 
 */
public class CmsSqlManager extends org.opencms.db.CmsSqlManager {

    /** Default pool size for EntityManager instances. */
    public static final int DEFAULT_ENTITY_MANAGER_POOL_SIZE = 250;

    /** The persistence unit name in the persistence.xml file for OpenCms'es persistence classes. */
    public static final String JPA_PERSISTENCE_UNIT = "OpenCmsJPAPool";

    /** Property name for pool size configuration. */
    public static final String JPA_POOL_SIZE_PROPERTY_NAME = "opencms.jpa.EntityManagerPoolSize";

    /** The fully qualified Java class name of the JDBC driver to be used. */
    public static final String KEY_DRIVER_CLASS_NAME = "driverClassName";

    /** The initial number of connections that are created when the pool is started. */
    public static final String KEY_INITIAL_SIZE = "initialSize";

    /** 
     * The maximum number of active connections that can be allocated from 
     * this pool at the same time, or negative for no limit. 
     */
    public static final String KEY_MAX_ACTIVE = "maxActive";

    /** 
     * The maximum number of connections that can remain idle in the pool, 
     * without extra ones being released, or negative for no limit. 
     */
    public static final String KEY_MAX_IDLE = "maxIdle";

    /** 
     * The maximum number of milliseconds that the pool will wait (when there are no available connections) 
     * for a connection to be returned before throwing an exception, or <= 0 to wait indefinitely. 
     */
    public static final String KEY_MAX_WAIT = "maxWait";

    /**  
     * The minimum amount of time an object may sit idle in the pool 
     * before it is eligable for eviction by the idle object evictor (if any). 
     */
    public static final String KEY_MIN_EVICTABLE_IDLE_TIME = "minEvictableIdleTimeMillis";

    /** 
     * The minimum number of active connections that can remain idle in the pool, 
     * without extra ones being created, or 0 to create none. 
     */
    public static final String KEY_MIN_IDLE = "minIdle";

    /** The number of objects to examine during each run of the idle object evictor thread (if any). */
    public static final String KEY_NUM_TESTS_PER_EVICTION_RUN = "numTestsPerEvictionRun";

    /** The connection password to be passed to our JDBC driver to establish a connection. */
    public static final String KEY_PASS = "password";

    /** Prepared statement pooling for this pool. */
    public static final String KEY_PREP_STATEMENTS = "poolPreparedStatements";

    /** The indication of whether objects will be validated before being borrowed from the pool. */
    public static final String KEY_TEST_ON_BORROW = "testOnBorrow";

    /** The indication of whether objects will be validated by the idle object evictor (if any). */
    public static final String KEY_TEST_WHILE_IDLE = "testWhileIdle";

    /** The number of milliseconds to sleep between runs of the idle object evictor thread. */
    public static final String KEY_TIME_BETWEEN_EVICTION_RUNS = "timeBetweenEvictionRunsMillis";

    /** The connection URL to be passed to our JDBC driver to establish a connection. */
    public static final String KEY_URL = "url";

    /** The connection username to be passed to our JDBC driver to establish a connection. */
    public static final String KEY_USER = "username";

    /**  The SQL query that will be used to validate connections from this pool before returning them to the caller. */
    public static final String KEY_VALIDATION_QUERY = "validationQuery";

    /** Poll of EntityManager instances for OpenCms. */
    protected static ObjectPool m_openCmsEmPool;

    /** The value to be replaced with for online project. */
    protected static final String OFFLINE_PROJECT = "Offline";

    /** The value to be replaced with for online project. */
    protected static final String ONLINE_PROJECT = "Online";

    /** A pattern being replaced in JPQL queries to generate JPQL queries to access online/offline tables. */
    protected static final String QUERY_PROJECT_SEARCH_PATTERN = "${PROJECT}";

    /** String which indicates project parameter in the queries. */
    protected static final String QUERY_PROJECT_STRING = "PROJECT";

    /** Contains JPQL placeholder for query parameters. Currently it's question mark.*/
    private static final String JPQL_PARAMETER_PLACEHOLDER = "?";

    /** Number of characters for JPQL parameter's placeholder. */
    private static final int JPQL_PARAMETER_PLACEHOLDER_LENGTH = JPQL_PARAMETER_PLACEHOLDER.length();

    /** The log object for this class. */
    private static final Log LOG = CmsLog.getLog(CmsSqlManager.class);

    /** The hashtable with all factories. You may have additional factories for OpenCms modules. */
    private static Hashtable<String, EntityManagerFactory> m_factoryTable = new Hashtable<String, EntityManagerFactory>();

    /** Contains the state of initialization of the static part of the class. */
    private static boolean m_isInitialized;

    /** EntityManager factory for OpenCms application. */
    private static EntityManagerFactory m_persistenceFactory;

    /** Contains information about uncleared EntityManager instances. */
    private static Hashtable<EntityManager, StackTraceElement[]> m_trackOn = new Hashtable<EntityManager, StackTraceElement[]>();

    /** The filename/path of the JPQL query properties. */
    private static final String QUERY_PROPERTIES = "org/opencms/db/jpa/query.properties";

    /** A map to cache queries with replaced search patterns. */
    protected Hashtable<String, String> m_cachedQueries;

    /** A map holding all JPQL queries. */
    protected Hashtable<String, String> m_queries;

    /** Queries with parameters. */
    protected Hashtable<String, String> m_queriesWithParameters;

    /**
     * The constructor.<p>
     * 
     * @throws CmsDbException if the manager is not initialized yet 
     */
    public CmsSqlManager() throws CmsDbException {

        if (!m_isInitialized) {
            throw new CmsDbException(Messages.get().container(Messages.ERR_SQLMANAGER_NOT_INITIALIZED));
        }
        m_cachedQueries = new Hashtable<String, String>();
        m_queries = new Hashtable<String, String>();
        m_queriesWithParameters = new Hashtable<String, String>();
        loadQueryProperties(QUERY_PROPERTIES);
    }

    /**
     * Create EntityManager instance for given unit name. If factory
     * for this unit is not already created it creates one.<p>
     * 
     * @param unitName - the unit name in the persistence.xml file
     * @return EntityManager instance for given unit name
     */
    public static EntityManager createEntityManager(String unitName) {

        EntityManager em = null;
        EntityManagerFactory factory = getFactory(unitName);

        if (factory != null) {
            em = factory.createEntityManager();
        }
        return em;
    }

    /**
     * Close all instances of EntityManagerFactory.
     */
    public static synchronized void destroy() {

        if (CmsLog.INIT.isDebugEnabled()) {
            trackOn();
        }

        try {
            m_openCmsEmPool.close();
        } catch (Exception e) {
            // do nothing
        }
        if (m_factoryTable != null) {
            Set<String> s = m_factoryTable.keySet();
            EntityManagerFactory emf;
            for (String f : s) {
                emf = m_factoryTable.get(f);
                if (emf != null) {
                    emf.close();
                    m_factoryTable.remove(f);
                }
            }
        }
        m_isInitialized = false;
    }

    /**
     * Creates EntityManager from OpenCms's factory.<p>
     * 
     * @return EntityManager created from OpenCms's factory
     */
    public static EntityManager getEntityManager() {

        EntityManager em = null;
        try {
            em = (EntityManager) m_openCmsEmPool.borrowObject();
            if (CmsLog.INIT.isDebugEnabled()) {
                m_trackOn.put(em, Thread.currentThread().getStackTrace());
            }
        } catch (Exception e) {
            LOG.error(e);
        }

        return em;
    }

    /**
     * Returns EntityManagerFactory for given unit name. If the factory does not already exists it creates one.<p>
     * 
     * @param unitName - the unit name in the persistence.xml file
     * 
     * @return EntityManagerFactory for given unit name
     */
    public static EntityManagerFactory getFactory(String unitName) {

        EntityManagerFactory factory = m_factoryTable.get(unitName);
        if (factory == null) {
            factory = Persistence.createEntityManagerFactory(unitName);
            m_factoryTable.put(unitName, factory);
        }
        return factory;
    }

    /**
     * Creates a new instance of a SQL manager.<p>
     * 
     * @param classname the classname of the SQL manager
     * 
     * @return a new instance of the SQL manager
     */
    public static CmsSqlManager getInstance(String classname) {

        CmsSqlManager sqlManager;

        try {
            sqlManager = new CmsSqlManager();
        } catch (Throwable t) {
            LOG.error(Messages.get().getBundle().key(Messages.LOG_SQL_MANAGER_INIT_FAILED_1, classname), t);
            sqlManager = null;
        }

        if (CmsLog.INIT.isInfoEnabled()) {
            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_SQL_MANAGER_1, classname));
        }

        return sqlManager;

    }

    /**
     * Initialize the static part of the class.<p>
     * 
     * @param config the combined configuration of "opencms.properties" and the "persistence.xml"
     */
    public static void init(CmsParameterConfiguration config) {

        if (!m_isInitialized) {
            m_isInitialized = true;

            String connProps = buildConnectionPropertiesValue(config, CmsDbPool.OPENCMS_DEFAULT_POOL_NAME);
            Properties systemProps = System.getProperties();
            systemProps.setProperty(CmsPersistenceUnitConfiguration.ATTR_CONNECTION_PROPERTIES, connProps);

            m_persistenceFactory = Persistence.createEntityManagerFactory(JPA_PERSISTENCE_UNIT, systemProps);

            m_factoryTable.put(JPA_PERSISTENCE_UNIT, m_persistenceFactory);
            CmsPoolEntityManagerFactory entityMan = new CmsPoolEntityManagerFactory(m_persistenceFactory);
            int entityManagerPoolSize = config.getInteger(CmsDbPool.KEY_DATABASE_POOL + "."
                    + CmsDbPool.OPENCMS_DEFAULT_POOL_NAME + "." + CmsDbPool.KEY_ENTITY_MANAGER_POOL_SIZE,
                    DEFAULT_ENTITY_MANAGER_POOL_SIZE);
            m_openCmsEmPool = new StackObjectPool(entityMan, entityManagerPoolSize, 0);
        }
    }

    /**
     * Returns EntityManager instance from OpenCms, back to pool.<p>
     * 
     * @param em - instance which returns back to pool of OpenCmsJPAPool persistence context.
     */
    public static void returnEntityManager(EntityManager em) {

        try {
            m_openCmsEmPool.returnObject(em);
            if (CmsLog.INIT.isDebugEnabled()) {
                m_trackOn.remove(em);
            }
        } catch (Exception e) {
            LOG.error(e);
        }
    }

    /**
     * Builds the connection property value for JPA.<p>
     * 
     * @param config the opencms properties
     * @param key the pool name
     * 
     * @return the connection properties value 
     */
    private static String buildConnectionPropertiesValue(CmsParameterConfiguration config, String key) {

        StringBuffer propValue = new StringBuffer();

        // read the values of the pool configuration specified by the given key
        String jdbcDriver = config.get(CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_JDBC_DRIVER);
        String jdbcUrl = config.get(CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_JDBC_URL);
        String jdbcUrlParams = config
                .get(CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_JDBC_URL_PARAMS);
        int maxActive = config.getInteger(CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_MAX_ACTIVE,
                10);
        int maxWait = config.getInteger(CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_MAX_WAIT,
                2000);
        int maxIdle = config.getInteger(CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_MAX_IDLE, 5);
        int minEvictableIdleTime = config.getInteger(
                CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_MIN_EVICTABLE_IDLE_TIME, 1800000);
        int minIdle = config.getInteger(CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_MIN_IDLE, 0);
        int numTestsPerEvictionRun = config.getInteger(
                CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_NUM_TESTS_PER_EVICTION_RUN, 3);
        int timeBetweenEvictionRuns = config.getInteger(
                CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_TIME_BETWEEN_EVICTION_RUNS, 3600000);

        String username = config.getString(CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_USERNAME,
                "");
        String password = config.getString(CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_PASSWORD,
                "");

        boolean testOnBorrow = Boolean.valueOf(config
                .getString(CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_TEST_ON_BORROW, "false")
                .trim()).booleanValue();
        boolean testWhileIdle = Boolean.valueOf(config
                .getString(CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_TEST_WHILE_IDLE, "false")
                .trim()).booleanValue();

        String testQuery = config.get(CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + CmsDbPool.KEY_TEST_QUERY);
        if ("".equals(testQuery)) {
            testQuery = null;
        }

        int initialSize = config.getInteger(CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + KEY_INITIAL_SIZE, 20);

        boolean poolPreparedStatements = config
                .getBoolean(CmsDbPool.KEY_DATABASE_POOL + '.' + key + '.' + KEY_PREP_STATEMENTS, true);

        propValue.append(KEY_DRIVER_CLASS_NAME);
        propValue.append("=");
        propValue.append(jdbcDriver);
        propValue.append(", ");

        propValue.append(KEY_URL);
        propValue.append("=");
        propValue.append(jdbcUrl);
        propValue.append(StringUtils.defaultString(jdbcUrlParams));
        propValue.append(", ");

        propValue.append(KEY_USER);
        propValue.append("=");
        propValue.append(username);
        propValue.append(", ");

        propValue.append(KEY_PASS);
        propValue.append("=");
        propValue.append(StringUtils.defaultString(password));
        propValue.append(", ");

        propValue.append(KEY_MAX_ACTIVE);
        propValue.append("=");
        propValue.append(maxActive);
        propValue.append(", ");

        propValue.append(KEY_MAX_IDLE);
        propValue.append("=");
        propValue.append(maxIdle);
        propValue.append(", ");

        propValue.append(KEY_MAX_WAIT);
        propValue.append("=");
        propValue.append(maxWait);
        propValue.append(", ");

        propValue.append(KEY_MIN_IDLE);
        propValue.append("=");
        propValue.append(minIdle);
        propValue.append(", ");

        if (testQuery != null) {

            propValue.append(KEY_VALIDATION_QUERY);
            propValue.append("=");
            propValue.append(StringUtils.defaultString(testQuery));
            propValue.append(", ");

            propValue.append(KEY_TEST_ON_BORROW);
            propValue.append("=");
            propValue.append(testOnBorrow);
            propValue.append(", ");

            propValue.append(KEY_TEST_WHILE_IDLE);
            propValue.append("=");
            propValue.append(testWhileIdle);
            propValue.append(", ");

            propValue.append(KEY_TIME_BETWEEN_EVICTION_RUNS);
            propValue.append("=");
            propValue.append(timeBetweenEvictionRuns);
            propValue.append(", ");

            propValue.append(KEY_NUM_TESTS_PER_EVICTION_RUN);
            propValue.append("=");
            propValue.append(numTestsPerEvictionRun);
            propValue.append(", ");

            propValue.append(KEY_MIN_EVICTABLE_IDLE_TIME);
            propValue.append("=");
            propValue.append(minEvictableIdleTime);
            propValue.append(", ");
        }

        propValue.append(KEY_INITIAL_SIZE);
        propValue.append("=");
        propValue.append(initialSize);
        propValue.append(", ");

        propValue.append(KEY_PREP_STATEMENTS);
        propValue.append("=");
        propValue.append(poolPreparedStatements);

        return propValue.toString();
    }

    /**
     * Replaces the project search pattern in JPQL queries by the pattern _ONLINE_ or _OFFLINE_ depending on the 
     * specified project ID.<p> 
     * 
     * @param projectId the ID of the current project
     * @param query the JPQL query
     * @return String the JPQL query with the table key search pattern replaced
     */
    private static String replaceProjectPattern(CmsUUID projectId, String query) {

        // make the statement project dependent
        String replacePattern = ((projectId == null) || projectId.equals(CmsProject.ONLINE_PROJECT_ID))
                ? ONLINE_PROJECT
                : OFFLINE_PROJECT;
        return CmsStringUtil.substitute(query, QUERY_PROJECT_SEARCH_PATTERN, replacePattern);
    }

    /**
     * Write information about uncleared EntityManager instances in the log.<p>
     */
    private static void trackOn() {

        LOG.debug("#################### Start Tracking on EM instances ");
        LOG.debug(" there is " + m_trackOn.keySet().size() + " instances uncleared");
        Set<EntityManager> set = m_trackOn.keySet();
        int i = 0;
        for (EntityManager em : set) {
            i++;
            LOG.debug("--- " + i + " instance tracelog --- ");
            StackTraceElement[] el = m_trackOn.get(em);
            for (int b = 0; b < el.length; b++) {
                LOG.debug(el[b].toString());
            }
        }
        LOG.debug("#################### Stop Tracking on EM instances ");

    }

    /**
     * Returns a Query for a EntityManagerContext specified by the key of a SQL query
     * and the project-ID.<p>
     * 
     * @param dbc the the db context
     * @param projectId the ID of the specified CmsProject
     * @param queryKey the key of the SQL query
     * 
     * @return Query a new Query containing the pre-compiled SQL statement  
     */
    public Query createNativeQuery(CmsDbContext dbc, CmsUUID projectId, String queryKey) {

        org.opencms.db.jpa.CmsDbContext jpaDbc = (org.opencms.db.jpa.CmsDbContext) dbc;
        jpaDbc.getEntityManager().flush();
        String rawSql = readQuery(projectId, queryKey);

        return jpaDbc.getEntityManager().createNativeQuery(prepareQueryParameters(rawSql).toUpperCase());
    }

    /**
     * Returns a Query for a JDBC connection specified by the key of a JPQL query
     * and the CmsProject.<p>
     * 
     * @param dbc the db context
     * @param project the specified CmsProject
     * @param queryKey the key of the JPQL query
     * 
     * @return Query a new Query containing the pre-compiled JPQL statement  
     */
    public Query createQuery(CmsDbContext dbc, CmsProject project, String queryKey) {

        return createQuery(dbc, project.getUuid(), queryKey);
    }

    /**
     * Returns a Query for a EntityManagerContext specified by the key of a JPQL query
     * and the project-ID.<p>
     * 
     * @param dbc the dbc context
     * @param projectId the ID of the specified CmsProject
     * @param queryKey the key of the JPQL query
     * 
     * @return Query a new Query containing the pre-compiled JPQL statement  
     * 
     */
    public Query createQuery(CmsDbContext dbc, CmsUUID projectId, String queryKey) {

        org.opencms.db.jpa.CmsDbContext jpaDbc = (org.opencms.db.jpa.CmsDbContext) dbc;
        jpaDbc.getEntityManager().flush();
        String rawJpql = readQuery(projectId, queryKey);
        return jpaDbc.getEntityManager().createQuery(prepareQueryParameters(rawJpql));
    }

    /**
     * Returns a Query for a EntityManagerContext specified by the key of a JPQL query.<p>
     * 
     * @param dbc the db context
     * @param queryKey the key of the JPQL query
     * @return Query a new Query containing the pre-compiled JPQL statement 
     */
    public Query createQuery(CmsDbContext dbc, String queryKey) {

        org.opencms.db.jpa.CmsDbContext jpaDbc = (org.opencms.db.jpa.CmsDbContext) dbc;
        jpaDbc.getEntityManager().flush();
        String rawJpql = readQuery(CmsUUID.getNullUUID(), queryKey);
        return jpaDbc.getEntityManager().createQuery(prepareQueryParameters(rawJpql));
    }

    /**
     * Returns a Query for a JDBC connection specified by the JPQL query.<p>
     * 
     * @param dbc the db context object 
     * @param query the JPQL query
     * @return Query a new Query containing the pre-compiled JPQL statement  
     */
    public Query createQueryFromJPQL(CmsDbContext dbc, String query) {

        org.opencms.db.jpa.CmsDbContext jpaDbc = (org.opencms.db.jpa.CmsDbContext) dbc;
        jpaDbc.getEntityManager().flush();
        return jpaDbc.getEntityManager().createQuery(prepareQueryParameters(query));
    }

    /**
     * Returns a Query for a JDBC connection specified by the JPQL query.<p>
     * 
     * @param dbc the db context object 
     * @param query the JPQL query
     * @param params the parameters to insert into the query
     *  
     * @return Query a new Query containing the pre-compiled JPQL statement  
     */
    public Query createQueryWithParametersFromJPQL(CmsDbContext dbc, String query, List<Object> params) {

        org.opencms.db.jpa.CmsDbContext jpaDbc = (org.opencms.db.jpa.CmsDbContext) dbc;
        jpaDbc.getEntityManager().flush();
        query = CmsStringUtil.substitute(query, "\t", " ");
        query = CmsStringUtil.substitute(query, "\n", " ");
        String realQuery = prepareQueryParameters(query, false);
        Query queryObj = jpaDbc.getEntityManager().createQuery(realQuery);
        int index = 1;
        for (Object param : params) {
            if ((param instanceof String) || (param instanceof Integer) || (param instanceof Long)) {
                queryObj.setParameter(index, param);
            } else {
                throw new IllegalArgumentException();
            }
            index += 1;
        }
        return queryObj;
    }

    /**
     * Finds an object in the db and returns it.<p>
     * 
     * @param <T> the class to be returned
     * @param dbc the current dbc
     * @param cls the class information of the object to be returned 
     * @param o the object to search for
     * 
     * @return returns the found object 
     */
    public <T> T find(org.opencms.db.CmsDbContext dbc, Class<T> cls, Object o) {

        org.opencms.db.jpa.CmsDbContext jpaDbc = (org.opencms.db.jpa.CmsDbContext) dbc;
        return jpaDbc.getEntityManager().find(cls, o);
    }

    /**
     * Returns the entity manager from the current dbc.<p>
     * 
     * @param dbc the current dbc
     * 
     * @return the according entity manager
     */
    public EntityManager getEntityManager(org.opencms.db.CmsDbContext dbc) {

        org.opencms.db.jpa.CmsDbContext jpaDbc = (org.opencms.db.jpa.CmsDbContext) dbc;
        return jpaDbc.getEntityManager();
    }

    /**
     * Persists an object.<p>
     * 
     * @param dbc the current dbc
     * @param o the object to persist
     */
    public void persist(org.opencms.db.CmsDbContext dbc, Object o) {

        org.opencms.db.jpa.CmsDbContext jpaDbc = (org.opencms.db.jpa.CmsDbContext) dbc;
        jpaDbc.getEntityManager().persist(o);
    }

    /**
     * Searches for the JPQL query with the specified key and CmsProject.<p>
     * 
     * @param project the specified CmsProject
     * @param queryKey the key of the JPQL query
     * @return the the JPQL or JPQL query in this property list with the specified key
     */
    public String readQuery(CmsProject project, String queryKey) {

        return readQuery(project.getUuid(), queryKey);
    }

    /**
     * Searches for the JPQL query with the specified key and project-ID.<p>
     * 
     * For projectIds &ne; 0, the pattern {@link #QUERY_PROJECT_SEARCH_PATTERN} in table names of queries is 
     * replaced with "Online" or "Offline" to choose the right database 
     * tables for JPQL queries that are project dependent!
     * 
     * @param projectId the ID of the specified CmsProject
     * @param queryKey the key of the JPQL query
     * @return the the JPQL query in this property list with the specified key
     */
    public String readQuery(CmsUUID projectId, String queryKey) {

        String key;
        if ((projectId != null) && !projectId.isNullUUID()) {
            // id 0 is special, please see below
            StringBuffer buffer = new StringBuffer(128);
            buffer.append(queryKey);
            if (projectId.equals(CmsProject.ONLINE_PROJECT_ID)) {
                buffer.append(ONLINE_PROJECT);
            } else {
                buffer.append(OFFLINE_PROJECT);
            }
            key = buffer.toString();
        } else {
            key = queryKey;
        }

        // look up the query in the cache
        String query = m_cachedQueries.get(key);

        if (query == null) {
            // the query has not been cached yet
            // get the JPQL statement from the properties hash
            query = readQuery(queryKey);

            if (query == null) {
                throw new CmsRuntimeException(Messages.get().container(Messages.ERR_QUERY_NOT_FOUND_1, queryKey));
            }

            // replace control chars.
            query = CmsStringUtil.substitute(query, "\t", " ");
            query = CmsStringUtil.substitute(query, "\n", " ");

            if ((projectId != null) && !projectId.isNullUUID()) {
                // a project ID = 0 is an internal indicator that a project-independent 
                // query was requested - further regex operations are not required then
                query = CmsSqlManager.replaceProjectPattern(projectId, query);
            }
            // to minimize costs, all statements with replaced expressions are cached in a map
            m_cachedQueries.put(key, query);
        }

        return query;
    }

    /**
     * Searches for the JPQL query with the specified key.<p>
     * 
     * @param queryKey the JPQL query key
     * @return the the JPQL query in this property list with the specified key
     */
    public String readQuery(String queryKey) {

        String value = m_queries.get(queryKey);
        if ((value == null) && (!QUERY_PROJECT_STRING.equalsIgnoreCase(queryKey))) {
            if (LOG.isErrorEnabled()) {
                LOG.error(Messages.get().getBundle().key(Messages.LOG_QUERY_NOT_FOUND_1, queryKey));
            }
        }
        return value;
    }

    /**
     * Removes an object from the db.<p>
     * 
     * @param dbc the current dbc
     * @param o the object to remove
     */
    public void remove(org.opencms.db.CmsDbContext dbc, Object o) {

        org.opencms.db.jpa.CmsDbContext jpaDbc = (org.opencms.db.jpa.CmsDbContext) dbc;
        jpaDbc.getEntityManager().remove(o);
    }

    /**
     * Replaces null or empty Strings with a String with one space character <code>" "</code>.<p>
     * 
     * @param value the string to validate
     * @return the validate string or a String with one space character if the validated string is null or empty
     */
    public String validateEmpty(String value) {

        if (CmsStringUtil.isNotEmpty(value)) {
            return value;
        }

        return " ";
    }

    /**
     * Loads a Java properties hash containing JPQL queries.<p>
     * 
     * @param propertyFilename the package/filename of the properties hash
     */
    protected void loadQueryProperties(String propertyFilename) {

        Properties properties = new Properties();

        try {
            properties.load(getClass().getClassLoader().getResourceAsStream(propertyFilename));
            m_queries.putAll(CmsCollectionsGenericWrapper.<String, String>map(properties));
            replaceQuerySearchPatterns();
        } catch (Throwable t) {
            if (LOG.isErrorEnabled()) {
                LOG.error(Messages.get().getBundle().key(Messages.LOG_LOAD_QUERY_PROP_FILE_FAILED_1,
                        propertyFilename), t);
            }

            properties = null;
        }
    }

    /**
     * Replaces patterns ${XXX} by another property value, if XXX is a property key with a value.<p>
     */
    protected void replaceQuerySearchPatterns() {

        String currentKey = null;
        String currentValue = null;
        int startIndex = 0;
        int endIndex = 0;
        int lastIndex = 0;

        Iterator<String> allKeys = m_queries.keySet().iterator();
        while (allKeys.hasNext()) {
            currentKey = allKeys.next();
            currentValue = m_queries.get(currentKey);
            startIndex = 0;
            endIndex = 0;
            lastIndex = 0;

            while ((startIndex = currentValue.indexOf("${", lastIndex)) != -1) {
                endIndex = currentValue.indexOf('}', startIndex);
                if ((endIndex != -1) && !currentValue.startsWith(QUERY_PROJECT_SEARCH_PATTERN, startIndex - 1)) {

                    String replaceKey = currentValue.substring(startIndex + 2, endIndex);
                    String searchPattern = currentValue.substring(startIndex, endIndex + 1);
                    String replacePattern = this.readQuery(replaceKey);

                    if (replacePattern != null) {
                        currentValue = CmsStringUtil.substitute(currentValue, searchPattern, replacePattern);
                    }
                }

                lastIndex = endIndex + 2;
            }
            m_queries.put(currentKey, currentValue);
        }
    }

    /**
     * Set numbers for parameters of giving JPQL query.<p>
     * 
     * @param query - the query
     * 
     * @return query with numbered parameter's placeholders
     */
    private String prepareQueryParameters(String query) {

        return prepareQueryParameters(query, true);
    }

    /**
     * Set numbers for parameters of giving JPQL query.<p>
     * 
     * @param query - the query
     * @param cache if true, the query will be cached 
     * 
     * @return query with numbered parameter's placeholders
     */
    private String prepareQueryParameters(String query, boolean cache) {

        String jpqlQuery = m_queriesWithParameters.get(query);
        if (jpqlQuery != null) {
            return jpqlQuery;
        }

        StringBuilder builder = new StringBuilder(query);
        int startPosition = 0;
        int currPosition = 0;
        int counter = 0;

        while ((currPosition = builder.indexOf(JPQL_PARAMETER_PLACEHOLDER, startPosition)) != -1) {
            builder.insert(currPosition + JPQL_PARAMETER_PLACEHOLDER_LENGTH, ++counter);
            startPosition = currPosition + JPQL_PARAMETER_PLACEHOLDER_LENGTH + (counter < 10 ? 1 : 2); // assumes we have not more than 99 parameters per query :)
        }

        jpqlQuery = builder.toString();
        if (cache) {
            m_queriesWithParameters.put(query, jpqlQuery);
        }
        return jpqlQuery;
    }
}