blob: 7fdcb88507a2d3dce5e0e1e37dbc00db7c4422c7 [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 java.util.Calendar;
import java.util.Iterator;
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.event.impl.jobs.config.JobManagerConfiguration;
import org.apache.sling.event.impl.jobs.config.TopologyCapabilities;
import org.apache.sling.event.impl.jobs.scheduling.JobSchedulerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Maintenance task...
*
* In the default configuration, this task runs every minute
*/
public class CleanUpTask {
/** Logger. */
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/** Job manager configuration. */
private final JobManagerConfiguration configuration;
/** Job scheduler. */
private final JobSchedulerImpl jobScheduler;
/** We count the scheduler runs. */
private volatile long schedulerRuns;
/**
* Constructor
*/
public CleanUpTask(final JobManagerConfiguration config, final JobSchedulerImpl jobScheduler) {
this.configuration = config;
this.jobScheduler = jobScheduler;
}
/**
* One maintenance run
*/
public void run() {
this.schedulerRuns++;
logger.debug("Job manager maintenance: Starting #{}", this.schedulerRuns);
final TopologyCapabilities topologyCapabilities = configuration.getTopologyCapabilities();
if ( topologyCapabilities != null ) {
// Clean up
final String cleanUpUnassignedPath;;
if ( topologyCapabilities.isLeader() ) {
cleanUpUnassignedPath = this.configuration.getUnassignedJobsPath();
} else {
cleanUpUnassignedPath = null;
}
// job scheduler is handled every third run
if ( schedulerRuns % 3 == 1 ) {
this.jobScheduler.maintenance();
}
if ( schedulerRuns % 60 == 0 ) { // full clean up is done every hour
this.fullEmptyFolderCleanup(topologyCapabilities, this.configuration.getLocalJobsPath());
if ( cleanUpUnassignedPath != null ) {
this.fullEmptyFolderCleanup(topologyCapabilities, cleanUpUnassignedPath);
}
} else if ( schedulerRuns % 5 == 0 ) { // simple clean up every 5 minutes
this.simpleEmptyFolderCleanup(topologyCapabilities, this.configuration.getLocalJobsPath());
if ( cleanUpUnassignedPath != null ) {
this.simpleEmptyFolderCleanup(topologyCapabilities, cleanUpUnassignedPath);
}
}
}
logger.debug("Job manager maintenance: Finished #{}", this.schedulerRuns);
}
/**
* Simple empty folder removes empty folders for the last ten minutes
* starting five minutes ago.
* If folder for minute 59 is removed, we check the hour folder as well.
*/
private void simpleEmptyFolderCleanup(final TopologyCapabilities caps, final String basePath) {
this.logger.debug("Cleaning up job resource tree: looking for empty folders");
final ResourceResolver resolver = this.configuration.createResourceResolver();
try {
final Calendar cleanUpDate = Calendar.getInstance();
// go back five minutes
cleanUpDate.add(Calendar.MINUTE, -5);
final Resource baseResource = resolver.getResource(basePath);
// sanity check - should never be null
if ( baseResource != null ) {
final Iterator<Resource> topicIter = baseResource.listChildren();
while ( caps.isActive() && topicIter.hasNext() ) {
final Resource topicResource = topicIter.next();
for(int i = 0; i < 10; i++) {
if ( caps.isActive() ) {
final StringBuilder sb = new StringBuilder(topicResource.getPath());
sb.append('/');
sb.append(cleanUpDate.get(Calendar.YEAR));
sb.append('/');
sb.append(cleanUpDate.get(Calendar.MONTH) + 1);
sb.append('/');
sb.append(cleanUpDate.get(Calendar.DAY_OF_MONTH));
sb.append('/');
sb.append(cleanUpDate.get(Calendar.HOUR_OF_DAY));
sb.append('/');
sb.append(cleanUpDate.get(Calendar.MINUTE));
final String path = sb.toString();
final Resource dateResource = resolver.getResource(path);
if ( dateResource != null && !dateResource.listChildren().hasNext() ) {
resolver.delete(dateResource);
resolver.commit();
}
// check hour folder
if ( path.endsWith("59") ) {
final String hourPath = path.substring(0, path.length() - 3);
final Resource hourResource = resolver.getResource(hourPath);
if ( hourResource != null && !hourResource.listChildren().hasNext() ) {
resolver.delete(hourResource);
resolver.commit();
}
}
// go back another minute in time
cleanUpDate.add(Calendar.MINUTE, -1);
}
}
}
}
} 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();
}
}
/**
* Full cleanup - this scans all directories!
*/
private void fullEmptyFolderCleanup(final TopologyCapabilities caps, final String basePath) {
this.logger.debug("Cleaning up job resource tree: removing ALL empty folders");
final ResourceResolver resolver = this.configuration.createResourceResolver();
if ( resolver == null ) {
return;
}
try {
final Resource baseResource = resolver.getResource(basePath);
// sanity check - should never be null
if ( baseResource != null ) {
final Calendar now = Calendar.getInstance();
final int removeYear = now.get(Calendar.YEAR);
final int removeMonth = now.get(Calendar.MONTH) + 1;
final int removeDay = now.get(Calendar.DAY_OF_MONTH);
final int removeHour = now.get(Calendar.HOUR_OF_DAY);
final Iterator<Resource> topicIter = baseResource.listChildren();
while ( caps.isActive() && topicIter.hasNext() ) {
final Resource topicResource = topicIter.next();
// now years
final Iterator<Resource> yearIter = topicResource.listChildren();
while ( caps.isActive() && yearIter.hasNext() ) {
final Resource yearResource = yearIter.next();
final int year = Integer.valueOf(yearResource.getName());
// we should not have a year higher than "now", but we test anyway
if ( year > removeYear ) {
continue;
}
final boolean oldYear = year < removeYear;
// months
final Iterator<Resource> monthIter = yearResource.listChildren();
while ( caps.isActive() && 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 ( caps.isActive() && 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 ( caps.isActive() && hourIter.hasNext() ) {
final Resource hourResource = hourIter.next();
final int hour = Integer.valueOf(hourResource.getName());
if ( !oldDay && hour > removeHour ) {
continue;
}
final boolean oldHour = (oldDay && (oldMonth || removeHour > 0)) || hour < (removeHour -1);
// we only remove minutes if the hour is old
if ( oldHour ) {
final Iterator<Resource> minuteIter = hourResource.listChildren();
while ( caps.isActive() && minuteIter.hasNext() ) {
final Resource minuteResource = minuteIter.next();
// check if we can delete the minute
if ( !minuteResource.listChildren().hasNext() ) {
resolver.delete(minuteResource);
resolver.commit();
}
}
}
// check if we can delete the hour
if ( caps.isActive() && oldHour && !hourResource.listChildren().hasNext()) {
resolver.delete(hourResource);
resolver.commit();
}
}
// check if we can delete the day
if ( caps.isActive() && oldDay && !dayResource.listChildren().hasNext()) {
resolver.delete(dayResource);
resolver.commit();
}
}
// check if we can delete the month
if ( caps.isActive() && oldMonth && !monthResource.listChildren().hasNext() ) {
resolver.delete(monthResource);
resolver.commit();
}
}
// check if we can delete the year
if ( caps.isActive() && oldYear && !yearResource.listChildren().hasNext() ) {
resolver.delete(yearResource);
resolver.commit();
}
}
}
}
} 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();
}
}
}