Project

General

Profile

Download (32 KB) Statistics
| Branch: | Tag: | Revision:
1
// $Id$
2
/**
3
 * Copyright (C) 2009 EDIT
4
 * European Distributed Institute of Taxonomy
5
 * http://www.e-taxonomy.eu
6
 *
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.
9
 */
10

    
11
package eu.etaxonomy.cdm.server;
12

    
13
import static eu.etaxonomy.cdm.server.AssumedMemoryRequirements.KB;
14
import static eu.etaxonomy.cdm.server.CommandOptions.CONTEXT_PATH_PREFIX;
15
import static eu.etaxonomy.cdm.server.CommandOptions.DATASOURCES_FILE;
16
import static eu.etaxonomy.cdm.server.CommandOptions.FORCE_SCHEMA_UPDATE;
17
import static eu.etaxonomy.cdm.server.CommandOptions.HELP;
18
import static eu.etaxonomy.cdm.server.CommandOptions.HTTP_PORT;
19
import static eu.etaxonomy.cdm.server.CommandOptions.JMX;
20
import static eu.etaxonomy.cdm.server.CommandOptions.LOG_DIR;
21
import static eu.etaxonomy.cdm.server.CommandOptions.WEBAPP;
22
import static eu.etaxonomy.cdm.server.CommandOptions.WEBAPP_CLASSPATH;
23
import static eu.etaxonomy.cdm.server.CommandOptions.WIN32SERVICE;
24

    
25
import java.io.BufferedReader;
26
import java.io.File;
27
import java.io.FileNotFoundException;
28
import java.io.FileOutputStream;
29
import java.io.FilenameFilter;
30
import java.io.IOException;
31
import java.io.InputStream;
32
import java.io.InputStreamReader;
33
import java.io.OutputStream;
34
import java.lang.management.ManagementFactory;
35
import java.net.URL;
36
import java.util.ArrayList;
37
import java.util.List;
38
import java.util.Properties;
39
import java.util.jar.Attributes;
40
import java.util.jar.JarFile;
41
import java.util.jar.Manifest;
42
import java.util.regex.Pattern;
43

    
44
import org.apache.commons.cli.CommandLine;
45
import org.apache.commons.cli.CommandLineParser;
46
import org.apache.commons.cli.GnuParser;
47
import org.apache.commons.cli.HelpFormatter;
48
import org.apache.commons.cli.ParseException;
49
import org.apache.commons.io.FileUtils;
50
import org.apache.log4j.Logger;
51
import org.apache.log4j.PatternLayout;
52
import org.apache.log4j.RollingFileAppender;
53
import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
54
import org.eclipse.jetty.jmx.MBeanContainer;
55
import org.eclipse.jetty.plus.annotation.ContainerInitializer;
56
import org.eclipse.jetty.security.HashLoginService;
57
import org.eclipse.jetty.server.Handler;
58
import org.eclipse.jetty.server.Server;
59
import org.eclipse.jetty.server.ServerConnector;
60
import org.eclipse.jetty.server.handler.ContextHandler;
61
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
62
import org.eclipse.jetty.util.log.Log;
63
import org.eclipse.jetty.webapp.WebAppClassLoader;
64
import org.eclipse.jetty.webapp.WebAppContext;
65

    
66
import eu.etaxonomy.cdm.server.instance.CdmInstance;
67
import eu.etaxonomy.cdm.server.instance.Configuration;
68
import eu.etaxonomy.cdm.server.instance.InstanceManager;
69
import eu.etaxonomy.cdm.server.instance.SharedAttributes;
70
import eu.etaxonomy.cdm.server.instance.Status;
71
import eu.etaxonomy.cdm.server.win32service.Win32Service;
72

    
73

    
74
/**
75
 * A bootstrap class for starting Jetty Runner using an embedded war.
76
 *
77
 * @version $Revision$
78
 */
