Project

General

Profile

Download (32.7 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.commons.io.FilenameUtils;
51
import org.apache.log4j.Logger;
52
import org.apache.log4j.PatternLayout;
53
import org.apache.log4j.RollingFileAppender;
54
import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
55
import org.eclipse.jetty.jmx.MBeanContainer;
56
import org.eclipse.jetty.plus.annotation.ContainerInitializer;
57
import org.eclipse.jetty.security.HashLoginService;
58
import org.eclipse.jetty.server.Handler;
59
import org.eclipse.jetty.server.Server;
60
import org.eclipse.jetty.server.ServerConnector;
61
import org.eclipse.jetty.server.handler.ContextHandler;
62
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
63
import org.eclipse.jetty.util.log.Log;
64
import org.eclipse.jetty.webapp.WebAppClassLoader;
65
import org.eclipse.jetty.webapp.WebAppContext;
66

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

    
74

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

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

    
88

    
89

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

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

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

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

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

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

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

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

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

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

    
122
    private String logPath = null;
123

    
124
    private String webAppClassPath = null;
125

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

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

    
135
    private CommandLine cmdLine;
136

    
137
    private boolean isRunningFromSource;
138

    
139
    private boolean isRunningfromTargetFolder;
140

    
141
    private boolean isRunningFromWarFile;
142

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

    
146

    
147
    /* thread save singleton implementation */
148

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

    
151
    private Bootloader() {}
152

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

    
160
    /* end of singleton implementation */
161

    
162

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

    
183

    
184

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

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

    
198

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

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

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

    
237

    
238
        if (resource == null) {
239
            // no way finding the war file :-(
240
            System.exit(1);
241
        }
242

    
243

    
244
        File extractedWarFile = new File(TMP_PATH, warName + "-" + WAR_POSTFIX);
245
        logger.info("Extracting " + resource + " to " + extractedWarFile + " ...");
246

    
247
        writeStreamTo(resource.openStream(), new FileOutputStream(extractedWarFile), 8 * KB);
248

    
249
        if(!unpack) {
250
            // return the war file
251
            return extractedWarFile;
252
        } else {
253
            // unpack the archive
254
            File explodedWebApp = null;
255
            try {
256
                logger.info("Unpacking " + extractedWarFile);
257
                explodedWebApp  = unzip(extractedWarFile);
258

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

    
291
                            if(cdmlibServicesVersion == null || cdmlibServicesLastModified == null) {
292
                                throw new IllegalStateException("Invalid cdmlib-services manifest file");
293
                            }
294
                        } else {
295
                            throw new IllegalStateException("cdmlib-services jar not found ");
296
                        }
297
                    }
298
                }
299
            } catch (IOException e) {
300
                logger.error("extractWar() - Unziping of war file " + explodedWebApp + " failed. Will return the war file itself instead of the extracted folder.", e);
301
            }
302
            return explodedWebApp;
303
        }
304
    }
305

    
306

    
307
    public String getCdmlibServicesVersion() {
308
        return cdmlibServicesVersion;
309
    }
310

    
311
    public String getCdmlibServicesLastModified() {
312
        return cdmlibServicesLastModified;
313
    }
314
    /**
315
     * @param extractWar
316
     * @return
317
     * @throws IOException
318
     */
319
    private File unzip(File extractWar) throws IOException {
320
        UnzipUtility unzip = new UnzipUtility();
321

    
322
        String targetFolderName = FilenameUtils.getBaseName(extractWar.getName());
323
        File destDirectory = new File(TMP_PATH + File.separator + targetFolderName);
324
        unzip.unzip(extractWar, destDirectory);
325
        return destDirectory;
326
    }
327

    
328

    
329

    
330
    /**
331
     * MAIN METHOD
332
     *
333
     * @param args
334
     * @throws Exception
335
     */
336
    public static void main(String[] args) throws Exception {
337

    
338
        Bootloader bootloader = Bootloader.getBootloader();
339

    
340
        bootloader.parseCommandOptions(args);
341

    
342
        bootloader.startServer();
343
    }
344

    
345

    
346

    
347
    public void startServer() throws IOException,
