blob: ec4d290c32297f83b08810fb49b6fbc601bc3081 [file] [log] [blame]
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.cluster.deploy;
import org.apache.catalina.cluster.ClusterDeployer;
import org.apache.catalina.cluster.ClusterMessage;
import org.apache.catalina.cluster.CatalinaCluster;
import org.apache.catalina.LifecycleException;
import java.io.File;
import java.net.URL;
import java.io.IOException;
import org.apache.catalina.cluster.Member;
import java.util.HashMap;
/**
* <p>
* A farm war deployer is a class that is able to
* deploy/undeploy web applications in WAR form
* within the cluster.</p>
* Any host can act as the admin, and will have three directories
* <ul>
* <li> deployDir - the directory where we watch for changes</li>
* <li> applicationDir - the directory where we install applications</li>
* <li> tempDir - a temporaryDirectory to store binary data when downloading a war
* from the cluster </li>
* </ul>
* Currently we only support deployment of WAR files since they are easier to send
* across the wire.
*
* @author Filip Hanik
* @version 1.0
*/
public class FarmWarDeployer implements ClusterDeployer, FileChangeListener {
/*--Static Variables----------------------------------------*/
public static org.apache.commons.logging.Log log =
org.apache.commons.logging.LogFactory.getLog(FarmWarDeployer.class);
/*--Instance Variables--------------------------------------*/
protected CatalinaCluster cluster = null;
protected boolean started = false; //default 5 seconds
protected HashMap fileFactories = new HashMap();
protected String deployDir;
protected String tempDir;
protected String watchDir;
protected boolean watchEnabled = false;
protected WarWatcher watcher = null;
/*--Constructor---------------------------------------------*/
public FarmWarDeployer() {
}
/*--Logic---------------------------------------------------*/
public void start() throws Exception {
if (started)return;
getCluster().addClusterListener(this);
if (watchEnabled) {
watcher = new WarWatcher(this, new File(getWatchDir()), (long) 5000);
Thread t = new Thread(watcher);
t.start();
log.info("Cluster deployment is watching " + getWatchDir() +
" for changes.");
} //end if
started = true;
log.info("Cluster FarmWarDeployer started.");
}
public void stop() throws LifecycleException {
started = false;
getCluster().removeClusterListener(this);
if (watcher != null) watcher.stop();
log.info("Cluster FarmWarDeployer stopped.");
}
public void cleanDeployDir() {
throw new java.lang.UnsupportedOperationException(
"Method cleanDeployDir() not yet implemented.");
}
/**
* Callback from the cluster, when a message is received,
* The cluster will broadcast it invoking the messageReceived
* on the receiver.
* @param msg ClusterMessage - the message received from the cluster
*/
public void messageReceived(ClusterMessage msg) {
try {
if (msg instanceof FileMessage && msg != null) {
FileMessage fmsg = (FileMessage) msg;
FileMessageFactory factory = getFactory(fmsg);
if (factory.writeMessage(fmsg)) {
//last message received
String name = factory.getFile().getName();
if (!name.endsWith(".war"))
name = name + ".war";
File deployable = new File(getDeployDir(), name);
factory.getFile().renameTo(deployable);
// FIXME
/*
try {
if (getDeployer().findDeployedApp(fmsg.getContextPath()) != null)
getDeployer().remove(fmsg.getContextPath(), true);
} catch (Exception x) {
log.info(
"Error removing existing context before installing a new one.",
x);
}
getDeployer().install(fmsg.getContextPath(),
deployable.toURL());
*/
removeFactory(fmsg);
} //end if
} else if (msg instanceof UndeployMessage && msg != null) {
UndeployMessage umsg = (UndeployMessage) msg;
// FIXME
/*
if (getDeployer().findDeployedApp(umsg.getContextPath()) != null)
getDeployer().remove(umsg.getContextPath(),
umsg.getUndeploy());
*/
} //end if
} catch (java.io.IOException x) {
log.error("Unable to read farm deploy file message.", x);
}
}
public synchronized FileMessageFactory getFactory(FileMessage msg) throws
java.io.FileNotFoundException, java.io.IOException {
File tmpFile = new File(msg.getFileName());
File writeToFile = new File(getTempDir(), tmpFile.getName());
FileMessageFactory factory = (FileMessageFactory) fileFactories.get(msg.
getFileName());
if (factory == null) {
factory = FileMessageFactory.getInstance(writeToFile, true);
fileFactories.put(msg.getFileName(), factory);
}
return factory;
}
public void removeFactory(FileMessage msg) {
fileFactories.remove(msg.getFileName());
}
/**
* Before the cluster invokes messageReceived the
* cluster will ask the receiver to accept or decline the message,
* In the future, when messages get big, the accept method will only take
* a message header
* @param msg ClusterMessage
* @return boolean - returns true to indicate that messageReceived
* should be invoked. If false is returned, the messageReceived method
* will not be invoked.
*/
public boolean accept(ClusterMessage msg) {
return (msg instanceof FileMessage) ||
(msg instanceof UndeployMessage);
}
/**
* Install a new web application, whose web application archive is at the
* specified URL, into this container and all the other
* members of the cluster with the specified context path.
* A context path of "" (the empty string) should be used for the root
* application for this container. Otherwise, the context path must
* start with a slash.
* <p>
* If this application is successfully installed locally,
* a ContainerEvent of type
* <code>INSTALL_EVENT</code> will be sent to all registered listeners,
* with the newly created <code>Context</code> as an argument.
*
* @param contextPath The context path to which this application should
* be installed (must be unique)
* @param war A URL of type "jar:" that points to a WAR file, or type
* "file:" that points to an unpacked directory structure containing
* the web application to be installed
*
* @exception IllegalArgumentException if the specified context path
* is malformed (it must be "" or start with a slash)
* @exception IllegalStateException if the specified context path
* is already attached to an existing web application
* @exception IOException if an input/output error was encountered
* during installation
*/
public void install(String contextPath, URL war) throws IOException {
// FIXME
/*
if (getDeployer().findDeployedApp(contextPath) != null)
getDeployer().remove(contextPath, true);
//step 1. Install it locally
getDeployer().install(contextPath, war);
*/
//step 2. Send it to each member in the cluster
Member[] members = getCluster().getMembers();
Member localMember = getCluster().getLocalMember();
FileMessageFactory factory = FileMessageFactory.getInstance(new File(
war.getFile()), false);
FileMessage msg = new FileMessage(localMember, war.getFile(),
contextPath);
msg = factory.readMessage(msg);
while (msg != null) {
for (int i = 0; i < members.length; i++) {
getCluster().send(msg, members[i]);
} //for
msg = factory.readMessage(msg);
} //while
}
/**
* Remove an existing web application, attached to the specified context
* path. If this application is successfully removed, a
* ContainerEvent of type <code>REMOVE_EVENT</code> will be sent to all
* registered listeners, with the removed <code>Context</code> as
* an argument. Deletes the web application war file and/or directory
* if they exist in the Host's appBase.
*
* @param contextPath The context path of the application to be removed
* @param undeploy boolean flag to remove web application from server
*
* @exception IllegalArgumentException if the specified context path
* is malformed (it must be "" or start with a slash)
* @exception IllegalArgumentException if the specified context path does
* not identify a currently installed web application
* @exception IOException if an input/output error occurs during
* removal
*/
public void remove(String contextPath, boolean undeploy) throws IOException {
log.info("Cluster wide remove of web app " + contextPath);
//step 1. Remove it locally
// FIXME
/*
if (getDeployer().findDeployedApp(contextPath) != null)
getDeployer().remove(contextPath, undeploy);
*/
//step 2. Send it to each member in the cluster
Member[] members = getCluster().getMembers();
Member localMember = getCluster().getLocalMember();
UndeployMessage msg = new UndeployMessage(localMember,
System.currentTimeMillis(),
"Undeploy:" + contextPath +
":" +
System.currentTimeMillis(),
contextPath,
undeploy);
cluster.send(msg);
}
public void fileModified(File newWar) {
try {
File deployWar = new File(getDeployDir(),newWar.getName());
copy(newWar,deployWar);
String contextName = "/" + deployWar.getName().substring(0,
deployWar.getName().lastIndexOf(".war"));
log.info("Installing webapp[" + contextName + "] from " + deployWar.getAbsolutePath());
try {
remove(contextName, true);
} catch (Exception x) {
log.error("No removal", x);
}
install(contextName, deployWar.toURL());
} catch (Exception x) {
log.error("Unable to install WAR file", x);
}
}
public void fileRemoved(File removeWar) {
try {
String contextName = "/" + removeWar.getName().substring(0,
removeWar.getName().lastIndexOf(".war"));
log.info("Removing webapp[" + contextName + "]");
remove(contextName, true);
} catch (Exception x) {
log.error("Unable to remove WAR file", x);
}
}
/*--Instance Getters/Setters--------------------------------*/
public CatalinaCluster getCluster() {
return cluster;
}
public void setCluster(CatalinaCluster cluster) {
this.cluster = cluster;
}
public boolean equals(Object listener) {
return super.equals(listener);
}
public int hashCode() {
return super.hashCode();
}
public String getDeployDir() {
return deployDir;
}
public void setDeployDir(String deployDir) {
this.deployDir = deployDir;
}
public String getTempDir() {
return tempDir;
}
public void setTempDir(String tempDir) {
this.tempDir = tempDir;
}
public String getWatchDir() {
return watchDir;
}
public void setWatchDir(String watchDir) {
this.watchDir = watchDir;
}
public boolean isWatchEnabled() {
return watchEnabled;
}
public boolean getWatchEnabled() {
return watchEnabled;
}
public void setWatchEnabled(boolean watchEnabled) {
this.watchEnabled = watchEnabled;
}
/**
/**
* Copy a file to the specified temp directory. This is required only
* because Jasper depends on it.
*/
private boolean copy(File from, File to) {
try {
if ( !to.exists() ) to.createNewFile();
java.io.FileInputStream is = new java.io.FileInputStream(from);
java.io.FileOutputStream os = new java.io.FileOutputStream(to,false);
byte[] buf = new byte[4096];
while (true) {
int len = is.read(buf);
if (len < 0)
break;
os.write(buf, 0, len);
}
is.close();
os.close();
} catch (IOException e) {
log.error("Unable to copy file from:"+from+" to:"+to,e);
return false;
}
return true;
}
}