KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > geronimo > deployment > hot > DirectoryMonitor


1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.geronimo.deployment.hot;
18
19 import org.apache.commons.logging.LogFactory;
20 import org.apache.commons.logging.Log;
21 import org.apache.geronimo.deployment.cli.DeployUtils;
22 import org.apache.geronimo.kernel.repository.Artifact;
23 import org.apache.geronimo.kernel.config.IOUtil;
24
25 import java.io.File JavaDoc;
26 import java.io.Serializable JavaDoc;
27 import java.io.IOException JavaDoc;
28 import java.util.Map JavaDoc;
29 import java.util.HashMap JavaDoc;
30 import java.util.HashSet JavaDoc;
31 import java.util.Iterator JavaDoc;
32 import java.util.List JavaDoc;
33 import java.util.LinkedList JavaDoc;
34 import java.util.Set JavaDoc;
35
36 /**
37  * Meant to be run as a Thread that tracks the contents of a directory.
38  * It sends notifications for changes to its immediate children (it
39  * will look into subdirs for changes, but will not send notifications
40  * for files within subdirectories). If a file continues to change on
41  * every pass, this will wait until it stabilizes before sending an
42  * add or update notification (to handle slow uploads, etc.).
43  *
44  * @version $Rev: 483713 $ $Date: 2006-12-07 17:45:45 -0500 (Thu, 07 Dec 2006) $
45  */

