blob: 6cda61388c01296ea09b208c5ac8c8ddb77dcb38 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sling.event.impl.jobs.tasks;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.event.impl.jobs.JobImpl;
import org.apache.sling.event.impl.jobs.config.JobManagerConfiguration;
import org.apache.sling.event.jobs.Job;
import org.apache.sling.event.jobs.consumer.JobExecutionContext;
import org.apache.sling.event.jobs.consumer.JobExecutionResult;
import org.apache.sling.event.jobs.consumer.JobExecutor;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
/**
* Task to clean up the history,
* A clean up task can be configured with three properties:
* - age : only jobs older than this amount of minutes are removed (default is two days)
* - topic : only jobs with this topic are removed (default is no topic, meaning all jobs are removed)
* The value should either be a string or an array of string
* - state : only jobs in this state are removed (default is no state, meaning all jobs are removed)
* The value should either be a string or an array of string. Allowed values are:
* SUCCEEDED, STOPPED, GIVEN_UP, ERROR, DROPPED
*/
@Component(service = JobExecutor.class,
property = {
JobExecutor.PROPERTY_TOPICS + "=org/apache/sling/event/impl/jobs/tasks/HistoryCleanUpTask",
Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
})
public class HistoryCleanUpTask implements JobExecutor {
private static final String PROPERTY_AGE = "age";
private static final String PROPERTY_TOPIC = "topic";
private static final String PROPERTY_STATE = "state";
private static final int DEFAULT_AGE = 60 * 24 * 2; // older than two days
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Reference
private JobManagerConfiguration configuration;
@Override
public JobExecutionResult process(final Job job, final JobExecutionContext context) {
int age = job.getProperty(PROPERTY_AGE, DEFAULT_AGE);
if ( age < 1 ) {
age = DEFAULT_AGE;
}
final Calendar removeDate = Calendar.getInstance();
removeDate.add(Calendar.MINUTE, -age);
final String[] topics = job.getProperty(PROPERTY_TOPIC, String[].class);
final String[] states = job.getProperty(PROPERTY_STATE, String[].class);
final String logTopics = (topics == null ? "ALL" : Arrays.toString(topics));
final String logStates = (states == null ? "ALL" : Arrays.toString(states));
context.log("Cleaning up job history. Removing all jobs older than {0}, with topics {1} and states {2}",
removeDate, logTopics, logStates);
final List<String> stateList;
if ( states != null ) {
stateList = new ArrayList<>();
for(final String s : states) {
stateList.add(s);
}
} else {
stateList = null;
}
final ResourceResolver resolver = this.configuration.createResourceResolver();
try {
if ( stateList == null || stateList.contains(Job.JobState.SUCCEEDED.name()) ) {
this.cleanup(removeDate, resolver, context, configuration.getStoredSuccessfulJobsPath(), topics, null);
}
if ( stateList == null || stateList.contains(Job.JobState.DROPPED.name())
|| stateList.contains(Job.JobState.ERROR.name())
|| stateList.contains(Job.JobState.GIVEN_UP.name())
|| stateList.contains(Job.JobState.STOPPED.name())) {
this.cleanup(removeDate, resolver, context, configuration.getStoredCancelledJobsPath(), topics, stateList);
}
} catch (final PersistenceException pe) {
// in the case of an error, we just log this as a warning
this.logger.warn("Exception during job resource tree cleanup.", pe);
} finally {
resolver.close();
}
return context.result().succeeded();
}
static void cleanup(final Calendar removeDate,
final ResourceResolver resolver,
final JobExecutionContext context,
final String basePath,
final String[] topics,
final List<String> stateList)
throws PersistenceException {
final Resource baseResource = resolver.getResource(basePath);
// sanity check - should never be null
if ( baseResource != null ) {
final Iterator<Resource> topicIter = baseResource.listChildren();
while ( !context.isStopped() && topicIter.hasNext() ) {
final Resource topicResource = topicIter.next();
// check topic
boolean found = topics == null;
int index = 0;
while ( !found && index < topics.length ) {
if ( topicResource.getName().equals(topics[index]) ) {
found = true;
}
index++;
}
if ( !found ) {
continue;
}
final int removeYear = removeDate.get(Calendar.YEAR);
final int removeMonth = removeDate.get(Calendar.MONTH) + 1;
final int removeDay = removeDate.get(Calendar.DAY_OF_MONTH);
final int removeHour = removeDate.get(Calendar.HOUR_OF_DAY);
final int removeMinute = removeDate.get(Calendar.MINUTE);
// start with years
final Iterator<Resource> yearIter = topicResource.listChildren();
while ( !context.isStopped() && yearIter.hasNext() ) {
final Resource yearResource = yearIter.next();
final int year = Integer.valueOf(yearResource.getName());
if ( year > removeYear ) {
continue;
}
final boolean oldYear = year < removeYear;
// months
final Iterator<Resource> monthIter = yearResource.listChildren();
while ( !context.isStopped() && monthIter.hasNext() ) {
final Resource monthResource = monthIter.next();
final int month = Integer.valueOf(monthResource.getName());
if ( !oldYear && month > removeMonth) {
continue;
}
final boolean oldMonth = oldYear || month < removeMonth;
// days
final Iterator<Resource> dayIter = monthResource.listChildren();
while ( !context.isStopped() && dayIter.hasNext() ) {
final Resource dayResource = dayIter.next();
final int day = Integer.valueOf(dayResource.getName());
if ( !oldMonth && day > removeDay) {
continue;
}
final boolean oldDay = oldMonth || day < removeDay;
// hours
final Iterator<Resource> hourIter = dayResource.listChildren();
while ( !context.isStopped() && hourIter.hasNext() ) {
final Resource hourResource = hourIter.next();
final int hour = Integer.valueOf(hourResource.getName());
if ( !oldDay && hour > removeHour) {
continue;
}
final boolean oldHour = oldDay || hour < removeHour;
// minutes
final Iterator<Resource> minuteIter = hourResource.listChildren();
while ( !context.isStopped() && minuteIter.hasNext() ) {
final Resource minuteResource = minuteIter.next();
// check if we can delete the minute
final int minute = Integer.valueOf(minuteResource.getName());
final boolean oldMinute = oldHour || minute <= removeMinute;
if ( oldMinute ) {
final Iterator<Resource> jobIter = minuteResource.listChildren();
while ( !context.isStopped() && jobIter.hasNext() ) {
final Resource jobResource = jobIter.next();
boolean remove = stateList == null;
if ( !remove ) {
final ValueMap vm = ResourceUtil.getValueMap(jobResource);
final String state = vm.get(JobImpl.PROPERTY_FINISHED_STATE, String.class);
if ( state != null && stateList.contains(state) ) {
remove = true;
}
}
if ( remove ) {
resolver.delete(jobResource);
resolver.commit();
}
}
// check if we can delete the minute
if ( !context.isStopped() && !minuteResource.listChildren().hasNext()) {
resolver.delete(minuteResource);
resolver.commit();
}
}
}
// check if we can delete the hour
if ( !context.isStopped() && oldHour && !hourResource.listChildren().hasNext()) {
resolver.delete(hourResource);
resolver.commit();
}
}
// check if we can delete the day
if ( !context.isStopped() && oldDay && !dayResource.listChildren().hasNext()) {
resolver.delete(dayResource);
resolver.commit();
}
}
// check if we can delete the month
if ( !context.isStopped() && oldMonth && !monthResource.listChildren().hasNext() ) {
resolver.delete(monthResource);
resolver.commit();
}
}
// check if we can delete the year
if ( !context.isStopped() && oldYear && !yearResource.listChildren().hasNext() ) {
resolver.delete(yearResource);
resolver.commit();
}
}
}
}
}
}