From c2e50b6b0dda96273ed0a33ab96d906abfa99c9d Mon Sep 17 00:00:00 2001 From: Guillaume Poirier-Morency Date: Fri, 7 Jun 2024 22:53:46 -0700 Subject: [PATCH] Add new event subclasses and improve auditing APIs Add two abstract event types to easily identify events that affect the needs attention and troubled flags. Add new APIs for retrieving all the auditables that have an event of a given type. Use generic types. --- .../report/ArrayDesignReportServiceImpl.java | 15 +- ...ExpressionExperimentReportServiceImpl.java | 22 +- .../analysis/report/WhatsNewServiceImpl.java | 8 +- .../core/context/SpringContextUtils.java | 2 +- .../eventType/DoesNotNeedAttentionEvent.java | 2 +- .../NeedsAttentionAlteringEvent.java | 9 + .../eventType/NeedsAttentionEvent.java | 2 +- .../eventType/NotTroubledStatusFlagEvent.java | 2 +- .../TroubledStatusFlagAlteringEvent.java | 10 + .../eventType/TroubledStatusFlagEvent.java | 2 +- .../auditAndSecurity/AuditEventDao.java | 59 +++-- .../auditAndSecurity/AuditEventDaoImpl.java | 243 +++++++++--------- .../auditAndSecurity/AuditEventService.java | 30 +-- .../AuditEventServiceImpl.java | 47 ++-- .../maintenance/TableMaintenanceUtilImpl.java | 43 ++-- .../eventType/AuditEventType.hbm.xml | 56 ++-- .../auditAndSecurity/AuditEventDaoTest.java | 87 ++++++- .../AuditEventServiceTest.java | 27 +- 18 files changed, 376 insertions(+), 290 deletions(-) create mode 100644 gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/NeedsAttentionAlteringEvent.java create mode 100644 gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/TroubledStatusFlagAlteringEvent.java diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/report/ArrayDesignReportServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/report/ArrayDesignReportServiceImpl.java index 94b2fe6a0c..3473d9b6b1 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/report/ArrayDesignReportServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/report/ArrayDesignReportServiceImpl.java @@ -28,7 +28,7 @@ import org.springframework.security.access.annotation.Secured; import org.springframework.stereotype.Component; import ubic.basecode.util.FileTools; -import ubic.gemma.model.common.auditAndSecurity.Auditable; +import ubic.gemma.core.config.Settings; import ubic.gemma.model.common.auditAndSecurity.AuditEvent; import ubic.gemma.model.common.auditAndSecurity.eventType.*; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; @@ -36,7 +36,6 @@ import ubic.gemma.persistence.service.common.auditAndSecurity.AuditEventService; import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService; import ubic.gemma.persistence.util.EntityUtils; -import ubic.gemma.core.config.Settings; import java.io.*; import java.util.*; @@ -287,14 +286,14 @@ public void fillEventInformation( Collection adVos ) { Map idMap = EntityUtils.getIdMap( arrayDesigns ); - Map, Map> events = auditEventService + Map, Map> events = auditEventService .getLastEvents( arrayDesigns, typesToGet ); - Map geneMappingEvents = events.get( ArrayDesignGeneMappingEvent.class ); - Map sequenceUpdateEvents = events.get( ArrayDesignSequenceUpdateEvent.class ); - Map sequenceAnalysisEvents = events.get( ArrayDesignSequenceAnalysisEvent.class ); - Map repeatAnalysisEvents = events.get( ArrayDesignRepeatAnalysisEvent.class ); - Map creationEvents = auditEventService.getCreateEvents( arrayDesigns ); + Map geneMappingEvents = events.get( ArrayDesignGeneMappingEvent.class ); + Map sequenceUpdateEvents = events.get( ArrayDesignSequenceUpdateEvent.class ); + Map sequenceAnalysisEvents = events.get( ArrayDesignSequenceAnalysisEvent.class ); + Map repeatAnalysisEvents = events.get( ArrayDesignRepeatAnalysisEvent.class ); + Map creationEvents = auditEventService.getCreateEvents( arrayDesigns ); for ( ArrayDesignValueObject adVo : adVos ) { diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/report/ExpressionExperimentReportServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/report/ExpressionExperimentReportServiceImpl.java index e2366f81af..a90f886d4a 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/report/ExpressionExperimentReportServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/report/ExpressionExperimentReportServiceImpl.java @@ -32,8 +32,8 @@ import org.springframework.transaction.annotation.Transactional; import ubic.gemma.core.visualization.ExperimentalDesignVisualizationService; import ubic.gemma.model.analysis.expression.diff.DifferentialExpressionAnalysisValueObject; -import ubic.gemma.model.common.auditAndSecurity.Auditable; import ubic.gemma.model.common.auditAndSecurity.AuditEvent; +import ubic.gemma.model.common.auditAndSecurity.Auditable; import ubic.gemma.model.common.auditAndSecurity.eventType.*; import ubic.gemma.model.expression.experiment.BatchEffectType; import ubic.gemma.model.expression.experiment.ExpressionExperiment; @@ -195,16 +195,16 @@ public void populateEventInformation( Collection lastArrayDesignUpdates = expressionExperimentService.getLastArrayDesignUpdate( ees ); Collection> typesToGet = Arrays.asList( eventTypes ); - Map, Map> events = this.getEvents( ees, typesToGet ); + Map, Map> events = this.getEvents( ees, typesToGet ); - Map linkAnalysisEvents = events.get( LinkAnalysisEvent.class ); - Map missingValueAnalysisEvents = events.get( MissingValueAnalysisEvent.class ); - Map rankComputationEvents = events.get( ProcessedVectorComputationEvent.class ); + Map linkAnalysisEvents = events.get( LinkAnalysisEvent.class ); + Map missingValueAnalysisEvents = events.get( MissingValueAnalysisEvent.class ); + Map rankComputationEvents = events.get( ProcessedVectorComputationEvent.class ); - Map differentialAnalysisEvents = events.get( DifferentialExpressionAnalysisEvent.class ); - Map batchFetchEvents = events.get( BatchInformationFetchingEvent.class ); - Map batchMissingEvents = events.get( BatchInformationMissingEvent.class ); - Map pcaAnalysisEvents = events.get( PCAAnalysisEvent.class ); + Map differentialAnalysisEvents = events.get( DifferentialExpressionAnalysisEvent.class ); + Map batchFetchEvents = events.get( BatchInformationFetchingEvent.class ); + Map batchMissingEvents = events.get( BatchInformationMissingEvent.class ); + Map pcaAnalysisEvents = events.get( PCAAnalysisEvent.class ); Map> sampleRemovalEvents = this.getSampleRemovalEvents( ees ); @@ -435,11 +435,9 @@ public void recalculateExperimentBatchInfo( ExpressionExperiment ee ) { } } - private Map, Map> getEvents( + private Map, Map> getEvents( Collection ees, Collection> types ) { - return auditEventService.getLastEvents( ees, types ); - } private Map> getSampleRemovalEvents( Collection ees ) { diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/report/WhatsNewServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/report/WhatsNewServiceImpl.java index 81781971f9..a8f3afc61d 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/report/WhatsNewServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/report/WhatsNewServiceImpl.java @@ -215,11 +215,15 @@ private WhatsNew getReportAsAnonymousUser( Date date ) { private WhatsNew getReport( Date date ) { WhatsNew wn = new WhatsNew( date ); - Collection updatedObjects = auditEventService.getUpdatedSinceDate( date ); + Collection updatedObjects = new HashSet<>(); + updatedObjects.addAll( auditEventService.getUpdatedSinceDate( ArrayDesign.class, date ) ); + updatedObjects.addAll( auditEventService.getUpdatedSinceDate( ExpressionExperiment.class, date ) ); wn.setUpdatedObjects( updatedObjects ); WhatsNewServiceImpl.log.info( wn.getUpdatedObjects().size() + " updated objects since " + date ); - Collection newObjects = auditEventService.getNewSinceDate( date ); + Collection newObjects = new HashSet<>(); + newObjects.addAll( auditEventService.getNewSinceDate( ArrayDesign.class, date ) ); + newObjects.addAll( auditEventService.getNewSinceDate( ExpressionExperiment.class, date ) ); wn.setNewObjects( newObjects ); WhatsNewServiceImpl.log.info( wn.getNewObjects().size() + " new objects since " + date ); diff --git a/gemma-core/src/main/java/ubic/gemma/core/context/SpringContextUtils.java b/gemma-core/src/main/java/ubic/gemma/core/context/SpringContextUtils.java index acc1a6dc1a..e1e8280442 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/context/SpringContextUtils.java +++ b/gemma-core/src/main/java/ubic/gemma/core/context/SpringContextUtils.java @@ -27,8 +27,8 @@ import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.security.core.context.SecurityContextHolder; -import ubic.gemma.core.util.BuildInfo; import ubic.gemma.core.config.Settings; +import ubic.gemma.core.util.BuildInfo; import java.util.ArrayList; import java.util.Arrays; diff --git a/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/DoesNotNeedAttentionEvent.java b/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/DoesNotNeedAttentionEvent.java index 23e35bec2b..287f3a23cc 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/DoesNotNeedAttentionEvent.java +++ b/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/DoesNotNeedAttentionEvent.java @@ -27,7 +27,7 @@ * * @author Paul */ -public class DoesNotNeedAttentionEvent extends CurationDetailsEvent { +public class DoesNotNeedAttentionEvent extends NeedsAttentionAlteringEvent { /** * The serial version UID of this class. Needed for serialization. diff --git a/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/NeedsAttentionAlteringEvent.java b/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/NeedsAttentionAlteringEvent.java new file mode 100644 index 0000000000..edb042cf2a --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/NeedsAttentionAlteringEvent.java @@ -0,0 +1,9 @@ +package ubic.gemma.model.common.auditAndSecurity.eventType; + +import ubic.gemma.model.common.auditAndSecurity.curation.CurationDetails; + +/** + * Base class for events altering {@link CurationDetails#getNeedsAttention()}. + */ +public abstract class NeedsAttentionAlteringEvent extends CurationDetailsEvent { +} diff --git a/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/NeedsAttentionEvent.java b/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/NeedsAttentionEvent.java index 6d68a492d2..dbec3a827a 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/NeedsAttentionEvent.java +++ b/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/NeedsAttentionEvent.java @@ -28,7 +28,7 @@ * * @author Paul */ -public class NeedsAttentionEvent extends CurationDetailsEvent { +public class NeedsAttentionEvent extends NeedsAttentionAlteringEvent { /** * The serial version UID of this class. Needed for serialization. diff --git a/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/NotTroubledStatusFlagEvent.java b/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/NotTroubledStatusFlagEvent.java index 54f4be1497..666ddbdd51 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/NotTroubledStatusFlagEvent.java +++ b/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/NotTroubledStatusFlagEvent.java @@ -26,7 +26,7 @@ * * @author Paul */ -public class NotTroubledStatusFlagEvent extends CurationDetailsEvent { +public class NotTroubledStatusFlagEvent extends TroubledStatusFlagAlteringEvent { /** * The serial version UID of this class. Needed for serialization. diff --git a/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/TroubledStatusFlagAlteringEvent.java b/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/TroubledStatusFlagAlteringEvent.java new file mode 100644 index 0000000000..32a81a0eff --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/TroubledStatusFlagAlteringEvent.java @@ -0,0 +1,10 @@ +package ubic.gemma.model.common.auditAndSecurity.eventType; + +import ubic.gemma.model.common.auditAndSecurity.curation.CurationDetails; + +/** + * Base class for events that alter the {@link CurationDetails#getTroubled()} flag. + * @author poirigui + */ +public abstract class TroubledStatusFlagAlteringEvent extends CurationDetailsEvent { +} diff --git a/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/TroubledStatusFlagEvent.java b/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/TroubledStatusFlagEvent.java index 955fd5799d..33551de705 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/TroubledStatusFlagEvent.java +++ b/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/TroubledStatusFlagEvent.java @@ -26,7 +26,7 @@ * * @author Paul */ -public class TroubledStatusFlagEvent extends CurationDetailsEvent { +public class TroubledStatusFlagEvent extends TroubledStatusFlagAlteringEvent { /** * The serial version UID of this class. Needed for serialization. diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDao.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDao.java index 28d6cc8d8e..63ad70e074 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDao.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDao.java @@ -18,11 +18,12 @@ */ package ubic.gemma.persistence.service.common.auditAndSecurity; -import ubic.gemma.model.common.auditAndSecurity.Auditable; import ubic.gemma.model.common.auditAndSecurity.AuditEvent; +import ubic.gemma.model.common.auditAndSecurity.Auditable; import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; import ubic.gemma.persistence.service.BaseDao; +import javax.annotation.Nullable; import java.util.Collection; import java.util.Date; import java.util.List; @@ -35,47 +36,53 @@ public interface AuditEventDao extends BaseDao { /** - * @param auditable auditable - * @return events for the given auditable. + * Obtain the audit events associated to a given auditable. + *

+ * Events are sorted by date in ascending order. */ List getEvents( Auditable auditable ); /** - * @param auditable auditable - * @param type type - * @return the last AuditEvent of the specified type from the given auditable. + * Obtain the creation events for the given auditables. + *

+ * If an auditable has more than one creation event (which is in itself a bug), the earliest one is returned. + */ + Map getCreateEvents( Collection auditables ); + + /** + * Obtain the latest event of a given type for a given auditable. + * @see #getLastEvent(Auditable, Class) */ + @Nullable AuditEvent getLastEvent( Auditable auditable, Class type ); /** - * Obtain the latest {@link AuditEvent} of a specified type, excluding a certain number of types. + * Obtain the latest event of a given type, excluding a certain number of types. + * @param type type of event to retrieve, augmented by its hierarchy + * @param excludedTypes excluded event types (their hierarchy is also excluded) */ + @Nullable AuditEvent getLastEvent( Auditable auditable, Class type, Collection> excludedTypes ); - Map, Map> getLastEventsByType( - Collection auditables, Collection> types ); - /** - * Get auditables that have been Created since the given date - * - * @param date date - * @return auditables + * Obtain the latest events of a specified type for all given auditables. + * @see #getLastEvent(Auditable, Class) */ - Collection getNewSinceDate( Date date ); + Map getLastEvents( Collection auditables, Class type ); /** - * Get auditables that have been Updated since the given date - * - * @param date date - * @return auditables + * Obtain the latest events of a specified type for all auditable of a given type. + * @see #getLastEvent(Auditable, Class) */ - Collection getUpdatedSinceDate( Date date ); + Map getLastEvents( Class auditableClass, Class type ); - boolean hasEvent( Auditable a, Class type ); - - void retainHavingEvent( Collection a, Class type ); - - void retainLackingEvent( Collection a, Class type ); + /** + * Get auditables that have been created since the given date. + */ + Collection getNewSinceDate( Class auditableClass, Date date ); - Map getCreateEvents( Collection auditables ); + /** + * Get auditables that have been updated since the given date. + */ + Collection getUpdatedSinceDate( Class auditableClass, Date date ); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDaoImpl.java index c8e1a3c256..d10fef1e5b 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDaoImpl.java @@ -18,8 +18,6 @@ */ package ubic.gemma.persistence.service.common.auditAndSecurity; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.collections4.Predicate; import org.apache.commons.lang3.time.StopWatch; import org.hibernate.Query; import org.hibernate.SessionFactory; @@ -27,8 +25,9 @@ import org.hibernate.persister.entity.SingleTableEntityPersister; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; -import ubic.gemma.model.common.auditAndSecurity.Auditable; +import org.springframework.util.Assert; import ubic.gemma.model.common.auditAndSecurity.AuditEvent; +import ubic.gemma.model.common.auditAndSecurity.Auditable; import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; import ubic.gemma.persistence.service.AbstractDao; @@ -46,13 +45,6 @@ @Repository public class AuditEventDaoImpl extends AbstractDao implements AuditEventDao { - /** - * Classes that we track for 'updated since'. This is used for "What's new" functionality. - */ - private static final String[] AUDITABLES_TO_TRACK_FOR_WHATS_NEW = { - "ubic.gemma.model.expression.arrayDesign.ArrayDesign", - "ubic.gemma.model.expression.experiment.ExpressionExperiment" }; - @Autowired public AuditEventDaoImpl( SessionFactory sessionFactory ) { super( AuditEvent.class, sessionFactory ); @@ -60,18 +52,13 @@ public AuditEventDaoImpl( SessionFactory sessionFactory ) { @Override public List getEvents( final Auditable auditable ) { - if ( auditable == null ) - throw new IllegalArgumentException( "Auditable cannot be null" ); - - if ( auditable.getAuditTrail() == null ) { - throw new IllegalStateException( "Auditable did not have an audit trail: " + auditable ); - } - - Long id = auditable.getAuditTrail().getId(); + Assert.notNull( auditable.getAuditTrail(), "Auditable did not have an audit trail: " + auditable ); + Assert.notNull( auditable.getAuditTrail().getId(), "Auditable did not have a persistent audit trail: " + auditable ); //noinspection unchecked return this.getSessionFactory().getCurrentSession() - .createQuery( "select e from AuditTrail t join t.events e where t.id = :id order by e.date,e.id " ) - .setParameter( "id", id ).list(); + .createQuery( "select e from AuditTrail t join t.events e where t = :at order by e.date,e.id " ) + .setParameter( "at", auditable.getAuditTrail() ) + .list(); } @@ -86,144 +73,135 @@ public AuditEvent getLastEvent( Auditable auditable, Class, Map> getLastEventsByType( - Collection auditables, Collection> types ) { - Map, Map> results = new HashMap<>(); - for ( Class ti : types ) { - Map results2 = getLastEvents( auditables, ti, null ); - results.put( ti, results2.entrySet().stream() - .filter( e -> ti.isAssignableFrom( e.getValue().getEventType().getClass() ) ) - .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) ) ); - } - return results; - } - - /** - * Note that this only returns selected classes of auditables. - * - * @param date date - * @return Collection of Auditables - * @see AuditEventDao#getNewSinceDate(java.util.Date) - */ - @Override - public Collection getNewSinceDate( Date date ) { - Collection result = new HashSet<>(); - for ( String clazz : AuditEventDaoImpl.AUDITABLES_TO_TRACK_FOR_WHATS_NEW ) { - //noinspection unchecked - result.addAll( this.getSessionFactory().getCurrentSession() - .createQuery( "select adb from " + clazz + " adb " - + "join adb.auditTrail atr " - + "join atr.events as ae " - + "where ae.date > :date and ae.action='C' " - + "group by adb" ) - .setParameter( "date", date ).list() ); - } - return result; + public Map getLastEvents( Collection auditables, Class type ) { + return getLastEvents( auditables, type, null ); } - /** - * Note that this only returns selected classes of auditables. - * - * @param date date - * @return Collection of Auditables - * @see AuditEventDao#getUpdatedSinceDate(Date) - */ @Override - public Collection getUpdatedSinceDate( Date date ) { - Collection result = new HashSet<>(); - for ( String clazz : AuditEventDaoImpl.AUDITABLES_TO_TRACK_FOR_WHATS_NEW ) { - //noinspection unchecked - result.addAll( this.getSessionFactory().getCurrentSession() - .createQuery( "select adb from " + clazz + " adb " - + "join adb.auditTrail atr " - + "join atr.events as ae " - + "where ae.date > :date and ae.action='U' " - + "group by adb" ) - .setParameter( "date", date ).list() ); - } - return result; + public Map getLastEvents( Class auditableClass, Class type ) { + return getLastEvents( auditableClass, type, null ); } @Override - public boolean hasEvent( Auditable a, Class type ) { - return this.getLastEvent( a, type ) != null; + public Collection getNewSinceDate( Class auditableClass, Date date ) { + String entityName = getSessionFactory().getClassMetadata( auditableClass ).getEntityName(); + //noinspection unchecked + return this.getSessionFactory().getCurrentSession() + .createQuery( "select adb from " + entityName + " adb " + + "join adb.auditTrail atr " + + "join atr.events as ae " + + "where ae.date >= :date and ae.action='C' " + + "group by adb" ) + .setParameter( "date", date ) + .list(); } @Override - public void retainHavingEvent( final Collection a, - final Class type ) { - - final Map events = this.getLastEvents( a, type, null ); - - CollectionUtils.filter( a, events::containsKey ); - + public Collection getUpdatedSinceDate( Class auditableClass, Date date ) { + String entityName = getSessionFactory().getClassMetadata( auditableClass ).getEntityName(); + //noinspection unchecked + return this.getSessionFactory().getCurrentSession() + .createQuery( "select adb from " + entityName + " adb " + + "join adb.auditTrail atr " + + "join atr.events as ae " + + "where ae.date >= :date and ae.action='U' " + + "group by adb" ) + .setParameter( "date", date ) + .list(); } - @Override - public void retainLackingEvent( final Collection a, - final Class type ) { - StopWatch timer = new StopWatch(); - timer.start(); - final Map events = this.getLastEvents( a, type, null ); - AbstractDao.log.info( "Phase I: " + timer.getTime() + "ms" ); - - CollectionUtils.filter( a, ( Predicate ) arg0 -> !events.containsKey( arg0 ) ); - + public Map getCreateEvents( final Collection auditables ) { + if ( auditables.isEmpty() ) { + return Collections.emptyMap(); + } + Map result = new HashMap<>( auditables.size() ); + final Map atMap = auditables.stream() + .collect( Collectors.toMap( a -> a.getAuditTrail().getId(), Function.identity() ) ); + //noinspection unchecked + List qr = this.getSessionFactory().getCurrentSession() + .createQuery( "select trail.id, ae from AuditTrail trail join trail.events" + + " ae where trail.id in :trails and ae.action = 'C'" ) + .setParameterList( "trails", optimizeParameterList( atMap.keySet() ) ).list(); + for ( Object[] o : qr ) { + Long t = ( Long ) o[0]; + AuditEvent e = ( AuditEvent ) o[1]; + T a = atMap.get( t ); + // only put the first create event encountered + if ( result.putIfAbsent( a, e ) != null ) { + log.warn( "Auditable has more than one creation event: " + a ); + } + } + return result; } - public Map getCreateEvents( final Collection auditables ) { - + private Map getLastEvents( final Collection auditables, Class types, @Nullable Collection> excludedTypes ) { if ( auditables.isEmpty() ) { return Collections.emptyMap(); } - Map result = new HashMap<>( auditables.size() ); + StopWatch timer = StopWatch.createStarted(); + + Map result = new HashMap<>( auditables.size() ); - final Map atMap = auditables.stream() + // getId() does not require proxy initialization, otherwise we might inadvertently initialize the audit trail + final Map atMap = auditables.stream() .collect( Collectors.toMap( a -> a.getAuditTrail().getId(), Function.identity() ) ); - final String queryString = "select trail.id, ae from AuditTrail trail join trail.events" + - " ae where trail.id in :trails and ae.action = 'C'"; + Set> classes = getClassHierarchy( types, excludedTypes ); + + //language=HQL + final String queryString = "select trail.id, ae from AuditTrail trail " + + "join trail.events ae " + + "join fetch ae.eventType et " // fetching here prevents a separate select query + + "where trail.id in :trails and type(et) in :classes " + // annoyingly, Hibernate does not select the latest event when grouping by trail, so we have to fetch + // them all + + "group by trail, ae " + // latest by date or ID to break ties + + "order by ae.date desc, ae.id desc"; + Query queryObject = this.getSessionFactory().getCurrentSession() .createQuery( queryString ) - .setParameterList( "trails", optimizeParameterList( atMap.keySet() ) ); + .setParameterList( "trails", optimizeParameterList( atMap.keySet() ) ) + .setParameterList( "classes", classes ); // optimizing this one is unnecessary + List qr = queryObject.list(); for ( Object o : qr ) { Object[] ar = ( Object[] ) o; Long t = ( Long ) ar[0]; AuditEvent e = ( AuditEvent ) ar[1]; - result.put( atMap.get( t ), e ); + // only retain the first one which is the latest (by date or ID) + result.putIfAbsent( atMap.get( t ), e ); } - return result; - } - private Map getLastEvents( final Collection auditables, Class types, @Nullable Collection> excludedTypes ) { - if ( auditables.isEmpty() ) { - return Collections.emptyMap(); + timer.stop(); + if ( timer.getTime() > 500 ) { + AbstractDao.log.info( String.format( "Last event of type %s (closure: %s) retrieved for %d items in %d ms", + types.getName(), classes.stream().map( Class::getName ).collect( Collectors.joining( ", " ) ), + auditables.size(), timer.getTime() ) ); } - StopWatch timer = StopWatch.createStarted(); - - Map result = new HashMap<>( auditables.size() ); + return result; + } - // getId() does not require proxy initialization, otherwise we might inadvertently initialize the audit trail - final Map atMap = auditables.stream() - .collect( Collectors.toMap( a -> a.getAuditTrail().getId(), Function.identity() ) ); + private Map getLastEvents( Class auditableClass, Class types, @Nullable Collection> excludedTypes ) { + StopWatch timer = StopWatch.createStarted(); - Set> classes = getClassHierarchy( types ); + // using a treeset to avoid initialization of proxies + Map result = new TreeMap<>( Comparator.comparing( Auditable::getId ) ); - // remove all the types we don't want - if ( excludedTypes != null ) { - for ( Class excludedType : excludedTypes ) { - classes.removeAll( getClassHierarchy( excludedType ) ); - } + Set> classes = getClassHierarchy( types, excludedTypes ); + if ( classes.isEmpty() ) { + throw new IllegalArgumentException( "No classes found" ); } + String entityName = getSessionFactory().getClassMetadata( auditableClass ).getEntityName(); //language=HQL - final String queryString = "select trail.id, ae from AuditTrail trail " + final String queryString = "select a.id, ae from " + entityName + " a " + + "join a.auditTrail trail " + "join trail.events ae " + "join fetch ae.eventType et " // fetching here prevents a separate select query - + "where trail.id in :trails and type(et) in :classes " + + "where type(et) in :classes " // annoyingly, Hibernate does not select the latest event when grouping by trail, so we have to fetch // them all + "group by trail, ae " @@ -232,7 +210,6 @@ private Map getLastEvents( final Collection qr = queryObject.list(); @@ -241,14 +218,15 @@ private Map getLastEvents( final Collection 500 ) { AbstractDao.log.info( String.format( "Last event of type %s (closure: %s) retrieved for %d items in %d ms", types.getName(), classes.stream().map( Class::getName ).collect( Collectors.joining( ", " ) ), - auditables.size(), timer.getTime() ) ); + result.keySet().size(), timer.getTime() ) ); } return result; @@ -258,21 +236,32 @@ private Map getLastEvents( final Collection> getClassHierarchy( Class type ) { + private Set> getClassHierarchy( Class type, @Nullable Collection> excludedTypes ) { // how to determine subclasses? There is no way to do this but the hibernate way. ClassMetadata classMetadata = this.getSessionFactory().getClassMetadata( type ); if ( classMetadata instanceof SingleTableEntityPersister ) { - Set> classes = new HashSet<>(); + Set> classes = new HashSet<>(); // this includes the superclass, fully qualified String[] subclasses = ( ( SingleTableEntityPersister ) classMetadata ).getSubclassClosure(); for ( String className : subclasses ) { try { - classes.add( Class.forName( className ) ); + //noinspection unchecked + classes.add( ( Class ) Class.forName( className ) ); } catch ( ClassNotFoundException e ) { - log.error( String.format( "Failed to find subclass %s of %s, it will not be included in the query." - , className, type.getName() ), e ); + log.error( String.format( "Failed to find subclass %s of %s, it will not be included in the query.", + className, type.getName() ), e ); + } + } + // remove all the types we don't want + if ( excludedTypes != null ) { + for ( Class excludedType : excludedTypes ) { + classes.removeAll( getClassHierarchy( excludedType, null ) ); + if ( classes.isEmpty() ) { + throw new IllegalStateException( "No event types are left after applying exclusions to " + type.getName() + "." ); + } } } return classes; diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventService.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventService.java index feb7c683f7..b18e6e4daf 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventService.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventService.java @@ -19,9 +19,8 @@ package ubic.gemma.persistence.service.common.auditAndSecurity; import org.springframework.security.access.annotation.Secured; -import org.springframework.transaction.annotation.Transactional; -import ubic.gemma.model.common.auditAndSecurity.Auditable; import ubic.gemma.model.common.auditAndSecurity.AuditEvent; +import ubic.gemma.model.common.auditAndSecurity.Auditable; import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; import javax.annotation.Nullable; @@ -35,20 +34,23 @@ */ public interface AuditEventService { - @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY" }) + @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "ACL_SECURABLE_READ" }) List getEvents( Auditable auditable ); - @Transactional(readOnly = true) - Map getCreateEvents( Collection auditable ); + @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "ACL_SECURABLE_COLLECTION_READ" }) + Map getCreateEvents( Collection auditable ); @Nullable - @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY" }) + @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "ACL_SECURABLE_READ" }) AuditEvent getLastEvent( Auditable auditable, Class type ); @Nullable - @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY" }) + @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "ACL_SECURABLE_READ" }) AuditEvent getLastEvent( Auditable auditable, Class type, Collection> excludedTypes ); + @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "AFTER_ACL_MAP_READ" }) + Map getLastEvents( Class auditableClass, Class type ); + /** * Fast method to retrieve auditEventTypes of multiple classes. * @@ -57,16 +59,14 @@ public interface AuditEventService { * @return map of AuditEventType to a Map of Auditable to the AuditEvent matching that type. * Note: cannot secure this very easily since map key is a Class. */ - @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY" }) - Map, Map> getLastEvents( - Collection auditables, Collection> types ); + @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "ACL_SECURABLE_COLLECTION_READ" }) + Map, Map> getLastEvents( + Collection auditables, Collection> types ); /** - * @param date date - * @return a collection of Auditables created since the date given. */ @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "AFTER_ACL_COLLECTION_READ" }) - Collection getNewSinceDate( Date date ); + Collection getNewSinceDate( Class auditableClass, Date date ); /** * @param date date @@ -76,12 +76,12 @@ Map, Map> getLastEvents( * applicationContext-security.xml */ @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "AFTER_ACL_COLLECTION_READ" }) - Collection getUpdatedSinceDate( Date date ); + Collection getUpdatedSinceDate( Class auditableClass, Date date ); + @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "ACL_SECURABLE_READ" }) boolean hasEvent( Auditable a, Class type ); void retainHavingEvent( Collection a, Class type ); void retainLackingEvent( Collection a, Class type ); - } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventServiceImpl.java index 9fef008d15..56cb159709 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventServiceImpl.java @@ -18,16 +18,17 @@ */ package ubic.gemma.persistence.service.common.auditAndSecurity; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.Predicate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ubic.gemma.model.common.auditAndSecurity.Auditable; import ubic.gemma.model.common.auditAndSecurity.AuditEvent; +import ubic.gemma.model.common.auditAndSecurity.Auditable; import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; /** * @author pavlidis @@ -51,7 +52,7 @@ public List getEvents( Auditable auditable ) { @Override @Transactional(readOnly = true) - public Map getCreateEvents( Collection auditables ) { + public Map getCreateEvents( Collection auditables ) { return this.auditEventDao.getCreateEvents( auditables ); } @@ -69,39 +70,53 @@ public AuditEvent getLastEvent( Auditable auditable, Class, Map> getLastEvents( - Collection auditables, Collection> types ) { - return this.auditEventDao.getLastEventsByType( auditables, types ); + public Map getLastEvents( Class auditableClass, Class type ) { + return auditEventDao.getLastEvents( auditableClass, type ); } @Override @Transactional(readOnly = true) - public java.util.Collection getNewSinceDate( java.util.Date date ) { - return this.auditEventDao.getNewSinceDate( date ); + public Map, Map> getLastEvents( + Collection auditables, Collection> types ) { + Map, Map> results = new HashMap<>(); + for ( Class ti : types ) { + Map results2 = auditEventDao.getLastEvents( auditables, ti ); + results.put( ti, results2.entrySet().stream() + .filter( e -> ti.isAssignableFrom( e.getValue().getEventType().getClass() ) ) + .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) ) ); + } + return results; } @Override @Transactional(readOnly = true) - public Collection getUpdatedSinceDate( java.util.Date date ) { - return this.auditEventDao.getUpdatedSinceDate( date ); + public Collection getNewSinceDate( Class auditableClass, Date date ) { + return this.auditEventDao.getNewSinceDate( auditableClass, date ); + } + + @Override + @Transactional(readOnly = true) + public Collection getUpdatedSinceDate( Class auditableClass, Date date ) { + return this.auditEventDao.getUpdatedSinceDate( auditableClass, date ); } @Override @Transactional(readOnly = true) public boolean hasEvent( Auditable a, Class type ) { - return this.auditEventDao.hasEvent( a, type ); + return this.auditEventDao.getLastEvent( a, type ) != null; } @Override @Transactional(readOnly = true) public void retainHavingEvent( Collection a, Class type ) { - this.auditEventDao.retainHavingEvent( a, type ); + final Map events = auditEventDao.getLastEvents( a, type ); + CollectionUtils.filter( a, events::containsKey ); } @Override @Transactional(readOnly = true) public void retainLackingEvent( Collection a, Class type ) { - this.auditEventDao.retainLackingEvent( a, type ); + final Map events = auditEventDao.getLastEvents( a, type ); + CollectionUtils.filter( a, ( Predicate ) arg0 -> !events.containsKey( arg0 ) ); } - } \ No newline at end of file diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/maintenance/TableMaintenanceUtilImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/maintenance/TableMaintenanceUtilImpl.java index 8343cb47d4..003142d9e2 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/maintenance/TableMaintenanceUtilImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/maintenance/TableMaintenanceUtilImpl.java @@ -29,7 +29,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; -import ubic.gemma.model.common.auditAndSecurity.Auditable; +import ubic.gemma.core.util.MailEngine; import ubic.gemma.model.common.auditAndSecurity.AuditEvent; import ubic.gemma.model.common.auditAndSecurity.eventType.ArrayDesignGeneMappingEvent; import ubic.gemma.model.common.description.ExternalDatabase; @@ -41,7 +41,6 @@ import ubic.gemma.persistence.service.common.auditAndSecurity.AuditEventService; import ubic.gemma.persistence.service.common.description.ExternalDatabaseService; import ubic.gemma.persistence.service.genome.GeneDao; -import ubic.gemma.core.util.MailEngine; import javax.annotation.Nullable; import java.io.IOException; @@ -179,33 +178,29 @@ public void updateGene2CsEntries() { } if ( !needToRefresh ) { - Collection newObj = auditEventService.getNewSinceDate( status.getLastUpdate() ); + Collection newObj = auditEventService.getNewSinceDate( ArrayDesign.class, status.getLastUpdate() ); - for ( Auditable a : newObj ) { - if ( a instanceof ArrayDesign ) { - needToRefresh = true; - annotation = a + " is new since " + status.getLastUpdate(); - TableMaintenanceUtilImpl.log.debug( annotation ); - break; - } + for ( ArrayDesign a : newObj ) { + needToRefresh = true; + annotation = a + " is new since " + status.getLastUpdate(); + TableMaintenanceUtilImpl.log.debug( annotation ); + break; } } if ( !needToRefresh ) { - Collection updatedObj = auditEventService.getUpdatedSinceDate( status.getLastUpdate() ); - for ( Auditable a : updatedObj ) { - if ( a instanceof ArrayDesign ) { - for ( AuditEvent ae : auditEventService.getEvents( a ) ) { - if ( ae == null ) - continue; // legacy of ordered-list which could end up with gaps; should - // not be needed any more - if ( ae.getEventType() != null && ae.getEventType() instanceof ArrayDesignGeneMappingEvent - && ae.getDate().after( status.getLastUpdate() ) ) { - needToRefresh = true; - annotation = a + " had probe mapping done since: " + status.getLastUpdate(); - TableMaintenanceUtilImpl.log.debug( annotation ); - break; - } + Collection updatedObj = auditEventService.getUpdatedSinceDate( ArrayDesign.class, status.getLastUpdate() ); + for ( ArrayDesign a : updatedObj ) { + for ( AuditEvent ae : auditEventService.getEvents( a ) ) { + if ( ae == null ) + continue; // legacy of ordered-list which could end up with gaps; should + // not be needed any more + if ( ae.getEventType() != null && ae.getEventType() instanceof ArrayDesignGeneMappingEvent + && ae.getDate().after( status.getLastUpdate() ) ) { + needToRefresh = true; + annotation = a + " had probe mapping done since: " + status.getLastUpdate(); + TableMaintenanceUtilImpl.log.debug( annotation ); + break; } } if ( needToRefresh ) diff --git a/gemma-core/src/main/resources/ubic/gemma/model/common/auditAndSecurity/eventType/AuditEventType.hbm.xml b/gemma-core/src/main/resources/ubic/gemma/model/common/auditAndSecurity/eventType/AuditEventType.hbm.xml index af6e36670d..dff5042de0 100644 --- a/gemma-core/src/main/resources/ubic/gemma/model/common/auditAndSecurity/eventType/AuditEventType.hbm.xml +++ b/gemma-core/src/main/resources/ubic/gemma/model/common/auditAndSecurity/eventType/AuditEventType.hbm.xml @@ -118,32 +118,38 @@ name="ubic.gemma.model.common.auditAndSecurity.eventType.CommentedEvent"/> - - - - + + + + - - - - - - - - - + + + + + + + + + + + + + diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDaoTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDaoTest.java index a992f0ad51..942779bbc0 100644 --- a/gemma-core/src/test/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDaoTest.java +++ b/gemma-core/src/test/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDaoTest.java @@ -8,21 +8,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; +import ubic.gemma.core.context.TestComponent; import ubic.gemma.core.util.test.BaseDatabaseTest; import ubic.gemma.model.common.auditAndSecurity.AuditAction; import ubic.gemma.model.common.auditAndSecurity.AuditEvent; -import ubic.gemma.model.common.auditAndSecurity.Auditable; -import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; import ubic.gemma.model.common.auditAndSecurity.eventType.BatchInformationFetchingEvent; import ubic.gemma.model.common.auditAndSecurity.eventType.DataReplacedEvent; import ubic.gemma.model.common.auditAndSecurity.eventType.ExpressionExperimentAnalysisEvent; import ubic.gemma.model.expression.experiment.ExpressionExperiment; -import ubic.gemma.persistence.service.common.auditAndSecurity.AuditEventDao; -import ubic.gemma.persistence.service.common.auditAndSecurity.AuditEventDaoImpl; -import ubic.gemma.core.context.TestComponent; import java.util.Arrays; -import java.util.Collections; import java.util.Date; import java.util.Map; @@ -47,13 +42,25 @@ public AuditEventDao auditEventDao( SessionFactory sessionFactory ) { @Test public void testFetch() { AuditEvent event = AuditEvent.Factory.newInstance( new Date(), AuditAction.C, null, null, null, null ); - auditEventDao.create( event ); + event = auditEventDao.create( event ); sessionFactory.getCurrentSession().evict( event ); auditEventDao.load( event.getId() ); assertTrue( Hibernate.isInitialized( event.getEventType() ) ); assertTrue( Hibernate.isInitialized( event.getPerformer() ) ); } + @Test + public void testGetEvents() { + ExpressionExperiment auditable = new ExpressionExperiment(); + sessionFactory.getCurrentSession().persist( auditable ); + assertNull( auditEventDao.getLastEvent( auditable, BatchInformationFetchingEvent.class ) ); + auditable.getAuditTrail().getEvents().add( AuditEvent.Factory.newInstance( new Date(), AuditAction.U, null, null, null, new BatchInformationFetchingEvent() ) ); + sessionFactory.getCurrentSession().flush(); + sessionFactory.getCurrentSession().clear(); + Assertions.assertThat( auditEventDao.getEvents( auditable ) ) + .hasSize( 1 ); + } + @Test public void testGetLastEvent() { ExpressionExperiment auditable = new ExpressionExperiment(); @@ -64,6 +71,7 @@ public void testGetLastEvent() { sessionFactory.getCurrentSession().clear(); // should also work on detached entities AuditEvent event = auditEventDao.getLastEvent( auditable, ExpressionExperimentAnalysisEvent.class ); + assertNotNull( event ); assertTrue( Hibernate.isInitialized( event.getPerformer() ) ); assertTrue( Hibernate.isInitialized( event.getEventType() ) ); assertEquals( BatchInformationFetchingEvent.class, event.getEventType().getClass() ); @@ -71,6 +79,37 @@ public void testGetLastEvent() { @Test public void testGetLastEvents() { + ExpressionExperiment auditable = new ExpressionExperiment(); + sessionFactory.getCurrentSession().persist( auditable ); + assertNull( auditEventDao.getLastEvent( auditable, BatchInformationFetchingEvent.class ) ); + auditable.getAuditTrail().getEvents().add( AuditEvent.Factory.newInstance( new Date(), AuditAction.U, null, null, null, new BatchInformationFetchingEvent() ) ); + sessionFactory.getCurrentSession().flush(); + sessionFactory.getCurrentSession().clear(); + // should also work on detached entities + Map events = auditEventDao.getLastEvents( ExpressionExperiment.class, ExpressionExperimentAnalysisEvent.class ); + Assertions.assertThat( events ) + .hasEntrySatisfying( auditable, ae -> { + Assertions.assertThat( ae.getEventType() ).isInstanceOf( BatchInformationFetchingEvent.class ); + } ); + } + + @Test + public void testGetLastEventsByType() { + ExpressionExperiment auditable = new ExpressionExperiment(); + auditable.getAuditTrail().getEvents().add( AuditEvent.Factory.newInstance( new Date(), AuditAction.U, null, null, null, new BatchInformationFetchingEvent() ) ); + sessionFactory.getCurrentSession().persist( auditable ); + ExpressionExperiment auditable2 = new ExpressionExperiment(); + auditable2.getAuditTrail().getEvents().add( AuditEvent.Factory.newInstance( new Date(), AuditAction.U, null, null, null, new DataReplacedEvent() ) ); + sessionFactory.getCurrentSession().persist( auditable2 ); + sessionFactory.getCurrentSession().flush(); + sessionFactory.getCurrentSession().clear(); + Map result = auditEventDao.getLastEvents( Arrays.asList( auditable, auditable2 ), ExpressionExperimentAnalysisEvent.class ); + Assertions.assertThat( result ) + .containsOnlyKeys( auditable, auditable2 ); + } + + @Test + public void testGetLastEventsByType2() { ExpressionExperiment auditable = new ExpressionExperiment(); auditable.getAuditTrail().getEvents().add( AuditEvent.Factory.newInstance( new Date(), AuditAction.U, null, null, null, new BatchInformationFetchingEvent() ) ); sessionFactory.getCurrentSession().persist( auditable ); @@ -79,10 +118,38 @@ public void testGetLastEvents() { sessionFactory.getCurrentSession().persist( auditable2 ); sessionFactory.getCurrentSession().flush(); sessionFactory.getCurrentSession().clear(); - Map, Map> result = auditEventDao.getLastEventsByType( Arrays.asList( auditable, auditable2 ), Collections.singleton( ExpressionExperimentAnalysisEvent.class ) ); + Map result = auditEventDao.getLastEvents( ExpressionExperiment.class, ExpressionExperimentAnalysisEvent.class ); Assertions.assertThat( result ) - .containsOnlyKeys( ExpressionExperimentAnalysisEvent.class ); - Assertions.assertThat( result.get( ExpressionExperimentAnalysisEvent.class ) ) .containsOnlyKeys( auditable, auditable2 ); } + + @Test + public void testGetNewSinceDate() { + Date before = new Date(); + ExpressionExperiment auditable = new ExpressionExperiment(); + sessionFactory.getCurrentSession().persist( auditable ); + assertNull( auditEventDao.getLastEvent( auditable, BatchInformationFetchingEvent.class ) ); + auditable.getAuditTrail().getEvents().add( AuditEvent.Factory.newInstance( new Date(), AuditAction.C, null, null, null, null ) ); + auditable.getAuditTrail().getEvents().add( AuditEvent.Factory.newInstance( new Date(), AuditAction.U, null, null, null, new BatchInformationFetchingEvent() ) ); + sessionFactory.getCurrentSession().flush(); + sessionFactory.getCurrentSession().clear(); + Assertions.assertThat( auditEventDao.getNewSinceDate( ExpressionExperiment.class, before ) ) + .hasSize( 1 ) + .contains( auditable ); + } + + @Test + public void testGetUpdatedSinceDate() { + Date before = new Date(); + ExpressionExperiment auditable = new ExpressionExperiment(); + sessionFactory.getCurrentSession().persist( auditable ); + assertNull( auditEventDao.getLastEvent( auditable, BatchInformationFetchingEvent.class ) ); + auditable.getAuditTrail().getEvents().add( AuditEvent.Factory.newInstance( new Date(), AuditAction.C, null, null, null, new BatchInformationFetchingEvent() ) ); + auditable.getAuditTrail().getEvents().add( AuditEvent.Factory.newInstance( new Date(), AuditAction.U, null, null, null, new BatchInformationFetchingEvent() ) ); + sessionFactory.getCurrentSession().flush(); + sessionFactory.getCurrentSession().clear(); + Assertions.assertThat( auditEventDao.getNewSinceDate( ExpressionExperiment.class, before ) ) + .hasSize( 1 ) + .contains( auditable ); + } } diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventServiceTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventServiceTest.java index 59122541ca..eb02078aa9 100644 --- a/gemma-core/src/test/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventServiceTest.java +++ b/gemma-core/src/test/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventServiceTest.java @@ -1,8 +1,8 @@ /* * The Gemma project - * + * * Copyright (c) 2007 University of British Columbia - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -21,18 +21,15 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; - import ubic.gemma.core.util.test.BaseSpringContextTest; -import ubic.gemma.model.common.auditAndSecurity.Auditable; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; -import ubic.gemma.persistence.service.common.auditAndSecurity.AuditEventService; import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService; import java.util.Calendar; import java.util.Collection; import java.util.Date; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; /** * @author pavlidis @@ -61,16 +58,11 @@ public void setUp() throws Exception { @Test public void testHandleGetNewSinceDate() { - Calendar c = Calendar.getInstance(); c.set( 2006, Calendar.DECEMBER, 1 ); Date d = c.getTime(); - Collection objs = auditEventService.getNewSinceDate( d ); - assertTrue( objs.size() > 0 ); - // for ( AbstractAuditable auditable : objs ) { - // if ( objs instanceof ArrayDesign ) { - // } - // } + Collection objs = auditEventService.getNewSinceDate( ArrayDesign.class, d ); + assertFalse( objs.isEmpty() ); } @Test @@ -78,12 +70,7 @@ public void testHandleGetUpdatedSinceDate() { Calendar c = Calendar.getInstance(); c.set( 2006, Calendar.DECEMBER, 1 ); Date d = c.getTime(); - Collection objs = auditEventService.getUpdatedSinceDate( d ); - assertTrue( objs.size() > 0 ); - // for ( AbstractAuditable auditable : objs ) { - // if ( objs instanceof ArrayDesign ) { - // } - // } + Collection objs = auditEventService.getUpdatedSinceDate( ArrayDesign.class, d ); + assertFalse( objs.isEmpty() ); } - }