/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* Copyright (c) 2013, MPL CodeInside http://codeinside.ru
*/
package ru.codeinside.gses.lazyquerycontainer;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import com.vaadin.data.Item;
import com.vaadin.data.util.BeanItem;
import com.vaadin.data.util.ObjectProperty;
public class EntityQuery implements Query, Serializable {
/** Java serialization version UID. */
private static final long serialVersionUID = 1L;
/** The JPA EntityManager. */
private final EntityManager entityManager;
/** Flag reflecting whether application manages transactions. */
private final boolean applicationTransactionManagement;
/** The JPA entity class. */
private final Class<?> entityClass;
/** The JPA select query. */
private final String selectPsql;
/** The JPA select count query. */
private final String selectCountPsql;
/** The PSQL for deleting entities. */
private final String deletePsql;
/** The parameters to set to JPA query. */
private final Map<String, Object> selectParameters;
/**
* QueryDefinition contains definition of the query properties and batch
* size.
*/
private final EntityQueryDefinition queryDefinition;
/** The size of the query. */
private int querySize = -1;
/** The entity PSQL definition. */
private final EntityQueryDefinition.EntityPsqlDefinition entityPsqlDefinition;
/**
* Constructor for configuring the query.
* @param entityQueryDefinition The entity query definition.
*/
public EntityQuery(final EntityQueryDefinition entityQueryDefinition) {
this.entityPsqlDefinition = entityQueryDefinition.getEntityPsqlDefinition();
this.entityManager = entityQueryDefinition.getEntityManager();
this.queryDefinition = entityQueryDefinition;
this.entityClass = entityQueryDefinition.getEntityClass();
this.selectPsql = entityPsqlDefinition.getSelectPsql();
this.selectCountPsql = entityPsqlDefinition.getSelectCountPsql();
this.deletePsql = entityPsqlDefinition.getDeletePsql();
this.selectParameters = entityQueryDefinition.getWhereParameters();
this.applicationTransactionManagement = entityQueryDefinition.isApplicationManagedTransactions();
}
/**
* Constructs new item based on QueryDefinition.
* @return new item.
*/
@Override
public final Item constructItem() {
try {
final Object entity = entityClass.newInstance();
final BeanInfo info = Introspector.getBeanInfo(entityClass);
for (final PropertyDescriptor pd : info.getPropertyDescriptors()) {
for (final Object propertyId : queryDefinition.getPropertyIds()) {
if (pd.getName().equals(propertyId)) {
pd.getWriteMethod().invoke(entity, queryDefinition.getPropertyDefaultValue(propertyId));
}
}
}
return toItem(entity);
} catch (final Exception e) {
throw new RuntimeException("Error in bean construction or property population with default values.", e);
}
}
/**
* Number of beans returned by query.
* @return number of beans.
*/
@Override
public int size() {
if (querySize == -1) {
final javax.persistence.Query query = entityManager.createQuery(selectCountPsql);
if (selectParameters != null) {
for (final String parameterKey : selectParameters.keySet()) {
query.setParameter(parameterKey, selectParameters.get(parameterKey));
}
}
querySize = ((Number) query.getSingleResult()).intValue();
}
return querySize;
}
/**
* Load batch of items.
* @param startIndex Starting index of the item list.
* @param count Count of the items to be retrieved.
* @return List of items.
*/
@Override
public List<Item> loadItems(final int startIndex, final int count) {
final javax.persistence.Query query = entityManager.createQuery(selectPsql);
if (selectParameters != null) {
for (final String parameterKey : selectParameters.keySet()) {
query.setParameter(parameterKey, selectParameters.get(parameterKey));
}
}
query.setFirstResult(startIndex);
query.setMaxResults(count);
final List<?> entities = query.getResultList();
final List<Item> items = new ArrayList<Item>();
for (final Object entity : entities) {
if (queryDefinition.isDetachedEntities()) {
entityManager.detach(entity);
}
items.add(toItem(entity));
}
return items;
}
/**
* Saves the modifications done by container to the query result. Query will
* be discarded after changes have been saved and new query loaded so that
* changed items are sorted appropriately.
* @param addedItems Items to be inserted.
* @param modifiedItems Items to be updated.
* @param removedItems Items to be deleted.
*/
@Override
public void saveItems(final List<Item> addedItems, final List<Item> modifiedItems, final List<Item> removedItems) {
if (applicationTransactionManagement) {
entityManager.getTransaction().begin();
}
try {
for (final Item item : addedItems) {
if (!removedItems.contains(item)) {
entityManager.persist(fromItem(item));
}
}
for (final Item item : modifiedItems) {
if (!removedItems.contains(item)) {
Object entity = fromItem(item);
if (queryDefinition.isDetachedEntities()) {
entity = entityManager.merge(entity);
}
entityManager.persist(entity);
}
}
for (final Item item : removedItems) {
if (!addedItems.contains(item)) {
Object entity = fromItem(item);
if (queryDefinition.isDetachedEntities()) {
entity = entityManager.merge(entity);
}
entityManager.remove(entity);
}
}
if (applicationTransactionManagement) {
entityManager.getTransaction().commit();
}
} catch (final Exception e) {
if (applicationTransactionManagement) {
if (entityManager.getTransaction().isActive()) {
entityManager.getTransaction().rollback();
}
}
throw new RuntimeException(e);
}
}
/**
* Removes all items. Query will be discarded after delete all items has
* been called.
* @return true if the operation succeeded or false in case of a failure.
*/
@Override
public boolean deleteAllItems() {
if (applicationTransactionManagement) {
entityManager.getTransaction().begin();
}
try {
entityManager.createQuery(deletePsql).executeUpdate();
if (applicationTransactionManagement) {
entityManager.getTransaction().commit();
}
} catch (final Exception e) {
if (applicationTransactionManagement) {
if (entityManager.getTransaction().isActive()) {
entityManager.getTransaction().rollback();
}
}
throw new RuntimeException(e);
}
return true;
}
/**
* Converts bean to Item. Implemented by encapsulating the Bean first to
* BeanItem and then to CompositeItem.
* @param entity bean to be converted.
* @return item converted from bean.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected final Item toItem(final Object entity) {
if (queryDefinition.isCompositeItems()) {
final BeanItem<?> beanItem = new BeanItem<Object>(entity);
final CompositeItem compositeItem = new CompositeItem();
compositeItem.addItem("bean", beanItem);
for (final Object propertyId : queryDefinition.getPropertyIds()) {
if (compositeItem.getItemProperty(propertyId) == null) {
compositeItem.addItemProperty(
propertyId,
new ObjectProperty(queryDefinition.getPropertyDefaultValue(propertyId), queryDefinition
.getPropertyType(propertyId), queryDefinition.isPropertyReadOnly(propertyId)));
}
}
return compositeItem;
} else {
return new BeanItem<Object>(entity);
}
}
/**
* Converts item back to bean.
* @param item Item to be converted to bean.
* @return Resulting bean.
*/
protected final Object fromItem(final Item item) {
if (queryDefinition.isCompositeItems()) {
return (Object) ((BeanItem<?>) (((CompositeItem) item).getItem("bean"))).getBean();
} else {
return ((BeanItem<?>) item).getBean();
}
}
/**
* @return the queryDefinition
*/
protected final EntityQueryDefinition getQueryDefinition() {
return queryDefinition;
}
/**
* @return the entityPsqlDefinition
*/
protected final EntityQueryDefinition.EntityPsqlDefinition getEntityPsqlDefinition() {
return entityPsqlDefinition;
}
}