79
public final class Bootloader {
80
    /**
81
     *
82
     */
83
    private static final String VERSION_PROPERTIES_FILE = "version.properties";
84

    
85
    //private static final String DEFAULT_WARFILE = "target/";
86

    
87

    
88

    
89
    private static final Logger logger = Logger.getLogger(Bootloader.class);
90

    
91
    private static final String DATASOURCE_BEANDEF_FILE = "datasources.xml";
92
    private static final String REALM_PROPERTIES_FILE = "cdm-server-realm.properties";
93

    
94
    private static final String USERHOME_CDM_LIBRARY_PATH = System.getProperty("user.home")+File.separator+".cdmLibrary"+File.separator;
95
    private static final String TMP_PATH = USERHOME_CDM_LIBRARY_PATH + "server" + File.separator;
96
    private static final String LOG_PATH = USERHOME_CDM_LIBRARY_PATH + "log" + File.separator;
97

    
98
    private static final String APPLICATION_NAME = "CDM Server";
99
    private static final String WAR_POSTFIX = ".war";
100

    
101
    private static final String CDM_WEBAPP = "cdm-webapp";
102
    private static final String CDM_WEBAPP_VERSION = "cdm-webapp.version";
103

    
104
    private static final String DEFAULT_WEBAPP_WAR_NAME = "default-webapp";
105
    private static final File DEFAULT_WEBAPP_TEMP_FOLDER = new File(TMP_PATH + DEFAULT_WEBAPP_WAR_NAME);
106
    private static final File CDM_WEBAPP_TEMP_FOLDER = new File(TMP_PATH + CDM_WEBAPP);
107

    
108
    private final InstanceManager instanceManager = new InstanceManager(new File(USERHOME_CDM_LIBRARY_PATH, DATASOURCE_BEANDEF_FILE));
109

    
110
    public List<CdmInstance> getCdmInstances() {
111
        return instanceManager.getInstances();
112
    }
113

    
114
    public InstanceManager getInstanceManager(){
115
        return instanceManager;
116
    }
117

    
118
    private File cdmRemoteWebAppFile = null;
119
    private File defaultWebAppFile = null;
120

    
121
    private String logPath = null;
122

    
123
    private String webAppClassPath = null;
124

    
125
    /**
126
     * The contextPathPrefix is expected to be normalized:
127
     * it ends with a slash and starts not with a slash character
128
     */
129
    private String contextPathPrefix = "";
130

    
131
    private Server server = null;
132
    private final ContextHandlerCollection contexts = new ContextHandlerCollection();
133

    
134
    private CommandLine cmdLine;
135

    
136
    private boolean isRunningFromSource;
137

    
138
    private boolean isRunningfromTargetFolder;
139

    
140
    private boolean isRunningFromWarFile;
141

    
142
    private String cdmlibServicesVersion = "";
143
    private String cdmlibServicesLastModified = "";
144

    
145

    
146
    /* thread save singleton implementation */
147

    
148
    private static Bootloader bootloader = new Bootloader();
149

    
150
    private Bootloader() {}
151

    
152
    /**
153
     * @return the thread save singleton instance of the Bootloader
154
     */
155
    public synchronized static Bootloader getBootloader(){
156
        return bootloader;
157
    }
158

    
159
    /* end of singleton implementation */
160

    
161

    
162
    /**
163
     * @param input
164
     * @param output
165
     * @param bufferSize
166
     * @return
167
     * @throws IOException
168
     */
169
    public int writeStreamTo(final InputStream input, final OutputStream output, int bufferSize) throws IOException {
170
        int available = Math.min(input.available(), 256 * KB);
171
        byte[] buffer = new byte[Math.max(bufferSize, available)];
172
        int answer = 0;
173
        int count = input.read(buffer);
174
        while (count >= 0) {
175
            output.write(buffer, 0, count);
176
            answer += count;
177
            count = input.read(buffer);
178
        }
179
        return answer;
180
    }
181

    
182

    
183

    
184
    public void parseCommandOptions(String[] args) throws ParseException {
185
        CommandLineParser parser = new GnuParser();
186
        cmdLine = parser.parse( CommandOptions.getOptions(), args );
187

    
188
         // print the help message
189
         if(cmdLine.hasOption(HELP.getOpt())){
190
             HelpFormatter formatter = new HelpFormatter();
191
             formatter.setWidth(200);
192
             formatter.printHelp( "java .. ", CommandOptions.getOptions() );
193
             System.exit(0);
194
         }
195
    }
196

    
197

    
198
    /**
199
     * Finds the named war file either in the resources known to the class loader
200
     * or in a target folder if the bootloader is started from within a maven project.
201
     * Once found the war file is copied to the temp folder defined by {@link TMP_PATH}.
202
     *
203
     * The war file can optionally be unpacked.
204
     *
205
     * @param warName
206
     * @param unpack
207
     *  unzip the war file after extraction
208
     * @return
209
     * @throws IOException
210
     * @throws FileNotFoundException
211
     */
212
    private File extractWar(String warName, boolean unpack) throws IOException, FileNotFoundException {
213
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
214
        String warFileName = warName + WAR_POSTFIX;
215

    
216
        // 1. find in classpath
217
        URL resource = classLoader.getResource(warFileName);
218
        if (resource == null) {
219
            logger.error("Could not find the " + warFileName + " on classpath!");
220

    
221
            File pomxml = new File("pom.xml");
222
            if(pomxml.exists()){
223
                // 2. try finding in target folder of maven project
224
                File warFile = new File("target" + File.separator + warFileName);
225
                logger.debug("looking for war file at " + warFile.getAbsolutePath());
226
                if (warFile.canRead()) {
227
                    resource = warFile.toURI().toURL();
228
                } else {
229
                    logger.error("Also could not find the " + warFileName + " in maven project, try excuting 'mvn install'");
230
                }
231
            }
232
        }
233

    
234
        if (resource == null) {
235
            // no way finding the war file :-(
236
            System.exit(1);
237
        }
238

    
239

    
240
        File warFile = new File(TMP_PATH, warName + "-" + WAR_POSTFIX);
241
        logger.info("Extracting " + warFileName + " to " + warFile + " ...");
242

    
243
        writeStreamTo(resource.openStream(), new FileOutputStream(warFile), 8 * KB);
244

    
245
        logger.info("Extracted " + warFileName);
246

    
247
        if(unpack) {
248
            try {
249
                logger.info("Unpacking " + warFileName);
250
                warFile = unzip(warFile, warName);
251

    
252
                // get the 'Bundle-Version' and 'Bnd-LastModified' properties of the
253
                // manifest file in the cdmlib services jar
254
                if(warFile != null && warFile.isDirectory()) {
255
                    // generate the webapp lib dir path
256
                    String warLibDirAbsolutePath = warFile.getAbsolutePath() +
257
                            File.separator +
258
                            "WEB-INF" +
259
                            File.separator +
260
                            "lib";
261
                    File warLibDir = new File(warLibDirAbsolutePath);
262
                    if(warLibDir.exists()) {
263
                        // get the cdmlib-services jar
264
                        File [] files = warLibDir.listFiles(new FilenameFilter() {
265
                            @Override
266
                            public boolean accept(File dir, String name) {
267
                                return name.startsWith("cdmlib-services") && name.endsWith(".jar");
268
                            }
269
                        });
270
                        if(files != null && files.length > 0) {
271
                            // get the relevant info from the jar manifest
272
                            JarFile jarFile = new JarFile(files[0]);
273
                            Manifest manifest = jarFile.getManifest();
274
                            Attributes attributes = manifest.getMainAttributes();
275
                            // from the OSGI spec the LastModified value is " the number of milliseconds
276
                            // since midnight Jan. 1, 1970 UTC with the condition that a change must
277
                            // always result in a higher value than the previous last modified time
278
                            // of any bundle"
279
                            cdmlibServicesVersion = attributes.getValue("Bundle-Version");
280
                            logger.warn("cdmlib-services version : " + cdmlibServicesVersion);
281
                            cdmlibServicesLastModified = attributes.getValue("Bnd-LastModified");
282
                            logger.warn("cdmlib-services last modified timestamp : " + cdmlibServicesLastModified);
283

    
284
                            if(cdmlibServicesVersion == null || cdmlibServicesLastModified == null) {
285
                                throw new IllegalStateException("Invalid cdmlib-services manifest file");
286
                            }
287
                        } else {
288
                            throw new IllegalStateException("cdmlib-services jar not found ");
289
                        }
290
                    }
291
                }
292
            } catch (IOException e) {
293
                logger.error("extractWar() - Unziping of war file " + warFile + " failed. Will return the war file itself instead of the extracted folder.", e);
294
            }
295
        }
296

    
297
        return warFile;
298
    }
299

    
300

    
301
    public String getCdmlibServicesVersion() {
302
        return cdmlibServicesVersion;
303
    }
304

    
305
    public String getCdmlibServicesLastModified() {
306
        return cdmlibServicesLastModified;
307
    }
308
    /**
309
     * @param extractWar
310
     * @return
311
     * @throws IOException
312
     */
313
    private File unzip(File extractWar, String warName) throws IOException {
314
        UnzipUtility unzip = new UnzipUtility();
315

    
316
        File destDirectory = new File(TMP_PATH + File.separator + warName);
317
        unzip.unzip(extractWar, destDirectory);
318
        return destDirectory;
319
    }
320

    
321

    
322

    
323
    /**
324
     * MAIN METHOD
325
     *
326
     * @param args
327
     * @throws Exception
328
     */
329
    public static void main(String[] args) throws Exception {
330

    
331
        Bootloader bootloader = Bootloader.getBootloader();
332

    
333
        bootloader.parseCommandOptions(args);
334

    
335
        bootloader.startServer();
336
    }
337

    
338

    
339

    
340
    public void startServer() throws IOException,
341
            FileNotFoundException, Exception, InterruptedException {
342

    
343

    
344
        if(cmdLine.hasOption(LOG_DIR.getOpt())){
345
            logPath = cmdLine.getOptionValue(LOG_DIR.getOpt());
346
        } else {
347
            logPath = LOG_PATH;
348
        }
349

    
350

    
351
        //assure LOG_PATH exists
352
        File logPathFile = new File(logPath);
353
        if(!logPathFile.exists()){
354
            FileUtils.forceMkdir(new File(logPath));
355
        }
356

    
357
        //append logger
358
        configureFileLogger();
359

    
360
        logger.info("Starting "+APPLICATION_NAME);
361
        logger.info("Using  " + System.getProperty("user.home") + " as home directory. Can be specified by -Duser.home=<FOLDER>");
362

    
363
        //assure TMP_PATH exists and clean it up
364
        File tempDir = new File(TMP_PATH);
365
        if(!tempDir.exists() && !tempDir.mkdirs()){
366
            logger.error("Error creating temporary directory for webapplications " + tempDir.getAbsolutePath());
367
            System.exit(-1);
368
        } else {
369
            if(FileUtils.deleteQuietly(tempDir)){
370
                tempDir.mkdirs();
371
                logger.info("Old webapplications successfully cleared");
372
            }
373
        }
374
        tempDir = null;
375

    
376

    
377
        // WEBAPP options
378
        //   prepare web application files to run either from war files (production mode)
379
        //   or from source (development mode)
380
        if(cmdLine.hasOption(WEBAPP.getOpt())){
381

    
382
            cdmRemoteWebAppFile = new File(cmdLine.getOptionValue(WEBAPP.getOpt()));
383
            if(cdmRemoteWebAppFile.isDirectory()){
384
                logger.info("using user defined web application folder: " + cdmRemoteWebAppFile.getAbsolutePath());
385
            } else {
386
                logger.info("using user defined warfile: " + cdmRemoteWebAppFile.getAbsolutePath());
387
            }
388

    
389
            updateServerRunMode();
390

    
391
            // load the default-web-application from source if running in development mode mode
392
            if(isRunningFromWarFile){
393
                defaultWebAppFile = extractWar(DEFAULT_WEBAPP_WAR_NAME, false);
394
            } else {
395
                defaultWebAppFile = new File("./src/main/webapp");
396
            }
397

    
398
            if(isRunningFromSource){
399
                if(cmdLine.hasOption(WEBAPP_CLASSPATH.getOpt())){
400
                    String classPathOption = cmdLine.getOptionValue(WEBAPP_CLASSPATH.getOpt());
401
                    normalizeClasspath(classPathOption);
402
                }
403
            }
404
        } else {
405
            // read version number
406
            String version = readCdmRemoteVersion();
407
            cdmRemoteWebAppFile = extractWar(CDM_WEBAPP + "-" + version, true);
408
            defaultWebAppFile = extractWar(DEFAULT_WEBAPP_WAR_NAME, false);
409
        }
410

    
411

    
412
        // HTTP Port
413
        int httpPort = 8080;
414
        if(cmdLine.hasOption(HTTP_PORT.getOpt())){
415
            try {
416
               httpPort = Integer.parseInt(cmdLine.getOptionValue(HTTP_PORT.getOpt()));
417
               logger.info(HTTP_PORT.getOpt()+" set to "+cmdLine.getOptionValue(HTTP_PORT.getOpt()));
418
           } catch (NumberFormatException e) {
419
               logger.error("Supplied portnumber is not an integer");
420
               System.exit(-1);
421
           }
422
        }
423

    
424
        if(cmdLine.hasOption(DATASOURCES_FILE.getOpt())){
425
             File datasourcesFile = new File(cmdLine.getOptionValue(DATASOURCES_FILE.getOpt()));
426
             if(datasourcesFile.canRead()) {
427
                instanceManager.setDatasourcesFile(datasourcesFile);
428
            } else {
429
                logger.error("File set as " + DATASOURCES_FILE.getOpt()
430
                        + " (" + cmdLine.getOptionValue(DATASOURCES_FILE.getOpt())
431
                        + ") is not readable.");
432
            }
433
         }
434

    
435
        if(cmdLine.hasOption(CONTEXT_PATH_PREFIX.getOpt())){
436

    
437
            String cppo  = cmdLine.getOptionValue(CONTEXT_PATH_PREFIX.getOpt());
438
            if(cppo.equals("/")) {
439
                this.contextPathPrefix = "";
440
            } else {
441
                Pattern pattern = Pattern.compile("^/*(.*?)/*$");
442
                String replacement = "$1";
443
                this.contextPathPrefix = pattern.matcher(cppo).replaceAll(replacement) + "/";
444
            }
445
        }
446

    
447
        verifySystemResources();
448

    
449
         // load the configured instances for the first time
450
        instanceManager.reLoadInstanceConfigurations();
451

    
452
        // in jetty 9 currently each connector uses
453
        // 2 threads -  1 to select for IO activity and 1 to accept new connections.
454
        // there fore we need to add 2 to the number of cores
455
//        QueuedThreadPool threadPool = new QueuedThreadPool(JvmManager.availableProcessors() +  + 200);
456
//        server = new Server(threadPool);
457
        server = new Server();
458
        server.addLifeCycleListener(instanceManager);
459
        ServerConnector connector=new ServerConnector(server);
460
        connector.setPort(httpPort);
461
        server.addConnector(connector );
462

    
463
        org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server);
464
        classlist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration", "org.eclipse.jetty.plus.webapp.EnvConfiguration", "org.eclipse.jetty.plus.webapp.PlusConfiguration");
465
        classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", "org.eclipse.jetty.annotations.AnnotationConfiguration");
466

    
467

    
468
        // JMX support
469
        if(cmdLine.hasOption(JMX.getOpt())){
470
            logger.info("adding JMX support ...");
471
            MBeanContainer mBeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
472
            server.addEventListener(mBeanContainer);
473
            server.addBean(Log.getLog());
474
        }
475

    
476
        if(cmdLine.hasOption(WIN32SERVICE.getOpt())){
477
            Win32Service win32Service = new Win32Service();
478
            win32Service.setServer(server);
479
            server.setStopAtShutdown(true);
480
            server.addBean(win32Service);
481
        }
482

    
483
        // add default servlet context
484
        logger.info("preparing default WebAppContext");
485
        WebAppContext defaultWebappContext = new WebAppContext();
486

    
487
        setWebApp(defaultWebappContext, defaultWebAppFile);
488

    
489
        // JSP
490
        //
491
        // configuring jsp according to http://eclipse.org/jetty/documentation/current/configuring-jsp.html
492
        // from example http://eclipse.org/jetty/documentation/current/embedded-examples.html#embedded-webapp-jsp
493
        // Set the ContainerIncludeJarPattern so that jetty examines these
494
        // container-path jars for tlds, web-fragments etc.
495
        // If you omit the jar that contains the jstl .tlds, the jsp engine will
496
        // scan for them instead.
497
        defaultWebappContext.setAttribute(
498
                "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
499
                ".*/[^/]*servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/[^/]*taglibs.*\\.jar$" );
500

    
501
        defaultWebappContext.setAttribute("org.eclipse.jetty.containerInitializers", jspInitializers());
502

    
503
        // Context path
504
        //
505
        defaultWebappContext.setContextPath("/" + (contextPathPrefix.isEmpty() ? "" : contextPathPrefix.substring(0, contextPathPrefix.length() - 1)));
506
        logger.info("defaultWebapp (manager) context path:" + defaultWebappContext.getContextPath());
507
        defaultWebappContext.setTempDirectory(DEFAULT_WEBAPP_TEMP_FOLDER);
508

    
509
        // configure security context
510
        // see for reference * http://docs.codehaus.org/display/JETTY/Realms
511
        //                   * http://wiki.eclipse.org/Jetty/Starting/Porting_to_Jetty_7
512
        HashLoginService loginService = new HashLoginService();
513
        loginService.setConfig(USERHOME_CDM_LIBRARY_PATH + REALM_PROPERTIES_FILE);
514
        defaultWebappContext.getSecurityHandler().setLoginService(loginService);
515

    
516
        // Important:
517
        // the defaultWebappContext MUST USE the super classloader
518
        // otherwise the status page (index.jsp) might not work
519
        defaultWebappContext.setClassLoader(this.getClass().getClassLoader());
520
        contexts.addHandler(defaultWebappContext);
521

    
522
        logger.info("setting contexts ...");
523
        server.setHandler(contexts);
524
        logger.info("starting jetty ...");
525
//        try {
526

    
527
            server.start();
528

    
529
//        } catch(org.springframework.beans.BeansException e){
530
//        	Throwable rootCause = null;
531
//        	while(e.getCause() != null){
532
//        		rootCause = e.getCause();
533
//        	}
534
//        	if(rootCause != null && rootCause.getClass().getSimpleName().equals("InvalidCdmVersionException")){
535
//
536
//        		logger.error("rootCause ----------->" + rootCause.getMessage());
537
////        		for(CdmInstanceProperties props : configAndStatus){
538
////        			if(props.getDataSourceName())
539
////        		}
540
//        	}
541
//        }
542

    
543
        if(cmdLine.hasOption(WIN32SERVICE.getOpt())){
544
            logger.info("jetty has started as win32 service");
545
        } else {
546
            server.join();
547
            logger.info(APPLICATION_NAME+" stopped.");
548
            System.exit(0);
549
        }
550
    }
