package edu.ualberta.med.biobank.common.wrappers;
import edu.ualberta.med.biobank.common.exception.BiobankException;
import edu.ualberta.med.biobank.common.wrappers.loggers.LogGroup;
import edu.ualberta.med.biobank.common.wrappers.tasks.InactiveQueryTask;
import edu.ualberta.med.biobank.common.wrappers.tasks.PreQueryTask;
import edu.ualberta.med.biobank.common.wrappers.tasks.QueryTask;
import edu.ualberta.med.biobank.common.wrappers.tasks.RebindableWrapperQueryTask;
import gov.nih.nci.system.applicationservice.ApplicationException;
import gov.nih.nci.system.applicationservice.WritableApplicationService;
import gov.nih.nci.system.query.SDKQuery;
import gov.nih.nci.system.query.SDKQueryResult;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Maintains an internal list of actions to perform on given
* {@code ModelWrapper<?>} objects, which can then be performed atomically (all
* or nothing).
*
* @author jferland
*
*/
public class WrapperTransaction {
private final WritableApplicationService service;
private final Collection<Action> actions;
private static class Action {
private enum Type {
PERSIST,
DELETE;
}
public final Type type;
public final ModelWrapper<?> wrapper;
Action(Type type, ModelWrapper<?> wrapper) {
this.type = type;
this.wrapper = wrapper;
}
}
public WrapperTransaction(WritableApplicationService service) {
this.service = service;
this.actions = new ArrayList<Action>();
}
public void persist(ModelWrapper<?> wrapper) {
// TODO: check that wrapper not already added?
actions.add(new Action(Action.Type.PERSIST, wrapper));
}
public void persist(Collection<? extends ModelWrapper<?>> wrappers) {
// TODO: check that wrapper not already added?
for (ModelWrapper<?> wrapper : wrappers) {
persist(wrapper);
}
}
public void delete(ModelWrapper<?> wrapper) {
// TODO: check that wrapper not already added?
actions.add(new Action(Action.Type.DELETE, wrapper));
}
public void delete(Collection<? extends ModelWrapper<?>> wrappers) {
// TODO: check that wrapper not already added?
for (ModelWrapper<?> wrapper : wrappers) {
delete(wrapper);
}
}
@Deprecated
public void commit() throws BiobankException, ApplicationException {
// don't build the TaskList until now because it may depend on the state
// of the wrappers, which may have been changed before now, but after
// being added to our list of actions.
TaskList tasks = new TaskList();
for (Action action : actions) {
switch (action.type) {
case PERSIST:
action.wrapper.addPersistAndLogTasks(tasks);
break;
case DELETE:
action.wrapper.addDeleteAndLogTasks(tasks);
}
}
addRebindTask(tasks);
execute(tasks);
}
public static void persist(ModelWrapper<?> wrapper,
WritableApplicationService appService) throws BiobankException,
ApplicationException {
WrapperTransaction tx = new WrapperTransaction(appService);
tx.persist(wrapper);
tx.commit();
}
@Deprecated
public static void delete(ModelWrapper<?> wrapper,
WritableApplicationService appService) throws BiobankException,
ApplicationException {
WrapperTransaction tx = new WrapperTransaction(appService);
tx.delete(wrapper);
tx.commit();
}
private void addRebindTask(TaskList tasks) {
Set<ModelWrapper<?>> wrappers = new HashSet<ModelWrapper<?>>();
for (QueryTask task : tasks.getQueryTasks()) {
if (task instanceof RebindableWrapperQueryTask) {
ModelWrapper<?> wrapper = ((RebindableWrapperQueryTask) task)
.getWrapperToRebind();
wrappers.add(wrapper);
}
}
tasks.add(new RebindWrappersQueryTask(wrappers));
}
/**
* Execute the tasks in this {@code TaskList}.
*
* @param service
* @throws ApplicationException
*/
private void execute(TaskList tasks) throws BiobankException,
ApplicationException {
executePreQueryTasks(tasks.getPreQueryTasks());
executeQueryTasks(tasks.getQueryTasks());
}
private void executePreQueryTasks(List<PreQueryTask> preQueryTasks)
throws BiobankException {
for (PreQueryTask task : preQueryTasks) {
task.beforeExecute();
}
}
private void executeQueryTasks(List<QueryTask> queryTasks)
throws ApplicationException {
if (!queryTasks.isEmpty()) {
List<SDKQuery> queries = new ArrayList<SDKQuery>();
for (QueryTask task : queryTasks) {
SDKQuery query = task.getSDKQuery();
queries.add(query);
}
List<SDKQueryResult> results = service.executeBatchQuery(queries);
int i = 0;
for (QueryTask task : queryTasks) {
SDKQueryResult result = results.get(i);
task.afterExecute(result);
i++;
}
}
}
/**
* Manages several {@link List}-s of tasks, such as {@link QueryTask}-s and
* {@link PreQueryTask}-s used by a {@link ModelWrapperTransaction} to
* persist or delete a {@link ModelWrapper<?>}.
*
* @author jferland
*
* @see ModelWrapper
* @see QueryTask
* @see PreQueryTask
* @see WrapperTransaction
*
*/
public static class TaskList {
private final Map<ModelWrapper<?>, ModelWrapper<?>> cascaded =
new IdentityHashMap<ModelWrapper<?>, ModelWrapper<?>>();
private final LinkedList<QueryTask> queryTasks =
new LinkedList<QueryTask>();
private final List<PreQueryTask> preQueryTasks =
new ArrayList<PreQueryTask>();
private final LogGroup logGroup;
private TaskList() {
this(new LogGroup());
}
private TaskList(LogGroup logGroup) {
this.logGroup = logGroup;
}
/**
* Add a {@link QueryTask} to the end of the {@link QueryTask} list.
*
* @param task
*/
public void add(QueryTask task) {
queryTasks.add(task);
}
/**
* Wrap a {@link SDKQuery} in an actionless {@link QueryTask} (e.g.
* {@link InactiveQueryTask}) and add it to the end of the
* {@link QueryTask} list.
*
* @param task
*/
public void add(SDKQuery query) {
QueryTask task = new InactiveQueryTask(query);
queryTasks.add(task);
}
/**
* Add a {@link PreQueryTask} to the end of the {@link PreQueryTask}
* list.
*
* @param task
*/
public void add(PreQueryTask task) {
preQueryTasks.add(task);
}
/**
* Get the {@link LogGroup} that is associated with this transaction.
*
* @return
*/
public LogGroup getLogGroup() {
return logGroup;
}
public List<QueryTask> getQueryTasks() {
return queryTasks;
}
public List<PreQueryTask> getPreQueryTasks() {
return preQueryTasks;
}
//
// START CASCADE METHODS
//
/**
* Adds the tasks to persist a {@link ModelWrapper}'s {@link Property}.
* This is only done if the given {@link Property} has been set (even if
* it has only been loaded), even if it has not been modified.
*
* @param wrapper which has the property
* @param property to persist
*/
public <M, P> void persist(ModelWrapper<M> wrapper,
Property<P, M> property) {
// If the property is initialised (in the wrapped object) but not in
// the wrapper, then persist it anyways since the data will be sent
// anyways and a Hibernate cascade may take place and we may as well
// be aware of it.
if (wrapper.isPropertyCached(property)
|| wrapper.isInitialized(property)) {
if (property.isCollection()) {
@SuppressWarnings("unchecked")
Property<Collection<P>, ? super M> tmp =
(Property<Collection<P>, ? super M>) property;
Collection<ModelWrapper<P>> list = wrapper
.getWrapperCollection(tmp, null, false);
for (ModelWrapper<?> wrappedProperty : list) {
addPersistTasks(wrappedProperty);
}
} else {
ModelWrapper<P> wrappedProperty = wrapper
.getWrappedProperty(property, null);
if (wrappedProperty != null) {
addPersistTasks(wrappedProperty);
}
}
}
}
/**
* Adds the tasks to delete a {@link ModelWrapper}'s {@link Property}.
* This will load the {@link Property} to delete it, whether it has
* already been loaded or not.
* <p>
* This method is <em>potentially network expensive</em> since it may
* require information to be lazily loaded from the database when
* called. Consider using {@link deleteRemovedUnchecked()} if checks and
* persists are unnecessary.
*
* @param wrapper which has the property
* @param property to delete
*/
public <M, P> void delete(ModelWrapper<M> wrapper,
Property<P, M> property) {
if (property.isCollection()) {
@SuppressWarnings("unchecked")
Property<Collection<P>, ? super M> tmp =
(Property<Collection<P>, ? super M>) property;
Collection<ModelWrapper<P>> list = wrapper
.getWrapperCollection(tmp, null, false);
for (ModelWrapper<?> wrappedProperty : list) {
addDeleteTasks(wrappedProperty);
}
} else {
ModelWrapper<P> wrappedProperty = wrapper.getWrappedProperty(
property, null);
if (wrappedProperty != null) {
addDeleteTasks(wrappedProperty);
}
}
}
/**
* Adds tasks to persist all newly added elements in the collection for
* the given {@link ModelWrapper}'s {@link Property}.
* <p>
* This is <em>dangerous</em> because if a property is added and
* modified then <em>all</em> changes will be persisted, not just it
* being added to the collection (e.g. if the name was changed, the name
* change will be persisted as well).
*
* @param wrapper which has the property
* @param property to persist the added elements of
*/
public <M, P> void persistAdded(ModelWrapper<M> wrapper,
Property<Collection<P>, M> property) {
Collection<ModelWrapper<P>> elements = wrapper.getElementTracker()
.getAddedElements(property);
for (ModelWrapper<P> element : elements) {
addPersistTasks(element);
}
}
/**
* Adds tasks to persist all newly removed elements in the collection
* for the given {@link ModelWrapper}'s {@link Property}.
*
* @param wrapper which has the property
* @param property to persist the removed elements of
*/
public <M, P> void persistRemoved(ModelWrapper<M> wrapper,
Property<Collection<P>, M> property) {
Collection<ModelWrapper<P>> elements = wrapper.getElementTracker()
.getRemovedElements(property);
for (ModelWrapper<P> element : elements) {
addPersistTasks(element);
}
}
/**
* Adds tasks to remove elements from the given {@link ModelWrapper}'s
* {@link Property} for a collection.
*
* @param wrapper which has the property
* @param property to persist the removed elements of
*/
public <M, P> void deleteRemoved(ModelWrapper<M> wrapper,
Property<Collection<P>, ? super M> property) {
Collection<ModelWrapper<P>> elements = wrapper.getElementTracker()
.getRemovedElements(property);
for (ModelWrapper<P> element : elements) {
addDeleteTasks(element);
}
}
/**
* Adds tasks to delete a removed wrapped property value from the given
* {@link ModelWrapper}'s {@link Property}.
*
* @param wrapper which has the property
* @param property to delete
*/
public <M, P> void deleteRemovedValue(ModelWrapper<M> wrapper,
Property<P, M> property) {
ModelWrapper<P> removedValue = wrapper.getElementTracker()
.getRemovedValue(property);
if (removedValue != null) {
addDeleteTasks(removedValue);
}
}
//
// END CASCADE METHODS
//
@Deprecated
private void addPersistTasks(ModelWrapper<?> wrapper) {
if (!cascaded.containsKey(wrapper)) {
cascaded.put(wrapper, wrapper);
wrapper.addPersistAndLogTasks(this);
}
}
@Deprecated
private void addDeleteTasks(ModelWrapper<?> wrapper) {
if (!cascaded.containsKey(wrapper)) {
cascaded.put(wrapper, wrapper);
wrapper.addDeleteAndLogTasks(this);
}
}
}
}