View Javadoc

1   /*--------------------------------------------------------------------------
2    *  Copyright 2008 utgenome.org
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  // utgb-shell Project
18  //
19  // TomcatServer.java
20  // Since: Sep 12, 2007
21  //
22  // $URL$ 
23  // $Author$
24  //--------------------------------------
25  package org.utgenome.shell.tomcat;
26  
27  import java.io.File;
28  import java.io.FileInputStream;
29  import java.io.FileOutputStream;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.net.InetAddress;
33  import java.net.URISyntaxException;
34  import java.net.URL;
35  import java.nio.channels.FileChannel;
36  import java.util.List;
37  
38  import org.apache.catalina.Context;
39  import org.apache.catalina.Engine;
40  import org.apache.catalina.LifecycleException;
41  import org.apache.catalina.core.StandardHost;
42  import org.apache.catalina.startup.Embedded;
43  import org.apache.catalina.startup.HostConfig;
44  import org.xerial.core.XerialErrorCode;
45  import org.xerial.core.XerialException;
46  import org.xerial.util.FileResource;
47  import org.xerial.util.io.VirtualFile;
48  import org.xerial.util.log.Logger;
49  import org.xerial.util.opt.Option;
50  import org.xerial.util.opt.OptionParser;
51  import org.xerial.util.opt.OptionParserException;
52  
53  /**
54   * Embedded Tomcat Server
55   * 
56   * @author leo
57   * 
58   */
59  public class TomcatServer {
60  	private static Logger _logger = Logger.getLogger(TomcatServer.class);
61  
62  	private TomcatServerConfiguration configuration;
63  	private Embedded embeddedTomcat = null;
64  	private Engine tomcatEngine = null;
65  	private StandardHost tomcatHost = null;
66  
67  	static {
68  	}
69  
70  	public static class Opt {
71  		@Option(symbol = "h", longName = "help", description = "display help message")
72  		boolean displayHelp = false;
73  
74  		@Option(symbol = "p", longName = "port", varName = "PORT", description = "server port number. default=8989")
75  		int port = 8989;
76  
77  		@Option(symbol = "t", longName = "tomcat_home", varName = "TOMCAT_HOME", description = "set the home directory of the Tomcat engine")
78  		String catalinaBase = null;
79  
80  		@Option(symbol = "c", longName = "contextPath", varName = "path", description = "/path: URL path of the web application")
81  		String contextPath = null;
82  
83  		@Option(symbol = "d", longName = "docBase", varName = "(WAR or docBase)", description = "path to the war file or docBase to deploy")
84  		String docBase = null;
85  	}
86  
87  	/**
88  	 * entry point for running Tomcat from CUI
89  	 * 
90  	 * @param args
91  	 */
92  	public static void main(String[] args) {
93  
94  		Opt opt = new Opt();
95  		final OptionParser optionParser = new OptionParser(opt);
96  		final TomcatServerConfiguration config = new TomcatServerConfiguration();
97  
98  		try {
99  			optionParser.parse(args);
100 
101 			config.setPort(opt.port);
102 
103 			if (opt.displayHelp) {
104 				throw new OptionParserException(XerialErrorCode.MISSING_ARGUMENT, "help");
105 			}
106 
107 			if (opt.catalinaBase != null)
108 				config.setCatalinaBase(opt.catalinaBase);
109 
110 			// start the server
111 			final TomcatServer server = new TomcatServer(config);
112 			server.start();
113 
114 			// deploy a given war
115 			if (opt.contextPath != null && opt.docBase != null) {
116 				File docBase = new File(opt.docBase);
117 				server.addContext(opt.contextPath, docBase.getAbsolutePath());
118 			}
119 
120 			Runtime.getRuntime().addShutdownHook(new Thread() {
121 				@Override
122 				public void run() {
123 					try {
124 						server.stop();
125 					}
126 					catch (XerialException e) {
127 						_logger.error(e);
128 					}
129 				}
130 			});
131 
132 			// wait until Ctrl+C terminates the program
133 			while (true) {
134 				Thread.sleep(10000000000000000L);
135 			}
136 
137 		}
138 		catch (OptionParserException e) {
139 			if (e.getMessage().equals("help")) {
140 				System.out.println("> TomcatServer [option]");
141 				optionParser.printUsage();
142 			}
143 			else
144 				System.err.println(e.getMessage());
145 			return;
146 		}
147 		catch (XerialException e) {
148 			_logger.error(e);
149 		}
150 		catch (InterruptedException e) {
151 			_logger.error(e);
152 		}
153 
154 	}
155 
156 	/**
157 	 * Creates a TomcatServer instance with the specified port
158 	 * 
159 	 * @param port
160 	 *            port used by the tomcat
161 	 * @throws XerialException
162 	 */
163 	public TomcatServer(int port) throws XerialException {
164 		this(TomcatServerConfiguration.newInstance(port));
165 	}
166 
167 	/**
168 	 * Configures the tomcat server
169 	 * 
170 	 * @param configuration
171 	 *            configuration parameters
172 	 * @throws XerialException
173 	 */
174 	public TomcatServer(TomcatServerConfiguration configuration) throws XerialException {
175 		setConfiguration(configuration);
176 
177 		_logger.debug("port: " + configuration.getPort());
178 		_logger.debug("catalina base: " + configuration.getCatalinaBase());
179 
180 		try {
181 			prepareScaffold(configuration.getCatalinaBase());
182 		}
183 		catch (IOException e) {
184 			throw new XerialException(XerialErrorCode.IO_EXCEPTION, e);
185 		}
186 
187 		// configure a logger
188 		String logConfigPath = new File(configuration.getCatalinaBase(), "conf/logging.properties").getPath();
189 		System.setProperty("catalina.base", configuration.getCatalinaBase());
190 		System.setProperty("java.util.logging.manager", "org.apache.juli.ClassLoaderLogManager");
191 		System.setProperty("java.util.logging.config.file", logConfigPath);
192 
193 		for (String p : new String[] { "catalina.base", "java.util.logging.manager", "java.util.logging.config.file" })
194 			_logger.info(String.format("%s = %s", p, System.getProperty(p)));
195 
196 		if (_logger.isDebugEnabled())
197 			_logger.debug("juli log config file: " + logConfigPath);
198 
199 	}
200 
201 	public void setConfiguration(TomcatServerConfiguration configuration) {
202 		this.configuration = configuration;
203 	}
204 
205 	private ClassLoader getExtensionClassLoader() {
206 		ClassLoader cl = Thread.currentThread().getContextClassLoader();
207 		if (cl != null) {
208 			ClassLoader parent = cl.getParent();
209 			if (parent != null)
210 				cl = parent;
211 		}
212 
213 		return cl;
214 
215 	}
216 
217 	/**
218 	 * Starts a Tomcat server
219 	 * 
220 	 * @throws TomcatException
221 	 *             when failed to launch tomcat
222 	 */
223 	public void start() throws XerialException {
224 
225 		// Create an embedded server
226 		embeddedTomcat = new Embedded();
227 		embeddedTomcat.setAwait(true);
228 		embeddedTomcat.setCatalinaBase(configuration.getCatalinaBase());
229 
230 		// Create an engine
231 		tomcatEngine = embeddedTomcat.createEngine();
232 		tomcatEngine.setName("utgb");
233 		tomcatEngine.setDefaultHost("localhost");
234 		tomcatEngine.setParentClassLoader(getExtensionClassLoader());
235 
236 		// Create a default virtual host
237 		String appBase = configuration.getCatalinaBase() + "/webapps";
238 		_logger.debug("appBase: " + appBase);
239 		tomcatHost = (StandardHost) embeddedTomcat.createHost("localhost", appBase);
240 
241 		// Hook up a host config to search for and pull in webapps.
242 		HostConfig hostConfig = new HostConfig();
243 		tomcatHost.addLifecycleListener(hostConfig);
244 
245 		// Tell the engine about the host
246 		tomcatEngine.addChild(tomcatHost);
247 		tomcatEngine.setDefaultHost(tomcatHost.getName());
248 
249 		// Tell the embedded manager about the engine
250 		embeddedTomcat.addEngine(tomcatEngine);
251 
252 		// Tell the embedded server about the connector
253 		InetAddress nullAddr = null;
254 		org.apache.catalina.connector.Connector conn = embeddedTomcat.createConnector(nullAddr, configuration.getPort(), false);
255 		conn.setEnableLookups(true);
256 		// connector.setProxyPort(configuration.getAjp13port());
257 		embeddedTomcat.addConnector(conn);
258 
259 		// Add AJP13 connector
260 		// <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
261 
262 		try {
263 			org.apache.catalina.connector.Connector ajp13connector = new org.apache.catalina.connector.Connector("org.apache.jk.server.JkCoyoteHandler");
264 			ajp13connector.setPort(configuration.getAjp13port());
265 			ajp13connector.setProtocol("AJP/1.3");
266 			ajp13connector.setRedirectPort(8443);
267 			embeddedTomcat.addConnector(ajp13connector);
268 		}
269 		catch (Exception e1) {
270 			throw new XerialException(XerialErrorCode.INVALID_STATE, e1);
271 		}
272 
273 		// create the ROOT context
274 		// Context rootContext = embeddedTomcat.createContext("", "ROOT");
275 		// tomcatHost.addChild(rootContext);
276 
277 		// // add manager context
278 		// Context managerContext = embeddedTomcat.createContext("/manager",
279 		// "manager");
280 		// managerContext.setPrivileged(true);
281 		// tomcatHost.addChild(managerContext);
282 
283 		// start up the tomcat
284 		try {
285 			embeddedTomcat.start();
286 		}
287 		catch (LifecycleException e) {
288 			_logger.error(e);
289 			String m = e.getMessage();
290 			if (m != null && m.indexOf("already in use") != -1)
291 				m = "port " + configuration.getPort() + " is already in use. You probably have another tomcat listening the same port";
292 
293 			// releaseTomcatResources();
294 
295 			throw new XerialException(XerialErrorCode.INVALID_STATE, e);
296 		}
297 
298 	}
299 
300 	public void registerWAR(String contextPath, String pathToTheWarFile) throws XerialException {
301 		addContext(contextPath, pathToTheWarFile);
302 	}
303 
304 	public void addContext(String contextPath, String docBase) throws XerialException {
305 		if (embeddedTomcat == null)
306 			throw new XerialException(XerialErrorCode.INVALID_STATE, "tomcat server is not started yet.");
307 
308 		_logger.debug("deploy: contextPath=" + contextPath + ", docBase=" + docBase);
309 
310 		Context context = embeddedTomcat.createContext(contextPath, docBase);
311 		// load the META-INF/context.xml
312 		context.setConfigFile(docBase + "/META-INF/context.xml");
313 		tomcatHost.addChild(context);
314 	}
315 
316 	/*
317 	 * private void releaseTomcatResources() { embeddedTomcat = null; tomcatEngine = null; tomcatHost = null; }
318 	 */
319 
320 	/**
321 	 * Stops the tomcat server
322 	 * 
323 	 * @throws TomcatException
324 	 *             failed to stop tomcat
325 	 */
326 	public void stop() throws XerialException {
327 		if (embeddedTomcat != null) {
328 			try {
329 				embeddedTomcat.stop();
330 				embeddedTomcat = null;
331 			}
332 			catch (LifecycleException e) {
333 				throw new XerialException(XerialErrorCode.INVALID_STATE, e);
334 			}
335 			finally {
336 				// releaseTomcatResources();
337 			}
338 		}
339 	}
340 
341 	/**
342 	 * Creates the folder structure for the tomcat
343 	 * 
344 	 * @param catalinaBase
345 	 * @throws IOException
346 	 */
347 	private void prepareScaffold(String catalinaBase) throws IOException {
348 		// create the base folder for the scaffold
349 		File tomcatBase = new File(catalinaBase);
350 
351 		List<VirtualFile> tomcatResources = FileResource.listResources("org.utgenome.shell.tomcat.scaffold");
352 
353 		if (tomcatResources.size() <= 0)
354 			throw new IllegalStateException("org.utgenome.shell.tomcat.scaffold is not found");
355 
356 		// sync scaffoldDir with tomcatBase
357 		for (VirtualFile vf : tomcatResources) {
358 			String srcLogicalPath = vf.getLogicalPath();
359 			File targetFile = new File(tomcatBase, srcLogicalPath);
360 
361 			if (vf.isDirectory()) {
362 				targetFile.mkdirs();
363 			}
364 			else {
365 				_logger.debug("rsync: src=" + vf.getLogicalPath() + " target=" + targetFile.getPath());
366 
367 				File parentFolder = targetFile.getParentFile();
368 				parentFolder.mkdirs();
369 
370 				// copy the file content without overwrite
371 				if (!targetFile.exists()) {
372 					InputStream reader = vf.getURL().openStream();
373 					FileOutputStream writer = new FileOutputStream(targetFile);
374 					byte[] buffer = new byte[1024];
375 					int bytesRead = 0;
376 					while ((bytesRead = reader.read(buffer)) > 0) {
377 						writer.write(buffer, 0, bytesRead);
378 					}
379 				}
380 			}
381 		}
382 
383 	}
384 
385 	private static String packagePath(String packageName) {
386 		String packageAsPath = packageName.replaceAll("\\.", "/");
387 		return packageAsPath.endsWith("/") ? packageAsPath : packageAsPath + "/";
388 	}
389 
390 	public File findDir(String resourceName) {
391 		String resourcePath = packagePath(resourceName);
392 		if (!resourcePath.startsWith("/"))
393 			resourcePath = "/" + resourcePath;
394 		URL resourceURL = this.getClass().getResource(resourcePath);
395 		if (resourceURL != null) {
396 			_logger.debug("found resource:" + resourceURL);
397 			String protocol = resourceURL.getProtocol();
398 			if (protocol.equals("file")) {
399 				try {
400 					return new File(resourceURL.toURI());
401 				}
402 				catch (URISyntaxException e) {
403 					_logger.error(e);
404 				}
405 			}
406 
407 			return new File(resourceURL.toString());
408 		}
409 		return null;
410 	}
411 
412 	/**
413 	 * Sync the srcDir content to the targetDir (no overwrite is performed from the source to the target)
414 	 * 
415 	 * @param srcDir
416 	 * @param targetDir
417 	 * @throws IOException
418 	 */
419 	private void rsync(File srcDir, File targetDir) throws IOException {
420 		_logger.trace("rsync: src=" + srcDir + ", dest=" + targetDir);
421 		/*
422 		 * if (!srcDir.isDirectory()) throw new IllegalStateException(srcDir + " is not a directory");
423 		 */
424 
425 		// omit the .svn folders
426 		if (srcDir.getName() == ".svn")
427 			return;
428 
429 		// create directories
430 		targetDir.mkdirs();
431 
432 		for (File file : srcDir.listFiles()) {
433 			String fileName = file.getName();
434 			if (file.isDirectory()) {
435 				rsync(file, new File(targetDir, fileName));
436 				continue;
437 			}
438 
439 			if (fileName.startsWith("~"))
440 				continue;
441 
442 			File newFile = new File(targetDir, fileName);
443 			// copy a file
444 			copyWithNoOverwrite(file, newFile);
445 		}
446 	}
447 
448 	private void copyWithNoOverwrite(File from, File to) throws IOException {
449 		if (to.exists())
450 			return;
451 		FileChannel srcChannel = new FileInputStream(from).getChannel();
452 		FileChannel destChannel = new FileOutputStream(to).getChannel();
453 		srcChannel.transferTo(0, srcChannel.size(), destChannel);
454 		srcChannel.close();
455 		destChannel.close();
456 	}
457 
458 }