551

    
552
    /**
553
     * @param classpath
554
     */
555
    private void normalizeClasspath(String classpath) {
556
        StringBuilder classPathBuilder = new StringBuilder((int) (classpath.length() * 1.2));
557
        String[] cpEntries = classpath.split("[\\:]");
558
        for(String cpEntry : cpEntries){
559
            classPathBuilder.append(',');
560
//            if(cpEntry.endsWith(".jar")){
561
//                classPathBuilder.append("jar:");
562
//            }
563
            classPathBuilder.append(cpEntry);
564
        }
565
        webAppClassPath = classPathBuilder.toString();
566
    }
567

    
568
    public String readCdmRemoteVersion() throws IOException {
569
        String version = "cdmlib version unreadable";
570
        InputStream versionInStream = Bootloader.class.getClassLoader().getResourceAsStream(VERSION_PROPERTIES_FILE);
571
        if (versionInStream != null){
572
            Properties versionProperties = new Properties();
573
            versionProperties.load(versionInStream);
574
            version = versionProperties.getProperty(CDM_WEBAPP_VERSION, version);
575
        }
576
        return version;
577
    }
578

    
579
    /**
580
    * Ensure the jsp engine is initialized correctly
581
    */
582
    private List<ContainerInitializer> jspInitializers()
