/*
* 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 ≠ 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;
}
}