/**
* Copyright (c) 2009--2014 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package com.redhat.rhn.common.hibernate;
import com.redhat.rhn.common.db.DatabaseException;
import com.redhat.rhn.common.db.datasource.CallableMode;
import com.redhat.rhn.common.db.datasource.DataResult;
import com.redhat.rhn.common.db.datasource.ModeFactory;
import com.redhat.rhn.common.db.datasource.SelectMode;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.EntityMode;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.metadata.ClassMetadata;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.sql.Blob;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* HibernateFactory - Helper superclass that contains methods for fetching and
* storing Objects from the DB using Hibernate.
* <p>
* Abstract methods define what the subclass must implement to determine what is
* specific to that Factory's instance.
*
* @version $Rev$
*/
public abstract class HibernateFactory {
private static ConnectionManager connectionManager = new ConnectionManager();
private static final Logger LOG = Logger.getLogger(HibernateFactory.class);
protected HibernateFactory() {
}
/**
* Register a class with HibernateFactory, to give the registered class a
* chance to modify the Hibernate configuration before creating the
* SessionFactory.
* @param c Configurator to override Hibernate configuration.
*/
public static void addConfigurator(Configurator c) {
connectionManager.addConfigurator(c);
}
/**
* Close the sessionFactory
*/
public static void closeSessionFactory() {
connectionManager.close();
}
/**
* Is the factory closed
* @return boolean
*/
public static boolean isClosed() {
return connectionManager.isClosed();
}
/**
* Create a SessionFactory, loading the hbm.xml files from the default
* location (com.redhat.rhn.domain).
*/
public static void createSessionFactory() {
connectionManager.initialize();
}
/**
* Create a SessionFactory, loading the hbm.xml files from alternate
* location
* @param additionalLocation Alternate location for hbm.xml files
*/
public static void createSessionFactory(String[] additionalLocation) {
connectionManager.setAdditionalPackageNames(additionalLocation);
connectionManager.initialize();
}
/**
* Get the Logger for the derived class so log messages show up on the
* correct class
* @return Logger for this class.
*/
protected abstract Logger getLogger();
/**
* Binds the values of the map to a named query parameter, whose value
* matches the key in the given Map, guessing the Hibernate type from the
* class of the given object.
* @param query Query to be modified.
* @param parameters named query parameters to be bound.
* @return Modified Query.
* @throws HibernateException if there is a problem with updating the Query.
* @throws ClassCastException if the key in the given Map is NOT a String.
*/
private Query bindParameters(Query query, Map parameters)
throws HibernateException {
if (parameters == null) {
return query;
}
Set entrySet = parameters.entrySet();
for (Iterator itr = entrySet.iterator(); itr.hasNext();) {
Map.Entry entry = (Map.Entry) itr.next();
if (entry.getValue() instanceof Collection) {
Collection c = (Collection) entry.getValue();
if (c.size() > 1000) {
LOG.error("Query executed with Collection larger than 1000");
}
query.setParameterList((String) entry.getKey(), c);
}
else {
query.setParameter((String) entry.getKey(), entry.getValue());
}
}
return query;
}
/**
* Finds a single instance of a persistent object given a named query.
* @param qryName The name of the query used to find the persistent object.
* It should be formulated to ensure a single object is returned or an error
* will occur.
* @param qryParams Map of named bind parameters whose keys are Strings. The
* map can also be null.
* @return Object found by named query or null if nothing found.
*/
protected Object lookupObjectByNamedQuery(String qryName, Map qryParams) {
return lookupObjectByNamedQuery(qryName, qryParams, false);
}
/**
* Finds a single instance of a persistent object given a named query.
* @param qryName The name of the query used to find the persistent object.
* It should be formulated to ensure a single object is returned or an error
* will occur.
* @param qryParams Map of named bind parameters whose keys are Strings. The
* map can also be null.
* @param cacheable if we should cache the results of this object
* @return Object found by named query or null if nothing found.
*/
protected Object lookupObjectByNamedQuery(String qryName, Map qryParams,
boolean cacheable) {
Object retval = null;
Session session = null;
try {
session = HibernateFactory.getSession();
Query query = session.getNamedQuery(qryName)
.setCacheable(cacheable);
bindParameters(query, qryParams);
retval = query.uniqueResult();
}
catch (MappingException me) {
throw new HibernateRuntimeException("Mapping not found for " + qryName, me);
}
catch (HibernateException he) {
throw new HibernateRuntimeException("Executing query " + qryName +
" with params " + qryParams + " failed", he);
}
return retval;
}
/**
* Using a named query, find all the objects matching the criteria within.
* Warning: This can be very expensive if the returned list is large. Use
* only for small tables with static data
* @param qryName Named query to use to find a list of objects.
* @param qryParams Map of named bind parameters whose keys are Strings. The
* map can also be null.
* @return List of objects returned by named query, or null if nothing
* found.
*/
protected List listObjectsByNamedQuery(String qryName, Map qryParams) {
return listObjectsByNamedQuery(qryName, qryParams, false);
}
/**
* Using a named query, find all the objects matching the criteria within.
* Warning: This can be very expensive if the returned list is large. Use
* only for small tables with static data
* @param qryName Named query to use to find a list of objects.
* @param qryParams Map of named bind parameters whose keys are Strings. The
* map can also be null.
* @param col the collection to use as an inclause
* @param colLabel the label the collection will have
* @return List of objects returned by named query, or null if nothing
* found.
*/
protected List listObjectsByNamedQuery(String qryName, Map qryParams,
Collection col, String colLabel) {
if (col.isEmpty()) {
return Collections.EMPTY_LIST;
}
ArrayList<Long> tmpList = new ArrayList<Long>();
List<Long> toRet = new ArrayList<Long>();
tmpList.addAll(col);
for (int i = 0; i < col.size();) {
int initial = i;
int fin = i + 500 < col.size() ? i + 500 : col.size();
List<Long> sublist = tmpList.subList(i, fin);
qryParams.put(colLabel, sublist);
toRet.addAll(listObjectsByNamedQuery(qryName, qryParams, false));
i = fin;
}
return toRet;
}
/**
* Using a named query, find all the objects matching the criteria within.
* Warning: This can be very expensive if the returned list is large. Use
* only for small tables with static data
* @param qryName Named query to use to find a list of objects.
* @param qryParams Map of named bind parameters whose keys are Strings. The
* map can also be null.
* @param cacheable if we should cache the results of this query
* @return List of objects returned by named query, or null if nothing
* found.
*/
protected List listObjectsByNamedQuery(String qryName, Map qryParams,
boolean cacheable) {
Session session = null;
List retval = null;
session = HibernateFactory.getSession();
Query query = session.getNamedQuery(qryName);
query.setCacheable(cacheable);
bindParameters(query, qryParams);
retval = query.list();
return retval;
}
/**
* Saves the given object to the database using Hibernate.
* @param toSave Object to be persisted.
* @param saveOrUpdate true if saveOrUpdate should be called, false if
* save() is to be called directly.
*/
protected void saveObject(Object toSave, boolean saveOrUpdate) {
Session session = null;
session = HibernateFactory.getSession();
if (saveOrUpdate) {
session.saveOrUpdate(toSave);
}
else {
session.save(toSave);
}
}
/**
* Saves the given object to the database using Hibernate.
* @param toSave Object to be persisted.
*/
protected void saveObject(Object toSave) {
saveObject(toSave, true);
}
/**
* Remove a Session from the DB
* @param toRemove Object to be removed.
* @return int number of objects affected.
*/
protected int removeObject(Object toRemove) {
Session session = null;
int numDeleted = 0;
session = HibernateFactory.getSession();
session.delete(toRemove);
numDeleted++;
return numDeleted;
}
/**
* Returns the Hibernate session stored in ThreadLocal storage. If not
* present, creates a new one and stores it in ThreadLocal; creating the
* session also begins a transaction implicitly.
*
* @return Session Session asked for
*/
public static Session getSession() {
return connectionManager.getSession();
}
/**
* Commit the transaction for the current session. This method or
* {@link #rollbackTransaction}can only be called once per session.
*
* @throws HibernateException if the commit fails
*/
public static void commitTransaction() throws HibernateException {
connectionManager.commitTransaction();
}
/**
* Roll the transaction for the current session back. This method or
* {@link #commitTransaction}can only be called once per session.
*
* @throws HibernateException if the commit fails
*/
public static void rollbackTransaction() throws HibernateException {
connectionManager.rollbackTransaction();
}
/**
* Is transaction pending for thread?
* @return boolean
*/
public static boolean inTransaction() {
return connectionManager.isTransactionPending();
}
/**
* Closes the Hibernate Session stored in ThreadLocal storage.
*/
public static void closeSession() {
connectionManager.closeSession();
}
/**
* Return the persistent instance of the given entity class with the given
* identifier, or null if there is no such persistent instance. (If the
* instance, or a proxy for the instance, is already associated with the
* session, return that instance or proxy.)
* @param clazz a persistent class
* @param id an identifier
* @return Object persistent instance or null
*/
public Object getObject(Class clazz, Serializable id) {
Object retval = null;
Session session = null;
try {
session = HibernateFactory.getSession();
retval = session.get(clazz, id);
}
catch (MappingException me) {
getLogger().error("Mapping not found for " + clazz.getName(), me);
}
catch (HibernateException he) {
getLogger().error("Hibernate exception: " + he.toString());
}
return retval;
}
/**
* Return a locked persistent instance of the given entity class with
* the given identifier, or null if there is no such persistent instance.
* (If the instance, or a proxy for the instance, is already associated
* with the session, return that instance or proxy.)
* @param clazz a persistent class
* @param id an identifier
* @return Object persistent instance or null
*/
protected Object lockObject(Class clazz, Serializable id) {
Object retval = null;
Session session = null;
try {
session = HibernateFactory.getSession();
retval = session.get(clazz, id, LockMode.UPGRADE);
}
catch (MappingException me) {
getLogger().error("Mapping not found for " + clazz.getName(), me);
}
catch (HibernateException he) {
getLogger().error("Hibernate exception: " + he.toString());
}
return retval;
}
/**
* Util to reload an object using Hibernate
* @param obj to be reloaded
* @return Object found if not, null
* @throws HibernateException if something bad happens.
*/
public static Object reload(Object obj) throws HibernateException {
// assertNotNull(obj);
ClassMetadata cmd = connectionManager.getMetadata(obj);
Serializable id = cmd.getIdentifier(obj, EntityMode.POJO);
Session session = getSession();
session.flush();
session.evict(obj);
/*
* In hibernate 3, the following doesn't work:
* session.load(obj.getClass(), id);
* load returns the proxy class instead of the persisted class, ie,
* Filter$$EnhancerByCGLIB$$9bcc734d_2 instead of Filter.
* session.get is set to not return the proxy class, so that is what we'll use.
*/
Object result = session.get(obj.getClass(), id);
// assertNotSame(obj, result);
return result;
}
/**
* utility to convert blob to byte array
* @param fromBlob blob to convert
* @return byte array converted from blob
*/
public static byte[] blobToByteArray(Blob fromBlob) {
if (fromBlob == null) {
return new byte[0];
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
return toByteArrayImpl(fromBlob, baos);
}
catch (SQLException e) {
LOG.error("SQL Error converting blob to byte array", e);
throw new DatabaseException(e.toString());
}
catch (IOException e) {
LOG.error("I/O Error converting blob to byte array", e);
throw new DatabaseException(e.toString());
}
finally {
try {
baos.close();
}
catch (IOException ex) {
throw new DatabaseException(ex.toString());
}
}
}
/**
* helper utility to convert blob to byte array
* @param fromBlob blob to convert
* @param baos byte array output stream
* @return String version of the byte array contents
*/
private static byte[] toByteArrayImpl(Blob fromBlob, ByteArrayOutputStream baos)
throws SQLException, IOException {
byte[] buf = new byte[4000];
InputStream is = fromBlob.getBinaryStream();
try {
for (;;) {
int dataSize = is.read(buf);
if (dataSize == -1) {
break;
}
baos.write(buf, 0, dataSize);
}
}
finally {
if (is != null) {
try {
is.close();
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
return baos.toByteArray();
}
/**
* Get the String version of the byte array contents
* used to return the string representation of byte arrays constructed from blobs
* @param barr byte array to convert to String
* @return String version of the byte array contents
*/
public static String getByteArrayContents(byte[] barr) {
String retval = "";
if (barr != null) {
try {
retval = new String(barr, "UTF-8");
}
catch (UnsupportedEncodingException uee) {
throw new RuntimeException("Illegal Argument: " +
"This VM or environment doesn't support UTF-8: Data - " +
barr, uee);
}
}
return retval;
}
/**
* Get the String version of an object corresponding to a BLOB column
* Handles both the byte[] and the Blob cases
* @param blob the blob to handle
* @return String version of the blob contents, null if the blob was null
* or if the specified object is not actually a Blob
*/
public static String getBlobContents(Object blob) {
// Returned by Hibernate, and also returned by mode queries
// from an Oracle database
if (blob instanceof byte[]) {
return getByteArrayContents((byte[]) blob);
}
// Returned only by mode queries from a Postgres database
if (blob instanceof Blob) {
return getByteArrayContents(blobToByteArray((Blob) blob));
}
return null;
}
/**
* Convert a byte[] array to a Blob object. Guards against
* null arrays and 0 length arrays.
* @param data array to convert to a Blob
* @return Blob if data[] is non-null and length > 0, null otherwise
*/
public static Blob byteArrayToBlob(byte[] data) {
if (data == null) {
return null;
}
if (data.length == 0) {
return null;
}
return Hibernate.createBlob(data);
}
/**
* Convert a String to a byte[] object. Guards against
* null arrays and 0 length arrays.
* @param data string to convert to a Blob
* @return Blob if data[] is non-null and length > 0, null otherwise
*/
public static byte[] stringToByteArray(String data) {
if (StringUtils.isEmpty(data)) {
return null;
}
try {
return data.getBytes("UTF-8");
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException("Illegal Argument: " +
"This VM or environment doesn't support UTF-8 - Data - " +
data, e);
}
}
/**
* Initialize the underlying db layer
*
*/
public static void initialize() {
connectionManager.initialize();
}
/**
* Returns the current initialization status
* @return boolean current status
*/
public static boolean isInitialized() {
return connectionManager.isInitialized();
}
protected static DataResult executeSelectMode(String name, String mode, Map params) {
SelectMode m = ModeFactory.getMode(name, mode);
return m.execute(params);
}
protected static void executeCallableMode(String name, String mode, Map params) {
CallableMode m = ModeFactory.getCallableMode(name, mode);
m.execute(params, new HashMap());
}
}