3 * Copyright (C) 2009 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
11 package eu
.etaxonomy
.cdm
.server
;
13 import static eu
.etaxonomy
.cdm
.server
.CommandOptions
.DATASOURCES_FILE
;
14 import static eu
.etaxonomy
.cdm
.server
.CommandOptions
.HELP
;
15 import static eu
.etaxonomy
.cdm
.server
.CommandOptions
.HTTP_PORT
;
16 import static eu
.etaxonomy
.cdm
.server
.CommandOptions
.JMX
;
17 import static eu
.etaxonomy
.cdm
.server
.CommandOptions
.WEBAPP
;
20 import java
.io
.FileNotFoundException
;
21 import java
.io
.FileOutputStream
;
22 import java
.io
.IOException
;
23 import java
.io
.InputStream
;
24 import java
.io
.OutputStream
;
25 import java
.lang
.management
.ManagementFactory
;
26 import java
.lang
.reflect
.InvocationTargetException
;
28 import java
.sql
.Connection
;
29 import java
.sql
.SQLException
;
30 import java
.util
.EventListener
;
33 import javax
.naming
.NamingException
;
34 import javax
.servlet
.ServletContextEvent
;
35 import javax
.servlet
.ServletContextListener
;
36 import javax
.sql
.DataSource
;
38 import org
.apache
.commons
.cli
.CommandLine
;
39 import org
.apache
.commons
.cli
.CommandLineParser
;
40 import org
.apache
.commons
.cli
.GnuParser
;
41 import org
.apache
.commons
.cli
.HelpFormatter
;
42 import org
.apache
.commons
.cli
.ParseException
;
43 import org
.apache
.commons
.io
.FileUtils
;
44 import org
.apache
.log4j
.Logger
;
45 import org
.eclipse
.jetty
.jmx
.MBeanContainer
;
46 import org
.eclipse
.jetty
.server
.Server
;
47 import org
.eclipse
.jetty
.server
.handler
.ContextHandlerCollection
;
48 import org
.eclipse
.jetty
.util
.component
.LifeCycle
;
49 import org
.eclipse
.jetty
.util
.log
.Log
;
50 import org
.eclipse
.jetty
.webapp
.WebAppClassLoader
;
51 import org
.eclipse
.jetty
.webapp
.WebAppContext
;
55 * A bootstrap class for starting Jetty Runner using an embedded war.
57 * Recommended start options for the java virtual machine:
62 * -XX:MaxPermSize=192m
64 * -XX:+UseConcMarkSweepGC
65 * -XX:+CMSClassUnloadingEnabled
66 * -XX:+CMSPermGenSweepingEnabled
71 public final class Bootloader
{
72 //private static final String DEFAULT_WARFILE = "target/";
74 private static final Logger logger
= Logger
.getLogger(Bootloader
.class);
76 private static final String DATASOURCE_BEANDEF_FILE
= "datasources.xml";
77 private static final String USERHOME_CDM_LIBRARY_PATH
= System
.getProperty("user.home")+File
.separator
+".cdmLibrary"+File
.separator
;
78 private static final String TMP_PATH
= USERHOME_CDM_LIBRARY_PATH
+ "server" + File
.separator
;
80 private static final String APPLICATION_NAME
= "CDM Server";
81 private static final String WAR_POSTFIX
= ".war";
83 private static final String CDM_WEBAPP_WAR_NAME
= "cdmserver";
84 private static final String DEFAULT_WEBAPP_WAR_NAME
= "default-webapp";
85 private static final File DEFAULT_WEBAPP_TEMP_FOLDER
= new File(TMP_PATH
+ DEFAULT_WEBAPP_WAR_NAME
);
86 private static final File CDM_WEBAPP_TEMP_FOLDER
= new File(TMP_PATH
+ CDM_WEBAPP_WAR_NAME
);
88 private static final String ATTRIBUTE_JDBC_JNDI_NAME
= "cdm.jdbcJndiName";
90 private static final int KB
= 1024;
92 private static Set
<CdmInstanceProperties
> configAndStatus
= null;
94 public static Set
<CdmInstanceProperties
> getConfigAndStatus() {
95 return configAndStatus
;
98 private static File webappFile
= null;
99 private static File defaultWebAppFile
= null;
101 private static Server server
= null;
102 private static ContextHandlerCollection contexts
= new ContextHandlerCollection();
104 private static Bootloader instance
= null;
106 private static CommandLine cmdLine
;
109 private Bootloader() {
114 public static Bootloader
getBootloader(){
115 if(instance
== null){
116 instance
= new Bootloader();
121 private static Set
<CdmInstanceProperties
> loadDataSources(){
122 if(configAndStatus
== null){
123 File datasourcesFile
= new File(USERHOME_CDM_LIBRARY_PATH
, DATASOURCE_BEANDEF_FILE
);
124 configAndStatus
= DataSourcePropertyParser
.parseDataSourceConfigs(datasourcesFile
);
125 logger
.info("cdm server instance names loaded: "+ configAndStatus
.toString());
127 return configAndStatus
;
130 public static int writeStreamTo(final InputStream input
, final OutputStream output
, int bufferSize
) throws IOException
{
131 int available
= Math
.min(input
.available(), 256 * KB
);
132 byte[] buffer
= new byte[Math
.max(bufferSize
, available
)];
134 int count
= input
.read(buffer
);
136 output
.write(buffer
, 0, count
);
138 count
= input
.read(buffer
);
143 private static boolean bindJndiDataSource(CdmInstanceProperties conf
) {
145 Class
<DataSource
> dsCass
= (Class
<DataSource
>) Thread
.currentThread().getContextClassLoader().loadClass("com.mchange.v2.c3p0.ComboPooledDataSource");
146 DataSource datasource
= dsCass
.newInstance();
147 dsCass
.getMethod("setDriverClass", new Class
[] {String
.class}).invoke(datasource
, new Object
[] {conf
.getDriverClass()});
148 dsCass
.getMethod("setJdbcUrl", new Class
[] {String
.class}).invoke(datasource
, new Object
[] {conf
.getUrl()});
149 dsCass
.getMethod("setUser", new Class
[] {String
.class}).invoke(datasource
, new Object
[] {conf
.getUsername()});
150 dsCass
.getMethod("setPassword", new Class
[] {String
.class}).invoke(datasource
, new Object
[] {conf
.getPassword()});
152 Connection connection
= null;
153 String sqlerror
= null;
155 connection
= datasource
.getConnection();
157 } catch (SQLException e
) {
158 sqlerror
= e
.getMessage() + "["+ e
.getSQLState() + "]";
159 conf
.getProblems().add(sqlerror
);
160 if(connection
!= null){
161 try {connection
.close();} catch (SQLException e1
) { /* IGNORE */ }
163 logger
.error(conf
.toString() + " has problem : "+ sqlerror
);
166 if(!conf
.hasProblems()){
167 logger
.info("binding jndi datasource at " + conf
.getJdbcJndiName() + " with "+conf
.getUsername() +"@"+ conf
.getUrl());
168 org
.eclipse
.jetty
.plus
.jndi
.Resource jdbcResource
= new org
.eclipse
.jetty
.plus
.jndi
.Resource(conf
.getJdbcJndiName(), datasource
);
172 } catch (IllegalArgumentException e
) {
175 } catch (SecurityException e
) {
177 } catch (ClassNotFoundException e
) {
179 } catch (InstantiationException e
) {
181 } catch (IllegalAccessException e
) {
183 } catch (InvocationTargetException e
) {
185 } catch (NoSuchMethodException e
) {
187 } catch (NamingException e
) {
193 private static void parseCommandOptions(String
[] args
) throws ParseException
{
194 CommandLineParser parser
= new GnuParser();
195 cmdLine
= parser
.parse( CommandOptions
.getOptions(), args
);
197 // print the help message
198 if(cmdLine
.hasOption(HELP
.getOpt())){
199 HelpFormatter formatter
= new HelpFormatter();
200 formatter
.printHelp( "java .. ", CommandOptions
.getOptions() );
206 private static File
extractWar(String warName
) throws IOException
, FileNotFoundException
{
207 ClassLoader classLoader
= Thread
.currentThread().getContextClassLoader();
208 String warFileName
= warName
+ WAR_POSTFIX
;
209 URL resource
= classLoader
.getResource(warFileName
);
210 if (resource
== null) {
211 logger
.error("Could not find the " + warFileName
+ " on classpath!");
215 File warFile
= new File(TMP_PATH
, warName
+ "-" + WAR_POSTFIX
);
216 logger
.info("Extracting " + warFileName
+ " to " + warFile
+ " ...");
218 writeStreamTo(resource
.openStream(), new FileOutputStream(warFile
), 8 * KB
);
220 logger
.info("Extracted " + warFileName
);
231 public static void main(String
[] args
) throws Exception
{
233 Bootloader bootloader
= Bootloader
.getBootloader();
235 bootloader
.parseCommandOptions(args
);
237 bootloader
.startServer();
240 private static void startServer() throws IOException
,
241 FileNotFoundException
, Exception
, InterruptedException
{
242 logger
.info("Starting "+APPLICATION_NAME
);
243 logger
.info("Using " + System
.getProperty("user.home") + " as home directory. Can be specified by -Duser.home=<FOLDER>");
245 //assure TMP_PATH exists and clean it up
246 File tempDir
= new File(TMP_PATH
);
247 if(!tempDir
.exists() && !tempDir
.mkdirs()){
248 logger
.error("Error creating temporary directory for webapplications " + tempDir
.getAbsolutePath());
251 if(FileUtils
.deleteQuietly(tempDir
)){
253 logger
.info("Old webapplications successfully cleared");
259 if(cmdLine
.hasOption(WEBAPP
.getOpt())){
260 webappFile
= new File(cmdLine
.getOptionValue(WEBAPP
.getOpt()));
261 if(webappFile
.isDirectory()){
262 logger
.info("using user defined web application folder: " + webappFile
.getAbsolutePath());
264 logger
.info("using user defined warfile: " + webappFile
.getAbsolutePath());
266 if(isRunningFromSource()){
267 defaultWebAppFile
= new File("./src/main/webapp");
269 //defaultWebAppFile = extractWar(DEFAULT_WEBAPP_WAR_NAME);
272 webappFile
= extractWar(CDM_WEBAPP_WAR_NAME
);
273 defaultWebAppFile
= extractWar(DEFAULT_WEBAPP_WAR_NAME
);
278 if(cmdLine
.hasOption(HTTP_PORT
.getOpt())){
280 httpPort
= Integer
.parseInt(cmdLine
.getOptionValue(HTTP_PORT
.getOpt()));
281 logger
.info(HTTP_PORT
.getOpt()+" set to "+cmdLine
.getOptionValue(HTTP_PORT
.getOpt()));
282 } catch (NumberFormatException e
) {
283 logger
.error("Supplied portnumber is not an integer");
288 if(cmdLine
.hasOption(DATASOURCES_FILE
.getOpt())){
289 logger
.error(DATASOURCES_FILE
.getOpt() + " NOT JET IMPLEMENTED!!!");
294 //System.setProperty("DEBUG", "true");
296 server
= new Server(httpPort
);
299 if(cmdLine
.hasOption(JMX
.getOpt())){
300 logger
.info("adding JMX support ...");
301 MBeanContainer mBeanContainer
= new MBeanContainer(ManagementFactory
.getPlatformMBeanServer());
302 server
.getContainer().addEventListener(mBeanContainer
);
303 mBeanContainer
.addBean(Log
.getLog());
304 mBeanContainer
.start();
307 // add servelet contexts
311 // 1. default context
313 logger
.info("preparing default WebAppContext");
314 WebAppContext defaultWebappContext
= new WebAppContext();
315 setWebApp(defaultWebappContext
, defaultWebAppFile
);
316 defaultWebappContext
.setContextPath("/");
317 defaultWebappContext
.setTempDirectory(DEFAULT_WEBAPP_TEMP_FOLDER
);
318 contexts
.addHandler(defaultWebappContext
);
321 // 2. cdm server contexts
323 server
.addLifeCycleListener(new LifeCycle
.Listener(){
326 public void lifeCycleFailure(LifeCycle event
, Throwable cause
) {
330 public void lifeCycleStarted(LifeCycle event
) {
331 logger
.info("cdmserver has started, now adding CDM server contexts");
333 addCdmServerContexts(true);
334 } catch (IOException e1
) {
340 public void lifeCycleStarting(LifeCycle event
) {
344 public void lifeCycleStopped(LifeCycle event
) {
348 public void lifeCycleStopping(LifeCycle event
) {
354 logger
.info("setting contexts ...");
355 server
.setHandler(contexts
);
356 logger
.info("starting jetty ...");
359 logger
.info(APPLICATION_NAME
+" stopped.");
363 private static void addCdmServerContexts(boolean austostart
) throws IOException
{
365 for(CdmInstanceProperties conf
: configAndStatus
){
366 conf
.setStatus(CdmInstanceProperties
.Status
.initializing
);
367 logger
.info("preparing WebAppContext for '"+ conf
.getDataSourceName() + "'");
368 WebAppContext cdmWebappContext
= new WebAppContext();
370 cdmWebappContext
.setContextPath("/"+conf
.getDataSourceName());
371 cdmWebappContext
.setTempDirectory(CDM_WEBAPP_TEMP_FOLDER
);
373 if(!bindJndiDataSource(conf
)){
374 // a problem with the datasource occurred skip this webapp
375 cdmWebappContext
= null;
376 logger
.error("a problem with the datasource occurred -> skipping /" + conf
.getDataSourceName());
377 conf
.setStatus(CdmInstanceProperties
.Status
.error
);
381 cdmWebappContext
.setAttribute(ATTRIBUTE_JDBC_JNDI_NAME
, conf
.getJdbcJndiName());
382 setWebApp(cdmWebappContext
, webappFile
);
384 if(webappFile
.isDirectory() && isRunningFromSource()){
387 * when running the webapp from {projectpath} src/main/webapp we
388 * must assure that each web application is using it's own
389 * classloader thus we tell the WebAppClassLoader where the
390 * dependencies of the webapplication can be found. Otherwise
391 * the system classloader would load these resources.
393 logger
.info("Running webapp from source folder, thus adding java.class.path to WebAppClassLoader");
394 String classPath
= System
.getProperty("java.class.path");
395 WebAppClassLoader classLoader
= new WebAppClassLoader(cdmWebappContext
);
396 classLoader
.addClassPath(classPath
);
397 cdmWebappContext
.setClassLoader(classLoader
);
400 contexts
.addHandler(cdmWebappContext
);
404 conf
.setStatus(CdmInstanceProperties
.Status
.starting
);
405 cdmWebappContext
.start();
406 conf
.setStatus(CdmInstanceProperties
.Status
.started
);
407 } catch (Exception e
) {
408 logger
.error("Could not start " + cdmWebappContext
.getContextPath());
409 conf
.setStatus(CdmInstanceProperties
.Status
.error
);
416 private static void setWebApp(WebAppContext context
, File webApplicationResource
) {
417 if(webApplicationResource
.isDirectory()){
418 context
.setResourceBase(webApplicationResource
.getAbsolutePath());
419 logger
.debug("setting directory " + webApplicationResource
.getAbsolutePath() + " as webapplication");
421 context
.setWar(webApplicationResource
.getAbsolutePath());
422 logger
.debug("setting war file " + webApplicationResource
.getAbsolutePath() + " as webapplication");
426 private static boolean isRunningFromSource() {
427 String webappPathNormalized
= webappFile
.getAbsolutePath().replace('\\', '/');
428 return webappPathNormalized
.endsWith("src/main/webapp") || webappPathNormalized
.endsWith("cdmlib-remote/target/cdmserver");