001: /**
002: * EasyBeans
003: * Copyright (C) 2007 Bull S.A.S.
004: * Contact: easybeans@ow2.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019: * USA
020: *
021: * --------------------------------------------------------------------------
022: * $Id: DirectoryDeployerMonitor.java 1970 2007-10-16 11:49:25Z benoitf $
023: * --------------------------------------------------------------------------
024: */package org.ow2.easybeans.server;
025:
026: import static org.ow2.easybeans.util.url.URLUtils.urlToFile;
027:
028: import java.io.File;
029: import java.io.FilenameFilter;
030: import java.net.URL;
031: import java.util.ArrayList;
032: import java.util.List;
033: import java.util.Map;
034: import java.util.Set;
035: import java.util.WeakHashMap;
036: import java.util.concurrent.ConcurrentHashMap;
037:
038: import org.ow2.easybeans.api.EZBContainer;
039: import org.ow2.easybeans.api.EZBContainerException;
040: import org.ow2.easybeans.deployable.DeployerFactory;
041: import org.ow2.util.ee.deploy.api.archive.ArchiveException;
042: import org.ow2.util.ee.deploy.api.archive.IArchive;
043: import org.ow2.util.ee.deploy.api.deployable.EJB3Deployable;
044: import org.ow2.util.ee.deploy.api.deployable.IDeployable;
045: import org.ow2.util.ee.deploy.api.deployer.DeployerException;
046: import org.ow2.util.ee.deploy.api.deployer.IDeployer;
047: import org.ow2.util.ee.deploy.impl.archive.ArchiveManager;
048: import org.ow2.util.ee.deploy.impl.helper.DeployableHelper;
049: import org.ow2.util.ee.deploy.impl.helper.DeployableHelperException;
050: import org.ow2.util.log.Log;
051: import org.ow2.util.log.LogFactory;
052:
053: /**
054: * This class monitors scan the directory and deploy archive if any. Also,
055: * archives are monitored. If there is a change, the archive is reloaded or
056: * undeployed and then deployed.
057: * @author Florent Benoit
058: */
059: public class DirectoryDeployerMonitor extends ContainersMonitor {
060:
061: /**
062: * Sleep time for the thread of the cleaner (5s).
063: */
064: private static final int SLEEP_TIME = 5000;
065:
066: /**
067: * Logger.
068: */
069: private Log logger = LogFactory
070: .getLog(DirectoryDeployerMonitor.class);
071:
072: /**
073: * Map between an File (monitored) and the last updated file.
074: */
075: private Map<File, Long> modifiedFiles = null;
076:
077: /**
078: * List of deployed files (by this monitor).
079: */
080: private Map<File, IDeployable> deployed = null;
081:
082: /**
083: * List of File that have a failed deployment (by this monitor).
084: */
085: private List<File> failed = null;
086:
087: /**
088: * Initializing period ?.
089: */
090: private boolean bootInProgress = false;
091:
092: /**
093: * Stop order received ?
094: */
095: private boolean stopped = false;
096:
097: /**
098: * Deployer.
099: */
100: private IDeployer deployer;
101:
102: /**
103: * Builds a new monitor by initializing lists.
104: * @param embedded the embedded server which is monitored.
105: * @throws EmbeddedException if there is an exception for this monitor.
106: */
107: public DirectoryDeployerMonitor(final Embedded embedded)
108: throws EmbeddedException {
109: super (embedded);
110: this .modifiedFiles = new WeakHashMap<File, Long>();
111: this .deployed = new ConcurrentHashMap<File, IDeployable>();
112: this .failed = new ArrayList<File>();
113:
114: // Set the name of this thread
115: setName(this .getClass().getSimpleName());
116:
117: // Get the deployer.
118: try {
119: deployer = DeployerFactory.getDeployer(embedded);
120: } catch (DeployerException e) {
121: throw new EmbeddedException("Cannot get a deployer", e);
122: }
123:
124: }
125:
126: /**
127: * Init containers (call at startup).
128: */
129: @Override
130: public void init() {
131: // Start the boot process.
132: bootInProgress = true;
133:
134: // Initialize containers
135: try {
136: detectNewArchives();
137: } catch (EmbeddedException e) {
138: logger.error("Cannot scan for new archives", e);
139: }
140:
141: // No more in the boot process.
142: bootInProgress = false;
143: }
144:
145: /**
146: * Start the thread of this class It will clean all the work entries.
147: */
148: @Override
149: public void run() {
150:
151: for (;;) {
152: // Stop the thread
153: if (stopped) {
154: return;
155: }
156:
157: // Check if existing EJB3 container have not been modified ?
158: for (EZBContainer container : getEmbedded().getContainers()
159: .values()) {
160: if (container.isAvailable()) {
161: checkContainer(container);
162: }
163: }
164:
165: // Check new archives/containers to start
166: try {
167: detectNewArchives();
168: } catch (Exception e) { // Catch all exception (including runtime)
169: logger
170: .error(
171: "Problem when trying to find and deploy new archives",
172: e);
173: }
174:
175: // Undeploy/ReDeploy archives for deployed modules
176: try {
177: checkModifiedDeployables();
178: } catch (Exception e) { // Catch all exception (including runtime)
179: logger.error(
180: "Problem when checking current deployables", e);
181: }
182:
183: // Thread wait
184: try {
185: Thread.sleep(SLEEP_TIME);
186: } catch (InterruptedException e) {
187: throw new RuntimeException("Thread fail to sleep");
188: }
189: }
190: }
191:
192: /**
193: * Filter to skip system file.
194: */
195: private static final FilenameFilter ARCHIVE_NAME_FILTER = new FilenameFilter() {
196:
197: public boolean accept(final File dir, final String name) {
198: if (name.startsWith(".")) {
199: return false;
200: }
201: return true;
202: }
203:
204: };
205:
206: /**
207: * Scan all files present in the deploy directory and deploy them. (if not
208: * deployed).
209: * @throws EmbeddedException if there is a problem during the scan
210: */
211: private void detectNewArchives() throws EmbeddedException {
212:
213: // Get the list of deploy directories
214: List<File> deployDirectories = getEmbedded().getServerConfig()
215: .getDeployDirectories();
216:
217: for (File deployDirectory : deployDirectories) {
218: // get files
219: File[] files = deployDirectory
220: .listFiles(ARCHIVE_NAME_FILTER);
221:
222: // next directory if there are no files to scan.
223: if (files == null) {
224: continue;
225: }
226:
227: // analyze each file to detect new modules that are not yet deployed.
228: for (File f : files) {
229: // already deployed ?
230: if (deployed.containsKey(f)) {
231: // yes, then check other files
232: continue;
233: }
234:
235: // This module has failed previously ?
236: if (failed.contains(f)) {
237: // If the module hasn't been updated, no need to deploy it again as it will fails again
238: if (!hasBeenUpdated(f)) {
239: continue;
240: }
241: // Cleanup the previous failure and try again the deployment
242: failed.remove(f);
243: }
244:
245: // Else, get the deployable
246: IArchive archive = ArchiveManager.getInstance()
247: .getArchive(f);
248: if (archive == null) {
249: logger.warn("Ignoring invalid file ''{0}''", f);
250: continue;
251: }
252: IDeployable deployable;
253: try {
254: deployable = DeployableHelper
255: .getDeployable(archive);
256: } catch (DeployableHelperException e) {
257: throw new EmbeddedException(
258: "Cannot get a deployable for the archive '"
259: + archive + "'", e);
260: }
261: if (!bootInProgress) {
262: // wait that files are fully copied before deploying the files
263: try {
264: Thread.sleep(SLEEP_TIME);
265: } catch (InterruptedException e) {
266: throw new RuntimeException(
267: "Thread fail to sleep");
268: }
269: }
270: logger
271: .debug(
272: "Detect a new Deployable ''{0}'' and deploying it.",
273: deployable);
274:
275: // Now, deploy the file
276: try {
277: deployer.deploy(deployable);
278: } catch (DeployerException e) {
279: // Deployment of this deployable has failed
280: failed.add(f);
281: throw new EmbeddedException(
282: "Cannot deploy the deployable '"
283: + deployable + "'", e);
284: } catch (RuntimeException e) {
285: // Runtime exception but deploiement has failed
286: failed.add(f);
287: throw new EmbeddedException(
288: "RuntimeException when deploying the deployable '"
289: + deployable + "'", e);
290: }
291:
292: // deployed is ok
293: deployed.put(f, deployable);
294: }
295: }
296: }
297:
298: /**
299: * Check if the given file has been updated since the last check.
300: * @param file the file to test
301: * @return true if the archive has been updated
302: */
303: protected boolean hasBeenUpdated(final File file) {
304:
305: // get lastmodified for this URL
306: long previousLastModified = 0;
307: Long l = modifiedFiles.get(file);
308: if (l != null) {
309: previousLastModified = l.longValue();
310: }
311:
312: long updatedModified = getLastModified(file);
313:
314: // first check. nothing to do
315: if (previousLastModified == 0) {
316: // Store initial time
317: modifiedFiles.put(file, Long.valueOf(updatedModified));
318: return false;
319: }
320: // URL has been updated since the last time
321: if (updatedModified > previousLastModified) {
322: modifiedFiles.put(file, Long.valueOf(updatedModified));
323: return true;
324: }
325:
326: return false;
327: }
328:
329: /**
330: * Check if the current deployables that are deployed have been updated.
331: * If it is the case, undeploy them and then deploy it again (except for EJB3 Deployable where there is a stop/start).
332: * @throws EmbeddedException if the redeployment fails
333: */
334: protected void checkModifiedDeployables() throws EmbeddedException {
335: // Get list of files that are deployed
336: Set<File> files = deployed.keySet();
337:
338: // Nothing to do if no modules are deployed.
339: if (files == null) {
340: return;
341: }
342:
343: // For each deployed module that is not an EJB3, check if the module has been updated
344: for (File f : files) {
345: IDeployable deployable = deployed.get(f);
346:
347: // Not yet deployed ?
348: if (deployable == null) {
349: continue;
350: }
351:
352: // EJB3 are managed in a different way
353: if (deployable instanceof EJB3Deployable) {
354: continue;
355: }
356:
357: // File has been removed
358: if (!f.exists()) {
359: // undeploy
360: logger
361: .info(
362: "Deployable ''{0}'' has been removed on the filesystem, undeploy it",
363: deployable);
364: try {
365: deployer.undeploy(deployable);
366: } catch (DeployerException e) {
367: logger.error("Undeploy of the deployable '"
368: + deployable + "' has failed", e);
369: failed.add(f);
370: } finally {
371: // even in error case, the file should have been removed
372: deployed.remove(f);
373: }
374: continue;
375: }
376:
377: // Update has been detected, need to undeploy and then to deploy again
378: if (hasBeenUpdated(f)) {
379: logger
380: .info(
381: "Deployable ''{0}'' has been updated, reloading it",
382: deployable);
383: try {
384: deployer.undeploy(deployable);
385: } catch (DeployerException e) {
386: logger.error("Undeploy of the deployable '"
387: + deployable + "' has failed", e);
388: // Deployment has failed, it is now undeployed
389: deployed.remove(f);
390: failed.add(f);
391: }
392:
393: // Get a new deployable
394: IArchive archive = ArchiveManager.getInstance()
395: .getArchive(f);
396: if (archive == null) {
397: logger.warn("Ignoring invalid file ''{0}''", f);
398: continue;
399: }
400: IDeployable newDeployable;
401: try {
402: newDeployable = DeployableHelper
403: .getDeployable(archive);
404: } catch (DeployableHelperException e) {
405: logger.error(
406: "Cannot get a deployable for the archive '"
407: + archive + "'", e);
408: continue;
409: }
410: try {
411: deployer.deploy(newDeployable);
412: } catch (DeployerException e) {
413: // Deployment of this deployable has failed
414: failed.add(f);
415: throw new EmbeddedException(
416: "Cannot redeploy the deployable '"
417: + deployable + "'.", e);
418: }
419: }
420:
421: }
422:
423: }
424:
425: /**
426: * Check a container (and its archive) and see if there is a need to reload
427: * the container.
428: * @param container the container to monitor.
429: */
430: @Override
431: protected void checkContainer(final EZBContainer container) {
432:
433: // get archive
434: IArchive archive = container.getArchive();
435:
436: // Get URL
437: URL url = null;
438: try {
439: url = archive.getURL();
440: } catch (ArchiveException e1) {
441: logger.warn("Cannot get URL on the container {0}", archive
442: .getName());
443: return;
444: }
445: File file = urlToFile(url);
446: // No file archive, means that it has been removed
447: if (!file.exists()) {
448: logger
449: .info(
450: "Archive ''{0}'' has been removed, then the associated EJB3 container is stopping",
451: archive.getName());
452: try {
453: container.stop();
454: getEmbedded().removeContainer(container);
455: } finally {
456: deployed.remove(file);
457: }
458:
459: return;
460: }
461:
462: // container was modified, need to relaunch it
463: if (hasBeenUpdated(file)) {
464: logger
465: .info(
466: "Container with archive {0} was modified. Reloading...",
467: archive.getName());
468: try {
469: container.stop();
470: getEmbedded().removeContainer(container);
471: } finally {
472: deployed.remove(file);
473: }
474: try {
475: container.start();
476: getEmbedded().addContainer(container);
477: } catch (EZBContainerException e) {
478: deployed.remove(file);
479: logger.error("Error while restarting archive {0}.",
480: archive.getName(), e);
481: }
482: }
483:
484: }
485:
486: /**
487: * Receives a stop order.
488: */
489: @Override
490: public void stopOrder() {
491: this .stopped = true;
492: }
493: }
|