KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > catalina > cluster > deploy > FarmWarDeployer


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

16
17 package org.apache.catalina.cluster.deploy;
18
19 import java.io.File JavaDoc;
20 import java.io.IOException JavaDoc;
21 import java.net.URL JavaDoc;
22 import java.util.HashMap JavaDoc;
23
24 import javax.management.MBeanServer JavaDoc;
25 import javax.management.ObjectName JavaDoc;
26
27 import org.apache.catalina.Context;
28 import org.apache.catalina.Engine;
29 import org.apache.catalina.Host;
30 import org.apache.catalina.Lifecycle;
31 import org.apache.catalina.LifecycleException;
32 import org.apache.catalina.cluster.CatalinaCluster;
33 import org.apache.catalina.cluster.ClusterDeployer;
34 import org.apache.catalina.cluster.ClusterMessage;
35 import org.apache.catalina.cluster.Member;
36 import org.apache.commons.modeler.Registry;
37
38 /**
39  * <p>
40  * A farm war deployer is a class that is able to deploy/undeploy web
41  * applications in WAR form within the cluster.
42  * </p>
43  * Any host can act as the admin, and will have three directories
44  * <ul>
45  * <li>deployDir - the directory where we watch for changes</li>
46  * <li>applicationDir - the directory where we install applications</li>
47  * <li>tempDir - a temporaryDirectory to store binary data when downloading a
48  * war from the cluster</li>
49  * </ul>
50  * Currently we only support deployment of WAR files since they are easier to
51  * send across the wire.
52  *
53  * @author Filip Hanik
54  * @author Peter Rossbach
55  * @version 1.1
56  */

