/*--------------------------------------------------------------------------
* Copyright 2008 utgenome.org
*
* 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.
*--------------------------------------------------------------------------*/
//--------------------------------------
// utgb-shell Project
//
// TomcatServer.java
// Since: Sep 12, 2007
//
// $URL$
// $Author$
//--------------------------------------
package org.utgenome.shell.tomcat;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.List;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Embedded;
import org.apache.catalina.startup.HostConfig;
import org.xerial.core.XerialErrorCode;
import org.xerial.core.XerialException;
import org.xerial.util.FileResource;
import org.xerial.util.io.VirtualFile;
import org.xerial.util.log.Logger;
import org.xerial.util.opt.Option;
import org.xerial.util.opt.OptionParser;
import org.xerial.util.opt.OptionParserException;
/**
* Embedded Tomcat Server
*
* @author leo
*
*/
public class TomcatServer {
private static Logger _logger = Logger.getLogger(TomcatServer.class);
private TomcatServerConfiguration configuration = new TomcatServerConfiguration();
private Embedded embeddedTomcat = null;
private Engine tomcatEngine = null;
private StandardHost tomcatHost = null;
public static class Opt {
@Option(symbol = "h", longName = "help", description = "display help message")
boolean displayHelp = false;
@Option(symbol = "p", longName = "port", varName = "PORT", description = "server port number. default=8989")
int port = 8989;
@Option(symbol = "t", longName = "tomcat_home", varName = "TOMCAT_HOME", description = "set the home directory of the Tomcat engine")
String catalinaBase = null;
@Option(symbol = "c", longName = "contextPath", varName = "path", description = "/path: URL path of the web application")
String contextPath = null;
@Option(symbol = "d", longName = "docBase", varName = "(WAR or docBase)", description = "path to the war file or docBase to deploy")
String docBase = null;
}
/**
* entry point for running Tomcat from CUI
*
* @param args
*/
public static void main(String[] args) {
Opt opt = new Opt();
final OptionParser optionParser = new OptionParser(opt);
final TomcatServerConfiguration config = new TomcatServerConfiguration();
try {
optionParser.parse(args);
config.setPort(opt.port);
if (opt.displayHelp) {
throw new OptionParserException(XerialErrorCode.MISSING_ARGUMENT, "help");
}
if (opt.catalinaBase != null)
config.setCatalinaBase(opt.catalinaBase);
// start the server
final TomcatServer server = new TomcatServer(config);
server.start();
// deploy a given war
if (opt.contextPath != null && opt.docBase != null) {
File docBase = new File(opt.docBase);
server.addContext(opt.contextPath, docBase.getAbsolutePath());
}
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
server.stop();
}
catch (XerialException e) {
_logger.error(e);
}
}
});
// wait until Ctrl+C terminates the program
while (true) {
Thread.sleep(10000000000000000L);
}
}
catch (OptionParserException e) {
if (e.getMessage().equals("help")) {
System.out.println("> TomcatServer [option]");
optionParser.printUsage();
}
else
System.err.println(e.getMessage());
return;
}
catch (XerialException e) {
_logger.error(e);
}
catch (InterruptedException e) {
_logger.error(e);
}
}
/**
* Creates a TomcatServer instance with the specified port
*
* @param port
* port used by the tomcat
*/
public TomcatServer(int port) {
configuration.setPort(port);
}
/**
* Configures the tomcat server
*
* @param configuration
* configuration parameters
*/
public TomcatServer(TomcatServerConfiguration configuration) {
setConfiguration(configuration);
}
public void setConfiguration(TomcatServerConfiguration configuration) {
this.configuration = configuration;
}
private ClassLoader getExtensionClassLoader() {
ClassLoader cl = this.getClass().getClassLoader();
if (cl != null) {
ClassLoader parent = cl.getParent();
if (parent != null)
cl = parent;
}
return cl;
}
/**
* Starts the tomcat server
*
* @throws TomcatException
* when failed to launch tomcat
*/
public void start() throws XerialException {
_logger.debug("port: " + configuration.getPort());
_logger.debug("catalina base: " + configuration.getCatalinaBase());
try {
prepareScaffold(configuration.getCatalinaBase());
}
catch (IOException e) {
throw new XerialException(XerialErrorCode.IO_EXCEPTION, e);
}
String logConfigPath = new File(configuration.getCatalinaBase(), "conf/logging.properties").getPath();
// Setup the logging. catalina.base is referred in the
// logging.properties file
// System.setProperty("catalina.base", configuration.getCatalinaBase());
System.setProperty("catalina.base", "target/tomcat");
// System.setProperty("java.util.logging.manager", "org.apache.juli.ClassLoaderLogManager");
// System.setProperty("java.util.logging.config.file", "target/tomcat/conf/logging.properties");
for (String p : new String[] { "catalina.base", "java.util.logging.manager", "java.util.logging.config.file" })
_logger.info(String.format("%s = %s", p, System.getProperty(p)));
if (_logger.isDebugEnabled())
_logger.debug("juli log config file: " + logConfigPath);
// Create an embedded server
embeddedTomcat = new Embedded();
embeddedTomcat.setAwait(true);
embeddedTomcat.setCatalinaBase(configuration.getCatalinaBase());
// Create an engine
tomcatEngine = embeddedTomcat.createEngine();
tomcatEngine.setName("utgb");
tomcatEngine.setDefaultHost("localhost");
tomcatEngine.setParentClassLoader(getExtensionClassLoader());
// Create a default virtual host
String appBase = configuration.getCatalinaBase() + "/webapps";
_logger.debug("appBase: " + appBase);
tomcatHost = (StandardHost) embeddedTomcat.createHost("localhost", appBase);
// Hook up a host config to search for and pull in webapps.
HostConfig hostConfig = new HostConfig();
tomcatHost.addLifecycleListener(hostConfig);
// Tell the engine about the host
tomcatEngine.addChild(tomcatHost);
tomcatEngine.setDefaultHost(tomcatHost.getName());
// Tell the embedded manager about the engine
embeddedTomcat.addEngine(tomcatEngine);
// Tell the embedded server about the connector
InetAddress nullAddr = null;
Connector connector = embeddedTomcat.createConnector(nullAddr, configuration.getPort(), false);
connector.setEnableLookups(true);
// connector.setProxyPort(configuration.getAjp13port());
embeddedTomcat.addConnector(connector);
// Add AJP13 connector
//
try {
Connector ajp13connector = new Connector("org.apache.jk.server.JkCoyoteHandler");
ajp13connector.setPort(configuration.getAjp13port());
ajp13connector.setProtocol("AJP/1.3");
ajp13connector.setRedirectPort(8443);
embeddedTomcat.addConnector(ajp13connector);
}
catch (Exception e1) {
throw new XerialException(XerialErrorCode.INVALID_STATE, e1);
}
// create the ROOT context
// Context rootContext = embeddedTomcat.createContext("", "ROOT");
// tomcatHost.addChild(rootContext);
// // add manager context
// Context managerContext = embeddedTomcat.createContext("/manager",
// "manager");
// managerContext.setPrivileged(true);
// tomcatHost.addChild(managerContext);
// start up the tomcat
try {
embeddedTomcat.start();
}
catch (LifecycleException e) {
_logger.error(e);
String m = e.getMessage();
if (m != null && m.indexOf("already in use") != -1)
m = "port " + configuration.getPort() + " is already in use. You probably have another tomcat listening the same port";
// releaseTomcatResources();
throw new XerialException(XerialErrorCode.INVALID_STATE, e);
}
}
public void registerWAR(String contextPath, String pathToTheWarFile) throws XerialException {
addContext(contextPath, pathToTheWarFile);
}
public void addContext(String contextPath, String docBase) throws XerialException {
if (embeddedTomcat == null)
throw new XerialException(XerialErrorCode.INVALID_STATE, "tomcat server is not started yet.");
_logger.debug("deploy: contextPath=" + contextPath + ", docBase=" + docBase);
Context context = embeddedTomcat.createContext(contextPath, docBase);
// load the META-INF/context.xml
context.setConfigFile(docBase + "/META-INF/context.xml");
tomcatHost.addChild(context);
}
/*
* private void releaseTomcatResources() { embeddedTomcat = null; tomcatEngine = null; tomcatHost = null; }
*/
/**
* Stops the tomcat server
*
* @throws TomcatException
* failed to stop tomcat
*/
public void stop() throws XerialException {
if (embeddedTomcat != null) {
try {
embeddedTomcat.stop();
embeddedTomcat = null;
}
catch (LifecycleException e) {
throw new XerialException(XerialErrorCode.INVALID_STATE, e);
}
finally {
// releaseTomcatResources();
}
}
}
/**
* Creates the folder structure for the tomcat
*
* @param catalinaBase
* @throws IOException
*/
private void prepareScaffold(String catalinaBase) throws IOException {
// create the base folder for the scaffold
File tomcatBase = new File(catalinaBase);
List tomcatResources = FileResource.listResources("org.utgenome.shell.tomcat.scaffold");
if (tomcatResources.size() <= 0)
throw new IllegalStateException("org.utgenome.shell.tomcat.scaffold is not found");
// sync scaffoldDir with tomcatBase
for (VirtualFile vf : tomcatResources) {
String srcLogicalPath = vf.getLogicalPath();
File targetFile = new File(tomcatBase, srcLogicalPath);
if (vf.isDirectory()) {
targetFile.mkdirs();
}
else {
_logger.debug("rsync: src=" + vf.getLogicalPath() + " target=" + targetFile.getPath());
File parentFolder = targetFile.getParentFile();
parentFolder.mkdirs();
// copy the file content without overwrite
if (!targetFile.exists()) {
InputStream reader = vf.getURL().openStream();
FileOutputStream writer = new FileOutputStream(targetFile);
byte[] buffer = new byte[1024];
int bytesRead = 0;
while ((bytesRead = reader.read(buffer)) > 0) {
writer.write(buffer, 0, bytesRead);
}
}
}
}
}
private static String packagePath(String packageName) {
String packageAsPath = packageName.replaceAll("\\.", "/");
return packageAsPath.endsWith("/") ? packageAsPath : packageAsPath + "/";
}
public File findDir(String resourceName) {
String resourcePath = packagePath(resourceName);
if (!resourcePath.startsWith("/"))
resourcePath = "/" + resourcePath;
URL resourceURL = this.getClass().getResource(resourcePath);
if (resourceURL != null) {
_logger.debug("found resource:" + resourceURL);
String protocol = resourceURL.getProtocol();
if (protocol.equals("file")) {
try {
return new File(resourceURL.toURI());
}
catch (URISyntaxException e) {
_logger.error(e);
}
}
return new File(resourceURL.toString());
}
return null;
}
/**
* Sync the srcDir content to the targetDir (no overwrite is performed from the source to the target)
*
* @param srcDir
* @param targetDir
* @throws IOException
*/
private void rsync(File srcDir, File targetDir) throws IOException {
_logger.trace("rsync: src=" + srcDir + ", dest=" + targetDir);
/*
* if (!srcDir.isDirectory()) throw new IllegalStateException(srcDir + " is not a directory");
*/
// omit the .svn folders
if (srcDir.getName() == ".svn")
return;
// create directories
targetDir.mkdirs();
for (File file : srcDir.listFiles()) {
String fileName = file.getName();
if (file.isDirectory()) {
rsync(file, new File(targetDir, fileName));
continue;
}
if (fileName.startsWith("~"))
continue;
File newFile = new File(targetDir, fileName);
// copy a file
copyWithNoOverwrite(file, newFile);
}
}
private void copyWithNoOverwrite(File from, File to) throws IOException {
if (to.exists())
return;
FileChannel srcChannel = new FileInputStream(from).getChannel();
FileChannel destChannel = new FileOutputStream(to).getChannel();
srcChannel.transferTo(0, srcChannel.size(), destChannel);
srcChannel.close();
destChannel.close();
}
}