com.actelion.research.spiritcore.services.dao.DAORevision.java Source code

Java tutorial

Introduction

Here is the source code for com.actelion.research.spiritcore.services.dao.DAORevision.java

Source

/*
 * Spirit, a study/biosample management tool for research.
 * Copyright (C) 2018 Idorsia Pharmaceuticals Ltd., Hegenheimermattweg 91,
 * CH-4123 Allschwil, Switzerland.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 * @author Joel Freyss
 */

package com.actelion.research.spiritcore.services.dao;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

import org.hibernate.LockMode;
import org.hibernate.envers.AuditReader;
import org.hibernate.envers.AuditReaderFactory;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.query.AuditQuery;
import org.slf4j.LoggerFactory;

import com.actelion.research.spiritcore.business.DataType;
import com.actelion.research.spiritcore.business.Document;
import com.actelion.research.spiritcore.business.IAuditable;
import com.actelion.research.spiritcore.business.IObject;
import com.actelion.research.spiritcore.business.audit.DifferenceItem.ChangeType;
import com.actelion.research.spiritcore.business.audit.DifferenceList;
import com.actelion.research.spiritcore.business.audit.Revision;
import com.actelion.research.spiritcore.business.audit.RevisionQuery;
import com.actelion.research.spiritcore.business.biosample.Biosample;
import com.actelion.research.spiritcore.business.biosample.Biotype;
import com.actelion.research.spiritcore.business.biosample.BiotypeMetadata;
import com.actelion.research.spiritcore.business.employee.Employee;
import com.actelion.research.spiritcore.business.employee.EmployeeGroup;
import com.actelion.research.spiritcore.business.location.Location;
import com.actelion.research.spiritcore.business.property.SpiritProperty;
import com.actelion.research.spiritcore.business.result.Result;
import com.actelion.research.spiritcore.business.result.Test;
import com.actelion.research.spiritcore.business.study.Phase;
import com.actelion.research.spiritcore.business.study.Sampling;
import com.actelion.research.spiritcore.business.study.Study;
import com.actelion.research.spiritcore.services.SpiritUser;
import com.actelion.research.spiritcore.util.Pair;

/**
 * DAO functions linked to audit functions
 *
 * @author Joel Freyss
 */
public class DAORevision {

    /**
     * Returns all revisions of the given entity
     * @param obj
     * @param maxRevId
     * @return
     */
    public static List<Revision> getLastRevisions(IAuditable obj) {
        return getLastRevisions(obj, -1, -1);
    }

    /**
     * Returns last revision of the given entity until the given maxRevId
     * @param obj
     * @param maxRevId (-1, to ignore)
     * @return
     */
    public static Revision getLastRevision(IAuditable obj, int maxRevId) {
        List<Revision> revisions = getLastRevisions(obj, maxRevId, 1);
        return revisions.size() > 0 ? revisions.get(0) : null;
    }

    /**
     * Returns all revisions of the given entity until the given maxRevId
     * @param obj
     * @param maxRevId (-1, to ignore)
     * @return
     */
    public static List<Revision> getLastRevisions(IAuditable obj, int maxRevId, int n) {
        Serializable id;
        if (obj instanceof IObject) {
            id = ((IObject) obj).getId();
        } else if (obj instanceof SpiritProperty) {
            id = ((SpiritProperty) obj).getId();
        } else {
            assert false;
            return null;
        }
        return getLastRevisions(obj.getClass(), id, maxRevId, n);

    }

    /**
     * Returns all revisions of the given entity until the given maxRevId
     * @param obj
     * @param maxRevId (-1, to ignore)
     * @return
     */
    @SuppressWarnings("unchecked")
    public static List<Revision> getLastRevisions(Class<?> claz, Serializable entityId, int maxRevId, int n) {

        long s = System.currentTimeMillis();
        EntityManager session = JPAUtil.getManager();
        AuditReader reader = AuditReaderFactory.get(session);
        AuditQuery query = reader.createQuery().forRevisionsOfEntity(claz, false, true).setCacheable(false)
                .setLockMode(LockMode.NONE);
        query.add(AuditEntity.id().eq(entityId));

        if (maxRevId > 0) {
            query.add(AuditEntity.revisionNumber().le(maxRevId));
        }
        if (n > 0) {
            query.setMaxResults(n);
            query.addOrder(AuditEntity.revisionNumber().desc());
        }
        List<Revision> revisions = getRevisions(null, query.getResultList());
        LoggerFactory.getLogger(DAORevision.class).debug("Loaded revisions for " + claz.getSimpleName() + "("
                + entityId + ") maxRevId=" + maxRevId + "-" + n + " in " + (System.currentTimeMillis() - s) + "ms");
        return revisions;
    }