348
            FileNotFoundException, Exception, InterruptedException {
349

    
350

    
351
        if(cmdLine.hasOption(LOG_DIR.getOpt())){
352
            logPath = cmdLine.getOptionValue(LOG_DIR.getOpt());
353
        } else {
354
            logPath = LOG_PATH;
355
        }
356

    
357

    
358
        //assure LOG_PATH exists
359
        File logPathFile = new File(logPath);
360
        if(!logPathFile.exists()){
361
            FileUtils.forceMkdir(new File(logPath));
362
        }
363

    
364
        //append logger
365
        configureFileLogger();
366

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

    
370
        //assure TMP_PATH exists and clean it up
371
        File tempDir = new File(TMP_PATH);
372
        if(!tempDir.exists() && !tempDir.mkdirs()){
373
            logger.error("Error creating temporary directory for webapplications " + tempDir.getAbsolutePath());
374
            System.exit(-1);
375
        } else {
376
            if(FileUtils.deleteQuietly(tempDir)){
377
                tempDir.mkdirs();
378
                logger.info("Old webapplications successfully cleared");
379
            }
380
        }
381
        tempDir = null;
382

    
383

    
384
        // WEBAPP options
385
        //   prepare web application files to run either from war files (production mode)
386
        //   or from source (development mode)
387
        if(cmdLine.hasOption(WEBAPP.getOpt())){
388

    
389
            cdmRemoteWebAppFile = new File(cmdLine.getOptionValue(WEBAPP.getOpt()));
390
            if(cdmRemoteWebAppFile.isDirectory()){
391
                logger.info("using user defined web application folder: " + cdmRemoteWebAppFile.getAbsolutePath());
392
            } else {
393
                logger.info("using user defined warfile: " + cdmRemoteWebAppFile.getAbsolutePath());
394
            }
395

    
396
            updateServerRunMode();
397

    
398
            // load the default-web-application from source if running in development mode mode
399
            if(isRunningFromWarFile){
400
                defaultWebAppFile = extractWar(DEFAULT_WEBAPP_WAR_NAME, false);
401
            } else {
402
                defaultWebAppFile = new File("./src/main/webapp");
403
            }
404

    
405
            if(isRunningFromSource){
406
                if(cmdLine.hasOption(WEBAPP_CLASSPATH.getOpt())){
407
                    String classPathOption = cmdLine.getOptionValue(WEBAPP_CLASSPATH.getOpt());
408
                    normalizeClasspath(classPathOption);
409
                }
410
            }
411
        } else {
412
            // read version number
413
            String version = readCdmRemoteVersion();
414
            cdmRemoteWebAppFile = extractWar(CDM_WEBAPP + "-" + version, true);
415
            defaultWebAppFile = extractWar(DEFAULT_WEBAPP_WAR_NAME, false);
416
        }
417

    
418

    
419
        // HTTP Port
420
        int httpPort = 8080;
421
        if(cmdLine.hasOption(HTTP_PORT.getOpt())){
422
            try {
423
               httpPort = Integer.parseInt(cmdLine.getOptionValue(HTTP_PORT.getOpt()));
424
               logger.info(HTTP_PORT.getOpt()+" set to "+cmdLine.getOptionValue(HTTP_PORT.getOpt()));
425
           } catch (NumberFormatException e) {
426
               logger.error("Supplied portnumber is not an integer");
427
               System.exit(-1);
428
           }
429
        }
430

    
431
        if(cmdLine.hasOption(DATASOURCES_FILE.getOpt())){
432
             File datasourcesFile = new File(cmdLine.getOptionValue(DATASOURCES_FILE.getOpt()));
433
             if(datasourcesFile.canRead()) {
434
                instanceManager.setDatasourcesFile(datasourcesFile);
435
            } else {
436
                logger.error("File set as " + DATASOURCES_FILE.getOpt()
437
                        + " (" + cmdLine.getOptionValue(DATASOURCES_FILE.getOpt())
438
                        + ") is not readable.");
439
            }
440
         }
441

    
442
        if(cmdLine.hasOption(CONTEXT_PATH_PREFIX.getOpt())){
443

    
444
            String cppo  = cmdLine.getOptionValue(CONTEXT_PATH_PREFIX.getOpt());
445
            if(cppo.equals("/")) {
446
                this.contextPathPrefix = "";
447
            } else {
448
                Pattern pattern = Pattern.compile("^/*(.*?)/*$");
449
                String replacement = "$1";
450
                this.contextPathPrefix = pattern.matcher(cppo).replaceAll(replacement) + "/";
451
            }
452
        }
453

    
454
        verifySystemResources();
455

    
456
         // load the configured instances for the first time
457
        instanceManager.reLoadInstanceConfigurations();
458

    
459
        // in jetty 9 currently each connector uses
460
        // 2 threads -  1 to select for IO activity and 1 to accept new connections.
461
        // there fore we need to add 2 to the number of cores
462
//        QueuedThreadPool threadPool = new QueuedThreadPool(JvmManager.availableProcessors() +  + 200);
463
//        server = new Server(threadPool);
464
        server = new Server();
465
        server.addLifeCycleListener(instanceManager);
466
        ServerConnector connector=new ServerConnector(server);
467
        connector.setPort(httpPort);
468
        server.addConnector(connector );
469

    
470
        org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server);
