001: //========================================================================
002: //$Id: Scanner.java 1182 2006-11-10 15:58:01Z janb $
003: //Copyright 2000-2004 Mort Bay Consulting Pty. Ltd.
004: //------------------------------------------------------------------------
005: //Licensed under the Apache License, Version 2.0 (the "License");
006: //you may not use this file except in compliance with the License.
007: //You may obtain a copy of the License at
008: //http://www.apache.org/licenses/LICENSE-2.0
009: //Unless required by applicable law or agreed to in writing, software
010: //distributed under the License is distributed on an "AS IS" BASIS,
011: //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: //See the License for the specific language governing permissions and
013: //limitations under the License.
014: //========================================================================
015:
016: package org.mortbay.jetty.plugin.util;
017:
018: import java.io.File;
019: import java.io.IOException;
020: import java.util.ArrayList;
021: import java.util.Collections;
022: import java.util.Date;
023: import java.util.HashMap;
024: import java.util.HashSet;
025: import java.util.Iterator;
026: import java.util.LinkedHashMap;
027: import java.util.List;
028: import java.util.Map;
029: import java.util.Set;
030:
031: /**
032: * Scanner
033: *
034: * Scans a list of files and directories on a periodic basis to detect changes.
035: * If a change in any of the watched files is found, then the target LifeCycle
036: * objects are stopped and restarted.
037: *
038: * This is used by the Jetty Maven plugin to watch the classes, dependencies
039: * and web.xml file of a web application and to restart the webapp if any
040: * of the above changes.
041: *
042: * @author janb
043: *
044: */
045: public class Scanner extends Thread {
046:
047: private int scanInterval;
048:
049: private List roots;
050:
051: private Map scanInfo = Collections.EMPTY_MAP;
052:
053: private List listeners;
054:
055: public interface Listener {
056: public void changesDetected(Scanner scanner, List changes);
057: }
058:
059: public Scanner() {
060: setDaemon(true);
061: }
062:
063: /**
064: * The files and directory roots to watch. Directories will be
065: * recursively scanned.
066: *
067: * @return Returns the roots.
068: */
069: public List getRoots() {
070: return this .roots;
071: }
072:
073: /**
074: * @param roots The roots to set.
075: */
076: public void setRoots(List roots) {
077: this .roots = roots;
078: //do an initializing scan
079: scanInfo = scan();
080: }
081:
082: /**
083: *
084: * @return Returns the scanInterval.
085: */
086: public int getScanInterval() {
087: return this .scanInterval;
088: }
089:
090: /**
091: *
092: * @param scanInterval The scanInterval in seconds to set.
093: */
094: public void setScanInterval(int scanInterval) {
095: this .scanInterval = scanInterval;
096: }
097:
098: /**
099: * List of Scanner.Listener implementations.
100: * @return Returns the listeners.
101: */
102: public List getListeners() {
103: return this .listeners;
104: }
105:
106: /**
107: * @param listeners The listeners to set.
108: */
109: public void setListeners(List listeners) {
110: this .listeners = listeners;
111: }
112:
113: /**
114: * Loop every scanInterval seconds until interrupted, checking to see if
115: * any of the watched files have changed. If they have, stop and restart
116: * the LifeCycle targets.
117: *
118: * @see java.lang.Runnable#run()
119: */
120: public void run() {
121: // set the sleep interval
122: long sleepMillis = getScanInterval() * 1000L;
123: boolean running = true;
124: while (running) {
125: try {
126: //wake up and scan the files
127: Thread.sleep(sleepMillis);
128: Map latestScanInfo = scan();
129:
130: List filesWithDifferences = getDifferences(
131: latestScanInfo, scanInfo);
132:
133: if (!filesWithDifferences.isEmpty()) {
134: if ((getListeners() != null)
135: && (!getListeners().isEmpty())) {
136: try {
137: PluginLog.getLog().debug(
138: "Calling scanner listeners ...");
139:
140: for (int i = 0; i < getListeners().size(); i++)
141: ((Scanner.Listener) getListeners().get(
142: i)).changesDetected(this ,
143: filesWithDifferences);
144:
145: PluginLog.getLog().debug(
146: "Listeners completed.");
147: } catch (Exception e) {
148: PluginLog.getLog().warn(
149: "Error doing stop/start", e);
150: }
151: }
152: }
153: scanInfo = latestScanInfo;
154: } catch (InterruptedException e) {
155: running = false;
156: }
157: }
158: }
159:
160: /**
161: * Scan the files and directories.
162: *
163: * @return
164: */
165: private Map scan() {
166: PluginLog.getLog().debug("Scanning ...");
167: List roots = getRoots();
168: if ((roots == null) || (roots.isEmpty()))
169: return Collections.EMPTY_MAP;
170:
171: LinkedHashMap scanInfoMap = new LinkedHashMap();
172: Iterator itor = roots.iterator();
173: while (itor.hasNext()) {
174: File f = (File) itor.next();
175: scan(f, scanInfoMap);
176: }
177:
178: if (PluginLog.getLog().isDebugEnabled()) {
179: itor = scanInfo.entrySet().iterator();
180: while (itor.hasNext()) {
181: Map.Entry e = (Map.Entry) itor.next();
182: PluginLog.getLog().debug(
183: "Scanned " + e.getKey() + " : " + e.getValue());
184: }
185: }
186:
187: PluginLog.getLog().debug(
188: "Scan complete at " + new Date().toString());
189: return scanInfoMap;
190: }
191:
192: /**
193: * Scan the file, or recurse into it if it is a directory.
194: * @param f
195: * @param scanInfoMap
196: */
197: private void scan(File f, Map scanInfoMap) {
198: try {
199: if (!f.exists()) {
200: return;
201: }
202:
203: if (f.isFile()) {
204: String name = f.getCanonicalPath();
205: long lastModified = f.lastModified();
206: scanInfoMap.put(name, new Long(lastModified));
207: } else if (f.isDirectory()) {
208: File[] files = f.listFiles();
209: for (int i = 0; i < files.length; i++)
210: scan(files[i], scanInfoMap);
211: } else
212: PluginLog.getLog().error(
213: "Skipping file of unacceptable type: "
214: + f.getName());
215: } catch (IOException e) {
216: PluginLog.getLog().error("Error scanning watched files", e);
217: }
218: }
219:
220: private List getDifferences(Map newScan, Map oldScan) {
221: ArrayList fileNames = new ArrayList();
222: Set oldScanKeys = new HashSet(oldScan.keySet());
223: Iterator itor = newScan.entrySet().iterator();
224: while (itor.hasNext()) {
225: Map.Entry entry = (Map.Entry) itor.next();
226: if (!oldScanKeys.contains(entry.getKey())) {
227: PluginLog.getLog().debug(
228: "File added: " + entry.getKey());
229: fileNames.add(entry.getKey());
230: } else if (!oldScan.get(entry.getKey()).equals(
231: entry.getValue())) {
232: PluginLog.getLog().debug(
233: "File changed: " + entry.getKey());
234: fileNames.add(entry.getKey());
235: oldScanKeys.remove(entry.getKey());
236: } else
237: oldScanKeys.remove(entry.getKey());
238: }
239:
240: if (!oldScanKeys.isEmpty()) {
241: fileNames.addAll(oldScanKeys);
242: if (PluginLog.getLog().isDebugEnabled()) {
243: Iterator keyItor = oldScanKeys.iterator();
244: while (keyItor.hasNext()) {
245: PluginLog.getLog().debug(
246: "File removed: " + keyItor.next());
247: }
248:
249: }
250: }
251:
252: return fileNames;
253: }
254:
255: }
|