package org.molgenis.data.elasticsearch.index.job; import org.molgenis.data.DataService; import org.molgenis.data.Entity; import org.molgenis.data.index.meta.IndexActionGroup; import org.molgenis.data.support.QueryImpl; import org.molgenis.security.core.runas.RunAsSystem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import java.util.Date; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.stream.Stream; import static java.time.OffsetDateTime.now; import static java.util.Date.from; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.counting; import static java.util.stream.Collectors.groupingBy; import static org.molgenis.data.elasticsearch.index.job.IndexJobExecutionMeta.INDEX_JOB_EXECUTION; import static org.molgenis.data.index.meta.IndexActionGroupMetaData.INDEX_ACTION_GROUP; import static org.molgenis.data.index.meta.IndexActionMetaData.*; import static org.molgenis.data.jobs.model.JobExecution.Status.SUCCESS; import static org.molgenis.data.jobs.model.JobExecutionMetaData.END_DATE; import static org.molgenis.data.jobs.model.JobExecutionMetaData.STATUS; import static org.molgenis.security.core.runas.RunAsSystemProxy.runAsSystem; public class IndexServiceImpl implements IndexService { private static final Logger LOG = LoggerFactory.getLogger(IndexServiceImpl.class); private final DataService dataService; private final IndexJobFactory indexJobFactory; private final IndexJobExecutionFactory indexJobExecutionFactory; /** * The {@link IndexJob}s are executed on this thread. */ private final ExecutorService executorService; private final IndexStatus indexStatus = new IndexStatus(); public IndexServiceImpl(DataService dataService, IndexJobFactory indexJobFactory, IndexJobExecutionFactory indexJobExecutionFactory, ExecutorService executorService) { this.dataService = requireNonNull(dataService); this.indexJobFactory = requireNonNull(indexJobFactory); this.indexJobExecutionFactory = requireNonNull(indexJobExecutionFactory); this.executorService = requireNonNull(executorService); } @Override @RunAsSystem public void rebuildIndex(String transactionId) { LOG.trace("Index transaction with id {}...", transactionId); IndexActionGroup indexActionGroup = dataService .findOneById(INDEX_ACTION_GROUP, transactionId, IndexActionGroup.class); if (indexActionGroup != null) { Stream<Entity> indexActions = dataService .findAll(INDEX_ACTION, new QueryImpl<>().eq(INDEX_ACTION_GROUP_ATTR, indexActionGroup)); Map<String, Long> numberOfActionsPerEntity = indexActions .collect(groupingBy(indexAction -> indexAction.getString(ENTITY_FULL_NAME), counting())); indexStatus.addActionCounts(numberOfActionsPerEntity); IndexJobExecution indexJobExecution = indexJobExecutionFactory.create(); indexJobExecution.setUser("admin"); indexJobExecution.setIndexActionJobID(transactionId); IndexJob job = indexJobFactory.createJob(indexJobExecution); CompletableFuture.runAsync(job::call, executorService) .whenComplete((a, b) -> indexStatus.removeActionCounts(numberOfActionsPerEntity)); } else { LOG.debug("No index job found for id [{}].", transactionId); } } @Override @RunAsSystem public void waitForAllIndicesStable() throws InterruptedException { indexStatus.waitForAllEntitiesToBeStable(); } @Override @RunAsSystem public void waitForIndexToBeStableIncludingReferences(String entityName) throws InterruptedException { indexStatus.waitForIndexToBeStableIncludingReferences(dataService.getEntityType(entityName)); } /** * Cleans up successful IndexJobExecutions that finished longer than five minutes ago. */ @Scheduled(fixedRate = 5 * 60 * 1000) public void cleanupJobExecutions() { runAsSystem(() -> { LOG.trace("Clean up Index job executions..."); Date fiveMinutesAgo = from(now().minusMinutes(5).toInstant()); boolean indexJobExecutionExists = dataService.hasRepository(INDEX_JOB_EXECUTION); if (indexJobExecutionExists) { Stream<Entity> executions = dataService.getRepository(INDEX_JOB_EXECUTION).query() .lt(END_DATE, fiveMinutesAgo).and().eq(STATUS, SUCCESS.toString()).findAll(); dataService.delete(INDEX_JOB_EXECUTION, executions); LOG.debug("Cleaned up Index job executions."); } else { LOG.warn(INDEX_JOB_EXECUTION + " does not exist"); } }); } }