/* * Copyright 2001-2009 Terracotta, Inc. * * Licensed 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.quartz.jobs; import java.io.File; import java.io.FileFilter; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.PersistJobDataAfterExecution; import org.quartz.SchedulerContext; import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Inspects a directory and compares whether any files' "last modified dates" * have changed since the last time it was inspected. If one or more files * have been updated (or created), the job invokes a "call-back" method on an * identified <code>DirectoryScanListener</code> that can be found in the * <code>SchedulerContext</code>. * * @author pl47ypus * @author jhouse * @see org.quartz.jobs.DirectoryScanListener * @see org.quartz.SchedulerContext */ @DisallowConcurrentExecution @PersistJobDataAfterExecution public class DirectoryScanJob implements Job { /** * <code>JobDataMap</code> key with which to specify the directory to be * monitored - an absolute path is recommended. */ public static final String DIRECTORY_NAME = "DIRECTORY_NAME"; /** * <code>JobDataMap</code> key with which to specify the * {@link org.quartz.jobs.DirectoryScanListener} to be * notified when the directory contents change. */ public static final String DIRECTORY_SCAN_LISTENER_NAME = "DIRECTORY_SCAN_LISTENER_NAME"; /** * <code>JobDataMap</code> key with which to specify a <code>long</code> * value that represents the minimum number of milliseconds that must have * past since the file's last modified time in order to consider the file * new/altered. This is necessary because another process may still be * in the middle of writing to the file when the scan occurs, and the * file may therefore not yet be ready for processing. * * <p>If this parameter is not specified, a default value of * <code>5000</code> (five seconds) will be used.</p> */ public static final String MINIMUM_UPDATE_AGE = "MINIMUM_UPDATE_AGE"; private static final String LAST_MODIFIED_TIME = "LAST_MODIFIED_TIME"; private final Logger log = LoggerFactory.getLogger(getClass()); public DirectoryScanJob() { } /** * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) */ public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap mergedJobDataMap = context.getMergedJobDataMap(); SchedulerContext schedCtxt = null; try { schedCtxt = context.getScheduler().getContext(); } catch (SchedulerException e) { throw new JobExecutionException("Error obtaining scheduler context.", e, false); } String dirName = mergedJobDataMap.getString(DIRECTORY_NAME); String listenerName = mergedJobDataMap.getString(DIRECTORY_SCAN_LISTENER_NAME); if(dirName == null) { throw new JobExecutionException("Required parameter '" + DIRECTORY_NAME + "' not found in merged JobDataMap"); } if(listenerName == null) { throw new JobExecutionException("Required parameter '" + DIRECTORY_SCAN_LISTENER_NAME + "' not found in merged JobDataMap"); } DirectoryScanListener listener = (DirectoryScanListener)schedCtxt.get(listenerName); if(listener == null) { throw new JobExecutionException("DirectoryScanListener named '" + listenerName + "' not found in SchedulerContext"); } long lastDate = -1; if(mergedJobDataMap.containsKey(LAST_MODIFIED_TIME)) { lastDate = mergedJobDataMap.getLong(LAST_MODIFIED_TIME); } long minAge = 5000; if(mergedJobDataMap.containsKey(MINIMUM_UPDATE_AGE)) { minAge = mergedJobDataMap.getLong(MINIMUM_UPDATE_AGE); } long maxAgeDate = System.currentTimeMillis() - minAge; File[] updatedFiles = getUpdatedOrNewFiles(dirName, lastDate, maxAgeDate); if(updatedFiles == null) { log.warn("Directory '"+dirName+"' does not exist."); return; } long latestMod = lastDate; for(File updFile: updatedFiles) { long lm = updFile.lastModified(); latestMod = (lm > latestMod) ? lm : latestMod; } if(updatedFiles.length > 0) { // notify call back... log.info("Directory '"+dirName+"' contents updated, notifying listener."); listener.filesUpdatedOrAdded(updatedFiles); } else if (log.isDebugEnabled()) { log.debug("Directory '"+dirName+"' contents unchanged."); } // It is the JobDataMap on the JobDetail which is actually stateful context.getJobDetail().getJobDataMap().put(LAST_MODIFIED_TIME, latestMod); } protected File[] getUpdatedOrNewFiles(String dirName, final long lastDate, final long maxAgeDate) { File dir = new File(dirName); if(!dir.exists() || !dir.isDirectory()) { return null; } File[] files = dir.listFiles(new FileFilter() { public boolean accept(File pathname) { if(pathname.lastModified() > lastDate && pathname.lastModified() < maxAgeDate) return true; return false; }}); if(files == null) files = new File[0]; return files; } }