583
    {
584
        JettyJasperInitializer sci = new JettyJasperInitializer();
585
        ContainerInitializer initializer = new ContainerInitializer(sci, null);
586
        List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
587
        initializers.add(initializer);
588
        return initializers;
589
    }
590

    
591

    
592

    
593
    private void verifySystemResources() {
594

    
595
        OsChecker osChecker = new OsChecker();
596
        if(osChecker.isLinux()){
597
            try {
598
                Process p = Runtime.getRuntime().exec(new String[] { "bash", "-c", "ulimit -n" });
599
                BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
600
                String line;
601
                StringBuilder response = new StringBuilder();
602
                     while ((line = in.readLine()) != null) {
603
                         response.append(line);
604
                     }
605
               logger.info("OS Limit (Linux): maximum number of open files: " + response);
606
            } catch (IOException e) {
607
                // TODO Auto-generated catch block
608
                e.printStackTrace();
609
            }
610
        } else {
611
            logger.info("verifySystemResources only implemented for linux");
612
        }
613
    }
614

    
615

    
616
    /**
617
     * Configures and adds a {@link RollingFileAppender} to the root logger
618
     *
619
     * The log files of the cdm-remote instances are configured by the
620
     * {@link eu.etaxonomy.cdm.remote.config.LoggingConfigurer}
621
     */