    /**
     * Get the different versions of several elements.
     * Returns a map of element to a list of audited versions, the first element of the list being the most recent version
     * @param obj
     * @return
     */
    public static <T extends IObject> Map<T, List<T>> getHistories(Collection<T> col) {
        Map<T, List<T>> res = new HashMap<>();
        for (T t : col) {
            res.put(t, getHistory(t));
        }
        return res;
    }

    /**
     * Get the different version of an element, the first element of the list shows the most recent version
     * @param obj
     * @return
     */
    public static <T extends IObject> List<T> getHistory(T obj) {
        return getHistory(obj.getClass(), obj.getId(), -1);
    }

    /**
     * Get the different version of an element, the first element of the list shows the most recent version
     * @param obj
     * @return
     */
    public static <T extends IObject> List<T> getHistory(Class claz, Serializable objectId, int maxRevs) {
        long s = System.currentTimeMillis();
        EntityManager session = JPAUtil.getManager();
        AuditReader reader = AuditReaderFactory.get(session);
        AuditQuery query = reader.createQuery().forRevisionsOfEntity(claz, true, false)
                .add(AuditEntity.id().eq(objectId)).addOrder(AuditEntity.revisionNumber().desc())
                .setCacheable(false).setLockMode(LockMode.NONE);
        if (maxRevs > 0) {
            query.setMaxResults(maxRevs);
        }
        List<T> res = query.getResultList();
        for (T t : res) {
            session.detach(t);
        }

        LoggerFactory.getLogger(DAORevision.class).debug("Loaded history for " + claz.getSimpleName() + ": ("
                + objectId + ") in " + (System.currentTimeMillis() - s) + "ms");
        return res;
    }

    public static Revision getRevision(int revId) {

        RevisionQuery q = new RevisionQuery();
        q.setRevId(revId);

        List<Revision> revisions = queryRevisions(q);
        return revisions.size() > 0 ? revisions.get(0) : null;
    }

    public static List<Revision> queryRevisions(RevisionQuery query) {
        assert query != null;
        long s = System.currentTimeMillis();
        EntityManager session = JPAUtil.getManager();
        AuditReader reader = AuditReaderFactory.get(session);

        int rev1 = 0;
        try {
            rev1 = query.getRevId() > 0 ? query.getRevId()
                    : query.getFromDate() == null ? 0
                            : reader.getRevisionNumberForDate(query.getFromDate()).intValue() + 1;
        } catch (Exception e) {
            //before first revision->ignore
        }
        int rev2 = Integer.MAX_VALUE;
        try {
            rev2 = query.getRevId() > 0 ? query.getRevId()
                    : query.getToDate() == null ? Integer.MAX_VALUE
                            : reader.getRevisionNumberForDate(query.getToDate()).intValue();
        } catch (Exception e) {
            //after last revision->ignore
        }
        LoggerFactory.getLogger(DAORevision.class).debug("getRevisions between " + rev1 + " and " + rev2);
        Set<Class<?>> entityClasses = new HashSet<>();
        if (query.isStudies())
            entityClasses.add(Study.class);
        if (query.isSamples())
            entityClasses.add(Biosample.class);
        if (query.isResults())
            entityClasses.add(Result.class);
        if (query.isLocations())
            entityClasses.add(Location.class);
        if (query.isAdmin())
            entityClasses.add(Biotype.class);
        if (query.isAdmin())
            entityClasses.add(Test.class);
        if (query.isAdmin())
            entityClasses.add(Employee.class);
        if (query.isAdmin())
            entityClasses.add(EmployeeGroup.class);
        if (query.isAdmin())
            entityClasses.add(SpiritProperty.class);

        List<Revision> revisions = getRevisions(entityClasses, queryForRevisions(reader, entityClasses, rev1, rev2,
                query.getUserIdFilter(), query.getSidFilter(), query.getStudyIdFilter()));
        LoggerFactory.getLogger(DAORevision.class)
                .debug("Loaded revisions in " + (System.currentTimeMillis() - s) + "ms");

        //Post filter per study
        List<Revision> res = new ArrayList<>();
        for (Revision revision : revisions) {
            System.out.println("DAORevision.queryRevisions() " + revision + " " + revision.getType() + " "
                    + revision.getDifference());
            if (query.getSidFilter() > 0
                    || (query.getStudyIdFilter() != null && query.getStudyIdFilter().length() > 0)) {
                boolean ok = false;
                if (!ok) {
                    for (Study study : revision.getStudies()) {
                        if (query.getSidFilter() > 0 && query.getSidFilter() == study.getId())
                            ok = true;
                        else if (query.getStudyIdFilter() != null
                                && query.getStudyIdFilter().contains(study.getStudyId()))
                            ok = true;
                    }
                }
                if (!ok) {
                    for (Biosample b : revision.getBiosamples()) {
                        if (b.getInheritedStudy() != null) {
                            if (query.getSidFilter() > 0 && query.getSidFilter() == b.getInheritedStudy().getId())
                                ok = true;
                            else if (query.getStudyIdFilter() != null
                                    && query.getStudyIdFilter().contains(b.getInheritedStudy().getStudyId()))
                                ok = true;
                        }
                    }
                }
                if (!ok) {
                    for (Result r : revision.getResults()) {
                        if (r.getStudy() != null) {
                            if (query.getSidFilter() > 0 && query.getSidFilter() == r.getStudy().getId())
                                ok = true;
                            else if (query.getStudyIdFilter() != null
                                    && query.getStudyIdFilter().contains(r.getStudy().getStudyId()))
                                ok = true;
                        }
                    }
                }

                if (!ok) {
                    continue;
                }
            }

            res.add(revision);
        }

        return res;
    }

