/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package org.tynamo.hibernate.services;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.tapestry5.grid.GridDataSource;
import org.apache.tapestry5.hibernate.HibernateGridDataSource;
import org.apache.tapestry5.hibernate.HibernateSessionManager;
import org.apache.tapestry5.ioc.services.PropertyAccess;
import org.apache.tapestry5.services.PropertyConduitSource;
import org.hibernate.Criteria;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.TransientObjectException;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.slf4j.Logger;
import org.tynamo.descriptor.CollectionDescriptor;
import org.tynamo.descriptor.TynamoClassDescriptor;
import org.tynamo.descriptor.TynamoPropertyDescriptor;
import org.tynamo.hibernate.QueryParameter;
import org.tynamo.internal.services.GenericPersistenceService;
import org.tynamo.services.DescriptorService;
@SuppressWarnings("unchecked")
public class HibernatePersistenceServiceImpl extends GenericPersistenceService implements HibernatePersistenceService
{
private Logger logger;
private DescriptorService descriptorService;
private HibernateSessionManager sessionManager;
private PropertyConduitSource propertyConduitSource;
public HibernatePersistenceServiceImpl(Logger logger,
DescriptorService descriptorService,
HibernateSessionManager sessionManager,
PropertyAccess propertyAccess,
PropertyConduitSource propertyConduitSource)
{
super(propertyAccess);
this.logger = logger;
this.descriptorService = descriptorService;
this.propertyConduitSource = propertyConduitSource;
// we need a sessionmanager because Tapestry session proxy doesn't implement Hibernate's SessionImplementator interface
this.sessionManager = sessionManager;
}
private Session getSession()
{
return sessionManager.getSession();
}
/**
* https://trails.dev.java.net/servlets/ReadMsg?listName=users&msgNo=1226
* <p/>
* Very often I find myself writing:
* <code>
* Object example = new Object(); example.setProperty(uniqueValue);
* List objects = ((TynamoPage)getPage()).getPersistenceService().getInstances(example);
* (MyObject)objects.get(0);
* </code>
* when, in fact, I know that the single property I populated my example object with should be unique, and thus only
* one object should be returned
*
* @param type The type to use to check for security restrictions.
* @param detachedCriteria
* @return
*/
public <T> T getInstance(final Class<T> type, DetachedCriteria detachedCriteria)
{
final DetachedCriteria criteria = alterCriteria(type, detachedCriteria);
return (T) criteria.getExecutableCriteria(getSession()).uniqueResult();
}
/**
* (non-Javadoc)
*
* @see org.tynamo.services.PersistenceService#getInstance(Class,Serializable)
*/
public <T> T getInstance(final Class<T> type, final Serializable id)
{
DetachedCriteria criteria = DetachedCriteria.forClass(type).add(Restrictions.idEq(id));
return getInstance(type, criteria);
}
/**
* Execute an HQL query.
*
* @param queryString a query expressed in Hibernate's query language
* @return a List of entities containing the results of the query execution
*/
public List findByQuery(String queryString)
{
return findByQuery(queryString, new QueryParameter[0]);
}
/**
* Execute an HQL query.
*
* @param queryString a query expressed in Hibernate's query language
* @param parameters the (optional) parameters for the query.
* @return a List of entities containing the results of the query execution
*/
public List findByQuery(String queryString, QueryParameter... parameters)
{
return findByQuery(queryString, 0, 0, parameters);
}
/**
* Execute an HQL query.
*
* @param queryString a query expressed in Hibernate's query language
* @param startIndex the index of the first item to be retrieved
* @param maxResults the number of items to be retrieved, if <code>0</code> it retrieves ALL the items
* @param parameters the (optional) parameters for the query.
* @return a List of entities containing the results of the query execution
*/
public List findByQuery(String queryString, int startIndex, int maxResults, QueryParameter... parameters)
{
Query query = getSession().createQuery(queryString);
for (QueryParameter parameter : parameters)
{
parameter.applyNamedParameterToQuery(query);
}
if (maxResults > 0)
query.setMaxResults(maxResults);
if (startIndex > 0)
query.setFirstResult(startIndex);
if (logger.isDebugEnabled())
logger.debug(query.getQueryString());
return query.list();
}
/**
* (non-Javadoc)
*
* @see org.tynamo.services.PersistenceService#getInstances(java.lang.Class)
*/
public <T> List<T> getInstances(final Class<T> type)
{
return getSession().createCriteria(type).list();
}
public <T> List<T> getInstances(final Class<T> type, int startIndex, int maxResults)
{
return getInstances(type, DetachedCriteria.forClass(type), startIndex, maxResults);
}
/**
* (non-Javadoc)
*
* @see org.tynamo.services.PersistenceService#save(java.lang.Object)
*/
public <T> T save(T instance)
{
TynamoClassDescriptor tynamoclassdescriptor = descriptorService.getClassDescriptor(instance.getClass());
// @todo: org.hibernate.MappingException: Unknown entity
/* check isTransient to avoid merging on entities not persisted yet. TRAILS-33 */
if (!tynamoclassdescriptor.getHasCyclicRelationships() || isTransient(instance, tynamoclassdescriptor))
{
getSession().saveOrUpdate(instance);
} else
{
instance = (T) getSession().merge(instance);
}
return instance;
}
public void removeAll(Collection collection)
{
// getSession().deleteAll(collection);
}
public void remove(Object instance)
{
getSession().delete(instance);
}
public <T> List<T> getInstances(Class<T> type, DetachedCriteria criteria)
{
criteria = alterCriteria(type, criteria);
criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
return criteria.getExecutableCriteria(getSession()).list();
}
public void reattach(Object model)
{
getSession().lock(model, LockMode.NONE);
}
public Serializable getIdentifier(final Object data, final TynamoClassDescriptor classDescriptor)
{
return (Serializable) getPropertyAccess().get(data, classDescriptor.getIdentifierDescriptor().getName());
}
public Serializable getIdentifier(final Object data)
{
return getIdentifier(data, descriptorService.getClassDescriptor(data.getClass()));
}
public boolean isTransient(Object data, TynamoClassDescriptor classDescriptor)
{
try
{
return getIdentifier(data, classDescriptor) == null;
} catch (TransientObjectException e)
{
return true;
}
}
public List getInstances(final Object example, final TynamoClassDescriptor classDescriptor)
{
//create Criteria instance
DetachedCriteria searchCriteria = DetachedCriteria.forClass(example.getClass());
searchCriteria = alterCriteria(example.getClass(), searchCriteria);
//loop over the example object's PropertyDescriptors
for (TynamoPropertyDescriptor propertyDescriptor : classDescriptor.getPropertyDescriptors())
{
//only add a Criterion to the Criteria instance if this property is searchable
if (propertyDescriptor.isSearchable())
{
String propertyName = propertyDescriptor.getName();
Class propertyClass = propertyDescriptor.getPropertyType();
Object value = getPropertyAccess().get(example, propertyName);
//only add a Criterion to the Criteria instance if the value for this property is non-null
if (value != null)
{
if (String.class.isAssignableFrom(propertyClass) && ((String) value).length() > 0)
{
searchCriteria
.add(Restrictions.like(propertyName, value.toString(), MatchMode.ANYWHERE));
}
/**
* 'one'-end of many-to-one, one-to-one
*
* Just match the identifier
*/
else if (propertyDescriptor.isObjectReference())
{
Serializable identifierValue = getIdentifier(value,
descriptorService.getClassDescriptor(propertyDescriptor.getBeanType()));
searchCriteria.createCriteria(propertyName).add(Restrictions.idEq(identifierValue));
} else if (propertyClass.isPrimitive())
{
//primitive types: ignore zeroes in case of numeric types, ignore booleans anyway (TODO come up with something...)
if (!propertyClass.equals(boolean.class) && ((Number) value).longValue() != 0)
{
searchCriteria.add(Restrictions.eq(propertyName, value));
}
} else if (propertyDescriptor.isCollection())
{
//one-to-many or many-to-many
CollectionDescriptor collectionDescriptor =
(CollectionDescriptor) propertyDescriptor;
TynamoClassDescriptor collectionClassDescriptor = descriptorService.getClassDescriptor(collectionDescriptor.getElementType());
if (collectionClassDescriptor != null)
{
String identifierName = collectionClassDescriptor.getIdentifierDescriptor().getName();
Collection<Serializable> identifierValues = new ArrayList<Serializable>();
Collection associatedItems = (Collection) value;
if (associatedItems != null && associatedItems.size() > 0)
{
for (Object o : associatedItems)
{
identifierValues.add(getIdentifier(o, collectionClassDescriptor));
}
//add a 'value IN collection' restriction
searchCriteria.createCriteria(propertyName)
.add(Restrictions.in(identifierName, identifierValues));
}
}
}
}
}
}
searchCriteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
// FIXME This won't work because the shadow proxy doesn't implement SessionImplementor
// that session is casted to. Maybe we should inject SessionManager instead
// and obtain the Session from it
return searchCriteria.getExecutableCriteria(getSession()).list();
}
public int count(Class type)
{
return count(type, DetachedCriteria.forClass(type));
}
public int count(Class type, DetachedCriteria detachedCriteria)
{
detachedCriteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
final DetachedCriteria criteria = alterCriteria(type, detachedCriteria);
Criteria executableCriteria = criteria.getExecutableCriteria(getSession()).setProjection(Projections.rowCount());
return ((Long) executableCriteria.uniqueResult()).intValue();
}
public <T> List<T> getInstances(Class<T> type, final DetachedCriteria detachedCriteria, final int startIndex, final int maxResults)
{
return getInstances(alterCriteria(type, detachedCriteria), startIndex, maxResults);
}
public List getInstances(final DetachedCriteria detachedCriteria, final int startIndex, final int maxResults)
{
detachedCriteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
Criteria executableCriteria = detachedCriteria.getExecutableCriteria(getSession());
if (startIndex >= 0)
{
executableCriteria.setFirstResult(startIndex);
}
if (maxResults > 0)
{
executableCriteria.setMaxResults(maxResults);
}
return executableCriteria.list();
}
/**
* This hook allows subclasses to modify the query criteria, such as for security
*
* @param detachedCriteria The original Criteria query
* @return The modified Criteria query for execution
*/
protected DetachedCriteria alterCriteria(Class type, DetachedCriteria detachedCriteria)
{
return detachedCriteria;
}
/**
* @see org.tynamo.hibernate.services.HibernatePersistenceService#saveOrUpdate(java.lang.Object)
*/
public <T> T merge(T instance)
{
return (T) getSession().merge(instance);
}
/**
* @see org.tynamo.hibernate.services.HibernatePersistenceService#saveOrUpdate(java.lang.Object)
*/
public <T> T saveOrUpdate(T instance)
{
getSession().saveOrUpdate(instance);
return instance;
}
public <T> T addToCollection(CollectionDescriptor descriptor, T element, Object collectionOwner)
{
Class elementType = descriptor.getElementType();
String addMethod = descriptor.getAddExpression() != null ? descriptor.getAddExpression() : "add" + elementType.getSimpleName();
try
{
Method method = descriptor.getBeanType().getMethod(addMethod, new Class[]{elementType});
method.invoke(collectionOwner, element);
return element;
} catch (NoSuchMethodException e)
{
// do nothing;
} catch (InvocationTargetException e)
{
throw new RuntimeException(e);
} catch (IllegalAccessException e)
{
throw new RuntimeException(e);
}
Collection collection = (Collection) getPropertyAccess().get(collectionOwner, descriptor.getName());
if (!(descriptor.isChildRelationship() && (collection instanceof List) && (collection.contains(element))))
{
collection.add(element);
}
return element;
}
public List getOrphanInstances(CollectionDescriptor descriptor, Object owner)
{
if (descriptor.getInverseProperty() != null && descriptor.isOneToMany())
{
Criteria criteria = getSession().createCriteria(descriptor.getElementType());
TynamoClassDescriptor elementDescriptor = descriptorService.getClassDescriptor(descriptor.getBeanType());
String idProperty = elementDescriptor.getIdentifierDescriptor().getName();
if (owner != null)
{
criteria.add(
Restrictions.disjunction()
.add(Restrictions.isNull(descriptor.getInverseProperty()))
.add(Restrictions.eq(descriptor.getInverseProperty() + "." + idProperty, getIdentifier(owner, elementDescriptor))));
} else
{
criteria.add(Restrictions.isNull(descriptor.getInverseProperty()));
}
return criteria.list();
}
return getInstances(descriptor.getElementType());
}
@Override
public <T> GridDataSource getGridDataSource(Class<T> type)
{
return new HibernateGridDataSource(getSession(), type);
}
}