622
    private void configureFileLogger() {
623

    
624
        PatternLayout layout = new PatternLayout("%d %p [%c] - %m%n");
625
        try {
626
            String logFile = logPath + File.separator + "cdmserver.log";
627
            RollingFileAppender appender = new RollingFileAppender(layout, logFile);
628
            appender.setMaxBackupIndex(3);
629
            appender.setMaxFileSize("2MB");
630
            Logger.getRootLogger().addAppender(appender);
631
            logger.info("logging to :" + logFile);
632
        } catch (IOException e) {
633
            logger.error("Creating RollingFileAppender failed:", e);
634
        }
635
    }
636

    
637

    
638
    /**
639
     * Adds a new WebAppContext to the contexts of the running jetty instance.
640
     * <ol>
641
     * <li>Initialize WebAppContext:
642
     * <ol>
643
     * <li>set context path</li>
644
     * <li>set tmp directory</li>
645
     * <li>bind JndiDataSource</li>
646
     * <li>set web app context attributes</li>
647
     * <li>create and setup individual classloader for the instance</li>
648
     * </ol>
649
     * </li>
650
     * <li>
651
     * finally add the new webappcontext to the contexts of the jetty instance</li>
652
     * </ol>
653
     *
654
     * @param instance
655
     * @return the instance given as parameter of null in case the instance has
656
     *         {@link Status.#disabled} or if it is already added.
657
     * @throws IOException
658
     */