    private static List<Object[]> queryForRevisions(AuditReader reader, Set<Class<?>> entityClasses, int minRev,
            int maxRev, String userFilter, int sid, String studyIdFilter) {
        List<Object[]> res = new ArrayList<>();
        LoggerFactory.getLogger(DAORevision.class).debug("queryForRevisions " + entityClasses + " " + userFilter
                + " " + sid + " " + studyIdFilter + " " + minRev + " " + maxRev);
        //Find the study Id from the studyId (the study may have been deleted)
        if (sid <= 0 && studyIdFilter != null && studyIdFilter.length() > 0) {
            AuditQuery query = reader.createQuery().forRevisionsOfEntity(Study.class, false, true)
                    .add(AuditEntity.revisionType().eq(RevisionType.ADD))
                    .add(AuditEntity.property("studyId").eq(studyIdFilter));
            List<Object[]> array = query.getResultList();
            for (Object[] a : array) {
                Study entity = (Study) a[0];
                sid = entity.getId();
                break;
            }
            if (sid <= 0)
                return res;
        }

        for (Class<?> claz : entityClasses) {
            AuditQuery query = reader.createQuery().forRevisionsOfEntity(claz, false, true)
                    .add(AuditEntity.revisionNumber().between(minRev, maxRev));
            if (userFilter != null && userFilter.length() > 0 && (claz == Result.class || claz == Biosample.class
                    || claz == Study.class || claz == Location.class)) {
                query = query.add(AuditEntity.property("updUser").eq(userFilter));
            }
            if (sid > 0) {
                //If a studyId filter is given, query the properyId directly
                if (claz == Study.class) {
                    query = query.add(AuditEntity.property("id").eq(sid));
                } else if (claz == Biosample.class) {
                    query = query.add(AuditEntity.property("inheritedStudy").eq(new Study(sid)));
                } else if (claz == Result.class) {
                    query = query.add(AuditEntity.property("study").eq(new Study(sid)));
                } else {
                    continue;
                }
            }
            res.addAll(query.getResultList());
        }
        return res;
    }

