001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */package org.apache.openejb.util;
017:
018: import java.io.File;
019: import java.io.Serializable;
020: import java.util.HashMap;
021: import java.util.HashSet;
022: import java.util.Map;
023:
024: /**
025: * @version $Rev: 602704 $ $Date: 2007-12-09 09:58:22 -0800 $
026: */
027: public class DirectoryMonitor {
028:
029: public static final Logger logger = Logger.getInstance(
030: LogCategory.OPENEJB_DEPLOY, DirectoryMonitor.class
031: .getPackage().getName());
032:
033: private boolean run;
034:
035: private int pollIntervalMillis;
036:
037: private File directory;
038:
039: private Listener listener;
040:
041: private Map files = new HashMap();
042:
043: public DirectoryMonitor(final File directory,
044: final Listener listener, final int pollIntervalMillis,
045: final Logger logger) {
046: assert listener == null : "No listener specified";
047: assert directory.isDirectory() : "File specified is not a directory. "
048: + directory.getAbsolutePath();
049: assert directory.canRead() : "Directory specified cannot be read. "
050: + directory.getAbsolutePath();
051: assert pollIntervalMillis > 0 : "Poll Interval must be above zero.";
052:
053: this .directory = directory;
054: this .listener = listener;
055: this .pollIntervalMillis = pollIntervalMillis;
056: }
057:
058: public Logger getLogger() {
059: return logger;
060: }
061:
062: public int getPollIntervalMillis() {
063: return pollIntervalMillis;
064: }
065:
066: public File getDirectory() {
067: return directory;
068: }
069:
070: public Listener getListener() {
071: return listener;
072: }
073:
074: public synchronized boolean isRunning() {
075: return run;
076: }
077:
078: public synchronized void stop() {
079: this .run = false;
080: }
081:
082: public void run() {
083: run = true;
084: initialize();
085:
086: getLogger().debug(
087: "Scanner running. Polling every " + pollIntervalMillis
088: + " milliseconds.");
089:
090: while (run) {
091: try {
092: scanDirectory();
093: } catch (Exception e) {
094: getLogger().error("Scan failed.", e);
095: }
096:
097: try {
098: Thread.sleep(pollIntervalMillis);
099: } catch (InterruptedException ignore) {
100: // empty
101: }
102: }
103: }
104:
105: public void initialize() {
106: getLogger().debug(
107: "Doing initial scan of " + directory.getAbsolutePath());
108:
109: File parent = directory;
110: File[] children = parent.listFiles();
111:
112: for (int i = 0; children != null && i < children.length; i++) {
113: File child = children[i];
114:
115: if (!child.canRead()) {
116: continue;
117: }
118:
119: FileInfo now = newInfo(child);
120: now.setChanging(false);
121: }
122: }
123:
124: private FileInfo newInfo(File child) {
125: FileInfo fileInfo = child.isDirectory() ? new DirectoryInfo(
126: child) : new FileInfo(child);
127: files.put(fileInfo.getPath(), fileInfo);
128: return fileInfo;
129: }
130:
131: /**
132: * Looks for changes to the immediate contents of the directory we're watching.
133: */
134: public void scanDirectory() {
135: File parent = directory;
136: File[] children = parent.listFiles();
137:
138: HashSet<String> missingFilesList = new HashSet(files.keySet());
139:
140: for (int i = 0; children != null && i < children.length; i++) {
141: File child = children[i];
142:
143: missingFilesList.remove(child.getAbsolutePath());
144:
145: if (!child.canRead()) {
146: getLogger().debug("not readable " + child.getName());
147: continue;
148: }
149:
150: FileInfo oldStatus = oldInfo(child);
151: FileInfo newStatus = newInfo(child);
152:
153: newStatus.diff(oldStatus);
154:
155: if (oldStatus == null) {
156: // Brand new, but assume it's changing and
157: // wait a bit to make sure it's not still changing
158: getLogger().debug("File Discovered: " + newStatus);
159: } else if (newStatus.isChanging()) {
160: // The two records are different -- record the latest as a file that's changing
161: // and later when it stops changing we'll do the add or update as appropriate.
162: getLogger().debug("File Changing: " + newStatus);
163: } else if (oldStatus.isNewFile()) {
164: // Used to be changing, now in (hopefully) its final state
165: getLogger().info("New File: " + newStatus);
166: newStatus.setNewFile(!listener.fileAdded(child));
167: } else if (oldStatus.isChanging()) {
168: getLogger().info("Updated File: " + newStatus);
169: listener.fileUpdated(child);
170:
171: missingFilesList.remove(oldStatus.getPath());
172: }
173: // else it's just totally unchanged and we ignore it this pass
174: }
175:
176: // Look for any files we used to know about but didn't find in this pass
177: for (String path : missingFilesList) {
178: getLogger().info("File removed: " + path);
179:
180: if (listener.fileRemoved(new File(path))) {
181: files.remove(path);
182: }
183: }
184: }
185:
186: private FileInfo oldInfo(File file) {
187: return (FileInfo) files.get(file.getAbsolutePath());
188: }
189:
190: /**
191: * Allows custom behavior to be hooked up to process file state changes.
192: */
193: public interface Listener {
194: boolean fileAdded(File file);
195:
196: boolean fileRemoved(File file);
197:
198: void fileUpdated(File file);
199: }
200:
201: /**
202: * Provides details about a directory.
203: */
204: private static class DirectoryInfo extends FileInfo {
205: public DirectoryInfo(final File dir) {
206: //
207: // We don't pay attention to the size of the directory or files in the
208: // directory, only the highest last modified time of anything in the
209: // directory. Hopefully this is good enough.
210: //
211: super (dir.getAbsolutePath(), 0, getLastModifiedInDir(dir));
212: }
213:
214: private static long getLastModifiedInDir(final File dir) {
215: assert dir != null;
216:
217: long value = dir.lastModified();
218: File[] children = dir.listFiles();
219: long test;
220:
221: for (int i = 0; i < children.length; i++) {
222: File child = children[i];
223:
224: if (!child.canRead()) {
225: continue;
226: }
227:
228: if (child.isDirectory()) {
229: test = getLastModifiedInDir(child);
230: } else {
231: test = child.lastModified();
232: }
233:
234: if (test > value) {
235: value = test;
236: }
237: }
238:
239: return value;
240: }
241: }
242:
243: /**
244: * Provides details about a file.
245: */
246: private static class FileInfo implements Serializable {
247: private String path;
248:
249: private long size;
250:
251: private long modified;
252:
253: private boolean newFile;
254:
255: private boolean changing;
256:
257: public FileInfo(final File file) {
258: this (file.getAbsolutePath(), file.length(), file
259: .lastModified());
260: }
261:
262: public FileInfo(final String path, final long size,
263: final long modified) {
264: assert path != null;
265:
266: this .path = path;
267: this .size = size;
268: this .modified = modified;
269: this .newFile = true;
270: this .changing = true;
271: }
272:
273: public String getPath() {
274: return path;
275: }
276:
277: public long getSize() {
278: return size;
279: }
280:
281: public void setSize(final long size) {
282: this .size = size;
283: }
284:
285: public long getModified() {
286: return modified;
287: }
288:
289: public void setModified(final long modified) {
290: this .modified = modified;
291: }
292:
293: public boolean isNewFile() {
294: return newFile;
295: }
296:
297: public void setNewFile(final boolean newFile) {
298: this .newFile = newFile;
299: }
300:
301: public boolean isChanging() {
302: return changing;
303: }
304:
305: public void setChanging(final boolean changing) {
306: this .changing = changing;
307: }
308:
309: public boolean isSame(final FileInfo info) {
310: assert info != null;
311:
312: if (!path.equals(info.path)) {
313: throw new IllegalArgumentException(
314: "Should only be used to compare two files representing the same path!");
315: }
316:
317: return size == info.size && modified == info.modified;
318: }
319:
320: public String toString() {
321: return path;
322: }
323:
324: public void diff(final FileInfo old) {
325: if (old != null) {
326: this.changing = !isSame(old);
327: this.newFile = old.newFile;
328: }
329: }
330: }
331: }
|