659
    public WebAppContext addCdmInstanceContext(CdmInstance instance) throws IOException {
660

    
661
        Configuration conf = instance.getConfiguration();
662
        if(!instance.isEnabled()){
663
            logger.info(conf.getInstanceName() + " is disabled, possibly due to JVM memory limitations");
664
            return null;
665
        }
666
        if(getContextFor(conf) != null){
667
            logger.info(conf.getInstanceName() + " is alreaddy added to the contexts - skipping");
668
            return null;
669
        }
670

    
671
        instance.setStatus(Status.initializing);
672
        logger.info("preparing WebAppContext for '"+ conf.getInstanceName() + "'");
673
        WebAppContext cdmWebappContext = new WebAppContext();
674

    
675
        cdmWebappContext.setContextPath(constructContextPath(conf));
676
        logger.info("contextPath: " + cdmWebappContext.getContextPath());
677
        cdmWebappContext.setTempDirectory(CDM_WEBAPP_TEMP_FOLDER);
678

    
679
//        if(!instance.bindJndiDataSource()){
680
//            // a problem with the datasource occurred skip this webapp
681
//            cdmWebappContext = null;
682
//            logger.error("a problem with the datasource occurred -> skipping /" + conf.getInstanceName());
683
//            instance.setStatus(Status.error);
684
//            return cdmWebappContext;
685
//        }
686

    
687
        cdmWebappContext.setAttribute(SharedAttributes.ATTRIBUTE_DATASOURCE_NAME, conf.getInstanceName());
688
        cdmWebappContext.setAttribute(SharedAttributes.ATTRIBUTE_JDBC_JNDI_NAME, conf.getJdbcJndiName());
689
        if(cmdLine.hasOption(FORCE_SCHEMA_UPDATE.getOpt())){
690
            cdmWebappContext.getAttributes().setAttribute(SharedAttributes.ATTRIBUTE_FORCE_SCHEMA_UPDATE, "true");
691
        }
692
        setWebApp(cdmWebappContext, getCdmRemoteWebAppFile());
693

    
694
        cdmWebappContext.setAttribute(SharedAttributes.ATTRIBUTE_CDM_LOGFILE,
695
                logPath + File.separator + "cdm-"
696
                        + conf.getInstanceName() + ".log");
697

    
698
        if( isRunningFromSource ){
699

    
700
            /*
701
             * when running the webapp from {projectpath} src/main/webapp we
702
             * must assure that each web application is using it's own
703
             * classloader thus we tell the WebAppClassLoader where the
704
             * dependencies of the webapplication can be found. Otherwise
705
             * the system classloader would load these resources.
706
             */
707
            WebAppClassLoader classLoader = new WebAppClassLoader(cdmWebappContext);
708
            if(webAppClassPath != null){
709
                logger.info("Running cdm-webapp from source folder: Adding class path supplied by option '-" +  WEBAPP_CLASSPATH.getOpt() +" =" + webAppClassPath +"'  to WebAppClassLoader");
710
                classLoader.addClassPath(webAppClassPath);
711
            } else {
712
                throw new RuntimeException("Classpath cdm-webapp for missing while running cdm-webapp from source folder. Please supplied cdm-server option '-" +  WEBAPP_CLASSPATH.getOpt() +"");
713
            }
714
            cdmWebappContext.setClassLoader(classLoader);
715
        }
716

    
717
        contexts.addHandler(cdmWebappContext);
718
        instance.setWebAppContext(cdmWebappContext);
719
        cdmWebappContext.addLifeCycleListener(instance);
720
        instance.setStatus(Status.stopped);
721

    
722
        return cdmWebappContext;
723
    }
