/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program 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 General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.enterprise.server.util;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.Query;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Hibernate;
import org.rhq.core.domain.criteria.Criteria;
import org.rhq.core.domain.criteria.Criteria.Restriction;
import org.rhq.core.domain.util.PageControl;
import org.rhq.core.domain.util.PageList;
public class CriteriaQueryRunner<T> {
private static final Log LOG = LogFactory.getLog(CriteriaQueryRunner.class);
private Criteria criteria;
private CriteriaQueryGenerator queryGenerator;
private EntityManager entityManager;
private boolean automaticFetching;
private static final QueryUtility.PagedDataFetchSettings DATA_FETCH_SETTINGS;
static {
DATA_FETCH_SETTINGS = new QueryUtility.PagedDataFetchSettings();
DATA_FETCH_SETTINGS.setThrowOnMaxAttempts(true);
}
public CriteriaQueryRunner(Criteria criteria, CriteriaQueryGenerator queryGenerator, EntityManager entityManager) {
this(criteria, queryGenerator, entityManager, true);
}
public CriteriaQueryRunner(Criteria criteria, CriteriaQueryGenerator queryGenerator, EntityManager entityManager,
boolean automaticFetching) {
this.criteria = criteria;
this.queryGenerator = queryGenerator;
this.entityManager = entityManager;
this.automaticFetching = automaticFetching;
}
@SuppressWarnings("unchecked")
public PageList<T> execute() {
PageList<T> results;
PageControl pageControl = CriteriaQueryGenerator.getPageControl(criteria);
Restriction criteriaRestriction = criteria.getRestriction();
if (criteriaRestriction == null) {
try {
results = QueryUtility.fetchPagedDataAndCount(queryGenerator.getQuery(entityManager),
queryGenerator.getCountQuery(entityManager), pageControl, DATA_FETCH_SETTINGS);
} catch (PhantomReadMaxAttemptsExceededException e) {
LOG.warn(
"Could not get consistent results of the paged data and a total count for "
+ CriteriaUtil.toString(criteria) + ". After " + e.getNumberOfAttempts()
+ " attempts, the collection size" + " is " + e.getList().size()
+ ", while the count query reports " + e.getList().getTotalSize() + " for " + pageControl
+ ". The discrepancy has not cleared up in " + e.getMillisecondsSpentTrying()
+ "ms so we're giving up, "
+ "returning inconsistent results. Note that is most possibly NOT an error. It is likely "
+ "caused by concurrent database activity that changes the contents of the database that the "
+ "criteria query is querying.", new Exception());
results = (PageList<T>) e.getList();
}
finalizeCollection(results);
if (LOG.isDebugEnabled()) {
LOG.debug("restriction=" + criteriaRestriction + ", resultSize=" + results.size() + ", resultCount="
+ results.getTotalSize());
}
} else if (criteriaRestriction == Restriction.COUNT_ONLY) {
results = new PageList<T>(getCount(), pageControl);
if (LOG.isDebugEnabled()) {
LOG.debug("restriction=" + criteriaRestriction + ", resultCount=" + results.getTotalSize());
}
} else if (criteriaRestriction == Restriction.COLLECTION_ONLY) {
results = new PageList<T>(getCollection(), pageControl);
if (LOG.isDebugEnabled()) {
LOG.debug("restriction=" + criteriaRestriction + ", resultSize=" + results.size());
}
} else {
throw new IllegalArgumentException(this.getClass().getSimpleName()
+ " does not support query execution for criteria with " + Restriction.class.getSimpleName() + " "
+ criteriaRestriction);
}
return results;
}
@SuppressWarnings("unchecked")
private Collection<? extends T> getCollection() {
Query query = queryGenerator.getQuery(entityManager);
List<T> results = query.getResultList();
finalizeCollection(results);
return results;
}
private void finalizeCollection(List<?> results) {
/*
* suppression of auto-fetch useful in cases where alterProject(String) was called on the generator, which
* changed the return type of the result set from List<T> to something else. in that case, the caller to
* this method must, as necessary, perform the fetch manually.
*/
if (automaticFetching) {
if (!queryGenerator.getPersistentBagFields().isEmpty()) {
for (Object entity : results) {
initPersistentBags(entity);
}
}
if (!queryGenerator.getJoinFetchFields().isEmpty()) {
for (Object entity : results) {
initJoinFetchFields(entity);
}
}
}
}
private int getCount() {
Query countQuery = queryGenerator.getCountQuery(entityManager);
long count = (Long) countQuery.getSingleResult();
return (int) count;
}
public void initFetchFields(Object entity) {
initPersistentBags(entity);
initJoinFetchFields(entity);
}
private void initPersistentBags(Object entity) {
for (Field persistentBagField : queryGenerator.getPersistentBagFields()) {
initialize(entity, persistentBagField);
}
}
private void initJoinFetchFields(Object entity) {
for (Field joinFetchField : queryGenerator.getJoinFetchFields()) {
initialize(entity, joinFetchField);
}
}
/**
* @param entity
* @param field
* @return true if the field was successfully initialize, false if there was any problem
*/
private boolean initialize(Object entity, Field field) {
boolean initialized = true;
try {
field.setAccessible(true);
Object instance = field.get(entity);
Hibernate.initialize(instance);
if (instance instanceof Iterable) {
Iterator<?> it = ((Iterable<?>) instance).iterator();
while (it.hasNext())
it.next();
}
} catch (EntityNotFoundException e) {
// TODO: See BZ 1025756, we should try and get rid of required join fields that allow 0.
if (LOG.isDebugEnabled()) {
String msg = "Could not initialize [" + field
+ "]. This may happen if this entity field is required, lazy-loaded/proxied, but allows ID of 0:";
LOG.debug(msg, e);
}
initialized = false;
} catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Could not initialize " + field + " Following exception has caused the problem: ", e);
} else {
LOG.warn("Could not initialize " + field);
}
initialized = false;
}
// instead of likely leaving an unloaded Hibernate proxy, set null
if (!initialized) {
try {
field.set(entity, null);
} catch (Throwable t) {
if (LOG.isDebugEnabled()) {
LOG.debug("Could not nullify non-initialized field [" + field + "].", t);
}
}
}
return initialized;
}
}