    /**
     * Group the modified entities per revisionId/type
     * @param objects
     * @return
     */
    private static List<Revision> getRevisions(Set<Class<?>> entityClasses, List<Object[]> objects) {
        Map<Integer, Revision> map = new HashMap<>();
        for (Object[] a : objects) {

            Object entity = a[0];
            if (entityClasses == null && !(entity instanceof IAuditable))
                continue;
            else if (entityClasses != null && !entityClasses.contains(entity.getClass()))
                continue;

            SpiritRevisionEntity rev = (SpiritRevisionEntity) a[1];
            RevisionType type = (RevisionType) a[2];

            Revision r = map.get(rev.getId());
            if (r == null) {
                int sid = rev.getSid();
                Study study = DAOStudy.getStudy(sid);
                r = new Revision(rev.getId(), type, study, rev.getReason(), rev.getDifferenceList(),
                        rev.getUserId(), rev.getRevisionDate());
                map.put(rev.getId(), r);
            } else {
                if (type == RevisionType.DEL)
                    r.setType(RevisionType.DEL);
                else if (type == RevisionType.ADD && r.getType() != RevisionType.DEL)
                    r.setType(RevisionType.ADD);
            }
            r.getAuditables().add((IAuditable) entity);
        }

        List<Revision> res = new ArrayList<>(map.values());
        Collections.sort(res);
        return res;
    }

    /**
     * Restore the given objects. This function merges the objects, while setting the updDate, updUser and adding the comments
     * @param objects
     * @param user
     * @param comments
     * @throws Exception
     */
    public static void restore(Collection<? extends IObject> objects, SpiritUser user) throws Exception {
        EntityManager session = JPAUtil.getManager();
        EntityTransaction txn = session.getTransaction();

        try {
            txn.begin();
            Date now = JPAUtil.getCurrentDateFromDatabase();
            Map<String, IObject> mapMerged = new HashMap<>();
            for (IObject entity : objects) {
                remap(session, entity, now, user, mapMerged);
                session.merge(entity);
                mapMerged.put(entity.getClass() + "_" + entity.getId(), null);
            }
            txn.commit();
            txn = null;
        } catch (Exception e) {
            if (txn != null && txn.isActive())
                txn.rollback();
            throw e;
        }
    }

