Project

General

Profile

Download (34.1 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.WEBAPP;
20
import static eu.etaxonomy.cdm.server.CommandOptions.WEBAPP_CLASSPATH;
21
import static eu.etaxonomy.cdm.server.CommandOptions.WIN32SERVICE;
22

    
23
import java.io.BufferedReader;
24
import java.io.File;
25
import java.io.FileNotFoundException;
26
import java.io.FileOutputStream;
27
import java.io.FilenameFilter;
28
import java.io.IOException;
29
import java.io.InputStream;
30
import java.io.InputStreamReader;
31
import java.io.OutputStream;
32
import java.lang.management.ManagementFactory;
33
import java.lang.reflect.InvocationTargetException;
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.logging.LoggingConfigurator;
71
import eu.etaxonomy.cdm.server.win32service.Win32Service;
72

    
73

    
74
/**
75
 * A bootstrap class for starting Jetty Runner using an embedded war.
76
 *
77
 * @version $Revision$
78
 */
79
public final class Bootloader {
80

    
81
    private static final Logger logger = Logger.getLogger(Bootloader.class);
82

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

    
85
    private static final String DATASOURCE_BEANDEF_FILE = "datasources.xml";
86
    private static final String REALM_PROPERTIES_FILE = "cdm-server-realm.properties";
87

    
88
    private static final String USERHOME_CDM_LIBRARY_PATH = System.getProperty("user.home")+File.separator+".cdmLibrary"+File.separator;
89
    private static final String TMP_PATH = USERHOME_CDM_LIBRARY_PATH + "server" + File.separator;
90
    private static final String LOG_PATH = USERHOME_CDM_LIBRARY_PATH + "log" + File.separator;
91

    
92
    private static final String APPLICATION_NAME = "CDM Server";
93
    private static final String WAR_POSTFIX = ".war";
94

    
95
    private static final String CDM_WEBAPP = "cdm-webapp";
96
    private static final String CDM_WEBAPP_VERSION = "cdm-webapp.version";
97

    
98
    private static final String DEFAULT_WEBAPP_WAR_NAME = "default-webapp";
99
    private static final File DEFAULT_WEBAPP_TEMP_FOLDER = new File(TMP_PATH + DEFAULT_WEBAPP_WAR_NAME);
100
    private static final File CDM_WEBAPP_TEMP_FOLDER = new File(TMP_PATH + CDM_WEBAPP);
101

    
102
    private static final String SPRING_PROFILES_ACTIVE = "spring.profiles.active";
103
    private static final String VERSION_PROPERTIES_FILE = "version.properties";
104

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

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

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

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

    
118
    private String webAppClassPath = null;
119

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

    
126
    private Server server = null;
127
    private final ContextHandlerCollection contexts = new ContextHandlerCollection();
128
    private final LoggingConfigurator loggingConfigurator = new LoggingConfigurator();
129

    
130
    private CommandLine cmdLine;
131

    
132
    private boolean isRunningFromSource;
133

    
134
    private boolean isRunningfromTargetFolder;
135

    
136
    private boolean isRunningFromWarFile;
137

    
138
    private String cdmlibServicesVersion = "";
139
    private String cdmlibServicesLastModified = "";
140

    
141

    
142
    /* thread save singleton implementation */
143

    
144
    private static Bootloader bootloader = new Bootloader();
145

    
146
    private Bootloader() {}
147

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

    
155
    /* end of singleton implementation */
156

    
157

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

    
178

    
179

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

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

    
193

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

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

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

    
232

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

    
238

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

    
242
        writeStreamTo(resource.openStream(), new FileOutputStream(extractedWarFile), 8 * KB);
243

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

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

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

    
301

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

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

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

    
323

    
324

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

    
333
        Bootloader bootloader = Bootloader.getBootloader();
334

    
335
        bootloader.parseCommandOptions(args);
336

    
337
        bootloader.startServer();
338
    }
339

    
340

    
341

    
342
    public void startServer() throws IOException,
343
            FileNotFoundException, Exception, InterruptedException {
344

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

    
348
        //assure TMP_PATH exists and clean it up
349
        File tempDir = new File(TMP_PATH);
350
        if(!tempDir.exists() && !tempDir.mkdirs()){
351
            logger.error("Error creating temporary directory for webapplications " + tempDir.getAbsolutePath());
352
            System.exit(-1);
353
        } else {
354
            if(FileUtils.deleteQuietly(tempDir)){
355
                tempDir.mkdirs();
356
                logger.info("Old webapplications successfully cleared");
357
            }
358
        }
359
        tempDir = null;
360

    
361

    
362
        // WEBAPP options
363
        //   prepare web application files to run either from war files (production mode)
364
        //   or from source (development mode)
365
        if(cmdLine.hasOption(WEBAPP.getOpt())){
366

    
367
            cdmRemoteWebAppFile = new File(cmdLine.getOptionValue(WEBAPP.getOpt()));
368
            if(cdmRemoteWebAppFile.isDirectory()){
369
                logger.info("using user defined web application folder: " + cdmRemoteWebAppFile.getAbsolutePath());
370
            } else {
371
                logger.info("using user defined warfile: " + cdmRemoteWebAppFile.getAbsolutePath());
372
            }
373

    
374
            updateServerRunMode();
375

    
376
            // load the default-web-application from source if running in development mode mode
377
            if(isRunningFromWarFile){
378
                defaultWebAppFile = extractWar(DEFAULT_WEBAPP_WAR_NAME, false);
379
            } else {
380
                defaultWebAppFile = new File("./src/main/webapp");
381
            }
382

    
383
            if(isRunningFromSource){
384
                if(cmdLine.hasOption(WEBAPP_CLASSPATH.getOpt())){
385
                    String classPathOption = cmdLine.getOptionValue(WEBAPP_CLASSPATH.getOpt());
386
                    normalizeClasspath(classPathOption);
387
                }
388
            }
389
        } else {
390
            // read version number
391
            String version = readCdmRemoteVersion();
392
            cdmRemoteWebAppFile = extractWar(CDM_WEBAPP + "-" + version, true);
393
            defaultWebAppFile = extractWar(DEFAULT_WEBAPP_WAR_NAME, false);
394
        }
395

    
396

    
397
        // HTTP Port
398
        int httpPort = 8080;
399
        if(cmdLine.hasOption(HTTP_PORT.getOpt())){
400
            try {
401
               httpPort = Integer.parseInt(cmdLine.getOptionValue(HTTP_PORT.getOpt()));
402
               logger.info(HTTP_PORT.getOpt()+" set to "+cmdLine.getOptionValue(HTTP_PORT.getOpt()));
403
           } catch (NumberFormatException e) {
404
               logger.error("Supplied portnumber is not an integer");
405
               System.exit(-1);
406
           }
407
        }
408

    
409
        if(cmdLine.hasOption(DATASOURCES_FILE.getOpt())){
410
             File datasourcesFile = new File(cmdLine.getOptionValue(DATASOURCES_FILE.getOpt()));
411
             if(datasourcesFile.canRead()) {
412
                instanceManager.setDatasourcesFile(datasourcesFile);
413
            } else {
414
                logger.error("File set as " + DATASOURCES_FILE.getOpt()
415
                        + " (" + cmdLine.getOptionValue(DATASOURCES_FILE.getOpt())
416
                        + ") is not readable.");
417
            }
418
         }
419

    
420
        if(cmdLine.hasOption(CONTEXT_PATH_PREFIX.getOpt())){
421

    
422
            String cppo  = cmdLine.getOptionValue(CONTEXT_PATH_PREFIX.getOpt());
423
            if(cppo.equals("/")) {
424
                this.contextPathPrefix = "";
425
            } else {
426
                Pattern pattern = Pattern.compile("^/*(.*?)/*$");
427
                String replacement = "$1";
428
                this.contextPathPrefix = pattern.matcher(cppo).replaceAll(replacement) + "/";
429
            }
430
        }
431

    
432
        verifySystemResources();
433

    
434
         // load the configured instances for the first time
435
        instanceManager.reLoadInstanceConfigurations();
436

    
437
        if(System.getProperty(SPRING_PROFILES_ACTIVE) == null){
438
            logger.info(SPRING_PROFILES_ACTIVE + " is undefined, and will be set to : \"remoting\"");
439
            System.setProperty(SPRING_PROFILES_ACTIVE, "remoting");
440
        }
441

    
442
        // in jetty 9 currently each connector uses
443
        // 2 threads -  1 to select for IO activity and 1 to accept new connections.
444
        // there fore we need to add 2 to the number of cores
445
//        QueuedThreadPool threadPool = new QueuedThreadPool(JvmManager.availableProcessors() +  + 200);
446
//        server = new Server(threadPool);
447
        server = new Server();
448

    
449
        jdk8MemleakFixServer();
450

    
451
        loggingConfigurator.configureServer();
452

    
453
        server.addLifeCycleListener(instanceManager);
454
        ServerConnector connector = new ServerConnector(server);
455
        connector.setPort(httpPort);
456
        logger.info("http port: " + connector.getPort());
457
        server.addConnector(connector );
458

    
459
        org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server);
460
        classlist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration", "org.eclipse.jetty.plus.webapp.EnvConfiguration", "org.eclipse.jetty.plus.webapp.PlusConfiguration");
461
        classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", "org.eclipse.jetty.annotations.AnnotationConfiguration");
462

    
463

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

    
472
        if(cmdLine.hasOption(WIN32SERVICE.getOpt())){
473
            Win32Service win32Service = new Win32Service();
474
            win32Service.setServer(server);
475
            server.setStopAtShutdown(true);
476
            server.addBean(win32Service);
477
        }
478

    
479
        // add default servlet context
480
        logger.info("preparing default WebAppContext");
481
        WebAppContext defaultWebappContext = new WebAppContext();
482

    
483
        setWebApp(defaultWebappContext, defaultWebAppFile);
484

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

    
497
        defaultWebappContext.setAttribute("org.eclipse.jetty.containerInitializers", jspInitializers());
498

    
499
        // Context path
500
        //
501
        defaultWebappContext.setContextPath("/" + (contextPathPrefix.isEmpty() ? "" : contextPathPrefix.substring(0, contextPathPrefix.length() - 1)));
502
        logger.info("defaultWebapp (manager) context path:" + defaultWebappContext.getContextPath());
503
        defaultWebappContext.setTempDirectory(DEFAULT_WEBAPP_TEMP_FOLDER);
504

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

    
512
        // Important:
513
        // the defaultWebappContext MUST USE the super classloader
514
        // otherwise the status page (index.jsp) might not work
515
        defaultWebappContext.setClassLoader(this.getClass().getClassLoader());
516
        contexts.addHandler(defaultWebappContext);
517

    
518
        logger.info("setting contexts ...");
519
        server.setHandler(contexts);
520
        // server.setContexts(contexts);
521

    
522
        logger.info("starting jetty ...");
523
//        try {
524

    
525
            server.start();
526

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

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

    
550
    /**
551
     * jdk8 memleak workaround: disable url caching
552
     *  see https://dev.e-taxonomy.eu/redmine/issues/5048
553
     *
554
     * @throws IOException
555
     * @throws MalformedURLException
556
     */
557
    private void jdk8MemleakFixServer() throws IOException, MalformedURLException {
558
        String javaVersion = System.getProperty("java.version");
559
        if(javaVersion.startsWith("1.8")){
560
            logger.info("jdk8 memory leak fix: jdk8 detected (" + javaVersion + ") disabling url caching to avoid memory leak.");
561
            org.eclipse.jetty.util.resource.Resource.setDefaultUseCaches(false);
562
            File tmpio = new File(System.getProperty("java.io.tmpdir"));
563
            tmpio.toURI().toURL().openConnection().setDefaultUseCaches(false);
564
        } else {
565
            logger.info("jdk8 memory leak fix: unaffected jdk " + javaVersion + " detected");
566
        }
567
    }
568

    
569
    private void jdk8MemleakFixInstance(ClassLoader classLoader, CdmInstance instance) throws IOException, MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
570
        String javaVersion = System.getProperty("java.version");
571
        if(javaVersion.startsWith("1.8")){
572
            logger.info("jdk8 memory leak fix for " + instance.getName() + ": jdk8 detected (" + javaVersion + ") disabling url caching to avoid memory leak.");
573
            Class<?> fileClass = classLoader.loadClass("java.io.File");
574
            File tmpio = (File) fileClass.getConstructor(String.class).newInstance("java.io.tmpdir");
575
            tmpio.toURI().toURL().openConnection().setDefaultUseCaches(false);
576
        } else {
577
            logger.info("jdk8 memory leak fix, " + instance.getName() + "unaffected jdk " + javaVersion + " detected");
578
        }
579
    }
