/*
* Copyright (c) 2009-2010 Lockheed Martin Corporation
*
* 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.eurekastreams.server.persistence;
import java.util.ArrayList;
import java.util.HashMap;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.apache.commons.logging.Log;
import org.eurekastreams.commons.hibernate.QueryOptimizer;
import org.eurekastreams.commons.logging.LogFactory;
import org.eurekastreams.server.domain.PagedSet;
import org.hibernate.Session;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
/**
* Parent class for domain entity mappers.
*
* @param <T>
* the type of domain entity this class maps
*/
public abstract class DomainEntityMapper<T>
{
/**
* Logger.
*/
private Log log = LogFactory.make();
/**
* The QueryOptimizer to use for specialized functions.
*/
private QueryOptimizer queryOptimizer;
/**
* Constructor.
*
* @param inQueryOptimizer
* the QueryOptimizer to use for specialized functions.
*/
public DomainEntityMapper(final QueryOptimizer inQueryOptimizer)
{
queryOptimizer = inQueryOptimizer;
}
/**
* Get the QueryOptimizer to use for specialized functions.
*
* @return the QueryOptimizer to use for specialized functions.
*/
protected QueryOptimizer getQueryOptimizer()
{
return queryOptimizer;
}
/**
* EntityManager to use for all ORM operations.
*/
private EntityManager entityManager;
/**
* Getter for entityManager.
*
* @return The entityManager.
*/
protected EntityManager getEntityManager()
{
return entityManager;
}
/**
* Set the entity manager to use for all ORM operations.
*
* @param inEntityManager
* the EntityManager to use for all ORM operations.
*/
@PersistenceContext
public void setEntityManager(final EntityManager inEntityManager)
{
this.entityManager = inEntityManager;
}
/**
* Get the domain entity name for the generic query operations.
*
* @return the domain entity name for the generic query operations.
*/
protected abstract String getDomainEntityName();
/**
* Insert the domain entity.
*
* @param domainEntity
* The domainEntity to operate on.
*/
public void insert(final T domainEntity)
{
entityManager.persist(domainEntity);
}
/**
* Update all entities that have changed since they were loaded within the same context.
*/
public void flush()
{
entityManager.flush();
}
/**
* Clear the entity manager. Only use this if you know what you're doing. A good example is when you want to add an
* item to an indexed collection, and get its proper indexColumn value back.
*/
public void clear()
{
entityManager.clear();
}
/**
* Refresh the domain entity.
*
* @param domainEntity
* the domain entity.
*/
public void refresh(final T domainEntity)
{
entityManager.refresh(domainEntity);
}
/**
* Find the domain entity by id.
*
* @param domainEntityId
* ID of the entity to look up
*
* @return the entity with the input
*/
@SuppressWarnings("unchecked")
public T findById(final Long domainEntityId)
{
Query q = entityManager.createQuery("from " + getDomainEntityName() + " where id = :domainEntityId")
.setParameter("domainEntityId", domainEntityId);
return (T) q.getSingleResult();
}
/**
* Find the domain entity by id.
*
* @param domainEntityId
* ID of the entity to look up
* @return the entity with the input
*/
public T findById(final Integer domainEntityId)
{
return findById(domainEntityId.longValue());
}
/**
* Get a PagedSet of type V, built from the input query string, with the count determined by the input
* countQueryString (more efficient than the version of this query without it).
*
* @param <V>
* the type of objects to return
* @param from
* the starting index
* @param to
* the ending index
* @param queryString
* the query string
* @param countQueryString
* the query string to use to determine the count - must return an integer
* @param parameters
* the parameters to inject into the query string
* @return a paged set of objects of type V as built from the input query string
*/
@SuppressWarnings("unchecked")
public <V> PagedSet<V> getTypedPagedResults(final int from, final int to, final String queryString,
final String countQueryString, final HashMap<String, Object> parameters)
{
PagedSet<V> results = new PagedSet<V>();
if (!results.isRangeValid(from, to))
{
throw new IllegalArgumentException("from/to are invalid");
}
Query count = entityManager.createQuery(countQueryString);
for (String key : parameters.keySet())
{
count.setParameter(key, parameters.get(key));
}
long total;
Object totalValue = count.getSingleResult();
if (totalValue instanceof Long)
{
total = (Long) totalValue;
}
else
{
total = (Integer) totalValue;
}
// if no results, return empty set
if (total == 0)
{
return results;
}
// return valid range even if you requested out of range
int validTo = to;
if (to >= total)
{
validTo = (int) total - 1;
}
// query to get the actual results
Query select = entityManager.createQuery(queryString);
for (String key : parameters.keySet())
{
select.setParameter(key, parameters.get(key));
}
select.setFirstResult(from);
select.setMaxResults(validTo - from + 1);
ArrayList<V> resultList = (ArrayList<V>) select.getResultList();
results.setFromIndex(from);
results.setToIndex(validTo);
results.setTotal((int) total);
results.setPagedSet(resultList);
return results;
}
/**
* Get a PagedSet of type V, built from the input query string.
*
* @param <V>
* the type of objects to return
* @param from
* the starting index
* @param to
* the ending index
* @param queryString
* the query string
* @param parameters
* the parameters to inject into the query string
* @return a paged set of objects of type V as built from the input query string
*/
@SuppressWarnings("unchecked")
public <V> PagedSet<V> getTypedPagedResults(final int from, final int to, final String queryString,
final HashMap<String, Object> parameters)
{
PagedSet<V> results = new PagedSet<V>();
log.debug("from: " + from + ", to: " + to);
if (!results.isRangeValid(from, to))
{
throw new IllegalArgumentException("from/to are invalid");
}
// TODO construct count query to avoid returning results of an entire
// query just to get the count
// like getPagedResults() does.
// these didn't work:
// "select count(q) from (select c.ideas from Campaign c where c.id=" +
// containerId + ") q";
// "select count(c.ideas) from Campaign c where c.id=id"
Query count = entityManager.createQuery(queryString);
for (String key : parameters.keySet())
{
count.setParameter(key, parameters.get(key));
}
int total = count.getResultList().size();
// if no results, return empty set
if (total == 0)
{
return results;
}
// return valid range even if you requested out of range
int validTo = to;
if (to >= total)
{
log.debug("to>=total - to: " + to + ", total: " + total);
validTo = total - 1;
}
log.debug("Query: " + queryString + ", total: " + total + ", validTo: " + validTo);
// query to get the actual results
Query select = entityManager.createQuery(queryString);
for (String key : parameters.keySet())
{
select.setParameter(key, parameters.get(key));
}
select.setFirstResult(from);
int maxResults = validTo - from + 1;
if (maxResults <= 0)
{
log.debug("Maxresults would have been negative or zero - "
+ "most likely an error on the client - returning no records");
return results;
}
select.setMaxResults(maxResults);
ArrayList<V> resultList = (ArrayList<V>) select.getResultList();
results.setFromIndex(from);
results.setToIndex(validTo);
results.setTotal(total);
results.setPagedSet(resultList);
return results;
}
/**
* get paged result set of type T.
*
* Subclasses are expected to construct an appropriate parameterized query string.
*
* suppression of type check warnings is because the JPA call doesn't support generics.
*
* @param from
* the from index to return, inclusive. This is not an index like the PK index, this is more like an
* array index, the array being the set of returned results from the query.
* @param to
* the to index to select to, inclusive. This is not an index like the PK index, this is more like an
* array index, the array being the set of returned results from the query.
* @param queryString
* to turn into a query
* @param parameters
* to use for parameterizing the query.
* @return paged results for a given query, index is inclusive.
*/
public PagedSet<T> getPagedResults(final int from, final int to, final String queryString,
final HashMap<String, Object> parameters)
{
return getTypedPagedResults(from, to, queryString, parameters);
}
/**
* get paged result set of type T.
*
* Subclasses are expected to construct an appropriate parameterized query string.
*
* suppression of type check warnings is because the JPA call doesn't support generics.
*
* @param from
* the from index to return, inclusive. This is not an index like the PK index, this is more like an
* array index, the array being the set of returned results from the query.
* @param to
* the to index to select to, inclusive. This is not an index like the PK index, this is more like an
* array index, the array being the set of returned results from the query.
* @param countQueryString
* the query string to use to determine the count - mut return an integer, and will be injected with the
* parameters
* @param queryString
* to turn into a query
* @param parameters
* to use for parameterizing the query.
* @return paged results for a given query, index is inclusive.
*/
public PagedSet<T> getPagedResults(final int from, final int to, final String countQueryString,
final String queryString, final HashMap<String, Object> parameters)
{
return getTypedPagedResults(from, to, countQueryString, queryString, parameters);
}
/**
* Get the FullTextSession for reindexing entities in the search index.
*
* @return the FullTextSession to use for reindexing in the search index
*/
protected FullTextSession getFullTextSession()
{
return Search.getFullTextSession((Session) entityManager.getDelegate());
}
}