    /**
     * Cancel the change, ie. go to the version minus one for each object in the revision.
     *
     * If this revision is a deletion->insert older version
     * If this revision is a insert->delete
     * If this revision is an update->update older version
     *
     * @param revision
     * @param user
     * @param comments
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static void revert(Revision revision, SpiritUser user) throws Exception {
        EntityManager session = JPAUtil.getManager();
        EntityTransaction txn = session.getTransaction();

        try {
            Date now = JPAUtil.getCurrentDateFromDatabase();
            int revId = revision.getRevId();

            AuditReader reader = AuditReaderFactory.get(session);

            //Query modified entities during this revision
            Map<String, IObject> mapMerged = new HashMap<>();
            txn.begin();

            for (Class<IObject> claz : new Class[] { Biotype.class, Test.class, Study.class, Location.class,
                    Biosample.class, Result.class }) {
                List<Object[]> res = queryForRevisions(reader, Collections.singleton(claz), revId, revId, null, -1,
                        null);

                List<IObject> toDelete = new ArrayList<>();
                List<IObject> toMerge = new ArrayList<>();

                for (Object[] a : res) {
                    IObject entity = (IObject) a[0];
                    RevisionType type = (RevisionType) a[2];
                    if (revision.getType() == type) {
                        if (type == RevisionType.ADD) {
                            //Attach the entity to be deleted
                            IObject obj = session.merge(entity);
                            toDelete.add(obj);
                        } else {
                            //Attach the revision to be restored
                            IObject obj = reader.find(entity.getClass(), entity.getId(), revId - 1);
                            if (obj == null)
                                throw new Exception("The " + entity.getClass().getSimpleName() + " "
                                        + entity.getId() + " could not be found at rev=" + (revId - 1));
                            toMerge.add(obj);
                        }
                        mapMerged.put(entity.getClass() + "_" + entity.getId(), null);
                    }
                }

                LoggerFactory.getLogger(DAORevision.class)
                        .debug(claz + " >  toMerge=" + toMerge.size() + " toDelete=" + toDelete.size());
                int step = 0;
                while (toMerge.size() > 0 && step++ < 10) {
                    for (IObject o : new ArrayList<>(toMerge)) {
                        int id = o.getId();
                        boolean success = remap(session, o, now, user, mapMerged);
                        if (!success)
                            continue;
                        toMerge.remove(o);

                        LoggerFactory.getLogger(DAORevision.class)
                                .debug("merge " + o.getClass().getSimpleName() + " " + o.getId() + ":" + o);
                        mapMerged.put(o.getClass() + "_" + id, session.merge(o));
                    }
                }
                for (IObject o : toDelete) {
                    LoggerFactory.getLogger(DAORevision.class)
                            .debug("remove " + o.getClass().getSimpleName() + " " + o.getId() + ":" + o);
                    session.remove(o);
                }
            }
            txn.commit();
            txn = null;
        } catch (Exception e) {
            e.printStackTrace();
            if (txn != null && txn.isActive())
                txn.rollback();
            throw e;
        }
    }

    /**
     * Gets the last version and the change for the given entity, in the given context.
     * This method should return the last change, even if the changes of the given entity have not been committed.
     *
     * This method should be used exclusively from the SpritRevisionEntityListener to record the differences between the object to be saved and the last revision.
     * (when the record to be saved in already in the audit table)
     *
     * @param entityClass
     * @param entityId
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T extends IAuditable> Pair<IAuditable, DifferenceList> getLastChange(RevisionType revisionType,
            Class<T> entityClass, Serializable entityId) {
        //Query the 2 last revisions of entityClass:entityId
        EntityManager session = JPAUtil.getManager();
        AuditReader reader = AuditReaderFactory.get(session);
        AuditQuery query = reader.createQuery().forRevisionsOfEntity(entityClass, false, true)
                .add(AuditEntity.id().eq(entityId)).addOrder(AuditEntity.revisionNumber().desc()).setMaxResults(2)
                .setCacheable(false).setLockMode(LockMode.NONE);
        List<Object[]> histories = query.getResultList();

        //Compute the difference between those 2 last versions
        assert histories.size() > 0;

        DifferenceList diff = null;
        if (revisionType == RevisionType.DEL) {
            diff = ((T) histories.get(0)[0]).getDifferenceList(null);
            diff.add("", ChangeType.DEL);
            assert diff.size() == 1;
        } else if (revisionType == RevisionType.ADD) {
            diff = ((T) histories.get(0)[0]).getDifferenceList(null);
            diff.add("", ChangeType.ADD);
            assert diff.size() == 1;
        } else if (histories.size() >= 2) {
            diff = ((T) histories.get(0)[0]).getDifferenceList((T) histories.get(1)[0]);
        } else {
            return null;
        }
        return new Pair<IAuditable, DifferenceList>((T) histories.get(0)[0], diff);
    }

    /**
     * Returns the lastChange from the given entity.
     * This method should be called outside the SpritRevisionEntityListener (when the record is not yet in the audit table)
     *
     * @param auditable
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T extends IAuditable> Pair<T, DifferenceList> getLastChange(IAuditable auditable) {
        //Query the 2 last revisions of entityClass:entityId
        EntityManager session = JPAUtil.getManager();
        AuditReader reader = AuditReaderFactory.get(session);
        AuditQuery query = reader.createQuery().forRevisionsOfEntity(auditable.getClass(), true, true)
                .add(AuditEntity.id().eq(auditable.getSerializableId()))
                .addOrder(AuditEntity.revisionNumber().desc()).setMaxResults(1).setCacheable(false)
                .setLockMode(LockMode.NONE);
        List<IAuditable> histories = query.getResultList();

        //Compute the difference between those 2 last versions
        if (histories.size() == 0) {
            return new Pair<T, DifferenceList>(null, new DifferenceList());
        } else {
            return new Pair<T, DifferenceList>((T) histories.get(0), auditable.getDifferenceList(histories.get(0)));
        }
    }

    /**
     * Updates the date, user, comments of the given object
     * @param clone
     * @param now
     * @param user
     * @param comments
     * @throws Exception
     */
    private static boolean remap(EntityManager session, IObject clone, Date now, SpiritUser user,
            Map<String, IObject> mapMerged) throws Exception {

        LoggerFactory.getLogger(DAORevision.class)
                .debug("Restore " + clone.getClass().getSimpleName() + ": " + clone);
        boolean success = true;
        if (clone instanceof Study) {
            Study s = ((Study) clone);
            s.setUpdDate(now);
            s.setUpdUser(user.getUsername());

        } else if (clone instanceof Biosample) {
            Biosample b = (Biosample) clone;
            if (mapMerged != null && b.getInheritedStudy() != null
                    && mapMerged.containsKey(Study.class + "_" + b.getInheritedStudy().getId())) {
                Study s = (Study) mapMerged.get(Study.class + "_" + b.getInheritedStudy().getId());
                if (s != null) {
                    b.setInheritedStudy(s);
                    b.setAttachedStudy(b.getAttachedStudy() == null ? null : s);
                    b.setInheritedGroup(
                            b.getInheritedGroup() == null ? null : s.getGroup(b.getInheritedGroup().getName()));
                    b.setInheritedPhase(
                            b.getInheritedPhase() == null ? null : s.getPhase(b.getInheritedPhase().getName()));
                    if (b.getAttachedSampling() != null && b.getAttachedSampling().getNamedSampling() != null) {
                        Sampling sampling = s.getSampling(b.getAttachedSampling().getNamedSampling().getName(),
                                b.getAttachedSampling().getDetailsLong());
                        b.setAttachedSampling(sampling);
                    }
                } else {
                    success = false;
                }
            }
            if (mapMerged != null && b.getParent() != null
                    && mapMerged.containsKey(Biosample.class + "_" + b.getParent().getId())) {
                Biosample parentMerged = (Biosample) mapMerged.get(Biosample.class + "_" + b.getParent().getId());
                if (parentMerged != null) {
                    b.setParent(parentMerged);
                } else {
                    success = false;
                }
            }

            b.setChildren(new HashSet<Biosample>());
            b.setUpdDate(now);
            b.setUpdUser(user.getUsername());

            //Update linked documents
            for (BiotypeMetadata bm : b.getBiotype().getMetadata()) {
                if (bm.getDataType() == DataType.D_FILE || bm.getDataType() == DataType.FILES) {
                    Document doc = b.getMetadataDocument(bm);
                    b.setMetadataDocument(bm, doc == null ? null : new Document(doc.getFileName(), doc.getBytes()));
                }
            }

        } else if (clone instanceof Test) {
            ((Test) clone).setUpdDate(now);
            ((Test) clone).setUpdUser(user.getUsername());
        } else if (clone instanceof Result) {
            Result r = (Result) clone;
            if (r.getPhase() != null && r.getPhase().getStudy() != null) {
                if (mapMerged != null
                        && mapMerged.containsKey(Study.class + "_" + r.getPhase().getStudy().getId())) {
                    Study s = (Study) mapMerged.get(Study.class + "_" + r.getPhase().getStudy().getId());
                    Phase p = s == null ? null : s.getPhase(r.getPhase().getName());
                    if (s != null) {
                        r.setPhase(p);
                    } else {
                        success = false;
                    }
                } else if (!session.contains(r.getPhase())) {
                    Study s = DAOStudy.getStudyByStudyId(r.getPhase().getStudy().getStudyId());
                    Phase p = s.getPhase(r.getPhase().getName());
                    r.setPhase(p);
                }
            }
            if (r.getBiosample() != null) {
                if (mapMerged != null && mapMerged.containsKey(Biosample.class + "_" + r.getBiosample().getId())) {
                    //The linked object is also reverted
                    Biosample b = (Biosample) mapMerged.get(Biosample.class + "_" + r.getBiosample().getId());
                    if (b != null) {
                        r.setBiosample(b);
                    } else {
                        success = false;
                    }
                } else if (!session.contains(r.getBiosample())) {
                    //The linked object was not reverted. However its id may differ, if it was reverted before
                    Biosample b = DAOBiosample.getBiosample(r.getBiosample().getSampleId());
                    r.setBiosample(b);
                }
            }
            r.setUpdDate(now);
            r.setUpdUser(user.getUsername());
        } else if (clone instanceof Location) {
            ((Location) clone).setUpdDate(now);
            ((Location) clone).setUpdUser(user.getUsername());
        } else if (clone instanceof Test) {
            ((Test) clone).setUpdDate(now);
            ((Test) clone).setUpdUser(user.getUsername());
        } else if (clone instanceof Biotype) {
            ((Biotype) clone).setUpdDate(now);
            ((Biotype) clone).setUpdUser(user.getUsername());
        } else {
            throw new Exception("Invalid object " + clone);
        }
        return success;
    }

}