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() ); } - }