471
        classlist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration", "org.eclipse.jetty.plus.webapp.EnvConfiguration", "org.eclipse.jetty.plus.webapp.PlusConfiguration");
472
        classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", "org.eclipse.jetty.annotations.AnnotationConfiguration");
473

    
474

    
475
        // JMX support
476
        if(cmdLine.hasOption(JMX.getOpt())){
477
            logger.info("adding JMX support ...");
478
            MBeanContainer mBeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
479
            server.addEventListener(mBeanContainer);
480
            server.addBean(Log.getLog());
481
        }
482

    
483
        if(cmdLine.hasOption(WIN32SERVICE.getOpt())){
484
            Win32Service win32Service = new Win32Service();
485
            win32Service.setServer(server);
486
            server.setStopAtShutdown(true);
487
            server.addBean(win32Service);
488
        }
489

    
490
        // add default servlet context
491
        logger.info("preparing default WebAppContext");
492
        WebAppContext defaultWebappContext = new WebAppContext();
493

    
494
        setWebApp(defaultWebappContext, defaultWebAppFile);
495

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

    
508
        defaultWebappContext.setAttribute("org.eclipse.jetty.containerInitializers", jspInitializers());
509

    
510
        // Context path
511
        //
512
        defaultWebappContext.setContextPath("/" + (contextPathPrefix.isEmpty() ? "" : contextPathPrefix.substring(0, contextPathPrefix.length() - 1)));
513
        logger.info("defaultWebapp (manager) context path:" + defaultWebappContext.getContextPath());
514
        defaultWebappContext.setTempDirectory(DEFAULT_WEBAPP_TEMP_FOLDER);
515

    
516
        // configure security context
517
        // see for reference * http://docs.codehaus.org/display/JETTY/Realms
518
        //                   * http://wiki.eclipse.org/Jetty/Starting/Porting_to_Jetty_7
519
        HashLoginService loginService = new HashLoginService();
520
        loginService.setConfig(USERHOME_CDM_LIBRARY_PATH + REALM_PROPERTIES_FILE);
521
        defaultWebappContext.getSecurityHandler().setLoginService(loginService);
522

    
523
        // Important:
524
        // the defaultWebappContext MUST USE the super classloader
525
        // otherwise the status page (index.jsp) might not work
526
        defaultWebappContext.setClassLoader(this.getClass().getClassLoader());
527
        contexts.addHandler(defaultWebappContext);
528

    
529
        logger.info("setting contexts ...");
530
        server.setHandler(contexts);
531
        logger.info("starting jetty ...");
532
//        try {
533

    
534
            server.start();
535

    
536
//        } catch(org.springframework.beans.BeansException e){
537
//        	Throwable rootCause = null;
538
//        	while(e.getCause() != null){
539
//        		rootCause = e.getCause();
540
//        	}
541
//        	if(rootCause != null && rootCause.getClass().getSimpleName().equals("InvalidCdmVersionException")){
542
//
543
//        		logger.error("rootCause ----------->" + rootCause.getMessage());
544
////        		for(CdmInstanceProperties props : configAndStatus){
545
////        			if(props.getDataSourceName())
546
////        		}
547
//        	}
548
//        }
549

    
550
        if(cmdLine.hasOption(WIN32SERVICE.getOpt())){
551
            logger.info("jetty has started as win32 service");
552
        } else {
553
            server.join();
554
            logger.info(APPLICATION_NAME+" stopped.");
555
            System.exit(0);
556
        }
