Project

General

Profile

Download (33.9 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.MalformedURLException;
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.eclipse.jetty.apache.jsp.JettyJasperInitializer;
53
import org.eclipse.jetty.jmx.MBeanContainer;
54
import org.eclipse.jetty.plus.annotation.ContainerInitializer;
55
import org.eclipse.jetty.security.HashLoginService;
56
import org.eclipse.jetty.server.Handler;
57
import org.eclipse.jetty.server.Server;
58
import org.eclipse.jetty.server.ServerConnector;
59
import org.eclipse.jetty.server.handler.ContextHandler;
60
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
61
import org.eclipse.jetty.util.log.Log;
62
import org.eclipse.jetty.webapp.WebAppClassLoader;
63
import org.eclipse.jetty.webapp.WebAppContext;
64

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

    
72

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

    
84
    /**
85
     *
86
     */
87
    private static final String VERSION_PROPERTIES_FILE = "version.properties";
88

    
89
    //private static final String DEFAULT_WARFILE = "target/";
90

    
91

    
92

    
93
    private static final Logger logger = Logger.getLogger(Bootloader.class);
94

    
95
    private static final String DATASOURCE_BEANDEF_FILE = "datasources.xml";
96
    private static final String REALM_PROPERTIES_FILE = "cdm-server-realm.properties";
97

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

    
102
    private static final String APPLICATION_NAME = "CDM Server";
103
    private static final String WAR_POSTFIX = ".war";
104

    
105
    private static final String CDM_WEBAPP = "cdm-webapp";
106
    private static final String CDM_WEBAPP_VERSION = "cdm-webapp.version";
107

    
108
    private static final String DEFAULT_WEBAPP_WAR_NAME = "default-webapp";
109
    private static final File DEFAULT_WEBAPP_TEMP_FOLDER = new File(TMP_PATH + DEFAULT_WEBAPP_WAR_NAME);
110
    private static final File CDM_WEBAPP_TEMP_FOLDER = new File(TMP_PATH + CDM_WEBAPP);
111

    
112
    private final InstanceManager instanceManager = new InstanceManager(new File(USERHOME_CDM_LIBRARY_PATH, DATASOURCE_BEANDEF_FILE));
113

    
114
    public List<CdmInstance> getCdmInstances() {
115
        return instanceManager.getInstances();
116
    }
117

    
118
    public InstanceManager getInstanceManager(){
119
        return instanceManager;
120
    }
121

    
122
    private File cdmRemoteWebAppFile = null;
123
    private File defaultWebAppFile = null;
124

    
125
    private String logPath = null;
126

    
127
    private String webAppClassPath = null;
128

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

    
135
    private Server server = null;
136
    private final ContextHandlerCollection contexts = new ContextHandlerCollection();
137

    
138
    private CommandLine cmdLine;
139

    
140
    private boolean isRunningFromSource;
141

    
142
    private boolean isRunningfromTargetFolder;
143

    
144
    private boolean isRunningFromWarFile;
145

    
146
    private String cdmlibServicesVersion = "";
147
    private String cdmlibServicesLastModified = "";
148

    
149

    
150
    /* thread save singleton implementation */
151

    
152
    private static Bootloader bootloader = new Bootloader();
153

    
154
    private Bootloader() {}
155

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

    
163
    /* end of singleton implementation */
164

    
165

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

    
186

    
187

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

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

    
201

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

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

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

    
240

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

    
246

    
247
        File extractedWarFile = new File(TMP_PATH, warName + "-" + WAR_POSTFIX);
248
        logger.info("Extracting " + resource + " to " + extractedWarFile + " ...");
249

    
250
        writeStreamTo(resource.openStream(), new FileOutputStream(extractedWarFile), 8 * KB);
251

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

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

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

    
309

    
310
    public String getCdmlibServicesVersion() {
311
        return cdmlibServicesVersion;
312
    }
313

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

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

    
331

    
332

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

    
341
        Bootloader bootloader = Bootloader.getBootloader();
342

    
343
        bootloader.parseCommandOptions(args);
344

    
345
        bootloader.startServer();
346
    }
347

    
348

    
349

    
350
    public void startServer() throws IOException,
351
            FileNotFoundException, Exception, InterruptedException {
352

    
353

    
354
        if(cmdLine.hasOption(LOG_DIR.getOpt())){
355
            logPath = cmdLine.getOptionValue(LOG_DIR.getOpt());
356
        } else {
357
            logPath = LOG_PATH;
358
        }
359

    
360

    
361
        //assure LOG_PATH exists
362
        File logPathFile = new File(logPath);
363
        if(!logPathFile.exists()){
364
            FileUtils.forceMkdir(new File(logPath));
365
        }
366

    
367
        //append logger
368
//        configureFileLogger();
369

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

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

    
386

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

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

    
399
            updateServerRunMode();
400

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

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

    
421

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

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

    
445
        if(cmdLine.hasOption(CONTEXT_PATH_PREFIX.getOpt())){
446

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

    
457
        verifySystemResources();
458

    
459
         // load the configured instances for the first time
460
        instanceManager.reLoadInstanceConfigurations();
461

    
462
        if(System.getProperty(SPRING_PROFILES_ACTIVE) == null){
463
            logger.info(SPRING_PROFILES_ACTIVE + " is undefined, and will be set to : \"remoting\"");
464
            System.setProperty(SPRING_PROFILES_ACTIVE, "remoting");
465
        }
466

    
467
        // in jetty 9 currently each connector uses
468
        // 2 threads -  1 to select for IO activity and 1 to accept new connections.
469
        // there fore we need to add 2 to the number of cores
470
//        QueuedThreadPool threadPool = new QueuedThreadPool(JvmManager.availableProcessors() +  + 200);
471
//        server = new Server(threadPool);
472
        server = new Server();
473

    
474
        jdk8MemleakFix();
475

    
476
        server.addLifeCycleListener(instanceManager);
477
        ServerConnector connector=new ServerConnector(server);
478
        connector.setPort(httpPort);
479
        server.addConnector(connector );
480

    
481
        org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server);
482
        classlist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration", "org.eclipse.jetty.plus.webapp.EnvConfiguration", "org.eclipse.jetty.plus.webapp.PlusConfiguration");
483
        classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", "org.eclipse.jetty.annotations.AnnotationConfiguration");
484

    
485

    
486
        // JMX support
487
        if(cmdLine.hasOption(JMX.getOpt())){
488
            logger.info("adding JMX support ...");
489
            MBeanContainer mBeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
490
            server.addEventListener(mBeanContainer);
491
            server.addBean(Log.getLog());
492
        }
493

    
494
        if(cmdLine.hasOption(WIN32SERVICE.getOpt())){
495
            Win32Service win32Service = new Win32Service();
496
            win32Service.setServer(server);
497
            server.setStopAtShutdown(true);
498
            server.addBean(win32Service);
499
        }
500

    
501
        // add default servlet context
502
        logger.info("preparing default WebAppContext");
503
        WebAppContext defaultWebappContext = new WebAppContext();
504

    
505
        setWebApp(defaultWebappContext, defaultWebAppFile);
506

    
507
        // JSP
508
        //
509
        // configuring jsp according to http://eclipse.org/jetty/documentation/current/configuring-jsp.html
510
        // from example http://eclipse.org/jetty/documentation/current/embedded-examples.html#embedded-webapp-jsp
511
        // Set the ContainerIncludeJarPattern so that jetty examines these
512
        // container-path jars for tlds, web-fragments etc.
513
        // If you omit the jar that contains the jstl .tlds, the jsp engine will
514
        // scan for them instead.
515
        defaultWebappContext.setAttribute(
516
                "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
517
                ".*/[^/]*servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/[^/]*taglibs.*\\.jar$" );
518

    
519
        defaultWebappContext.setAttribute("org.eclipse.jetty.containerInitializers", jspInitializers());
520

    
521
        // Context path
522
        //
523
        defaultWebappContext.setContextPath("/" + (contextPathPrefix.isEmpty() ? "" : contextPathPrefix.substring(0, contextPathPrefix.length() - 1)));
524
        logger.info("defaultWebapp (manager) context path:" + defaultWebappContext.getContextPath());
525
        defaultWebappContext.setTempDirectory(DEFAULT_WEBAPP_TEMP_FOLDER);
526

    
527
        // configure security context
528
        // see for reference * http://docs.codehaus.org/display/JETTY/Realms
529
        //                   * http://wiki.eclipse.org/Jetty/Starting/Porting_to_Jetty_7
530
        HashLoginService loginService = new HashLoginService();
531
        loginService.setConfig(USERHOME_CDM_LIBRARY_PATH + REALM_PROPERTIES_FILE);
532
        defaultWebappContext.getSecurityHandler().setLoginService(loginService);
533

    
534
        // Important:
535
        // the defaultWebappContext MUST USE the super classloader
536
        // otherwise the status page (index.jsp) might not work
537
        defaultWebappContext.setClassLoader(this.getClass().getClassLoader());
538
        contexts.addHandler(defaultWebappContext);
539

    
540
        logger.info("setting contexts ...");
541
        server.setHandler(contexts);
542
        logger.info("starting jetty ...");
543
//        try {
544

    
545
            server.start();
546

    
547
//        } catch(org.springframework.beans.BeansException e){
548
//        	Throwable rootCause = null;
549
//        	while(e.getCause() != null){
550
//        		rootCause = e.getCause();
551
//        	}
552
//        	if(rootCause != null && rootCause.getClass().getSimpleName().equals("InvalidCdmVersionException")){
553
//
554
//        		logger.error("rootCause ----------->" + rootCause.getMessage());
555
////        		for(CdmInstanceProperties props : configAndStatus){
556
////        			if(props.getDataSourceName())
557
////        		}
558
//        	}
559
//        }
560

    
561
        if(cmdLine.hasOption(WIN32SERVICE.getOpt())){
562
            logger.info("jetty has started as win32 service");
563
        } else {
564
            server.join();
565
            logger.info(APPLICATION_NAME+" stopped.");
566
            System.exit(0);
567
        }
568
    }
569

    
570
    /**
571
     * jdk8 memleak workaround: disable url caching
572
     *  see https://dev.e-taxonomy.eu/redmine/issues/5048
573
     *
574
     * @throws IOException
575
     * @throws MalformedURLException
576
     */
577
    private void jdk8MemleakFix() throws IOException, MalformedURLException {
578
        String javaVersion = System.getProperty("java.version");
579
        if(javaVersion.startsWith("1.8")){
580
            logger.info("jdk8 detected (" + javaVersion + ") disabling url caching to avoid memory leak.");
581
            org.eclipse.jetty.util.resource.Resource.setDefaultUseCaches(false);
582
            File tmpio = new File(System.getProperty("java.io.tmpdir"));
583
            tmpio.toURI().toURL().openConnection().setDefaultUseCaches(false);
584
        }
585
    }
586

    
587
    /**
588
     * @param classpath
589
     */
590
    private void normalizeClasspath(String classpath) {
591
        StringBuilder classPathBuilder = new StringBuilder((int) (classpath.length() * 1.2));
592
        String[] cpEntries = classpath.split("[\\:]");
593
        for(String cpEntry : cpEntries){
594
            classPathBuilder.append(',');
595
//            if(cpEntry.endsWith(".jar")){
596
//                classPathBuilder.append("jar:");
597
//            }
598
            classPathBuilder.append(cpEntry);
599
        }
600
        webAppClassPath = classPathBuilder.toString();
601
    }
602

    
603
    public String readCdmRemoteVersion() throws IOException {
604
        String version = "cdmlib version unreadable";
605
        InputStream versionInStream = Bootloader.class.getClassLoader().getResourceAsStream(VERSION_PROPERTIES_FILE);
606
        if (versionInStream != null){
607
            Properties versionProperties = new Properties();
608
            versionProperties.load(versionInStream);
609
            version = versionProperties.getProperty(CDM_WEBAPP_VERSION, version);
610
        }
611
        return version;
612
    }
613

    
614
    /**
615
    * Ensure the jsp engine is initialized correctly
616
    */
617
    private List<ContainerInitializer> jspInitializers()
618
    {
619
        JettyJasperInitializer sci = new JettyJasperInitializer();
620
        ContainerInitializer initializer = new ContainerInitializer(sci, null);
621
        List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
622
        initializers.add(initializer);
623
        return initializers;
624
    }
625

    
626

    
627

    
628
    private void verifySystemResources() {
629

    
630
        OsChecker osChecker = new OsChecker();
631
        if(osChecker.isLinux()){
632
            try {
633
                Process p = Runtime.getRuntime().exec(new String[] { "bash", "-c", "ulimit -n" });
634
                BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
635
                String line;
636
                StringBuilder response = new StringBuilder();
637
                     while ((line = in.readLine()) != null) {
638
                         response.append(line);
639
                     }
640
               logger.info("OS Limit (Linux): maximum number of open files: " + response);
641
            } catch (IOException e) {
642
                // TODO Auto-generated catch block
643
                e.printStackTrace();
644
            }
645
        } else {
646
            logger.info("verifySystemResources only implemented for linux");
647
        }
648
    }
649

    
650

    
651
    /**
652
     * Configures and adds a {@link RollingFileAppender} to the root logger
653
     *
654
     * The log files of the cdm-remote instances are configured by the
655
     * {@link eu.etaxonomy.cdm.api.config.LoggingConfigurer}
656
     *
657
     *
658
     */
659
//   ===== removing useless RollingFileAppender logger === see #6287
660
//    private void configureFileLogger() {
661
//
662
//        PatternLayout layout = new PatternLayout("%d %p [%c] - %m%n");
663
//        try {
664
//            String logFile = logPath + File.separator + "cdmserver.log";
665
//            RollingFileAppender appender = new RollingFileAppender(layout, logFile);
666
//            appender.setMaxBackupIndex(3);
667
//            appender.setMaxFileSize("2MB");
668
//            Logger.getRootLogger().addAppender(appender);
669
//            logger.info("logging to :" + logFile);
670
//        } catch (IOException e) {
671
//            logger.error("Creating RollingFileAppender failed:", e);
672
//        }
673
//    }
674

    
675

    
676
    /**
677
     * Adds a new WebAppContext to the contexts of the running jetty instance.
678
     * <ol>
679
     * <li>Initialize WebAppContext:
680
     * <ol>
681
     * <li>set context path</li>
682
     * <li>set tmp directory</li>
683
     * <li>bind JndiDataSource</li>
684
     * <li>set web app context attributes</li>
685
     * <li>create and setup individual classloader for the instance</li>
686
     * </ol>
687
     * </li>
688
     * <li>
689
     * finally add the new webappcontext to the contexts of the jetty instance</li>
690
     * </ol>
691
     *
692
     * @param instance
693
     * @return the instance given as parameter of null in case the instance has
694
     *         {@link Status.#disabled} or if it is already added.
695
     * @throws IOException
696
     */
697
    public WebAppContext addCdmInstanceContext(CdmInstance instance) throws IOException {
698

    
699
        Configuration conf = instance.getConfiguration();
700
        if(!instance.isEnabled()){
701
            logger.info(conf.getInstanceName() + " is disabled, possibly due to JVM memory limitations");
702
            return null;
703
        }
704
        if(getContextFor(conf) != null){
705
            logger.info(conf.getInstanceName() + " is alreaddy added to the contexts - skipping");
706
            return null;
707
        }
708

    
709
        instance.setStatus(Status.initializing);
710
        logger.info("preparing WebAppContext for '"+ conf.getInstanceName() + "'");
711
        WebAppContext cdmWebappContext = new WebAppContext();
712

    
713
        cdmWebappContext.setContextPath(constructContextPath(conf));
714
        logger.info("contextPath: " + cdmWebappContext.getContextPath());
715
        // set persistTempDirectory to prevent jetty from creating and deleting this directory for each instance,
716
        // since this behaviour can cause conflicts during parallel start up  of instances.
717
        cdmWebappContext.setPersistTempDirectory(true);
718

    
719

    
720
//        if(!instance.bindJndiDataSource()){
721
//            // a problem with the datasource occurred skip this webapp
722
//            cdmWebappContext = null;
723
//            logger.error("a problem with the datasource occurred -> skipping /" + conf.getInstanceName());
724
//            instance.setStatus(Status.error);
725
//            return cdmWebappContext;
726
//        }
727

    
728
        cdmWebappContext.setInitParameter(SharedAttributes.ATTRIBUTE_DATASOURCE_NAME, conf.getInstanceName());
729
        cdmWebappContext.setInitParameter(SharedAttributes.ATTRIBUTE_JDBC_JNDI_NAME, conf.getJdbcJndiName());
730
        if(cmdLine.hasOption(FORCE_SCHEMA_UPDATE.getOpt())){
731
            cdmWebappContext.setInitParameter(SharedAttributes.ATTRIBUTE_FORCE_SCHEMA_UPDATE, "true");
732
        }
733
        setWebApp(cdmWebappContext, getCdmRemoteWebAppFile());
734

    
735
        cdmWebappContext.setInitParameter(SharedAttributes.ATTRIBUTE_CDM_LOGFILE,
736
                logPath + File.separator + "cdm-"
737
                        + conf.getInstanceName() + ".log");
738

    
739
        if( isRunningFromSource ){
740

    
741
            /*
742
             * when running the webapp from {projectpath} src/main/webapp we
743
             * must assure that each web application is using it's own
744
             * classloader thus we tell the WebAppClassLoader where the
745
             * dependencies of the webapplication can be found. Otherwise
746
             * the system classloader would load these resources.
747
             */
748
            WebAppClassLoader classLoader = new WebAppClassLoader(cdmWebappContext);
749
            if(webAppClassPath != null){
750
                logger.info("Running cdm-webapp from source folder: Adding class path supplied by option '-" +  WEBAPP_CLASSPATH.getOpt() +" =" + webAppClassPath +"'  to WebAppClassLoader");
751
                classLoader.addClassPath(webAppClassPath);
752
            } else {
753
                throw new RuntimeException("Classpath cdm-webapp for missing while running cdm-webapp from source folder. Please supplied cdm-server option '-" +  WEBAPP_CLASSPATH.getOpt() +"");
754
            }
755
            cdmWebappContext.setClassLoader(classLoader);
756
        }
757

    
758
        contexts.addHandler(cdmWebappContext);
759
        instance.setWebAppContext(cdmWebappContext);
760
        cdmWebappContext.addLifeCycleListener(instance);
761
        instance.setStatus(Status.stopped);
762

    
763
        return cdmWebappContext;
764
    }
765

    
766
    /**
767
     * @param conf
768
     * @return
769
     */
770
    public String constructContextPath(Configuration conf) {
771

    
772
        return "/" + contextPathPrefix + conf.getInstanceName();
773
    }
774

    
775
    /**
776
     * Removes the given instance from the contexts. If the instance is running
777
     * at the time of calling this method it will be stopped first.
778
     * The JndiDataSource and the webapplicationContext will be released and removed.
779
     *
780
     * @param instance the instance to be removed
781
     *
782
     * @throws Exception in case stopping the instance fails
783
     */
784
    public void removeCdmInstanceContext(CdmInstance instance) throws Exception {
785

    
786
        if(instance.getWebAppContext() != null){
787
            if(instance.getWebAppContext().isRunning()){
788
                try {
789
                    instance.getWebAppContext().stop();
790
                } catch (Exception e) {
791
                    instance.getProblems().add("Error while stopping instance: " + e.getMessage());
792
                    throw e;
793
                }
794
            }
795
            contexts.removeHandler(instance.getWebAppContext());
796
            instance.releaseWebAppContext();
797
        } else  {
798
            // maybe something went wrong before, try to find the potentially lost
799
            // contexts directly in the server
800
            ContextHandler handler = getContextFor(instance.getConfiguration());
801
            if(handler != null){
802
                contexts.removeHandler(handler);
803
            }
804
        }
805
        instance.unbindJndiDataSource();
806
    }
807

    
808
    /**
809
     * Sets the webapp specified by the <code>webApplicationResource</code> to
810
     * the given <code>context</code>.
811
     *
812
     * @param context
813
     * @param webApplicationResource the resource can either be a directory containing
814
     * a Java web application or *.war file.
815
     *
816
     */
817
    private void setWebApp(WebAppContext context, File webApplicationResource) {
818
        if(webApplicationResource.isDirectory()){
819
            context.setResourceBase(webApplicationResource.getAbsolutePath());
820
            logger.debug("setting directory " + webApplicationResource.getAbsolutePath() + " as webapplication");
821
        } else {
822
            context.setWar(webApplicationResource.getAbsolutePath());
823
            logger.debug("setting war file " + webApplicationResource.getAbsolutePath() + " as webapplication");
824
        }
825
    }
826

    
827
    private void updateServerRunMode() {
828
        String webappPathNormalized = cdmRemoteWebAppFile.getAbsolutePath().replace('\\', '/');
829
        isRunningFromSource =  webappPathNormalized.endsWith("src/main/webapp");
830
        isRunningfromTargetFolder = webappPathNormalized.endsWith("cdm-webapp/target/cdmserver");
831
        isRunningFromWarFile = !(isRunningFromSource || isRunningfromTargetFolder);
832
    }
833

    
834

    
835
    public Server getServer() {
836
        return server;
837
    }
838

    
839
    public ContextHandler getContextFor(Configuration conf) {
840
        return getContextFor(constructContextPath(conf));
841
    }
842

    
843
    public ContextHandler getContextFor(String contextPath) {
844
        for( Handler handler : contexts.getHandlers()){
845
            if(handler instanceof ContextHandler){
846
                if(((ContextHandler)handler).getContextPath().equals(contextPath)){
847
                    return (ContextHandler)handler;
848
                }
849
            }
850
        }
851
        return null;
852
    }
853

    
854
    public ContextHandlerCollection getContexts() {
855
        return contexts;
856
    }
857

    
858
    /**
859
     * @return a File object pointing to the location of the cdm-webapp
860
     */
861
    public File getCdmRemoteWebAppFile(){
862
        if(cdmRemoteWebAppFile == null){
863
            throw new RuntimeException("Invalid order of action. Server not yet started. The startServer() method must be called first. ");
864
        }
865
        return cdmRemoteWebAppFile;
866
    }
867
}
(2-2/6)