46 public class DirectoryMonitor implements Runnable JavaDoc {
47     private static final Log log = LogFactory.getLog(DirectoryMonitor.class);
48
49     public static interface Listener {
50         /**
51          * The directory monitor doesn't take any action unless this method
52          * returns true (to avoid deploying before the deploy GBeans are
53          * running, etc.).
54          */

55         boolean isServerRunning();
56
57         /**
58          * Called during initialization on all files in the hot deploy
59          * directory.
60          *
61          * @return true if the file in question is already available in the
62          * server, false if it should be deployed on the next pass.
63          */

64         boolean isFileDeployed(File JavaDoc file, String JavaDoc configId);
65
66         /**
67          * Called during initialization on previously deployed files.
68          *
69          * @return The time that the file was deployed. If the current
70          * version in the directory is newer, the file will be
71          * updated on the first pass.
72          */

73         long getDeploymentTime(File JavaDoc file, String JavaDoc configId);
74
75         /**
76          * Called to indicate that the monitor has fully initialized
77          * and will be doing normal deployment operations from now on.
78          */

79         void started();
80
81         /**
82          * Called to check whether a file passes the smell test before
83          * attempting to deploy it.
84          *
85          * @return true if there's nothing obviously wrong with this file.
86          * false if there is (for example, it's clearly not
87          * deployable).
88          */

89         boolean validateFile(File JavaDoc file, String JavaDoc configId);
90
91         /**
92          * @return A configId for the deployment if the addition was processed
93          * successfully (or an empty String if the addition was OK but
94          * the configId could not be determined). null if the addition
95          * failed, in which case the file will be added again next time
96          * it changes.
97          */

98         String JavaDoc fileAdded(File JavaDoc file);
99
100         /**
101          * @return true if the removal was processed successfully. If not
102          * the file will be removed again on the next pass.
103          */

104         boolean fileRemoved(File JavaDoc file, String JavaDoc configId);
105
106         String JavaDoc fileUpdated(File JavaDoc file, String JavaDoc configId);
107
108         /**
109          * This method returns the module id of an application deployed in the default group.
110          * @return String respresenting the ModuleId if the application is already deployed
111          */

112         String JavaDoc getModuleId(String JavaDoc config);
113
114     }
115
116     private int pollIntervalMillis;
117     private File JavaDoc directory;
118     private boolean done = false;
119     private Listener listener; // a little cheesy, but do we really need multiple listeners?
120
private final Map JavaDoc files = new HashMap JavaDoc();
121     private volatile String JavaDoc workingOnConfigId;
122
123     public DirectoryMonitor(File JavaDoc directory, Listener listener, int pollIntervalMillis) {
124         this.directory = directory;
125         this.listener = listener;
126         this.pollIntervalMillis = pollIntervalMillis;
127     }
128
129     public int getPollIntervalMillis() {
130         return pollIntervalMillis;
131     }
132
133     public void setPollIntervalMillis(int pollIntervalMillis) {
134         this.pollIntervalMillis = pollIntervalMillis;
135     }
136
137     public Listener getListener() {
138         return listener;
139     }
140
141     public void setListener(Listener listener) {
142         this.listener = listener;
143     }
144
145     public File JavaDoc getDirectory() {
146         return directory;
147     }
148
149     /**
150      * Warning: changing the directory at runtime will cause all files in the
151      * old directory to be removed and all files in the new directory to be
152      * added, next time the thread awakens.
153      */

154     public void setDirectory(File JavaDoc directory) {
155         if (!directory.isDirectory() || !directory.canRead()) {
156             throw new IllegalArgumentException JavaDoc("Cannot monitor directory " + directory.getAbsolutePath());
157         }
158         this.directory = directory;
159     }
160
161     public synchronized boolean isDone() {
162         return done;
163     }
164
165     public synchronized void close() {
166         this.done = true;
167     }
168
169     public void removeModuleId(Artifact id) {
170         log.info("Hot deployer notified that an artifact was removed: "+id);
171         if(id.toString().equals(workingOnConfigId)) {
172             // since the redeploy process inserts a new thread to handle progress,
173
// this is called by a different thread than the hot deploy thread during
174
// a redeploy, and this check must be executed outside the synchronized
175
// block or else it will cause a deadlock!
176
return; // don't react to events we generated ourselves
177
}
178         synchronized(files) {
179             for (Iterator JavaDoc it = files.keySet().iterator(); it.hasNext();) {
180                 String JavaDoc path = (String JavaDoc) it.next();
181                 FileInfo info = (FileInfo) files.get(path);
182                 Artifact target = Artifact.create(info.getConfigId());
183                 if(id.matches(target)) { // need to remove record & delete file
184
File JavaDoc file = new File JavaDoc(path);
185                     if(file.exists()) { // if not, probably it's deletion kicked off this whole process
186
log.info("Hot deployer deleting "+id);
187                         if(!IOUtil.recursiveDelete(file)) {
188                             log.error("Hot deployer unable to delete "+path);
189                         }
190                         it.remove();
191                     }
192                 }
193             }
194         }
195     }
196
197     public void run() {
198         boolean serverStarted = false, initialized = false;
199         while (!done) {
200             try {
201                 Thread.sleep(pollIntervalMillis);
202             } catch (InterruptedException JavaDoc e) {
203                 continue;
204             }
205             try {
206                 if (listener != null) {
207                     if (!serverStarted && listener.isServerRunning()) {
208                         serverStarted = true;
209                     }
210                     if (serverStarted) {
211                         if (!initialized) {
212                             initialized = true;
213                             initialize();
214                             listener.started();
215                         } else {
216                             scanDirectory();
217                         }
218                     }
219                 }
220             } catch (Exception JavaDoc e) {
221                 log.error("Error during hot deployment", e);
222             }
223         }
224     }
225
226     public void initialize() {
227         File JavaDoc parent = directory;
228         File JavaDoc[] children = parent.listFiles();
229         for (int i = 0; i < children.length; i++) {
230             File JavaDoc child = children[i];
231             if (!child.canRead()) {
232                 continue;
233             }
234             FileInfo now = child.isDirectory() ? getDirectoryInfo(child) : getFileInfo(child);
235             now.setChanging(false);
236             try {
237                 now.setConfigId(calculateModuleId(child));
238                 if (listener == null || listener.isFileDeployed(child, now.getConfigId())) {
239                     if (listener != null) {
240                         now.setModified(listener.getDeploymentTime(child, now.getConfigId()));
241                     }
242 log.info("At startup, found "+now.getPath()+" with deploy time "+now.getModified()+" and file time "+new File JavaDoc(now.getPath()).lastModified());
243                     files.put(now.getPath(), now);
244                 }
245             } catch (Exception JavaDoc e) {
246                 log.error("Unable to scan file " + child.getAbsolutePath() + " during initialization", e);
247             }
248         }
249     }
250
251     /**
252      * Looks for changes to the immediate contents of the directory we're watching.
253      */

254     private void scanDirectory() {
255         File JavaDoc parent = directory;
256         File JavaDoc[] children = parent.listFiles();
257         if (!directory.exists() || children == null) {
258             log.error("Hot deploy directory has disappeared! Shutting down directory monitor.");
259             done = true;
260             return;
261         }
262         synchronized (files) {
263             Set JavaDoc oldList = new HashSet JavaDoc(files.keySet());
264             List JavaDoc actions = new LinkedList JavaDoc();
265             for (int i = 0; i < children.length; i++) {
266                 File JavaDoc child = children[i];
267                 if (!child.canRead()) {
268                     continue;
269                 }
270                 FileInfo now = child.isDirectory() ? getDirectoryInfo(child) : getFileInfo(child);
271                 FileInfo then = (FileInfo) files.get(now.getPath());
272                 if (then == null) { // Brand new, wait a bit to make sure it's not still changing
273
now.setNewFile(true);
274                     files.put(now.getPath(), now);
275                     log.debug("New File: " + now.getPath());
276                 } else {
277                     oldList.remove(then.getPath());
278                     if (now.isSame(then)) { // File is the same as the last time we scanned it
279
if (then.isChanging()) {
280                             log.debug("File finished changing: " + now.getPath());
281                             // Used to be changing, now in (hopefully) its final state
282
if (then.isNewFile()) {
283                                 actions.add(new FileAction(FileAction.NEW_FILE, child, then));
284                             } else {
285                                 actions.add(new FileAction(FileAction.UPDATED_FILE, child, then));
286                             }
287                             then.setChanging(false);
288                         } // else it's just totally unchanged and we ignore it this pass
289
} else if(then.isNewFile() || now.getModified() > then.getModified()) {
290                         // The two records are different -- record the latest as a file that's changing
291
// and later when it stops changing we'll do the add or update as appropriate.
292
now.setConfigId(then.getConfigId());
293                         now.setNewFile(then.isNewFile());
294                         files.put(now.getPath(), now);
295                         log.debug("File Changed: " + now.getPath());
296                     }
297                 }
298             }
299             // Look for any files we used to know about but didn't find in this pass
300
for (Iterator JavaDoc it = oldList.iterator(); it.hasNext();) {
301                 String JavaDoc name = (String JavaDoc) it.next();
302                 FileInfo info = (FileInfo) files.get(name);
303                 log.debug("File removed: " + name);
304                 if (info.isNewFile()) { // Was never added, just whack it
305
files.remove(name);
306                 } else {
307                     actions.add(new FileAction(FileAction.REMOVED_FILE, new File JavaDoc(name), info));
308                 }
309             }
310             if (listener != null) {
311                 // First pass: validate all changed files, so any obvious errors come out first
312
for (Iterator JavaDoc it = actions.iterator(); it.hasNext();) {
313                     FileAction action = (FileAction) it.next();
314                     if (!listener.validateFile(action.child, action.info.getConfigId())) {
315                         resolveFile(action);
316                         it.remove();
317                     }
318                 }
319                 // Second pass: do what we're meant to do
320
for (Iterator JavaDoc it = actions.iterator(); it.hasNext();) {
321                     FileAction action = (FileAction) it.next();
322                     try {
323                         if (action.action == FileAction.REMOVED_FILE) {
324                             workingOnConfigId = action.info.getConfigId();
325                             if (listener.fileRemoved(action.child, action.info.getConfigId())) {
326                                 files.remove(action.child.getPath());
327                             }
328                             workingOnConfigId = null;
329                         } else if (action.action == FileAction.NEW_FILE) {
330                             String JavaDoc result = listener.fileAdded(action.child);
331                             if (result != null) {
332                                 if (!result.equals("")) {
333                                     action.info.setConfigId(result);
334                                 } else {
335                                     action.info.setConfigId(calculateModuleId(action.child));
336                                 }
337                                 action.info.setNewFile(false);
338                             }
339                         } else if (action.action == FileAction.UPDATED_FILE) {
340                             workingOnConfigId = action.info.getConfigId();
341                             String JavaDoc result = listener.fileUpdated(action.child, action.info.getConfigId());
342                             FileInfo update = action.info;
343                             if (result != null) {
344                                 if (!result.equals("")) {
345                                     update.setConfigId(result);
346                                 } else {
347                                     update.setConfigId(calculateModuleId(action.child));
348                                 }
349                             }
350                             workingOnConfigId = null;
351                         }
352                     } catch (Exception JavaDoc e) {
353                         log.error("Unable to " + action.getActionName() + " file " + action.child.getAbsolutePath(), e);
354                     } finally {
355                         resolveFile(action);
356                     }
357                 }
358             }
359         }
360     }
361
362     private void resolveFile(FileAction action) {
363         if (action.action == FileAction.REMOVED_FILE) {
364             files.remove(action.child.getPath());
365         } else {
366             action.info.setChanging(false);
367         }
368     }
369
370     private String JavaDoc calculateModuleId(File JavaDoc module) {
371         String JavaDoc moduleId = null;
372         try {
373             moduleId = DeployUtils.extractModuleIdFromArchive(module);
374         } catch (Exception JavaDoc e) {
375             try {
376                 moduleId = DeployUtils.extractModuleIdFromPlan(module);
377             } catch (IOException JavaDoc e2) {
378                 log.warn("Unable to calculate module ID for file " + module.getAbsolutePath() + " [" + e2.getMessage() + "]");
379             }
380         }
381         if (moduleId == null) {
382             int pos = module.getName().lastIndexOf('.');
383             moduleId = pos > -1 ? module.getName().substring(0, pos) : module.getName();
384             moduleId = listener.getModuleId(moduleId);
385         }
386         return moduleId;
387     }
388
389     /**
390      * We don't pay attention to the size of the directory or files in the
391      * directory, only the highest last modified time of anything in the
392      * directory. Hopefully this is good enough.
393      */

394     private FileInfo getDirectoryInfo(File JavaDoc dir) {
395         FileInfo info = new FileInfo(dir.getAbsolutePath());
396         info.setSize(0);
397         info.setModified(getLastModifiedInDir(dir));
398         return info;
399     }
400
401     private long getLastModifiedInDir(File JavaDoc dir) {
402         long value = dir.lastModified();
403         File JavaDoc[] children = dir.listFiles();
404         long test;
405         for (int i = 0; i < children.length; i++) {
406             File JavaDoc child = children[i];
407             if (!child.canRead()) {
408                 continue;
409             }
410             if (child.isDirectory()) {
411                 test = getLastModifiedInDir(child);
412             } else {
413                 test = child.lastModified();
414             }
415             if (test > value) {
416                 value = test;
417             }
418         }
419         return value;
420     }
421
422     private FileInfo getFileInfo(File JavaDoc child) {
423         FileInfo info = new FileInfo(child.getAbsolutePath());
424         info.setSize(child.length());
425         info.setModified(child.lastModified());
426         return info;
427     }
428
429     private static class FileAction {
430         private static int NEW_FILE = 1;
431         private static int UPDATED_FILE = 2;
432         private static int REMOVED_FILE = 3;
433         private int action;
434         private File JavaDoc child;
435         private FileInfo info;
436
437         public FileAction(int action, File JavaDoc child, FileInfo info) {
438             this.action = action;
439             this.child = child;
440             this.info = info;
441         }
442
443         public String JavaDoc getActionName() {
444             return action == NEW_FILE ? "deploy" : action == UPDATED_FILE ? "redeploy" : "undeploy";
445         }
446     }
447
448     private static class FileInfo implements Serializable JavaDoc {
449         private String JavaDoc path;
450         private long size;
451         private long modified;
452         private boolean newFile;
453         private boolean changing;
454         private String JavaDoc configId;
455
456         public FileInfo(String JavaDoc path) {
457             this.path = path;
458             newFile = false;
459             changing = true;
460         }
461
462         public String JavaDoc getPath() {
463             return path;
464         }
465
466         public long getSize() {
467             return size;
468         }
469
470         public void setSize(long size) {
471             this.size = size;
472         }
473
474         public long getModified() {
475             return modified;
476         }
477
478         public void setModified(long modified) {
479             this.modified = modified;
480         }
481
482         public boolean isNewFile() {
483             return newFile;
484         }
485
486         public void setNewFile(boolean newFile) {
487             this.newFile = newFile;
488         }
489
490         public boolean isChanging() {
491             return changing;
492         }
493
494         public void setChanging(boolean changing) {
495             this.changing = changing;
496         }
497
498         public String JavaDoc getConfigId() {
499             return configId;
500         }
501
502         public void setConfigId(String JavaDoc configId) {
503             this.configId = configId;
504         }
505
506         public boolean isSame(FileInfo info) {
507             if (!path.equals(info.path)) {
508                 throw new IllegalArgumentException JavaDoc("Should only be used to compare two files representing the same path!");
509             }
510             return size == info.size && modified == info.modified;
511         }
512     }
513 }
514
Popular Tags