001: /*
002: * Copyright 1999,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.catalina.cluster.deploy;
018:
019: import org.apache.catalina.cluster.ClusterDeployer;
020: import org.apache.catalina.cluster.ClusterMessage;
021: import org.apache.catalina.cluster.CatalinaCluster;
022: import org.apache.catalina.LifecycleException;
023: import org.apache.catalina.Deployer;
024: import java.io.File;
025: import java.net.URL;
026: import java.io.IOException;
027: import org.apache.catalina.cluster.Member;
028: import java.util.HashMap;
029:
030: /**
031: * <p>
032: * A farm war deployer is a class that is able to
033: * deploy/undeploy web applications in WAR form
034: * within the cluster.</p>
035: * Any host can act as the admin, and will have three directories
036: * <ul>
037: * <li> deployDir - the directory where we watch for changes</li>
038: * <li> applicationDir - the directory where we install applications</li>
039: * <li> tempDir - a temporaryDirectory to store binary data when downloading a war
040: * from the cluster </li>
041: * </ul>
042: * Currently we only support deployment of WAR files since they are easier to send
043: * across the wire.
044: *
045: * @author Filip Hanik
046: * @version 1.0
047: */
048: public class FarmWarDeployer implements ClusterDeployer,
049: FileChangeListener {
050: /*--Static Variables----------------------------------------*/
051: public static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
052: .getLog(FarmWarDeployer.class);
053: /*--Instance Variables--------------------------------------*/
054: protected CatalinaCluster cluster = null;
055: protected Deployer deployer = null;
056: protected boolean started = false; //default 5 seconds
057: protected HashMap fileFactories = new HashMap();
058: protected String deployDir;
059: protected String tempDir;
060: protected String watchDir;
061: protected boolean watchEnabled = false;
062: protected WarWatcher watcher = null;
063:
064: /*--Constructor---------------------------------------------*/
065: public FarmWarDeployer() {
066: }
067:
068: /*--Logic---------------------------------------------------*/
069: public void start() throws Exception {
070: if (started)
071: return;
072: getCluster().addClusterListener(this );
073: if (watchEnabled) {
074: watcher = new WarWatcher(this , new File(getWatchDir()),
075: (long) 5000);
076: Thread t = new Thread(watcher);
077: t.start();
078: log.info("Cluster deployment is watching " + getWatchDir()
079: + " for changes.");
080: } //end if
081: started = true;
082: log.info("Cluster FarmWarDeployer started.");
083: }
084:
085: public void stop() throws LifecycleException {
086: started = false;
087: getCluster().removeClusterListener(this );
088: if (watcher != null)
089: watcher.stop();
090: log.info("Cluster FarmWarDeployer stopped.");
091: }
092:
093: public void cleanDeployDir() {
094: throw new java.lang.UnsupportedOperationException(
095: "Method cleanDeployDir() not yet implemented.");
096: }
097:
098: /**
099: * Callback from the cluster, when a message is received,
100: * The cluster will broadcast it invoking the messageReceived
101: * on the receiver.
102: * @param msg ClusterMessage - the message received from the cluster
103: */
104: public void messageReceived(ClusterMessage msg) {
105: try {
106: if (msg instanceof FileMessage && msg != null) {
107: FileMessage fmsg = (FileMessage) msg;
108: FileMessageFactory factory = getFactory(fmsg);
109: if (factory.writeMessage(fmsg)) {
110: //last message received
111: String name = factory.getFile().getName();
112: if (!name.endsWith(".war"))
113: name = name + ".war";
114: File deployable = new File(getDeployDir(), name);
115: factory.getFile().renameTo(deployable);
116: try {
117: if (getDeployer().findDeployedApp(
118: fmsg.getContextPath()) != null)
119: getDeployer().remove(fmsg.getContextPath(),
120: true);
121: } catch (Exception x) {
122: log
123: .info(
124: "Error removing existing context before installing a new one.",
125: x);
126: }
127: getDeployer().install(fmsg.getContextPath(),
128: deployable.toURL());
129: removeFactory(fmsg);
130: } //end if
131: } else if (msg instanceof UndeployMessage && msg != null) {
132: UndeployMessage umsg = (UndeployMessage) msg;
133: if (getDeployer()
134: .findDeployedApp(umsg.getContextPath()) != null)
135: getDeployer().remove(umsg.getContextPath(),
136: umsg.getUndeploy());
137: } //end if
138: } catch (java.io.IOException x) {
139: log.error("Unable to read farm deploy file message.", x);
140: }
141: }
142:
143: public synchronized FileMessageFactory getFactory(FileMessage msg)
144: throws java.io.FileNotFoundException, java.io.IOException {
145: File tmpFile = new File(msg.getFileName());
146: File writeToFile = new File(getTempDir(), tmpFile.getName());
147: FileMessageFactory factory = (FileMessageFactory) fileFactories
148: .get(msg.getFileName());
149: if (factory == null) {
150: factory = FileMessageFactory.getInstance(writeToFile, true);
151: fileFactories.put(msg.getFileName(), factory);
152: }
153: return factory;
154: }
155:
156: public void removeFactory(FileMessage msg) {
157: fileFactories.remove(msg.getFileName());
158: }
159:
160: /**
161: * Before the cluster invokes messageReceived the
162: * cluster will ask the receiver to accept or decline the message,
163: * In the future, when messages get big, the accept method will only take
164: * a message header
165: * @param msg ClusterMessage
166: * @return boolean - returns true to indicate that messageReceived
167: * should be invoked. If false is returned, the messageReceived method
168: * will not be invoked.
169: */
170: public boolean accept(ClusterMessage msg) {
171: return (msg instanceof FileMessage)
172: || (msg instanceof UndeployMessage);
173: }
174:
175: /**
176: * Install a new web application, whose web application archive is at the
177: * specified URL, into this container and all the other
178: * members of the cluster with the specified context path.
179: * A context path of "" (the empty string) should be used for the root
180: * application for this container. Otherwise, the context path must
181: * start with a slash.
182: * <p>
183: * If this application is successfully installed locally,
184: * a ContainerEvent of type
185: * <code>INSTALL_EVENT</code> will be sent to all registered listeners,
186: * with the newly created <code>Context</code> as an argument.
187: *
188: * @param contextPath The context path to which this application should
189: * be installed (must be unique)
190: * @param war A URL of type "jar:" that points to a WAR file, or type
191: * "file:" that points to an unpacked directory structure containing
192: * the web application to be installed
193: *
194: * @exception IllegalArgumentException if the specified context path
195: * is malformed (it must be "" or start with a slash)
196: * @exception IllegalStateException if the specified context path
197: * is already attached to an existing web application
198: * @exception IOException if an input/output error was encountered
199: * during installation
200: */
201: public void install(String contextPath, URL war) throws IOException {
202: if (getDeployer().findDeployedApp(contextPath) != null)
203: getDeployer().remove(contextPath, true);
204: //step 1. Install it locally
205: getDeployer().install(contextPath, war);
206: //step 2. Send it to each member in the cluster
207: Member[] members = getCluster().getMembers();
208: Member localMember = getCluster().getLocalMember();
209: FileMessageFactory factory = FileMessageFactory.getInstance(
210: new File(war.getFile()), false);
211: FileMessage msg = new FileMessage(localMember, war.getFile(),
212: contextPath);
213: msg = factory.readMessage(msg);
214: while (msg != null) {
215: for (int i = 0; i < members.length; i++) {
216: getCluster().send(msg, members[i]);
217: } //for
218: msg = factory.readMessage(msg);
219: } //while
220: }
221:
222: /**
223: * Remove an existing web application, attached to the specified context
224: * path. If this application is successfully removed, a
225: * ContainerEvent of type <code>REMOVE_EVENT</code> will be sent to all
226: * registered listeners, with the removed <code>Context</code> as
227: * an argument. Deletes the web application war file and/or directory
228: * if they exist in the Host's appBase.
229: *
230: * @param contextPath The context path of the application to be removed
231: * @param undeploy boolean flag to remove web application from server
232: *
233: * @exception IllegalArgumentException if the specified context path
234: * is malformed (it must be "" or start with a slash)
235: * @exception IllegalArgumentException if the specified context path does
236: * not identify a currently installed web application
237: * @exception IOException if an input/output error occurs during
238: * removal
239: */
240: public void remove(String contextPath, boolean undeploy)
241: throws IOException {
242: log.info("Cluster wide remove of web app " + contextPath);
243: //step 1. Remove it locally
244: if (getDeployer().findDeployedApp(contextPath) != null)
245: getDeployer().remove(contextPath, undeploy);
246: //step 2. Send it to each member in the cluster
247: Member[] members = getCluster().getMembers();
248: Member localMember = getCluster().getLocalMember();
249: UndeployMessage msg = new UndeployMessage(localMember, System
250: .currentTimeMillis(), "Undeploy:" + contextPath + ":"
251: + System.currentTimeMillis(), contextPath, undeploy);
252: cluster.send(msg);
253: }
254:
255: public void fileModified(File newWar) {
256: try {
257: File deployWar = new File(getDeployDir(), newWar.getName());
258: copy(newWar, deployWar);
259: String contextName = "/"
260: + deployWar.getName().substring(0,
261: deployWar.getName().lastIndexOf(".war"));
262: log.info("Installing webapp[" + contextName + "] from "
263: + deployWar.getAbsolutePath());
264: try {
265: remove(contextName, true);
266: } catch (Exception x) {
267: log.error("No removal", x);
268: }
269: install(contextName, deployWar.toURL());
270: } catch (Exception x) {
271: log.error("Unable to install WAR file", x);
272: }
273: }
274:
275: public void fileRemoved(File removeWar) {
276: try {
277: String contextName = "/"
278: + removeWar.getName().substring(0,
279: removeWar.getName().lastIndexOf(".war"));
280: log.info("Removing webapp[" + contextName + "]");
281: remove(contextName, true);
282: } catch (Exception x) {
283: log.error("Unable to remove WAR file", x);
284: }
285: }
286:
287: /*--Instance Getters/Setters--------------------------------*/
288: public CatalinaCluster getCluster() {
289: return cluster;
290: }
291:
292: public void setCluster(CatalinaCluster cluster) {
293: this .cluster = cluster;
294: }
295:
296: public void setDeployer(Deployer deployer) {
297: this .deployer = deployer;
298: }
299:
300: public boolean equals(Object listener) {
301: return super .equals(listener);
302: }
303:
304: public int hashCode() {
305: return super .hashCode();
306: }
307:
308: public String getDeployDir() {
309: return deployDir;
310: }
311:
312: public void setDeployDir(String deployDir) {
313: this .deployDir = deployDir;
314: }
315:
316: public String getTempDir() {
317: return tempDir;
318: }
319:
320: public void setTempDir(String tempDir) {
321: this .tempDir = tempDir;
322: }
323:
324: public Deployer getDeployer() {
325: return deployer;
326: }
327:
328: public String getWatchDir() {
329: return watchDir;
330: }
331:
332: public void setWatchDir(String watchDir) {
333: this .watchDir = watchDir;
334: }
335:
336: public boolean isWatchEnabled() {
337: return watchEnabled;
338: }
339:
340: public boolean getWatchEnabled() {
341: return watchEnabled;
342: }
343:
344: public void setWatchEnabled(boolean watchEnabled) {
345: this .watchEnabled = watchEnabled;
346: }
347:
348: /**
349:
350:
351:
352: /**
353: * Copy a file to the specified temp directory. This is required only
354: * because Jasper depends on it.
355: */
356: private boolean copy(File from, File to) {
357: try {
358: if (!to.exists())
359: to.createNewFile();
360: java.io.FileInputStream is = new java.io.FileInputStream(
361: from);
362: java.io.FileOutputStream os = new java.io.FileOutputStream(
363: to, false);
364: byte[] buf = new byte[4096];
365: while (true) {
366: int len = is.read(buf);
367: if (len < 0)
368: break;
369: os.write(buf, 0, len);
370: }
371: is.close();
372: os.close();
373: } catch (IOException e) {
374: log.error("Unable to copy file from:" + from + " to:" + to,
375: e);
376: return false;
377: }
378: return true;
379: }
380:
381: }
|