All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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