57 public class FarmWarDeployer implements ClusterDeployer, FileChangeListener {
58     /*--Static Variables----------------------------------------*/
59     public static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
60             .getLog(FarmWarDeployer.class);
61
62     /*--Instance Variables--------------------------------------*/
63     protected CatalinaCluster cluster = null;
64
65     protected boolean started = false; //default 5 seconds
66

67     protected HashMap JavaDoc fileFactories = new HashMap JavaDoc();
68
69     protected String JavaDoc deployDir;
70
71     protected String JavaDoc tempDir;
72
73     protected String JavaDoc watchDir;
74
75     protected boolean watchEnabled = false;
76
77     protected WarWatcher watcher = null;
78
79     /**
80      * Iteration count for background processing.
81      */

82     private int count = 0;
83
84     /**
85      * Frequency of the Farm watchDir check. Cluster wide deployment will be
86      * done once for the specified amount of backgrondProcess calls (ie, the
87      * lower the amount, the most often the checks will occur).
88      */

89     protected int processDeployFrequency = 2;
90
91     /**
92      * Path where context descriptors should be deployed.
93      */

94     protected File JavaDoc configBase = null;
95
96     /**
97      * The associated host.
98      */

99     protected Host host = null;
100
101     /**
102      * The host appBase.
103      */

104     protected File JavaDoc appBase = null;
105
106     /**
107      * MBean server.
108      */

109     protected MBeanServer JavaDoc mBeanServer = null;
110
111     /**
112      * The associated deployer ObjectName.
113      */

114     protected ObjectName JavaDoc oname = null;
115
116     /*--Constructor---------------------------------------------*/
117     public FarmWarDeployer() {
118     }
119
120     /*--Logic---------------------------------------------------*/
121     public void start() throws Exception JavaDoc {
122         if (started)
123             return;
124         getCluster().addClusterListener(this);
125         if (watchEnabled) {
126             watcher = new WarWatcher(this, new File JavaDoc(getWatchDir()));
127             if (log.isInfoEnabled())
128                 log.info("Cluster deployment is watching " + getWatchDir()
129                         + " for changes.");
130         } //end if
131

132         // Check to correct engine and host setup
133
host = (Host) getCluster().getContainer();
134         Engine engine = (Engine) host.getParent();
135         try {
136             oname = new ObjectName JavaDoc(engine.getName() + ":type=Deployer,host="
137                     + host.getName());
138         } catch (Exception JavaDoc e) {
139             log.error("Can't construct MBean object name" + e);
140         }
141         configBase = new File JavaDoc(System.getProperty("catalina.base"), "conf");
142         if (engine != null) {
143             configBase = new File JavaDoc(configBase, engine.getName());
144         }
145         if (host != null) {
146             configBase = new File JavaDoc(configBase, host.getName());
147         }
148
149         // Retrieve the MBean server
150
mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
151
152         started = true;
153         count = 0;
154         if (log.isInfoEnabled())
155             log.info("Cluster FarmWarDeployer started.");
156     }
157
158     /*
159      * stop cluster wide deployments
160      *
161      * @see org.apache.catalina.cluster.ClusterDeployer#stop()
162      */

163     public void stop() throws LifecycleException {
164         started = false;
165         getCluster().removeClusterListener(this);
166         count = 0;
167         if (watcher != null) {
168             watcher.clear();
169             watcher = null;
170
171         }
172         if (log.isInfoEnabled())
173             log.info("Cluster FarmWarDeployer stopped.");
174     }
175
176     public void cleanDeployDir() {
177         throw new java.lang.UnsupportedOperationException JavaDoc(
178                 "Method cleanDeployDir() not yet implemented.");
179     }
180
181     /**
182      * Callback from the cluster, when a message is received, The cluster will
183      * broadcast it invoking the messageReceived on the receiver.
184      *
185      * @param msg
186      * ClusterMessage - the message received from the cluster
187      */

188     public void messageReceived(ClusterMessage msg) {
189         try {
190             if (msg instanceof FileMessage && msg != null) {
191                 FileMessage fmsg = (FileMessage) msg;
192                 if (log.isDebugEnabled())
193                     log.debug("receive cluster deployment [ path: "
194                             + fmsg.getContextPath() + " war: "
195                             + fmsg.getFileName() + " ]");
196                 FileMessageFactory factory = getFactory(fmsg);
197                 // TODO correct second try after app is in service!
198
if (factory.writeMessage(fmsg)) {
199                     //last message received war file is completed
200
String JavaDoc name = factory.getFile().getName();
201                     if (!name.endsWith(".war"))
202                         name = name + ".war";
203                     File JavaDoc deployable = new File JavaDoc(getDeployDir(), name);
204                     try {
205                         String JavaDoc path = fmsg.getContextPath();
206                         if (!isServiced(path)) {
207                             addServiced(path);
208                             try {
209                                 remove(path);
210                                 factory.getFile().renameTo(deployable);
211                                 check(path);
212                             } finally {
213                                 removeServiced(path);
214                             }
215                             if (log.isDebugEnabled())
216                                 log.debug("deployment from " + path
217                                         + " finished.");
218                         } else
219                             log.error("Application " + path
220                                     + " in used. touch war file " + name
221                                     + " again!");
222                     } catch (Exception JavaDoc ex) {
223                         log.error(ex);
224                     } finally {
225                         removeFactory(fmsg);
226                     }
227                 } //end if
228
} else if (msg instanceof UndeployMessage && msg != null) {
229                 try {
230                     UndeployMessage umsg = (UndeployMessage) msg;
231                     String JavaDoc path = umsg.getContextPath();
232                     if (log.isDebugEnabled())
233                         log.debug("receive cluster undeployment from " + path);
234                     if (!isServiced(path)) {
235                         addServiced(path);
236                         try {
237                             remove(path);
238                         } finally {
239                             removeServiced(path);
240                         }
241                         if (log.isDebugEnabled())
242                             log.debug("undeployment from " + path
243                                     + " finished.");
244                     } else
245                         log
246                                 .error("Application "
247                                         + path
248                                         + " in used. Sorry not remove from backup cluster nodes!");
249                 } catch (Exception JavaDoc ex) {
250                     log.error(ex);
251                 }
252             } //end if
253
} catch (java.io.IOException JavaDoc x) {
254             log.error("Unable to read farm deploy file message.", x);
255         }
256     }
257
258     /**
259      * create factory for all transported war files
260      *
261      * @param msg
262      * @return Factory for all app message (war files)
263      * @throws java.io.FileNotFoundException
264      * @throws java.io.IOException
265      */

266     public synchronized FileMessageFactory getFactory(FileMessage msg)
267             throws java.io.FileNotFoundException JavaDoc, java.io.IOException JavaDoc {
268         File JavaDoc tmpFile = new File JavaDoc(msg.getFileName());
269         File JavaDoc writeToFile = new File JavaDoc(getTempDir(), tmpFile.getName());
270         FileMessageFactory factory = (FileMessageFactory) fileFactories.get(msg
271                 .getFileName());
272         if (factory == null) {
273             factory = FileMessageFactory.getInstance(writeToFile, true);
274             fileFactories.put(msg.getFileName(), factory);
275         }
276         return factory;
277     }
278
279     /**
280      * Remove file (war) from messages)
281      *
282      * @param msg
283      */

284     public void removeFactory(FileMessage msg) {
285         fileFactories.remove(msg.getFileName());
286     }
287
288     /**
289      * Before the cluster invokes messageReceived the cluster will ask the
290      * receiver to accept or decline the message, In the future, when messages
291      * get big, the accept method will only take a message header
292      *
293      * @param msg
294      * ClusterMessage
295      * @return boolean - returns true to indicate that messageReceived should be
296      * invoked. If false is returned, the messageReceived method will
297      * not be invoked.
298      */

299     public boolean accept(ClusterMessage msg) {
300         return (msg instanceof FileMessage) || (msg instanceof UndeployMessage);
301     }
302
303     /**
304      * Install a new web application, whose web application archive is at the
305      * specified URL, into this container and all the other members of the
306      * cluster with the specified context path. A context path of "" (the empty
307      * string) should be used for the root application for this container.
308      * Otherwise, the context path must start with a slash.
309      * <p>
310      * If this application is successfully installed locally, a ContainerEvent
311      * of type <code>INSTALL_EVENT</code> will be sent to all registered
312      * listeners, with the newly created <code>Context</code> as an argument.
313      *
314      * @param contextPath
315      * The context path to which this application should be installed
316      * (must be unique)
317      * @param war
318      * A URL of type "jar:" that points to a WAR file, or type
319      * "file:" that points to an unpacked directory structure
320      * containing the web application to be installed
321      *
322      * @exception IllegalArgumentException
323      * if the specified context path is malformed (it must be ""
324      * or start with a slash)
325      * @exception IllegalStateException
326      * if the specified context path is already attached to an
327      * existing web application
328      * @exception IOException
329      * if an input/output error was encountered during
330      * installation
331      */

332     public void install(String JavaDoc contextPath, URL JavaDoc war) throws IOException JavaDoc {
333         Member[] members = getCluster().getMembers();
334         Member localMember = getCluster().getLocalMember();
335         FileMessageFactory factory = FileMessageFactory.getInstance(new File JavaDoc(
336                 war.getFile()), false);
337         FileMessage msg = new FileMessage(localMember, war.getFile(),
338                 contextPath);
339         if(log.isDebugEnabled())
340             log.debug("Send cluster war deployment [ path:"
341                     + contextPath + " war: " + war + " ] started.");
342         msg = factory.readMessage(msg);
343         while (msg != null) {
344             for (int i = 0; i < members.length; i++) {
345                 if (log.isDebugEnabled())
346                     log.debug("Send cluster war fragment [ path: "
347                             + contextPath + " war: " + war + " to: " + members[i] + " ]");
348                 getCluster().send(msg, members[i]);
349             } //for
350
msg = factory.readMessage(msg);
351         } //while
352
if(log.isDebugEnabled())
353             log.debug("Send cluster war deployment [ path: "
354                     + contextPath + " war: " + war + " ] finished.");
355     }
356
357     /**
358      * Remove an existing web application, attached to the specified context
359      * path. If this application is successfully removed, a ContainerEvent of
360      * type <code>REMOVE_EVENT</code> will be sent to all registered
361      * listeners, with the removed <code>Context</code> as an argument.
362      * Deletes the web application war file and/or directory if they exist in
363      * the Host's appBase.
364      *
365      * @param contextPath
366      * The context path of the application to be removed
367      * @param undeploy
368      * boolean flag to remove web application from server
369      *
370      * @exception IllegalArgumentException
371      * if the specified context path is malformed (it must be ""
372      * or start with a slash)
373      * @exception IllegalArgumentException
374      * if the specified context path does not identify a
375      * currently installed web application
376      * @exception IOException
377      * if an input/output error occurs during removal
378      */

379     public void remove(String JavaDoc contextPath, boolean undeploy) throws IOException JavaDoc {
380         if (log.isInfoEnabled())
381             log.info("Cluster wide remove of web app " + contextPath);
382         // send to other nodes
383
Member[] members = getCluster().getMembers();
384         Member localMember = getCluster().getLocalMember();
385         UndeployMessage msg = new UndeployMessage(localMember, System
386                 .currentTimeMillis(), "Undeploy:" + contextPath + ":"
387                 + System.currentTimeMillis(), contextPath, undeploy);
388         if (log.isDebugEnabled())
389             log.debug("Send cluster wide undeployment from "
390                     + contextPath );
391         cluster.send(msg);
392         // remove locally
393
if (undeploy) {
394             try {
395                 if (!isServiced(contextPath)) {
396                     addServiced(contextPath);
397                     try {
398                         remove(contextPath);
399                     } finally {
400                         removeServiced(contextPath);
401                     }
402                 } else
403                     log.error("Local remove from " + contextPath
404                             + "failed, other manager has app in service!");
405
406             } catch (Exception JavaDoc ex) {
407                 log.error("local remove from " + contextPath + " failed", ex);
408             }
409         }
410
411     }
412
413     /*
414      * Modifcation from watchDir war detected!
415      *
416      * @see org.apache.catalina.cluster.deploy.FileChangeListener#fileModified(java.io.File)
417      */

418     public void fileModified(File JavaDoc newWar) {
419         try {
420             File JavaDoc deployWar = new File JavaDoc(getDeployDir(), newWar.getName());
421             copy(newWar, deployWar);
422             String JavaDoc contextName = "/"
423                     + deployWar.getName().substring(0,
424                             deployWar.getName().lastIndexOf(".war"));
425             if (log.isInfoEnabled())
426                 log.info("Installing webapp[" + contextName + "] from "
427                         + deployWar.getAbsolutePath());
428             try {
429                 remove(contextName, false);
430             } catch (Exception JavaDoc x) {
431                 log.error("No removal", x);
432             }
433             install(contextName, deployWar.toURL());
434         } catch (Exception JavaDoc x) {
435             log.error("Unable to install WAR file", x);
436         }
437     }
438
439     /*
440      * War remvoe from watchDir
441      *
442      * @see org.apache.catalina.cluster.deploy.FileChangeListener#fileRemoved(java.io.File)
443      */

444     public void fileRemoved(File JavaDoc removeWar) {
445         try {
446             String JavaDoc contextName = "/"
447                     + removeWar.getName().substring(0,
448                             removeWar.getName().lastIndexOf(".war"));
449             if (log.isInfoEnabled())
450                 log.info("Removing webapp[" + contextName + "]");
451             remove(contextName, true);
452         } catch (Exception JavaDoc x) {
453             log.error("Unable to remove WAR file", x);
454         }
455     }
456
457     /**
458      * Given a context path, get the config file name.
459      */

460     protected String JavaDoc getConfigFile(String JavaDoc path) {
461         String JavaDoc basename = null;
462         if (path.equals("")) {
463             basename = "ROOT";
464         } else {
465             basename = path.substring(1).replace('/', '#');
466         }
467         return (basename);
468     }
469
470     /**
471      * Given a context path, get the config file name.
472      */

473     protected String JavaDoc getDocBase(String JavaDoc path) {
474         String JavaDoc basename = null;
475         if (path.equals("")) {
476             basename = "ROOT";
477         } else {
478             basename = path.substring(1);
479         }
480         return (basename);
481     }
482
483     /**
484      * Return a File object representing the "application root" directory for
485      * our associated Host.
486      */

487     protected File JavaDoc getAppBase() {
488
489         if (appBase != null) {
490             return appBase;
491         }
492
493         File JavaDoc file = new File JavaDoc(host.getAppBase());
494         if (!file.isAbsolute())
495             file = new File JavaDoc(System.getProperty("catalina.base"), host
496                     .getAppBase());
497         try {
498             appBase = file.getCanonicalFile();
499         } catch (IOException JavaDoc e) {
500             appBase = file;
501         }
502         return (appBase);
503
504     }
505
506     /**
507      * Invoke the remove method on the deployer.
508      */

509     protected void remove(String JavaDoc path) throws Exception JavaDoc {
510         // TODO Handle remove also work dir content !
511
// Stop the context first to be nicer
512
Context context = (Context) host.findChild(path);
513         if (context != null) {
514             if(log.isDebugEnabled())
515                 log.debug("Undeploy local context " +path );
516             ((Lifecycle) context).stop();
517             File JavaDoc war = new File JavaDoc(getAppBase(), getDocBase(path) + ".war");
518             File JavaDoc dir = new File JavaDoc(getAppBase(), getDocBase(path));
519             File JavaDoc xml = new File JavaDoc(configBase, getConfigFile(path) + ".xml");
520             if (war.exists()) {
521                 war.delete();
522             } else if (dir.exists()) {
523                 undeployDir(dir);
524             } else {
525                 xml.delete();
526             }
527             // Perform new deployment and remove internal HostConfig state
528
check(path);
529         }
530
531     }
532
533     /**
534      * Delete the specified directory, including all of its contents and
535      * subdirectories recursively.
536      *
537      * @param dir
538      * File object representing the directory to be deleted
539      */

540     protected void undeployDir(File JavaDoc dir) {
541
542         String JavaDoc files[] = dir.list();
543         if (files == null) {
544             files = new String JavaDoc[0];
545         }
546         for (int i = 0; i < files.length; i++) {
547             File JavaDoc file = new File JavaDoc(dir, files[i]);
548             if (file.isDirectory()) {
549                 undeployDir(file);
550             } else {
551                 file.delete();
552             }
553         }
554         dir.delete();
555
556     }
557
558     /*
559      * Call watcher to check for deploy changes
560      *
561      * @see org.apache.catalina.cluster.ClusterDeployer#backgroundProcess()
562      */

563     public void backgroundProcess() {
564         if (started) {
565             count = (count + 1) % processDeployFrequency;
566             if (count == 0 && watchEnabled) {
567                 watcher.check();
568             }
569         }
570
571     }
572
573     /*--Deployer Operations ------------------------------------*/
574
575     /**
576      * Invoke the check method on the deployer.
577      */

578     protected void check(String JavaDoc name) throws Exception JavaDoc {
579         String JavaDoc[] params = { name };
580         String JavaDoc[] signature = { "java.lang.String" };
581         mBeanServer.invoke(oname, "check", params, signature);
582     }
583
584     /**
585      * Invoke the check method on the deployer.
586      */

587     protected boolean isServiced(String JavaDoc name) throws Exception JavaDoc {
588         String JavaDoc[] params = { name };
589         String JavaDoc[] signature = { "java.lang.String" };
590         Boolean JavaDoc result = (Boolean JavaDoc) mBeanServer.invoke(oname, "isServiced",
591                 params, signature);
592         return result.booleanValue();
593     }
594
595     /**
596      * Invoke the check method on the deployer.
597      */

598     protected void addServiced(String JavaDoc name) throws Exception JavaDoc {
599         String JavaDoc[] params = { name };
600         String JavaDoc[] signature = { "java.lang.String" };
601         mBeanServer.invoke(oname, "addServiced", params, signature);
602     }
603
604     /**
605      * Invoke the check method on the deployer.
606      */

607     protected void removeServiced(String JavaDoc name) throws Exception JavaDoc {
608         String JavaDoc[] params = { name };
609         String JavaDoc[] signature = { "java.lang.String" };
610         mBeanServer.invoke(oname, "removeServiced", params, signature);
611     }
612
613     /*--Instance Getters/Setters--------------------------------*/
614     public CatalinaCluster getCluster() {
615         return cluster;
616     }
617
618     public void setCluster(CatalinaCluster cluster) {
619         this.cluster = cluster;
620     }
621
622     public boolean equals(Object JavaDoc listener) {
623         return super.equals(listener);
624     }
625
626     public int hashCode() {
627         return super.hashCode();
628     }
629
630     public String JavaDoc getDeployDir() {
631         return deployDir;
632     }
633
634     public void setDeployDir(String JavaDoc deployDir) {
635         this.deployDir = deployDir;
636     }
637
638     public String JavaDoc getTempDir() {
639         return tempDir;
640     }
641
642     public void setTempDir(String JavaDoc tempDir) {
643         this.tempDir = tempDir;
644     }
645
646     public String JavaDoc getWatchDir() {
647         return watchDir;
648     }
649
650     public void setWatchDir(String JavaDoc watchDir) {
651         this.watchDir = watchDir;
652     }
653
654     public boolean isWatchEnabled() {
655         return watchEnabled;
656     }
657
658     public boolean getWatchEnabled() {
659         return watchEnabled;
660     }
661
662     public void setWatchEnabled(boolean watchEnabled) {
663         this.watchEnabled = watchEnabled;
664     }
665
666     /**
667      * Return the frequency of watcher checks.
668      */

669     public int getProcessDeployFrequency() {
670
671         return (this.processDeployFrequency);
672
673     }
674
675     /**
676      * Set the watcher checks frequency.
677      *
678      * @param processDeployFrequency
679      * the new manager checks frequency
680      */

681     public void setProcessDeployFrequency(int processExpiresFrequency) {
682
683         if (processExpiresFrequency <= 0) {
684             return;
685         }
686         this.processDeployFrequency = processExpiresFrequency;
687     }
688
689     /**
690      * Copy a file to the specified temp directory. This is required only
691      * because Jasper depends on it.
692      */

693     protected boolean copy(File JavaDoc from, File JavaDoc to) {
694         try {
695             if (!to.exists())
696                 to.createNewFile();
697             java.io.FileInputStream JavaDoc is = new java.io.FileInputStream JavaDoc(from);
698             java.io.FileOutputStream JavaDoc os = new java.io.FileOutputStream JavaDoc(to,
699                     false);
700             byte[] buf = new byte[4096];
701             while (true) {
702                 int len = is.read(buf);
703                 if (len < 0)
704                     break;
705                 os.write(buf, 0, len);
706             }
707             is.close();
708             os.close();
709         } catch (IOException JavaDoc e) {
710             log.error("Unable to copy file from:" + from + " to:" + to, e);
711             return false;
712         }
713         return true;
714     }
715
716 }
Popular Tags