package edu.ualberta.med.biobank.common.wrappers; import java.util.ArrayList; import java.util.Collection; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.hibernate.Session; import edu.ualberta.med.biobank.common.wrappers.actions.WrapperAction; import edu.ualberta.med.biobank.common.wrappers.tasks.QueryTask; import edu.ualberta.med.biobank.server.applicationservice.exceptions.BiobankSessionException; import gov.nih.nci.system.query.SDKQuery; import gov.nih.nci.system.query.SDKQueryResult; public class RebindWrappersQueryTask implements QueryTask { private final ModelWrapper<?> wrapper; private final List<ModelWrapper<?>> wrappersToRebind = new ArrayList<ModelWrapper<?>>(); private final List<Object> oldModels = new ArrayList<Object>(); public RebindWrappersQueryTask(Set<ModelWrapper<?>> wrappers) { // arbitrarily pick the first wrapper to use for security checks this.wrapper = wrappers.iterator().next(); init(wrappers); } @Override public SDKQuery getSDKQuery() { return new ReturnAction(wrapper, oldModels); } @Override public void afterExecute(SDKQueryResult result) { @SuppressWarnings("unchecked") List<Object> newModels = (List<Object>) result.getObjectResult(); updateWrappedObjects(newModels); updateSession(); } private void updateWrappedObjects(List<Object> newModels) { for (int i = 0, n = wrappersToRebind.size(); i < n; i++) { ModelWrapper<?> wrapper = wrappersToRebind.get(i); setWrappedObject(wrapper, newModels.get(i)); // TODO: only thing that may have changed is the id, notify // listeners? // TODO: direct properties may have changed, but not // associations (e.g. label if container rename) so should do // some sort of notification. // clear old values since they are no longer valid after an insert, // update, or delete wrapper.getElementTracker().clear(); } } /** * Need to clean up the model wrapper map because after coming back from the * server, it will contain model objects that are no longer referenced by * the wrappers. Remove all old model object keys and replace with the new * model objects. * * @param newModels */ private void updateSession() { for (int i = 0, n = wrappersToRebind.size(); i < n; i++) { ModelWrapper<?> wrapper = wrappersToRebind.get(i); Object oldModel = oldModels.get(i); // TODO: note that uni-directional references INTO these model // objects will not be updated, they will still reference a now // detached model object graph. The wrappers could inform an object // when it calls set on it, then it could notify the object that has // a reference to it when it is updated, then a rebind could be // done? For example, if persist was called on an AddressWrapper // object (but not the CenterWrapper object that has it) then the // old center model object would contain a reference to the old // address model object, but the CenterWrapper would contain a // reference to the same AddressWrapper, which was rebound to a new // address model object. wrapper.session.remove(oldModel); wrapper.session.add(wrapper); } } private static <E> void setWrappedObject(ModelWrapper<E> wrapper, Object object) { wrapper.wrappedObject = wrapper.getWrappedClass().cast(object); } private void init(Collection<ModelWrapper<?>> wrappers) { Map<ModelWrapper<?>, Object> map = new IdentityHashMap<ModelWrapper<?>, Object>(); for (ModelWrapper<?> wrapper : wrappers) { readCache(wrapper, map); } for (Entry<ModelWrapper<?>, Object> entry : map.entrySet()) { wrappersToRebind.add(entry.getKey()); oldModels.add(entry.getValue()); } } private void readCache(ModelWrapper<?> wrapper, Map<ModelWrapper<?>, Object> map) { if (!map.containsKey(wrapper)) { map.put(wrapper, wrapper.wrappedObject); for (Object o : wrapper.propertyCache.values()) { if (o instanceof Collection) { for (Object e : (Collection<?>) o) { if (e instanceof ModelWrapper<?>) { readCache((ModelWrapper<?>) e, map); } } } else if (o instanceof ModelWrapper<?>) { readCache((ModelWrapper<?>) o, map); } } } } /** * Simply returns the given action. This is useful because we can pass a * list of wrapped objects, then get "new" references back after * deserialization. * * @author jferland * */ @SuppressWarnings("rawtypes") public static class ReturnAction extends WrapperAction { private static final long serialVersionUID = 1L; private Object result; @SuppressWarnings("unchecked") public ReturnAction(ModelWrapper<?> wrapper, Object result) { super(wrapper); this.result = result; } public void setResult(Object result) { this.result = result; } public Object getResult() { return result; } @Override public Object doAction(Session session) throws BiobankSessionException { return result; } } }