public void test_zeroDateIsNotNever() throws Exception { Long at1January1970 = DateTimeOperations.convert(Period.seconds(0)); Assert.assertFalse( DateTimeOperations.compare( at1January1970, CompareType.EQ, null, DateTimeFieldType.millisOfSecond()) || DateTimeOperations.compare( at1January1970, CompareType.EQ, DateTimeOperations.never(), DateTimeFieldType.millisOfSecond())); }
@JsonAutoDetect @AutoValue public abstract class SearchesClusterConfig { private static final Period DEFAULT_QUERY_TIME_RANGE_LIMIT = Period.ZERO; private static final Map<Period, String> DEFAULT_RELATIVE_TIMERANGE_OPTIONS = ImmutableMap.<Period, String>builder() .put(Period.minutes(5), "Search in the last 5 minutes") .put(Period.minutes(15), "Search in the last 15 minutes") .put(Period.minutes(30), "Search in the last 30 minutes") .put(Period.hours(1), "Search in the last 1 hour") .put(Period.hours(2), "Search in the last 2 hours") .put(Period.hours(8), "Search in the last 8 hours") .put(Period.days(1), "Search in the last 1 day") .put(Period.days(2), "Search in the last 2 days") .put(Period.days(5), "Search in the last 5 days") .put(Period.days(7), "Search in the last 7 days") .put(Period.days(14), "Search in the last 14 days") .put(Period.days(30), "Search in the last 30 days") .put(Period.ZERO, "Search in all messages") .build(); private static final Map<Period, String> DEFAULT_SURROUNDING_TIMERANGE_OPTIONS = ImmutableMap.<Period, String>builder() .put(Period.seconds(1), "1 second") .put(Period.seconds(5), "5 seconds") .put(Period.seconds(10), "10 seconds") .put(Period.seconds(30), "30 seconds") .put(Period.minutes(1), "1 minute") .put(Period.minutes(5), "5 minutes") .build(); private static final Set<String> DEFAULT_SURROUNDING_FILTER_FIELDS = ImmutableSet.<String>builder() .add("source") .add("gl2_source_input") .add("file") .add("source_file") .build(); @JsonProperty("query_time_range_limit") public abstract Period queryTimeRangeLimit(); @JsonProperty("relative_timerange_options") public abstract Map<Period, String> relativeTimerangeOptions(); @JsonProperty("surrounding_timerange_options") public abstract Map<Period, String> surroundingTimerangeOptions(); @JsonProperty("surrounding_filter_fields") public abstract Set<String> surroundingFilterFields(); @JsonCreator public static SearchesClusterConfig create( @JsonProperty("query_time_range_limit") Period queryTimeRangeLimit, @JsonProperty("relative_timerange_options") Map<Period, String> relativeTimerangeOptions, @JsonProperty("surrounding_timerange_options") Map<Period, String> surroundingTimerangeOptions, @JsonProperty("surrounding_filter_fields") Set<String> surroundingFilterFields) { return builder() .queryTimeRangeLimit(queryTimeRangeLimit) .relativeTimerangeOptions(relativeTimerangeOptions) .surroundingTimerangeOptions(surroundingTimerangeOptions) .surroundingFilterFields(surroundingFilterFields) .build(); } public static SearchesClusterConfig createDefault() { return builder() .queryTimeRangeLimit(DEFAULT_QUERY_TIME_RANGE_LIMIT) .relativeTimerangeOptions(DEFAULT_RELATIVE_TIMERANGE_OPTIONS) .surroundingTimerangeOptions(DEFAULT_SURROUNDING_TIMERANGE_OPTIONS) .surroundingFilterFields(DEFAULT_SURROUNDING_FILTER_FIELDS) .build(); } public static Builder builder() { return new AutoValue_SearchesClusterConfig.Builder(); } public abstract Builder toBuilder(); @AutoValue.Builder public abstract static class Builder { public abstract Builder queryTimeRangeLimit(Period queryTimeRangeLimit); public abstract Builder relativeTimerangeOptions(Map<Period, String> relativeTimerangeOptions); public abstract Builder surroundingTimerangeOptions( Map<Period, String> surroundingTimerangeOptions); public abstract Builder surroundingFilterFields(Set<String> surroundingFilterFields); public abstract SearchesClusterConfig build(); } }
@Service public class PortalRawEventsAggregatorImpl extends BaseAggrEventsJpaDao implements PortalRawEventsAggregator, DisposableBean { private static final String EVENT_SESSION_CACHE_KEY_SOURCE = AggregateEventsHandler.class.getName() + "-EventSession"; private IClusterLockService clusterLockService; private IPortalEventProcessingManager portalEventAggregationManager; private PortalEventDimensionPopulator portalEventDimensionPopulator; private IEventAggregationManagementDao eventAggregationManagementDao; private IPortalInfoProvider portalInfoProvider; private IPortalEventDao portalEventDao; private AggregationIntervalHelper intervalHelper; private EventSessionDao eventSessionDao; private DateDimensionDao dateDimensionDao; private Set<IntervalAwarePortalEventAggregator<PortalEvent>> intervalAwarePortalEventAggregators = Collections.emptySet(); private Set<SimplePortalEventAggregator<PortalEvent>> simplePortalEventAggregators = Collections.emptySet(); private List<ApplicationEventFilter<PortalEvent>> applicationEventFilters = Collections.emptyList(); private int eventAggregationBatchSize = 10000; private int intervalAggregationBatchSize = 5; private int cleanUnclosedAggregationsBatchSize = 1000; private int cleanUnclosedIntervalsBatchSize = 315; private ReadablePeriod aggregationDelay = Period.seconds(30); private final Map<Class<?>, List<String>> entityCollectionRoles = new HashMap<Class<?>, List<String>>(); private volatile boolean shutdown = false; @Autowired public void setDateDimensionDao(DateDimensionDao dateDimensionDao) { this.dateDimensionDao = dateDimensionDao; } @Autowired public void setPortalEventAggregationManager( IPortalEventProcessingManager portalEventAggregationManager) { this.portalEventAggregationManager = portalEventAggregationManager; } @Autowired public void setClusterLockService(IClusterLockService clusterLockService) { this.clusterLockService = clusterLockService; } @Autowired public void setPortalEventDimensionPopulator( PortalEventDimensionPopulator portalEventDimensionPopulator) { this.portalEventDimensionPopulator = portalEventDimensionPopulator; } @Autowired public void setEventAggregationManagementDao( IEventAggregationManagementDao eventAggregationManagementDao) { this.eventAggregationManagementDao = eventAggregationManagementDao; } @Autowired public void setPortalInfoProvider(IPortalInfoProvider portalInfoProvider) { this.portalInfoProvider = portalInfoProvider; } @Autowired public void setPortalEventDao(IPortalEventDao portalEventDao) { this.portalEventDao = portalEventDao; } @Autowired public void setIntervalHelper(AggregationIntervalHelper intervalHelper) { this.intervalHelper = intervalHelper; } @Autowired public void setEventSessionDao(EventSessionDao eventSessionDao) { this.eventSessionDao = eventSessionDao; } @Autowired @SuppressWarnings({"rawtypes", "unchecked"}) public void setPortalEventAggregators( Set<IPortalEventAggregator<PortalEvent>> portalEventAggregators) { final com.google.common.collect.ImmutableSet.Builder< IntervalAwarePortalEventAggregator<PortalEvent>> intervalAwarePortalEventAggregatorsBuilder = ImmutableSet.builder(); final com.google.common.collect.ImmutableSet.Builder<SimplePortalEventAggregator<PortalEvent>> simplePortalEventAggregatorsBuilder = ImmutableSet.builder(); for (final IPortalEventAggregator<PortalEvent> portalEventAggregator : portalEventAggregators) { if (portalEventAggregator instanceof IntervalAwarePortalEventAggregator) { intervalAwarePortalEventAggregatorsBuilder.add( (IntervalAwarePortalEventAggregator) portalEventAggregator); } else if (portalEventAggregator instanceof SimplePortalEventAggregator) { simplePortalEventAggregatorsBuilder.add( (SimplePortalEventAggregator) portalEventAggregator); } } this.intervalAwarePortalEventAggregators = intervalAwarePortalEventAggregatorsBuilder.build(); this.simplePortalEventAggregators = simplePortalEventAggregatorsBuilder.build(); } @Resource(name = "aggregatorEventFilters") public void setApplicationEventFilters( List<ApplicationEventFilter<PortalEvent>> applicationEventFilters) { this.applicationEventFilters = applicationEventFilters; } @Value("${org.jasig.portal.events.aggr.PortalRawEventsAggregatorImpl.aggregationDelay:PT30S}") public void setAggregationDelay(ReadablePeriod aggregationDelay) { this.aggregationDelay = aggregationDelay; } @Value( "${org.jasig.portal.events.aggr.PortalRawEventsAggregatorImpl.eventAggregationBatchSize:10000}") public void setEventAggregationBatchSize(int eventAggregationBatchSize) { this.eventAggregationBatchSize = eventAggregationBatchSize; } @Value( "${org.jasig.portal.events.aggr.PortalRawEventsAggregatorImpl.intervalAggregationBatchSize:5}") public void setIntervalAggregationBatchSize(int intervalAggregationBatchSize) { this.intervalAggregationBatchSize = intervalAggregationBatchSize; } @Value( "${org.jasig.portal.events.aggr.PortalRawEventsAggregatorImpl.cleanUnclosedAggregationsBatchSize:1000}") public void setCleanUnclosedAggregationsBatchSize(int cleanUnclosedAggregationsBatchSize) { this.cleanUnclosedAggregationsBatchSize = cleanUnclosedAggregationsBatchSize; } @Value( "${org.jasig.portal.events.aggr.PortalRawEventsAggregatorImpl.cleanUnclosedIntervalsBatchSize:300}") public void setCleanUnclosedIntervalsBatchSize(int cleanUnclosedIntervalsBatchSize) { this.cleanUnclosedIntervalsBatchSize = cleanUnclosedIntervalsBatchSize; } public void setShutdown(boolean shutdown) { this.shutdown = shutdown; } @Override public void destroy() throws Exception { this.shutdown = true; } private void checkShutdown() { if (shutdown) { // Mark ourselves as interupted and throw an exception Thread.currentThread().interrupt(); throw new RuntimeException( "uPortal is shutting down, throwing an exception to stop processing"); } } @RawEventsTransactional @Override public EventProcessingResult doAggregateRawEvents() { // Do RawTX around AggrTX. The AggrTX is MUCH more likely to fail than the RawTX and this // results in both rolling back return this.getTransactionOperations() .execute( new TransactionCallback<EventProcessingResult>() { @Override public EventProcessingResult doInTransaction(TransactionStatus status) { return doAggregateRawEventsInternal(); } }); } @AggrEventsTransactional @Override public void evictAggregates(Map<Class<?>, Collection<Serializable>> entitiesToEvict) { int evictedEntities = 0; int evictedCollections = 0; final Session session = getEntityManager().unwrap(Session.class); final SessionFactory sessionFactory = session.getSessionFactory(); final Cache cache = sessionFactory.getCache(); for (final Entry<Class<?>, Collection<Serializable>> evictedEntityEntry : entitiesToEvict.entrySet()) { final Class<?> entityClass = evictedEntityEntry.getKey(); final List<String> collectionRoles = getCollectionRoles(sessionFactory, entityClass); for (final Serializable id : evictedEntityEntry.getValue()) { cache.evictEntity(entityClass, id); evictedEntities++; for (final String collectionRole : collectionRoles) { cache.evictCollection(collectionRole, id); evictedCollections++; } } } logger.debug( "Evicted {} entities and {} collections from hibernate caches", evictedEntities, evictedCollections); } @Override @AggrEventsTransactional public EventProcessingResult doCloseAggregations() { if (!this.clusterLockService.isLockOwner(AGGREGATION_LOCK_NAME)) { throw new IllegalStateException( "The cluster lock " + AGGREGATION_LOCK_NAME + " must be owned by the current thread and server"); } final IEventAggregatorStatus cleanUnclosedStatus = eventAggregationManagementDao.getEventAggregatorStatus(ProcessingType.CLEAN_UNCLOSED, true); // Update status with current server name final String serverName = this.portalInfoProvider.getUniqueServerName(); cleanUnclosedStatus.setServerName(serverName); cleanUnclosedStatus.setLastStart(new DateTime()); // Determine date of most recently aggregated data final IEventAggregatorStatus eventAggregatorStatus = eventAggregationManagementDao.getEventAggregatorStatus(ProcessingType.AGGREGATION, false); if (eventAggregatorStatus == null || eventAggregatorStatus.getLastEventDate() == null) { // Nothing has been aggregated, skip unclosed cleanup cleanUnclosedStatus.setLastEnd(new DateTime()); eventAggregationManagementDao.updateEventAggregatorStatus(cleanUnclosedStatus); return new EventProcessingResult(0, null, null, true); } final DateTime lastAggregatedDate = eventAggregatorStatus.getLastEventDate(); // If lastCleanUnclosedDate is null use the oldest date dimension as there can be // no aggregations that exist before it final DateTime lastCleanUnclosedDate; if (cleanUnclosedStatus.getLastEventDate() == null) { final DateDimension oldestDateDimension = this.dateDimensionDao.getOldestDateDimension(); lastCleanUnclosedDate = oldestDateDimension.getDate().toDateTime(); } else { lastCleanUnclosedDate = cleanUnclosedStatus.getLastEventDate(); } if (!(lastCleanUnclosedDate.isBefore(lastAggregatedDate))) { logger.debug( "No events aggregated since last unclosed aggregation cleaning, skipping clean: {}", lastAggregatedDate); return new EventProcessingResult(0, lastCleanUnclosedDate, lastAggregatedDate, true); } // Switch to flush on commit to avoid flushes during queries final EntityManager entityManager = this.getEntityManager(); entityManager.flush(); entityManager.setFlushMode(FlushModeType.COMMIT); // Track the number of closed aggregations and the last date of a cleaned interval int closedAggregations = 0; int cleanedIntervals = 0; DateTime cleanUnclosedEnd; final Thread currentThread = Thread.currentThread(); final String currentName = currentThread.getName(); try { currentThread.setName(currentName + "-" + lastCleanUnclosedDate + "-" + lastAggregatedDate); // Local caches used to reduce db io final IntervalsForAggregatorHelper intervalsForAggregatorHelper = new IntervalsForAggregatorHelper(); final Map<AggregationInterval, AggregationIntervalInfo> previousIntervals = new HashMap<AggregationInterval, AggregationIntervalInfo>(); // A DateTime within the next interval to close aggregations in DateTime nextIntervalDate = lastCleanUnclosedDate; do { // Reset our goal of catching up to the last aggregated event on every iteration cleanUnclosedEnd = lastAggregatedDate; // For each interval the aggregator supports, cleanup the unclosed aggregations for (final AggregationInterval interval : intervalsForAggregatorHelper.getHandledIntervals()) { final AggregationIntervalInfo previousInterval = previousIntervals.get(interval); if (previousInterval != null && nextIntervalDate.isBefore(previousInterval.getEnd())) { logger.debug( "{} interval before {} has already been cleaned during this execution, ignoring", interval, previousInterval.getEnd()); continue; } // The END date of the last clean session will find us the next interval to clean final AggregationIntervalInfo nextIntervalToClean = intervalHelper.getIntervalInfo(interval, nextIntervalDate); previousIntervals.put(interval, nextIntervalToClean); if (nextIntervalToClean == null) { continue; } final DateTime start = nextIntervalToClean.getStart(); final DateTime end = nextIntervalToClean.getEnd(); if (!end.isBefore(lastAggregatedDate)) { logger.debug( "{} interval between {} and {} is still active, ignoring", new Object[] {interval, start, end}); continue; } // Track the oldest interval end, this ensures that nothing is missed if (end.isBefore(cleanUnclosedEnd)) { cleanUnclosedEnd = end; } logger.debug( "Cleaning unclosed {} aggregations between {} and {}", new Object[] {interval, start, end}); for (final IntervalAwarePortalEventAggregator<PortalEvent> portalEventAggregator : intervalAwarePortalEventAggregators) { checkShutdown(); final Class<? extends IPortalEventAggregator<?>> aggregatorType = getClass(portalEventAggregator); // Get aggregator specific interval info config final AggregatedIntervalConfig aggregatorIntervalConfig = intervalsForAggregatorHelper.getAggregatorIntervalConfig(aggregatorType); // If the aggregator is being used for the specified interval call // cleanUnclosedAggregations if (aggregatorIntervalConfig.isIncluded(interval)) { closedAggregations += portalEventAggregator.cleanUnclosedAggregations(start, end, interval); } } cleanedIntervals++; } // Set the next interval to the end date from the last aggregation run nextIntervalDate = cleanUnclosedEnd; logger.debug( "Closed {} aggregations across {} interval before {} with goal of {}", new Object[] { closedAggregations, cleanedIntervals, cleanUnclosedEnd, lastAggregatedDate }); // Loop until either the batchSize of cleaned aggregations has been reached or no // aggregation work is done } while (closedAggregations <= cleanUnclosedAggregationsBatchSize && cleanedIntervals <= cleanUnclosedIntervalsBatchSize && cleanUnclosedEnd.isBefore(lastAggregatedDate)); } finally { currentThread.setName(currentName); } // Update the status object and store it cleanUnclosedStatus.setLastEventDate(cleanUnclosedEnd); cleanUnclosedStatus.setLastEnd(new DateTime()); eventAggregationManagementDao.updateEventAggregatorStatus(cleanUnclosedStatus); return new EventProcessingResult( closedAggregations, lastCleanUnclosedDate, lastAggregatedDate, !cleanUnclosedEnd.isBefore(lastAggregatedDate)); } @SuppressWarnings("unchecked") protected final <T> Class<T> getClass(T object) { return (Class<T>) AopProxyUtils.ultimateTargetClass(object); } private List<String> getCollectionRoles( final SessionFactory sessionFactory, final Class<?> entityClass) { List<String> collectionRoles = entityCollectionRoles.get(entityClass); if (collectionRoles != null) { return collectionRoles; } final com.google.common.collect.ImmutableList.Builder<String> collectionRolesBuilder = ImmutableList.builder(); final ClassMetadata classMetadata = sessionFactory.getClassMetadata(entityClass); for (final Type type : classMetadata.getPropertyTypes()) { if (type.isCollectionType()) { collectionRolesBuilder.add(((CollectionType) type).getRole()); } } collectionRoles = collectionRolesBuilder.build(); entityCollectionRoles.put(entityClass, collectionRoles); return collectionRoles; } private EventProcessingResult doAggregateRawEventsInternal() { if (!this.clusterLockService.isLockOwner(AGGREGATION_LOCK_NAME)) { throw new IllegalStateException( "The cluster lock " + AGGREGATION_LOCK_NAME + " must be owned by the current thread and server"); } if (!this.portalEventDimensionPopulator.isCheckedDimensions()) { // First time aggregation has happened, run populateDimensions to ensure enough dimension data // exists final boolean populatedDimensions = this.portalEventAggregationManager.populateDimensions(); if (!populatedDimensions) { this.logger.warn( "Aborting raw event aggregation, populateDimensions returned false so the state of date/time dimensions is unknown"); return null; } } // Flush any dimension creation before aggregation final EntityManager entityManager = this.getEntityManager(); entityManager.flush(); entityManager.setFlushMode(FlushModeType.COMMIT); final IEventAggregatorStatus eventAggregatorStatus = eventAggregationManagementDao.getEventAggregatorStatus(ProcessingType.AGGREGATION, true); // Update status with current server name final String serverName = this.portalInfoProvider.getUniqueServerName(); final String previousServerName = eventAggregatorStatus.getServerName(); if (previousServerName != null && !serverName.equals(previousServerName)) { this.logger.debug( "Last aggregation run on {} clearing all aggregation caches", previousServerName); final Session session = getEntityManager().unwrap(Session.class); final Cache cache = session.getSessionFactory().getCache(); cache.evictEntityRegions(); } eventAggregatorStatus.setServerName(serverName); // Calculate date range for aggregation DateTime lastAggregated = eventAggregatorStatus.getLastEventDate(); if (lastAggregated == null) { lastAggregated = portalEventDao.getOldestPortalEventTimestamp(); // No portal events to aggregate, skip aggregation if (lastAggregated == null) { return new EventProcessingResult(0, null, null, true); } // First time aggregation has run, initialize the CLEAN_UNCLOSED status to save catch-up time final IEventAggregatorStatus cleanUnclosedStatus = eventAggregationManagementDao.getEventAggregatorStatus( ProcessingType.CLEAN_UNCLOSED, true); AggregationIntervalInfo oldestMinuteInterval = this.intervalHelper.getIntervalInfo(AggregationInterval.MINUTE, lastAggregated); cleanUnclosedStatus.setLastEventDate(oldestMinuteInterval.getStart().minusMinutes(1)); eventAggregationManagementDao.updateEventAggregatorStatus(cleanUnclosedStatus); } final DateTime newestEventTime = DateTime.now().minus(this.aggregationDelay).secondOfMinute().roundFloorCopy(); final Thread currentThread = Thread.currentThread(); final String currentName = currentThread.getName(); final MutableInt events = new MutableInt(); final MutableObject lastEventDate = new MutableObject(newestEventTime); boolean complete; try { currentThread.setName(currentName + "-" + lastAggregated + "_" + newestEventTime); logger.debug( "Starting aggregation of events between {} (inc) and {} (exc)", lastAggregated, newestEventTime); // Do aggregation, capturing the start and end dates eventAggregatorStatus.setLastStart(DateTime.now()); complete = portalEventDao.aggregatePortalEvents( lastAggregated, newestEventTime, this.eventAggregationBatchSize, new AggregateEventsHandler(events, lastEventDate, eventAggregatorStatus)); eventAggregatorStatus.setLastEventDate((DateTime) lastEventDate.getValue()); eventAggregatorStatus.setLastEnd(DateTime.now()); } finally { currentThread.setName(currentName); } // Store the results of the aggregation eventAggregationManagementDao.updateEventAggregatorStatus(eventAggregatorStatus); complete = complete && (this.eventAggregationBatchSize <= 0 || events.intValue() < this.eventAggregationBatchSize); return new EventProcessingResult( events.intValue(), lastAggregated, eventAggregatorStatus.getLastEventDate(), complete); } /** * Helper class that loads and caches the interval configuration for each aggregator as well as * the union of intervals handled by the set of aggregators. */ private final class IntervalsForAggregatorHelper { private final Map<Class<? extends IPortalEventAggregator<?>>, AggregatedIntervalConfig> aggregatorIntervalConfigsCache = new HashMap<Class<? extends IPortalEventAggregator<?>>, AggregatedIntervalConfig>(); private final AggregatedIntervalConfig defaultAggregatedIntervalConfig; private final Set<AggregationInterval> handledIntervals; public IntervalsForAggregatorHelper() { this.defaultAggregatedIntervalConfig = eventAggregationManagementDao.getDefaultAggregatedIntervalConfig(); // Create the set of intervals that are actually being aggregated final Set<AggregationInterval> handledIntervalsNotIncluded = EnumSet.allOf(AggregationInterval.class); final Set<AggregationInterval> handledIntervalsBuilder = EnumSet.noneOf(AggregationInterval.class); for (final IntervalAwarePortalEventAggregator<PortalEvent> portalEventAggregator : intervalAwarePortalEventAggregators) { final Class<? extends IPortalEventAggregator<?>> aggregatorType = PortalRawEventsAggregatorImpl.this.getClass(portalEventAggregator); // Get aggregator specific interval info config final AggregatedIntervalConfig aggregatorIntervalConfig = this.getAggregatorIntervalConfig(aggregatorType); for (final Iterator<AggregationInterval> intervalsIterator = handledIntervalsNotIncluded.iterator(); intervalsIterator.hasNext(); ) { final AggregationInterval interval = intervalsIterator.next(); if (aggregatorIntervalConfig.isIncluded(interval)) { handledIntervalsBuilder.add(interval); intervalsIterator.remove(); } } } handledIntervals = Sets.immutableEnumSet(handledIntervalsBuilder); } /** @return All of the intervals that are actually handled by the current set of aggregators */ public Set<AggregationInterval> getHandledIntervals() { return handledIntervals; } /** * @return The interval config for the aggregator, returns the default config if no aggregator * specific config is set */ public AggregatedIntervalConfig getAggregatorIntervalConfig( final Class<? extends IPortalEventAggregator<?>> aggregatorType) { AggregatedIntervalConfig config = aggregatorIntervalConfigsCache.get(aggregatorType); if (config != null) { return config; } config = eventAggregationManagementDao.getAggregatedIntervalConfig(aggregatorType); if (config == null) { config = defaultAggregatedIntervalConfig; } aggregatorIntervalConfigsCache.put(aggregatorType, config); return config; } } private final class AggregateEventsHandler implements Function<PortalEvent, Boolean> { // Event Aggregation Context - used by aggregators to track state private final EventAggregationContext eventAggregationContext = new EventAggregationContextImpl(); private final MutableInt eventCounter; private final MutableObject lastEventDate; private final IEventAggregatorStatus eventAggregatorStatus; private int intervalsCrossed = 0; // Local tracking of the current aggregation interval and info about said interval private final Map<AggregationInterval, AggregationIntervalInfo> currentIntervalInfo = new EnumMap<AggregationInterval, AggregationIntervalInfo>(AggregationInterval.class); // Local caches of per-aggregator config data, shouldn't ever change for the duration of an // aggregation run private final IntervalsForAggregatorHelper intervalsForAggregatorHelper = new IntervalsForAggregatorHelper(); private final Map<Class<? extends IPortalEventAggregator<?>>, AggregatedGroupConfig> aggregatorGroupConfigs = new HashMap<Class<? extends IPortalEventAggregator<?>>, AggregatedGroupConfig>(); private final Map< Class<? extends IPortalEventAggregator<?>>, Map<AggregationInterval, AggregationIntervalInfo>> aggregatorReadOnlyIntervalInfo = new HashMap< Class<? extends IPortalEventAggregator<?>>, Map<AggregationInterval, AggregationIntervalInfo>>(); private final AggregatedGroupConfig defaultAggregatedGroupConfig; private AggregateEventsHandler( MutableInt eventCounter, MutableObject lastEventDate, IEventAggregatorStatus eventAggregatorStatus) { this.eventCounter = eventCounter; this.lastEventDate = lastEventDate; this.eventAggregatorStatus = eventAggregatorStatus; this.defaultAggregatedGroupConfig = eventAggregationManagementDao.getDefaultAggregatedGroupConfig(); } @Override public Boolean apply(PortalEvent event) { if (shutdown) { // Mark ourselves as interupted and throw an exception Thread.currentThread().interrupt(); throw new RuntimeException( "uPortal is shutting down, throwing an exeption to stop aggregation"); } final DateTime eventDate = event.getTimestampAsDate(); this.lastEventDate.setValue(eventDate); // If no interval data yet populate it. if (this.currentIntervalInfo.isEmpty()) { initializeIntervalInfo(eventDate); } // Check each interval to see if an interval boundary has been crossed boolean intervalCrossed = false; for (final AggregationInterval interval : this.intervalsForAggregatorHelper.getHandledIntervals()) { AggregationIntervalInfo intervalInfo = this.currentIntervalInfo.get(interval); if (intervalInfo != null && !intervalInfo .getEnd() .isAfter( eventDate)) { // if there is no IntervalInfo that interval must not be supported // in the current environment logger.debug("Crossing {} Interval, triggered by {}", interval, event); this.doHandleIntervalBoundary(interval, this.currentIntervalInfo); intervalInfo = intervalHelper.getIntervalInfo(interval, eventDate); this.currentIntervalInfo.put(interval, intervalInfo); this.aggregatorReadOnlyIntervalInfo .clear(); // Clear out cached per-aggregator interval info whenever a current interval // info changes intervalCrossed = true; } } if (intervalCrossed) { this.intervalsCrossed++; // If we have crossed more intervals than the interval batch size return false to stop // aggregation before handling the triggering event if (this.intervalsCrossed >= intervalAggregationBatchSize) { return false; } } // Aggregate the event this.doAggregateEvent(event); // Update the status object with the event date this.lastEventDate.setValue(eventDate); // Continue processing return true; } private void initializeIntervalInfo(final DateTime eventDate) { final DateTime intervalDate; final DateTime lastEventDate = this.eventAggregatorStatus.getLastEventDate(); if (lastEventDate != null) { // If there was a previously aggregated event use that date to make sure an interval is not // missed intervalDate = lastEventDate; } else { // Otherwise just use the current event date intervalDate = eventDate; } for (final AggregationInterval interval : this.intervalsForAggregatorHelper.getHandledIntervals()) { final AggregationIntervalInfo intervalInfo = intervalHelper.getIntervalInfo(interval, intervalDate); if (intervalInfo != null) { this.currentIntervalInfo.put(interval, intervalInfo); } else { this.currentIntervalInfo.remove(interval); } } } private void doAggregateEvent(PortalEvent item) { checkShutdown(); eventCounter.increment(); for (final ApplicationEventFilter<PortalEvent> applicationEventFilter : applicationEventFilters) { if (!applicationEventFilter.supports(item)) { logger.trace( "Skipping event {} - {} excluded by filter {}", new Object[] {eventCounter, item, applicationEventFilter}); return; } } logger.trace("Aggregating event {} - {}", eventCounter, item); // Load or create the event session EventSession eventSession = getEventSession(item); // Give each interval aware aggregator a chance at the event for (final IntervalAwarePortalEventAggregator<PortalEvent> portalEventAggregator : intervalAwarePortalEventAggregators) { if (checkSupports(portalEventAggregator, item)) { final Class<? extends IPortalEventAggregator<?>> aggregatorType = PortalRawEventsAggregatorImpl.this.getClass(portalEventAggregator); // Get aggregator specific interval info map final Map<AggregationInterval, AggregationIntervalInfo> aggregatorIntervalInfo = this.getAggregatorIntervalInfo(aggregatorType); // If there is an event session get the aggregator specific version of it if (eventSession != null) { final AggregatedGroupConfig aggregatorGroupConfig = getAggregatorGroupConfig(aggregatorType); final CacheKey key = CacheKey.build(EVENT_SESSION_CACHE_KEY_SOURCE, eventSession, aggregatorGroupConfig); EventSession filteredEventSession = this.eventAggregationContext.getAttribute(key); if (filteredEventSession == null) { filteredEventSession = new FilteredEventSession(eventSession, aggregatorGroupConfig); this.eventAggregationContext.setAttribute(key, filteredEventSession); } eventSession = filteredEventSession; } // Aggregation magic happens here! portalEventAggregator.aggregateEvent( item, eventSession, eventAggregationContext, aggregatorIntervalInfo); } } // Give each simple aggregator a chance at the event for (final SimplePortalEventAggregator<PortalEvent> portalEventAggregator : simplePortalEventAggregators) { if (checkSupports(portalEventAggregator, item)) { portalEventAggregator.aggregateEvent(item, eventSession); } } } /** * @deprecated This method exists until uPortal 4.1 when IPortalEventAggregator#supports(Class) * can be deleted */ @Deprecated protected boolean checkSupports( IPortalEventAggregator<PortalEvent> portalEventAggregator, PortalEvent item) { try { return portalEventAggregator.supports(item); } catch (AbstractMethodError e) { return portalEventAggregator.supports(item.getClass()); } } protected EventSession getEventSession(PortalEvent item) { final String eventSessionId = item.getEventSessionId(); // First check the aggregation context for a cached session event, fall back // to asking the DAO if nothing in the context, cache the result final CacheKey key = CacheKey.build(EVENT_SESSION_CACHE_KEY_SOURCE, eventSessionId); EventSession eventSession = this.eventAggregationContext.getAttribute(key); if (eventSession == null) { eventSession = eventSessionDao.getEventSession(item); this.eventAggregationContext.setAttribute(key, eventSession); } // Record the session access eventSession.recordAccess(item.getTimestampAsDate()); eventSessionDao.storeEventSession(eventSession); return eventSession; } private void doHandleIntervalBoundary( AggregationInterval interval, Map<AggregationInterval, AggregationIntervalInfo> intervals) { for (final IntervalAwarePortalEventAggregator<PortalEvent> portalEventAggregator : intervalAwarePortalEventAggregators) { final Class<? extends IPortalEventAggregator<?>> aggregatorType = PortalRawEventsAggregatorImpl.this.getClass(portalEventAggregator); final AggregatedIntervalConfig aggregatorIntervalConfig = this.intervalsForAggregatorHelper.getAggregatorIntervalConfig(aggregatorType); // If the aggreagator is configured to use the interval notify it of the interval boundary if (aggregatorIntervalConfig.isIncluded(interval)) { final Map<AggregationInterval, AggregationIntervalInfo> aggregatorIntervalInfo = this.getAggregatorIntervalInfo(aggregatorType); portalEventAggregator.handleIntervalBoundary( interval, eventAggregationContext, aggregatorIntervalInfo); } } } /** @return The interval info map for the aggregator */ protected Map<AggregationInterval, AggregationIntervalInfo> getAggregatorIntervalInfo( final Class<? extends IPortalEventAggregator<?>> aggregatorType) { final AggregatedIntervalConfig aggregatorIntervalConfig = this.intervalsForAggregatorHelper.getAggregatorIntervalConfig(aggregatorType); Map<AggregationInterval, AggregationIntervalInfo> intervalInfo = this.aggregatorReadOnlyIntervalInfo.get(aggregatorType); if (intervalInfo == null) { final Builder<AggregationInterval, AggregationIntervalInfo> intervalInfoBuilder = ImmutableMap.builder(); for (Map.Entry<AggregationInterval, AggregationIntervalInfo> intervalInfoEntry : this.currentIntervalInfo.entrySet()) { final AggregationInterval key = intervalInfoEntry.getKey(); if (aggregatorIntervalConfig.isIncluded(key)) { intervalInfoBuilder.put(key, intervalInfoEntry.getValue()); } } intervalInfo = intervalInfoBuilder.build(); aggregatorReadOnlyIntervalInfo.put(aggregatorType, intervalInfo); } return intervalInfo; } /** * @return The group config for the aggregator, returns the default config if no aggregator * specific config is set */ protected AggregatedGroupConfig getAggregatorGroupConfig( final Class<? extends IPortalEventAggregator<?>> aggregatorType) { AggregatedGroupConfig config = this.aggregatorGroupConfigs.get(aggregatorType); if (config == null) { config = eventAggregationManagementDao.getAggregatedGroupConfig(aggregatorType); if (config == null) { config = this.defaultAggregatedGroupConfig; } this.aggregatorGroupConfigs.put(aggregatorType, config); } return config; } } }