724

    
725
    /**
726
     * @param conf
727
     * @return
728
     */
729
    public String constructContextPath(Configuration conf) {
730

    
731
        return "/" + contextPathPrefix + conf.getInstanceName();
732
    }
733

    
734
    /**
735
     * Removes the given instance from the contexts. If the instance is running
736
     * at the time of calling this method it will be stopped first.
737
     * The JndiDataSource and the webapplicationContext will be released and removed.
738
     *
739
     * @param instance the instance to be removed
740
     *
741
     * @throws Exception in case stopping the instance fails
742
     */
743
    public void removeCdmInstanceContext(CdmInstance instance) throws Exception {
744

    
745
        if(instance.getWebAppContext() != null){
746
            if(instance.getWebAppContext().isRunning()){
747
                try {
748
                    instance.getWebAppContext().stop();
749
                } catch (Exception e) {
750
                    instance.getProblems().add("Error while stopping instance: " + e.getMessage());
751
                    throw e;
752
                }
753
            }
754
            contexts.removeHandler(instance.getWebAppContext());
755
            instance.releaseWebAppContext();
756
        } else  {
757
            // maybe something went wrong before, try to find the potentially lost
758
            // contexts directly in the server
759
            ContextHandler handler = getContextFor(instance.getConfiguration());
760
            if(handler != null){
761
                contexts.removeHandler(handler);
762
            }
763
        }
764
        instance.unbindJndiDataSource();
765
    }
