
fi.evolver.basics.spring.clean.MaxAgeCleaner Maven / Gradle / Ivy
package fi.evolver.basics.spring.clean;
import static fi.evolver.utils.timing.TimingUtils.begin;
import java.sql.Timestamp;
import java.time.*;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import fi.evolver.basics.spring.job.ResultState;
import fi.evolver.basics.spring.job.TaskStatusService;
import fi.evolver.basics.spring.triggerable.TriggerableException;
import fi.evolver.utils.arg.Arg;
import fi.evolver.utils.arg.IntArg;
import fi.evolver.utils.arg.StringArg;
import fi.evolver.utils.timing.TimingUtils.AutoCloser;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
@Component
public class MaxAgeCleaner extends GenericCleaner {
private static final StringArg ARG_PROPERTY_NAME = new StringArg("PropertyName");
private static final IntArg ARG_MAX_AGE_DAYS = new IntArg("MaxAgeDays");
@PersistenceContext
private EntityManager em;
@Autowired
public MaxAgeCleaner(DataCleaner dataCleaner, TaskStatusService taskStatusService) {
super(dataCleaner, taskStatusService);
}
@Override
protected ResultState clean(Class> table, Map args) throws TriggerableException {
Instant maxStartTime = Instant.now().plusMillis(ARG_MAX_RUN_TIME_MS.get(args));
int maxBatchSize = ARG_MAX_BATCH_SIZE.get(args);
int timeToLiveDays = ARG_MAX_AGE_DAYS.get(args);
String property = ARG_PROPERTY_NAME.get(args);
List deleteIds = queryIds(table, property, timeToLiveDays);
int deleted = deleteInBatches(table, deleteIds, maxBatchSize, maxStartTime);
LOG.debug("Deleted {} {} rows where {} is more than {} day(s) in past", deleted, table.getSimpleName(), property, timeToLiveDays);
return ResultState.ok(deleted > 0, "Deleted %s %s rows", deleted, table.getSimpleName());
}
private int deleteInBatches(Class> table, List ids, int maxBatchSize, Instant maxStartTime) {
int deleted = 0;
for (int i = 0; i < ids.size(); i += maxBatchSize) {
taskStatusService.updateMessage("Deleted %d / %d %s rows", i, ids.size(), table.getSimpleName());
try (AutoCloser t = begin("Delete")) {
deleted += dataCleaner.deleteBatch(table, ids.subList(i, Math.min(ids.size(), i + maxBatchSize)));
if (Instant.now().isAfter(maxStartTime))
break;
}
}
return deleted;
}
private List queryIds(Class> table, String property, int value) throws TriggerableException {
try (AutoCloser t = begin("IdQuery")) {
taskStatusService.updateMessage("Querying %s entities to delete", table.getSimpleName());
long cutoff = Timestamp.valueOf(LocalDateTime.now().minusDays(value)).getTime();
StringBuilder builder = new StringBuilder();
builder.append("SELECT l.id FROM ").append(table.getSimpleName()).append(" l");
builder.append(" WHERE l.").append(property).append(" < :").append(property);
builder.append(" ORDER BY l.").append(property).append(" ASC");
TypedQuery query = em.createQuery(builder.toString(), Long.class);
query.setParameter(property, createParameter(inferPropertyType(table, property), cutoff));
return query.getResultList();
}
}
private static Object createParameter(Class> type, long cutoff) {
if (type == Long.class)
return cutoff;
else if (type == Date.class)
return new Date(cutoff);
else if (type == Timestamp.class)
return new Timestamp(cutoff);
else if (type == java.sql.Date.class)
return new java.sql.Date(cutoff);
else if (type == Instant.class)
return Instant.ofEpochMilli(cutoff);
else if (type == LocalDate.class)
return Instant.ofEpochMilli(cutoff).atZone(ZoneId.systemDefault()).toLocalDate();
else if (type == LocalDateTime.class)
return Instant.ofEpochMilli(cutoff).atZone(ZoneId.systemDefault()).toLocalDateTime();
else if (type == ZonedDateTime.class)
return Instant.ofEpochMilli(cutoff).atZone(ZoneId.systemDefault());
else if (type == OffsetDateTime.class)
return Instant.ofEpochMilli(cutoff).atZone(ZoneId.systemDefault()).toOffsetDateTime();
throw new IllegalArgumentException("Unsupported date type " + type.getName());
}
private static Class> inferPropertyType(Class> table, String property) throws TriggerableException {
try {
return table.getDeclaredField(property).getType();
}
catch (NoSuchFieldException | SecurityException e) {
throw new TriggerableException(e, "Could not get property {} of class {}", property, table.getName());
}
}
@Override
protected List> getAdditionalArgs() {
return Arrays.asList(ARG_PROPERTY_NAME, ARG_MAX_AGE_DAYS);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy