Project

General

Profile

Download (32.7 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
 * Copyright (C) 2009 EDIT
3
 * European Distributed Institute of Taxonomy
4
 * http://www.e-taxonomy.eu
5
 *
6
 * The contents of this file are subject to the Mozilla Public License Version 1.1
7
 * See LICENSE.TXT at the top of this package for the full license terms.
8
 */
9

    
10
package eu.etaxonomy.cdm.server;
11

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

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

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

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

    
71

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

    
83
    //private static final String DEFAULT_WARFILE = "target/";
84

    
85

    
86

    
87
    private static final Logger logger = Logger.getLogger(Bootloader.class);
88

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

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

    
96
    private static final String APPLICATION_NAME = "CDM Server";
97
    private static final String WAR_POSTFIX = ".war";
98

    
99
    private static final String CDM_WEBAPP = "cdm-webapp";
100
    private static final String CDM_WEBAPP_VERSION = "cdm-webapp.version";
101

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

    
106
    private final InstanceManager instanceManager = new InstanceManager(new File(USERHOME_CDM_LIBRARY_PATH, DATASOURCE_BEANDEF_FILE));
107

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

    
112
    public InstanceManager getInstanceManager(){
113
        return instanceManager;
114
    }
115

    
116
    private File cdmRemoteWebAppFile = null;
117
    private File defaultWebAppFile = null;
118

    
119
    private String logPath = null;
120

    
121
    private String webAppClassPath = null;
122

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

    
129
    private Server server = null;
130
    private final ContextHandlerCollection contexts = new ContextHandlerCollection();
131

    
132
    private CommandLine cmdLine;
133

    
134
    private boolean isRunningFromSource;
135

    
136
    private boolean isRunningfromTargetFolder;
137

    
138
    private boolean isRunningFromWarFile;
139

    
140
    private String cdmlibServicesVersion = "";
141
    private String cdmlibServicesLastModified = "";
142

    
143

    
144
    /* thread save singleton implementation */
145

    
146
    private static Bootloader bootloader = new Bootloader();
147

    
148
    private Bootloader() {}
149

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

    
157
    /* end of singleton implementation */
158

    
159

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

    
180

    
181

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

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

    
195

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

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

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

    
234

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

    
240

    
241
        File extractedWarFile = new File(TMP_PATH, warName + "-" + WAR_POSTFIX);
242
        logger.info("Extracting " + resource + " to " + extractedWarFile + " ...");
243

    
244
        writeStreamTo(resource.openStream(), new FileOutputStream(extractedWarFile), 8 * KB);
245

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

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

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

    
303

    
304
    public String getCdmlibServicesVersion() {
305
        return cdmlibServicesVersion;
306
    }
307

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

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

    
325

    
326

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

    
335
        Bootloader bootloader = Bootloader.getBootloader();
336

    
337
        bootloader.parseCommandOptions(args);
338

    
339
        bootloader.startServer();
340
    }
341

    
342

    
343

    
344
    public void startServer() throws IOException,
345
            FileNotFoundException, Exception, InterruptedException {
346

    
347

    
348
        if(cmdLine.hasOption(LOG_DIR.getOpt())){
349
            logPath = cmdLine.getOptionValue(LOG_DIR.getOpt());
350
        } else {
351
            logPath = LOG_PATH;
352
        }
353

    
354

    
355
        //assure LOG_PATH exists
356
        File logPathFile = new File(logPath);
357
        if(!logPathFile.exists()){
358
            FileUtils.forceMkdir(new File(logPath));
359
        }
360

    
361
        //append logger
362
//        configureFileLogger();
363

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

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

    
380

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

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

    
393
            updateServerRunMode();
394

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

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

    
415

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

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

    
439
        if(cmdLine.hasOption(CONTEXT_PATH_PREFIX.getOpt())){
440

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

    
451
        verifySystemResources();
452

    
453
         // load the configured instances for the first time
454
        instanceManager.reLoadInstanceConfigurations();
455

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

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

    
471

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

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

    
487
        // add default servlet context
488
        logger.info("preparing default WebAppContext");
489
        WebAppContext defaultWebappContext = new WebAppContext();
490

    
491
        setWebApp(defaultWebappContext, defaultWebAppFile);
492

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

    
505
        defaultWebappContext.setAttribute("org.eclipse.jetty.containerInitializers", jspInitializers());
506

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

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

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

    
526
        logger.info("setting contexts ...");
527
        server.setHandler(contexts);
528
        logger.info("starting jetty ...");
529
//        try {
530

    
531
            server.start();
532

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

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

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

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

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

    
595

    
596

    
597
    private void verifySystemResources() {
598

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

    
619

    
620
    /**
621
     * Configures and adds a {@link RollingFileAppender} to the root logger
622
     *
623
     * The log files of the cdm-remote instances are configured by the
624
     * {@link eu.etaxonomy.cdm.api.config.LoggingConfigurer}
625
     *
626
     *
627
     */
628
//   ===== removing useless RollingFileAppender logger === see #6287
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)