diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/util/DateUtils.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/util/DateUtils.java index 264a101c2861..f6750df6313a 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/util/DateUtils.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/util/DateUtils.java @@ -200,6 +200,16 @@ public static String toLongDateWithMillis(Date date) { return date != null ? LONG_DATE_FORMAT_WITH_MILLIS.print(new DateTime(date)) : null; } + /** + * Formats a Date to the format yyyy-MM-dd HH:mm:ss. + * + * @param date the Date to parse. + * @return A formatted date string. + */ + public static String toLongDate(Date date) { + return date != null ? LONG_DATE_FORMAT.print(new DateTime(date)) : null; + } + /** * Formats a Date to the format yyyy-MM-dd HH:mm:ss. * @@ -258,6 +268,20 @@ public static Date minusOneDay(Date date) { return new Date(date.getTime() - MS_PER_DAY); } + /** + * Creates a {@link Date} representing the given year, month and day. + * + * @param year the year. + * @param month the month, from 1. + * @param dayOfMonth the day of the month, from 1. + * @param hourOfDay the hour of day, from 0. + * @param minuteOfHour the minute of hour, from 0. + * @return a {@link Date}. + */ + public static Date getDate(int year, int month, int dayOfMonth, int hourOfDay, int minuteOfHour) { + return new DateTime(year, month, dayOfMonth, hourOfDay, minuteOfHour).toDate(); + } + /** * Formats a Date according to the HTTP specification standard date format. * diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/scheduling/ContinuousAnalyticsTableJob.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/scheduling/ContinuousAnalyticsTableJob.java index b2d65fb31860..35d834746275 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/scheduling/ContinuousAnalyticsTableJob.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/scheduling/ContinuousAnalyticsTableJob.java @@ -27,13 +27,14 @@ */ package org.hisp.dhis.analytics.table.scheduling; +import static com.google.common.base.MoreObjects.firstNonNull; import static org.hisp.dhis.util.DateUtils.getLongDateString; +import static org.hisp.dhis.util.DateUtils.toLongDate; -import com.google.common.base.Preconditions; import java.util.Date; +import java.util.Objects; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.ObjectUtils; import org.hisp.dhis.analytics.AnalyticsTableGenerator; import org.hisp.dhis.analytics.AnalyticsTableUpdateParams; import org.hisp.dhis.scheduling.Job; @@ -79,24 +80,14 @@ public void execute(JobConfiguration jobConfiguration, JobProgress progress) { ContinuousAnalyticsJobParameters parameters = (ContinuousAnalyticsJobParameters) jobConfiguration.getJobParameters(); - Integer fullUpdateHourOfDay = - ObjectUtils.firstNonNull(parameters.getFullUpdateHourOfDay(), DEFAULT_HOUR_OF_DAY); - - Date now = new Date(); - Date defaultNextFullUpdate = DateUtils.getNextDate(fullUpdateHourOfDay, now); - Date nextFullUpdate = - systemSettingManager.getSystemSetting( - SettingKey.NEXT_ANALYTICS_TABLE_UPDATE, defaultNextFullUpdate); + final int fullUpdateHourOfDay = + firstNonNull(parameters.getFullUpdateHourOfDay(), DEFAULT_HOUR_OF_DAY); + final Date startTime = new Date(); log.info( - "Starting continuous analytics table update, current time: '{}', default next full update: '{}', next full update: '{}'", - getLongDateString(now), - getLongDateString(defaultNextFullUpdate), - getLongDateString(nextFullUpdate)); + "Starting continuous analytics table update, current time: '{}'", toLongDate(startTime)); - Preconditions.checkNotNull(nextFullUpdate); - - if (now.after(nextFullUpdate)) { + if (runFullUpdate(startTime)) { log.info("Performing full analytics table update"); AnalyticsTableUpdateParams params = @@ -105,13 +96,13 @@ public void execute(JobConfiguration jobConfiguration, JobProgress progress) { .withSkipResourceTables(false) .withSkipTableTypes(parameters.getSkipTableTypes()) .withJobId(jobConfiguration) - .withStartTime(now) + .withStartTime(startTime) .build(); try { analyticsTableGenerator.generateTables(params, progress); } finally { - Date nextUpdate = DateUtils.getNextDate(fullUpdateHourOfDay, now); + Date nextUpdate = DateUtils.getNextDate(fullUpdateHourOfDay, startTime); systemSettingManager.saveSystemSetting(SettingKey.NEXT_ANALYTICS_TABLE_UPDATE, nextUpdate); log.info("Next full analytics table update: '{}'", getLongDateString(nextUpdate)); } @@ -124,10 +115,28 @@ public void execute(JobConfiguration jobConfiguration, JobProgress progress) { .withSkipResourceTables(true) .withSkipTableTypes(parameters.getSkipTableTypes()) .withJobId(jobConfiguration) - .withStartTime(now) + .withStartTime(startTime) .build(); analyticsTableGenerator.generateTables(params, progress); } } + + /** + * Indicates whether a full table update should be run. If the next full update time is not set, + * it indicates that a full update has never been run for this job, and a full update should be + * run immediately. Otherwise, a full update is run if the job start time argument is after the + * next full update time. + * + * @param startTime the job start time. + * @return true if a full table update should be run. + */ + boolean runFullUpdate(Date startTime) { + Objects.requireNonNull(startTime); + + Date nextFullUpdate = + systemSettingManager.getSystemSetting(SettingKey.NEXT_ANALYTICS_TABLE_UPDATE, Date.class); + + return nextFullUpdate == null || startTime.after(nextFullUpdate); + } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/scheduling/ContinuousAnalyticsTableJobTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/scheduling/ContinuousAnalyticsTableJobTest.java new file mode 100644 index 000000000000..a9bb1282e0b3 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/scheduling/ContinuousAnalyticsTableJobTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.hisp.dhis.analytics.table.scheduling; + +import static org.hisp.dhis.setting.SettingKey.NEXT_ANALYTICS_TABLE_UPDATE; +import static org.hisp.dhis.util.DateUtils.getDate; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +import java.util.Date; +import org.hisp.dhis.analytics.AnalyticsTableGenerator; +import org.hisp.dhis.setting.SystemSettingManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ContinuousAnalyticsTableJobTest { + @Mock private AnalyticsTableGenerator analyticsTableGenerator; + + @Mock private SystemSettingManager systemSettingManager; + + private ContinuousAnalyticsTableJob job; + + private final Date dateA = getDate(2024, 1, 4, 23, 0); + private final Date dateB = getDate(2024, 1, 5, 2, 0); + private final Date dateC = getDate(2024, 1, 5, 8, 0); + + @BeforeEach + public void beforeEach() { + job = new ContinuousAnalyticsTableJob(analyticsTableGenerator, systemSettingManager); + } + + @Test + void testRunFullUpdate() { + when(systemSettingManager.getSystemSetting(NEXT_ANALYTICS_TABLE_UPDATE, Date.class)) + .thenReturn(dateB); + + assertFalse(job.runFullUpdate(dateA)); + assertTrue(job.runFullUpdate(dateC)); + } + + @Test + void testRunFullUpdateNullNextUpdate() { + when(systemSettingManager.getSystemSetting(NEXT_ANALYTICS_TABLE_UPDATE, Date.class)) + .thenReturn(null); + + assertTrue(job.runFullUpdate(dateA)); + assertTrue(job.runFullUpdate(dateC)); + } +}