557
    }
558

    
559
    /**
560
     * @param classpath
561
     */
562
    private void normalizeClasspath(String classpath) {
563
        StringBuilder classPathBuilder = new StringBuilder((int) (classpath.length() * 1.2));
564
        String[] cpEntries = classpath.split("[\\:]");
565
        for(String cpEntry : cpEntries){
566
            classPathBuilder.append(',');
567
//            if(cpEntry.endsWith(".jar")){
568
//                classPathBuilder.append("jar:");
569
//            }
570
            classPathBuilder.append(cpEntry);
571
        }
572
        webAppClassPath = classPathBuilder.toString();
573
    }
574

    
575
    public String readCdmRemoteVersion() throws IOException {
576
        String version = "cdmlib version unreadable";
577
        InputStream versionInStream = Bootloader.class.getClassLoader().getResourceAsStream(VERSION_PROPERTIES_FILE);
578
        if (versionInStream != null){
579
            Properties versionProperties = new Properties();
580
            versionProperties.load(versionInStream);
581
            version = versionProperties.getProperty(CDM_WEBAPP_VERSION, version);
582
        }
583
        return version;
584
    }
585

    
586
    /**
587
    * Ensure the jsp engine is initialized correctly
588
    */
589
    private List<ContainerInitializer> jspInitializers()
590
    {
591
        JettyJasperInitializer sci = new JettyJasperInitializer();
592
        ContainerInitializer initializer = new ContainerInitializer(sci, null);
593
        List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
594
        initializers.add(initializer);
595
        return initializers;
596
    }