580

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

    
597
    public String readCdmRemoteVersion() throws IOException {
598
        String version = "cdmlib version unreadable";
599
        InputStream versionInStream = Bootloader.class.getClassLoader().getResourceAsStream(VERSION_PROPERTIES_FILE);
600
        if (versionInStream != null){
601
            Properties versionProperties = new Properties();
602
            versionProperties.load(versionInStream);
603
            version = versionProperties.getProperty(CDM_WEBAPP_VERSION, version);
604
        }
605
        return version;
606
    }
607

    
608
    /**
609
    * Ensure the jsp engine is initialized correctly
610
    */
611
    private List<ContainerInitializer> jspInitializers()
612
    {
613
        JettyJasperInitializer sci = new JettyJasperInitializer();
614
        ContainerInitializer initializer = new ContainerInitializer(sci, null);
615
        List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
616
        initializers.add(initializer);
617
        return initializers;
618
    }
619

    
620

    
621

    
622
    private void verifySystemResources() {
623

    
624
        OsChecker osChecker = new OsChecker();
625
        if(osChecker.isLinux()){
626
            try {
627
                Process p = Runtime.getRuntime().exec(new String[] { "bash", "-c", "ulimit -n" });
628
                BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
629
                String line;
630
                StringBuilder response = new StringBuilder();
631
                     while ((line = in.readLine()) != null) {
632
                         response.append(line);
633
                     }
634
               logger.info("OS Limit (Linux): maximum number of open files: " + response);
635
            } catch (IOException e) {
636
                // TODO Auto-generated catch block
637
                e.printStackTrace();
638
            }
639
        } else {
640
            logger.info("verifySystemResources only implemented for linux");
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.setInitParameter(SharedAttributes.ATTRIBUTE_DATASOURCE_NAME, conf.getInstanceName());
698
        cdmWebappContext.setInitParameter(SharedAttributes.ATTRIBUTE_JDBC_JNDI_NAME, conf.getJdbcJndiName());
699
        if(cmdLine.hasOption(FORCE_SCHEMA_UPDATE.getOpt())){
700
            cdmWebappContext.setInitParameter(SharedAttributes.ATTRIBUTE_FORCE_SCHEMA_UPDATE, "true");
701
        }
702
        setWebApp(cdmWebappContext, getCdmRemoteWebAppFile());
703

    
704
        if( isRunningFromSource ){
705

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

    
730
        contexts.addHandler(loggingConfigurator.configureWebApp(cdmWebappContext, instance));
731
        instance.setWebAppContext(cdmWebappContext);
732
        cdmWebappContext.addLifeCycleListener(instance);
733
        instance.setStatus(Status.stopped);
734

    
735
        return cdmWebappContext;
736
    }
737

    
738
    /**
739
     * @param conf
740
     * @return
741
     */
742
    public String constructContextPath(Configuration conf) {
743

    
744
        return "/" + contextPathPrefix + conf.getInstanceName();
745
    }
746

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

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

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

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

    
806

    
807
    public Server getServer() {
808
        return server;
809
    }
810

    
811
    public ContextHandler getContextFor(Configuration conf) {
812
        return getContextFor(constructContextPath(conf));
813
    }
814

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

    
826
    public ContextHandlerCollection getContexts() {
827
        return contexts;
828
    }
829

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