/*
* ome.security.basic.MergeEventListener
*
* Copyright 2006 University of Dundee. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.security.basic;
// Java imports
import java.util.Map;
import ome.conditions.SecurityViolation;
import ome.model.IEnum;
import ome.model.IObject;
import ome.model.meta.Event;
import ome.tools.hibernate.HibernateUtils;
import ome.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.criterion.Restrictions;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.event.EventSource;
import org.hibernate.event.MergeEvent;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.ForeignKeyDirection;
import org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener;
import org.springframework.util.Assert;
/**
* responsible for responding to merge events. in particular in load/re-loading
* certain types to make use by clients easier.
*
* In general, enforces the detached-graph re-attachment "Commandments" as
* outlined in TODO. Objects that are transient (no ID) are unchanged; objects
* that are managed (with ID) are checked for validity (i.e. must have a
* version); and unloaded/filtered objects & collections are re-filled.
*
* @author Josh Moore, josh.moore at gmx.de
* @version $Revision$, $Date$
* @since 3.0
*/
public class MergeEventListener extends IdTransferringMergeEventListener {
public final static String MERGE_EVENT = "MergeEvent";
private static final long serialVersionUID = 240558701677298961L;
private static Logger log = LoggerFactory.getLogger(MergeEventListener.class);
private final CurrentDetails cd;
private final TokenHolder th;
/** main constructor. Requires a non-null security system */
public MergeEventListener(CurrentDetails cd, TokenHolder th) {
Assert.notNull(cd);
Assert.notNull(th);
this.cd = cd;
this.th = th;
}
@Override
public void onMerge(MergeEvent event) throws HibernateException {
if (cd.isDisabled(MERGE_EVENT)) {
throw new SecurityViolation(
"The MergeEventListener has been disabled.");
}
if (earlyExit(event)) {
return;
}
super.onMerge(event);
}
@Override
public void onMerge(MergeEvent event, Map copyCache)
throws HibernateException {
if (cd.isDisabled(MERGE_EVENT)) {
throw new SecurityViolation(
"The MergeEventListener has been disabled.");
}
if (earlyExit(event)) {
return;
}
super.onMerge(event, copyCache);
}
private boolean earlyExit(MergeEvent event) {
final Object entity = event.getEntity();
final EventSource source = event.getSession();
if ( entity instanceof IObject) {
IObject iobject = (IObject) entity;
if (!iobject.isLoaded()) {
log.trace("ignoring unloaded iobject");
event.setResult(source.load(event.getEntityName(), iobject.getId()));
return true; //EARLY EXIT!
}
}
return false;
}
@Override
protected void copyValues(EntityPersister persister, Object entity,
Object target, SessionImplementor source, Map copyCache) {
if (entity instanceof IObject) {
HibernateUtils.fixNulledOrFilteredCollections((IObject) entity,
(IObject) target, persister, source);
propagateHiddenValues((IObject) entity, (IObject) target);
}
super.copyValues(persister, entity, target, source, copyCache);
}
@Override
protected void copyValues(EntityPersister persister, Object entity,
Object target, SessionImplementor source, Map copyCache,
ForeignKeyDirection foreignKeyDirection) {
if (entity instanceof IObject) {
HibernateUtils.fixNulledOrFilteredCollections((IObject) entity,
(IObject) target, persister, source);
propagateHiddenValues((IObject) entity, (IObject) target);
}
super.copyValues(persister, entity, target, source, copyCache,
foreignKeyDirection);
}
@Override
@SuppressWarnings( { "cast", "unchecked" })
protected void entityIsTransient(MergeEvent event, Map copyCache) {
Class cls = event.getOriginal().getClass();
IEnum extant = null;
if (IEnum.class.isAssignableFrom(cls)) {
String value = ((IEnum) event.getOriginal()).getValue();
Class type = ((IEnum) event.getOriginal()).getClass();
Criteria c = event.getSession().createCriteria(type).add(
Restrictions.eq("value", value));
extant = (IEnum) c.uniqueResult();
if (null != extant) {
log("Using existing Enum(", event.getEntityName(),
") with value:", value);
copyCache.put(event.getEntity(), extant);
event.setResult(extant);
}
}
// the above didn't succeed. process normally.
if (extant == null) {
super.entityIsTransient(event, copyCache);
}
fillReplacement(event);
}
@Override
@SuppressWarnings("unchecked")
protected void entityIsDetached(MergeEvent event, Map copyCache) {
final IObject orig = (IObject) event.getOriginal();
final EventSource source = event.getSession();
if (HibernateUtils.isUnloaded(orig)) {
log("Reloading unloaded entity:", event.getEntityName(), ":", orig
.getId());
Class<?> k = Utils.trueClass(orig.getClass());
Object obj = source.load(k, orig.getId());
event.setResult(obj);
copyCache.put(event.getEntity(), obj);
// TODO this was maybe a bug. check if findDirty is superfluous.
}
else if (orig instanceof Event) {
final Object obj = source.load(Event.class, orig.getId());
event.setResult(obj);
copyCache.put(event.getEntity(), obj);
}
else {
super.entityIsDetached(event, copyCache);
}
fillReplacement(event);
}
// ~ Helpers
// =========================================================================
protected void fillReplacement(MergeEvent event) {
if (event.getOriginal() instanceof IObject) {
IObject obj = (IObject) event.getOriginal();
obj.getGraphHolder().setReplacement((IObject) event.getResult());
}
}
protected void propagateHiddenValues(IObject from, IObject to) {
th.copyToken(from, to);
if (from.getDetails() != null && from.getDetails().filteredSize() > 0) {
to.getDetails().addFiltered(from.getDetails().filteredSet());
}
}
private void log(Object... objects) {
if (log.isDebugEnabled() && objects != null && objects.length > 0) {
StringBuilder sb = new StringBuilder(objects.length * 16);
for (Object obj : objects) {
sb.append(obj == null ? "null" : obj.toString());
}
log.debug(sb.toString());
}
}
}