597

    
598

    
599

    
600
    private void verifySystemResources() {
601

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

    
622

    
623
    /**
624
     * Configures and adds a {@link RollingFileAppender} to the root logger
625
     *
626
     * The log files of the cdm-remote instances are configured by the
627
     * {@link eu.etaxonomy.cdm.remote.config.LoggingConfigurer}
628
     */
629
    private void configureFileLogger() {
630

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

    
644

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

    
668
        Configuration conf = instance.getConfiguration();
669
        if(!instance.isEnabled()){
670
            logger.info(conf.getInstanceName() + " is disabled, possibly due to JVM memory limitations");
671
            return null;
672
        }
673
        if(getContextFor(conf) != null){
674
            logger.info(conf.getInstanceName() + " is alreaddy added to the contexts - skipping");
675
            return null;
676
        }
677

    
678
        instance.setStatus(Status.initializing);
679
        logger.info("preparing WebAppContext for '"+ conf.getInstanceName() + "'");
680
        WebAppContext cdmWebappContext = new WebAppContext();
681

    
682
        cdmWebappContext.setContextPath(constructContextPath(conf));
683
        logger.info("contextPath: " + cdmWebappContext.getContextPath());
684
        // set persistTempDirectory to prevent jetty from creating and deleting this directory for each instance,
685
        // since this behaviour can cause conflicts during parallel start up  of instances.
686
        cdmWebappContext.setPersistTempDirectory(true);
687

    
688

    
689
//        if(!instance.bindJndiDataSource()){
690
//            // a problem with the datasource occurred skip this webapp
691
//            cdmWebappContext = null;
692
//            logger.error("a problem with the datasource occurred -> skipping /" + conf.getInstanceName());
693
//            instance.setStatus(Status.error);
694
//            return cdmWebappContext;
695
//        }
696

    
697
        cdmWebappContext.setAttribute(SharedAttributes.ATTRIBUTE_DATASOURCE_NAME, conf.getInstanceName());
698
        cdmWebappContext.setAttribute(SharedAttributes.ATTRIBUTE_JDBC_JNDI_NAME, conf.getJdbcJndiName());
699
        if(cmdLine.hasOption(FORCE_SCHEMA_UPDATE.getOpt())){
700
            cdmWebappContext.getAttributes().setAttribute(SharedAttributes.ATTRIBUTE_FORCE_SCHEMA_UPDATE, "true");
701
        }
702
        setWebApp(cdmWebappContext, getCdmRemoteWebAppFile());
703

    
704
        cdmWebappContext.setAttribute(SharedAttributes.ATTRIBUTE_CDM_LOGFILE,
705
                logPath + File.separator + "cdm-"
706
                        + conf.getInstanceName() + ".log");
707

    
708
        if( isRunningFromSource ){
709

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

    
727
        contexts.addHandler(cdmWebappContext);
728
        instance.setWebAppContext(cdmWebappContext);
729
        cdmWebappContext.addLifeCycleListener(instance);
730
        instance.setStatus(Status.stopped);
731

    
732
        return cdmWebappContext;
733
    }
734

    
735
    /**
736
     * @param conf
737
     * @return
738
     */
739
    public String constructContextPath(Configuration conf) {
740

    
741
        return "/" + contextPathPrefix + conf.getInstanceName();
742
    }
743

    
744
    /**
745
     * Removes the given instance from the contexts. If the instance is running
746
     * at the time of calling this method it will be stopped first.
747
     * The JndiDataSource and the webapplicationContext will be released and removed.
748
     *
749
     * @param instance the instance to be removed
750
     *
751
     * @throws Exception in case stopping the instance fails
752
     */
753
    public void removeCdmInstanceContext(CdmInstance instance) throws Exception {
754

    
755
        if(instance.getWebAppContext() != null){
756
            if(instance.getWebAppContext().isRunning()){
757
                try {
758
                    instance.getWebAppContext().stop();
759
                } catch (Exception e) {
760
                    instance.getProblems().add("Error while stopping instance: " + e.getMessage());
761
                    throw e;
762
                }
763
            }
764
            contexts.removeHandler(instance.getWebAppContext());
765
            instance.releaseWebAppContext();
766
        } else  {
767
            // maybe something went wrong before, try to find the potentially lost
768
            // contexts directly in the server
769
            ContextHandler handler = getContextFor(instance.getConfiguration());
770
            if(handler != null){
771
                contexts.removeHandler(handler);
772
            }
773
        }
774
        instance.unbindJndiDataSource();
775
    }
776

    
777
    /**
778
     * Sets the webapp specified by the <code>webApplicationResource</code> to
779
     * the given <code>context</code>.
780
     *
781
     * @param context
782
     * @param webApplicationResource the resource can either be a directory containing
783
     * a Java web application or *.war file.
784
     *
785
     */
786
    private void setWebApp(WebAppContext context, File webApplicationResource) {
787
        if(webApplicationResource.isDirectory()){
788
            context.setResourceBase(webApplicationResource.getAbsolutePath());
789
            logger.debug("setting directory " + webApplicationResource.getAbsolutePath() + " as webapplication");
790
        } else {
791
            context.setWar(webApplicationResource.getAbsolutePath());
792
            logger.debug("setting war file " + webApplicationResource.getAbsolutePath() + " as webapplication");
793
        }
794
    }
795

    
796
    private void updateServerRunMode() {
797
        String webappPathNormalized = cdmRemoteWebAppFile.getAbsolutePath().replace('\\', '/');
798
        isRunningFromSource =  webappPathNormalized.endsWith("src/main/webapp");
799
        isRunningfromTargetFolder = webappPathNormalized.endsWith("cdm-webapp/target/cdmserver");
800
        isRunningFromWarFile = !(isRunningFromSource || isRunningfromTargetFolder);
801
    }
802

    
803

    
804
    public Server getServer() {
805
        return server;
806
    }
807

    
808
    public ContextHandler getContextFor(Configuration conf) {
809
        return getContextFor(constructContextPath(conf));
810
    }
811

    
812
    public ContextHandler getContextFor(String contextPath) {
813
        for( Handler handler : contexts.getHandlers()){
814
            if(handler instanceof ContextHandler){
815
                if(((ContextHandler)handler).getContextPath().equals(contextPath)){
816
                    return (ContextHandler)handler;
817
                }
818
            }
819
        }
820
        return null;
821
    }
822

    
823
    public ContextHandlerCollection getContexts() {
824
        return contexts;
825
    }
826

    
827
    /**
828
     * @return a File object pointing to the location of the cdm-webapp
829
     */
830
    public File getCdmRemoteWebAppFile(){
831
        if(cdmRemoteWebAppFile == null){
832
            throw new RuntimeException("Invalid order of action. Server not yet started. The startServer() method must be called first. ");
833
        }
834
        return cdmRemoteWebAppFile;
835
    }
836
}
(2-2/6)