766

    
767
    /**
768
     * Sets the webapp specified by the <code>webApplicationResource</code> to
769
     * the given <code>context</code>.
770
     *
771
     * @param context
772
     * @param webApplicationResource the resource can either be a directory containing
773
     * a Java web application or *.war file.
774
     *
775
     */
776
    private void setWebApp(WebAppContext context, File webApplicationResource) {
777
        if(webApplicationResource.isDirectory()){
778
            context.setResourceBase(webApplicationResource.getAbsolutePath());
779
            logger.debug("setting directory " + webApplicationResource.getAbsolutePath() + " as webapplication");
780
        } else {
781
            context.setWar(webApplicationResource.getAbsolutePath());
782
            logger.debug("setting war file " + webApplicationResource.getAbsolutePath() + " as webapplication");
783
        }
784
    }
785

    
786
    private void updateServerRunMode() {
787
        String webappPathNormalized = cdmRemoteWebAppFile.getAbsolutePath().replace('\\', '/');
788
        isRunningFromSource =  webappPathNormalized.endsWith("src/main/webapp");
789
        isRunningfromTargetFolder = webappPathNormalized.endsWith("cdm-webapp/target/cdmserver");
790
        isRunningFromWarFile = !(isRunningFromSource || isRunningfromTargetFolder);
791
    }
792

    
793

    
794
    public Server getServer() {
795
        return server;
796
    }
797

    
798
    public ContextHandler getContextFor(Configuration conf) {
799
        return getContextFor(constructContextPath(conf));
800
    }
801

    
802
    public ContextHandler getContextFor(String contextPath) {
803
        for( Handler handler : contexts.getHandlers()){
804
            if(handler instanceof ContextHandler){
805
                if(((ContextHandler)handler).getContextPath().equals(contextPath)){
806
                    return (ContextHandler)handler;
807
                }
808
            }
809
        }
810
        return null;
811
    }
812

    
813
    public ContextHandlerCollection getContexts() {
814
        return contexts;
815
    }
816

    
817
    /**
818
     * @return a File object pointing to the location of the cdm-webapp
819
     */
820
    public File getCdmRemoteWebAppFile(){
821
        if(cdmRemoteWebAppFile == null){
822
            throw new RuntimeException("Invalid order of action. Server not yet started. The startServer() method must be called first. ");
823
        }
824
        return cdmRemoteWebAppFile;
825
    }
826
}
(2-2/6)