Project

General

Profile

Download (36.5 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
package eu.etaxonomy.cdm.server;
10

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

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

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

    
69
import eu.etaxonomy.cdm.server.instance.CdmInstance;
70
import eu.etaxonomy.cdm.server.instance.Configuration;
71
import eu.etaxonomy.cdm.server.instance.InstanceManager;
72
import eu.etaxonomy.cdm.server.instance.SharedAttributes;
73
import eu.etaxonomy.cdm.server.instance.Status;
74
import eu.etaxonomy.cdm.server.logging.LoggingConfigurator;
75
import eu.etaxonomy.cdm.server.win32service.Win32Service;
76

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

    
84
    private static final Logger logger = Logger.getLogger(Bootloader.class);
85

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

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

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

    
94
    private static final String APPLICATION_NAME = "CDM Server";
95
    private static final String WAR_POSTFIX = ".war";
96

    
97
    private static final String CDM_WEBAPP = "cdm-webapp";
98
    private static final String CDM_WEBAPP_VERSION = "cdm-webapp.version";
99

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

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

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

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

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

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

    
120
    private String webAppClassPath = null;
121

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

    
128
    private Server server = null;
129
    private final ContextHandlerCollection contexts = new ContextHandlerCollection();
130
    private final LoggingConfigurator loggingConfigurator = new LoggingConfigurator();
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
    public int writeStreamTo(final InputStream input, final OutputStream output, int bufferSize) throws IOException {
160
        int available = Math.min(input.available(), 256 * KB);
161
        byte[] buffer = new byte[Math.max(bufferSize, available)];
162
        int answer = 0;
163
        int count = input.read(buffer);
164
        while (count >= 0) {
165
            output.write(buffer, 0, count);
166
            answer += count;
167
            count = input.read(buffer);
168
        }
169
        return answer;
170
    }
171

    
172
    public void parseCommandOptions(String[] args) throws ParseException {
173
        CommandLineParser parser = new GnuParser();  //TODO using DefaultParser instead currently still throws "org.apache.commons.cli.UnrecognizedOptionException: Unrecognized option: -httpPort=8080"
174

    
175
        cmdLine = parser.parse( CommandOptions.getOptions(), args );
176

    
177
        // print the help message
178
        if(cmdLine.hasOption(HELP.getOpt())){
179
            HelpFormatter formatter = new HelpFormatter();
180
            formatter.setWidth(200);
181
            formatter.printHelp( "java .. ", CommandOptions.getOptions() );
182
            System.exit(0);
183
        }
184
    }
185

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

    
204
        // 1. find in classpath
205
        URL resource = classLoader.getResource(warFileName);
206
        if (resource == null) {
207
            logger.error("Could not find the " + warFileName + " on classpath!");
208

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

    
224

    
225
        if (resource == null) {
226
            // no way finding the war file :-(
227
            System.exit(1);
228
        }
229

    
230

    
231
        File extractedWarFile = new File(TMP_PATH, warName + "-" + WAR_POSTFIX);
232
        logger.info("Extracting " + resource + " to " + extractedWarFile + " ...");
233

    
234
        writeStreamTo(resource.openStream(), new FileOutputStream(extractedWarFile), 8 * KB);
235

    
236
        if(!unpack) {
237
            // return the war file
238
            return extractedWarFile;
239
        } else {
240
            // unpack the archive
241
            File explodedWebApp = null;
242
            try {
243
                logger.info("Unpacking " + extractedWarFile);
244
                explodedWebApp  = unzip(extractedWarFile);
245

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

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

    
293

    
294
    public String getCdmlibServicesVersion() {
295
        return cdmlibServicesVersion;
296
    }
297

    
298
    public String getCdmlibServicesLastModified() {
299
        return cdmlibServicesLastModified;
300
    }
301

    
302
    private File unzip(File extractWar) throws IOException {
303
        UnzipUtility unzip = new UnzipUtility();
304

    
305
        String targetFolderName = FilenameUtils.getBaseName(extractWar.getName());
306
        File destDirectory = new File(TMP_PATH + File.separator + targetFolderName);
307
        unzip.unzip(extractWar, destDirectory);
308
        return destDirectory;
309
    }
310

    
311
    /**
312
     * MAIN METHOD
313
     *
314
     * @param args
315
     * @throws Exception
316
     */
317
    public static void main(String[] args) throws Exception {
318

    
319
        Bootloader bootloader = Bootloader.getBootloader();
320
        bootloader.parseCommandOptions(args);
321
        bootloader.startServer();
322
    }
323

    
324
    public void startServer() throws IOException,
325
            FileNotFoundException, Exception, InterruptedException {
326

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

    
330
        //assure TMP_PATH exists and clean it up
331
        File tempDir = new File(TMP_PATH);
332
        if(!tempDir.exists() && !tempDir.mkdirs()){
333
            logger.error("Error creating temporary directory for webapplications " + tempDir.getAbsolutePath());
334
            System.exit(-1);
335
        } else {
336
            if(FileUtils.deleteQuietly(tempDir)){
337
                tempDir.mkdirs();
338
                logger.info("Old webapplications successfully cleared");
339
            }
340
        }
341
        tempDir = null;
342

    
343

    
344
        // WEBAPP options
345
        //   prepare web application files to run either from war files (production mode)
346
        //   or from source (development mode)
347
        if(cmdLine.hasOption(WEBAPP.getOpt())){
348

    
349
            cdmRemoteWebAppFile = new File(cmdLine.getOptionValue(WEBAPP.getOpt()));
350
            if(cdmRemoteWebAppFile.isDirectory()){
351
                logger.info("using user defined web application folder: " + cdmRemoteWebAppFile.getAbsolutePath());
352
            } else {
353
                logger.info("using user defined warfile: " + cdmRemoteWebAppFile.getAbsolutePath());
354
            }
355

    
356
            updateServerRunMode();
357

    
358
            // load the default-web-application from source if running in development mode mode
359
            if(isRunningFromWarFile){
360
                defaultWebAppFile = extractWar(DEFAULT_WEBAPP_WAR_NAME, false);
361
            } else {
362
                defaultWebAppFile = new File("./src/main/webapp");
363
            }
364

    
365
            if(isRunningFromSource){
366
                if(cmdLine.hasOption(WEBAPP_CLASSPATH.getOpt())){
367
                    String classPathOption = cmdLine.getOptionValue(WEBAPP_CLASSPATH.getOpt());
368
                    normalizeClasspath(classPathOption);
369
                }
370
            }
371
        } else {
372
            // read version number
373
            String version = readCdmRemoteVersion();
374
            cdmRemoteWebAppFile = extractWar(CDM_WEBAPP + "-" + version, true);
375
            defaultWebAppFile = extractWar(DEFAULT_WEBAPP_WAR_NAME, false);
376
        }
377

    
378

    
379
        // HTTP Port
380
        int httpPort = 8080;
381
        if(cmdLine.hasOption(HTTP_PORT.getOpt())){
382
            try {
383
               httpPort = Integer.parseInt(cmdLine.getOptionValue(HTTP_PORT.getOpt()));
384
               logger.info(HTTP_PORT.getOpt()+" set to "+cmdLine.getOptionValue(HTTP_PORT.getOpt()));
385
           } catch (NumberFormatException e) {
386
               logger.error("Supplied portnumber is not an integer");
387
               System.exit(-1);
388
           }
389
        }
390

    
391
        if(cmdLine.hasOption(DATASOURCES_FILE.getOpt())){
392
             File datasourcesFile = new File(cmdLine.getOptionValue(DATASOURCES_FILE.getOpt()));
393
             if(datasourcesFile.canRead()) {
394
                instanceManager.setDatasourcesFile(datasourcesFile);
395
            } else {
396
                logger.error("File set as " + DATASOURCES_FILE.getOpt()
397
                        + " (" + cmdLine.getOptionValue(DATASOURCES_FILE.getOpt())
398
                        + ") is not readable.");
399
            }
400
         }
401

    
402
        if(cmdLine.hasOption(CONTEXT_PATH_PREFIX.getOpt())){
403

    
404
            String cppo  = cmdLine.getOptionValue(CONTEXT_PATH_PREFIX.getOpt());
405
            if(cppo.equals("/")) {
406
                this.contextPathPrefix = "";
407
            } else {
408
                Pattern pattern = Pattern.compile("^/*(.*?)/*$");
409
                String replacement = "$1";
410
                this.contextPathPrefix = pattern.matcher(cppo).replaceAll(replacement) + "/";
411
            }
412
        }
413

    
414
        verifySystemResources();
415

    
416
         // load the configured instances for the first time
417
        instanceManager.reLoadInstanceConfigurations();
418

    
419
        if(System.getProperty(SPRING_PROFILES_ACTIVE) == null){
420
            logger.info(SPRING_PROFILES_ACTIVE + " is undefined, and will be set to : \"remoting\"");
421
            System.setProperty(SPRING_PROFILES_ACTIVE, "remoting");
422
        }
423

    
424
        // in jetty 9 currently each connector uses
425
        // 2 threads -  1 to select for IO activity and 1 to accept new connections.
426
        // there fore we need to add 2 to the number of cores
427
//        QueuedThreadPool threadPool = new QueuedThreadPool(JvmManager.availableProcessors() +  + 200);
428
//        server = new Server(threadPool);
429
        server = new Server();
430

    
431
        jdk8MemleakFixServer();
432

    
433
        loggingConfigurator.configureServer();
434

    
435
        server.addLifeCycleListener(instanceManager);
436
        ServerConnector connector = new ServerConnector(server);
437
        connector.setPort(httpPort);
438
        logger.info("http port: " + connector.getPort());
439
        server.addConnector(connector );
440

    
441
        org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server);
442
        classlist.addAfter(
443
                org.eclipse.jetty.webapp.FragmentConfiguration.class.getName(),
444
                org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getName(),
445
                org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getName()
446
                );
447
        classlist.addBefore(
448
                org.eclipse.jetty.webapp.JettyWebXmlConfiguration.class.getName(),
449
                org.eclipse.jetty.annotations.AnnotationConfiguration.class.getName());
450

    
451

    
452
        // JMX support
453
        if(cmdLine.hasOption(JMX.getOpt())){
454
            logger.info("adding JMX support ...");
455
            MBeanContainer mBeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
456
            server.addEventListener(mBeanContainer);
457
            server.addBean(Log.getLog());
458
        }
459

    
460
        if(cmdLine.hasOption(WIN32SERVICE.getOpt())){
461
            Win32Service win32Service = new Win32Service();
462
            win32Service.setServer(server);
463
            server.setStopAtShutdown(true);
464
            server.addBean(win32Service);
465
        }
466

    
467
        WebAppContext defaultWebappContext = createDefaultWebappContext();
468
        contexts.addHandler(defaultWebappContext);
469

    
470
        logger.info("setting contexts ...");
471
        server.setHandler(contexts);
472
        logger.info("starting jetty ...");
473
//        try {
474

    
475
            server.start();
476

    
477
//        } catch(org.springframework.beans.BeansException e){
478
//        	Throwable rootCause = null;
479
//        	while(e.getCause() != null){
480
//        		rootCause = e.getCause();
481
//        	}
482
//        	if(rootCause != null && rootCause.getClass().getSimpleName().equals("InvalidCdmVersionException")){
483
//
484
//        		logger.error("rootCause ----------->" + rootCause.getMessage());
485
////        		for(CdmInstanceProperties props : configAndStatus){
486
////        			if(props.getDataSourceName())
487
////        		}
488
//        	}
489
//        }
490

    
491
        if(cmdLine.hasOption(WIN32SERVICE.getOpt())){
492
            logger.info("jetty has started as win32 service");
493
        } else {
494
            server.join();
495
            logger.info(APPLICATION_NAME+" stopped.");
496
            System.exit(0);
497
        }
498
    }
499

    
500
    private WebAppContext createDefaultWebappContext() throws FileNotFoundException {
501
        // add default servlet context
502
        logger.info("preparing default WebAppContext");
503

    
504
        WebAppContext defaultWebappContext = new WebAppContext();
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
        defaultWebappContext.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager());
521

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

    
528
        // configure security context
529
        // see for reference * http://docs.codehaus.org/display/JETTY/Realms
530
        //                   * http://wiki.eclipse.org/Jetty/Starting/Porting_to_Jetty_7
531
        HashLoginService loginService = new HashLoginService();
532
        File realmConfigFile = new File(USERHOME_CDM_LIBRARY_PATH + REALM_PROPERTIES_FILE);
533
        if(!realmConfigFile.canRead()) {
534
            throw new FileNotFoundException("Unable to find or read the realm file at " + realmConfigFile.getPath());
535
        }
536
        loginService.setConfig(realmConfigFile.getPath());
537
        defaultWebappContext.getSecurityHandler().setLoginService(loginService);
538

    
539
        // Set Classloader of Context to be sane (needed for JSTL)
540
        // JSP requires a non-System classloader, this simply wraps the
541
        // embedded System classloader in a way that makes it suitable
542
        // for JSP to use
543
        ClassLoader jspClassLoader = new URLClassLoader(new URL[0], this.getClass().getClassLoader());
544
        defaultWebappContext.setClassLoader(this.getClass().getClassLoader());
545
        // JspStarter to solve java.lang.IllegalStateException: No org.apache.tomcat.InstanceManager set in ServletContext problems
546
        // when running not from within the IDE (see https://issues.apache.org/jira/browse/KNOX-1639)
547
        defaultWebappContext.addBean(new JspStarter(defaultWebappContext));
548
        return defaultWebappContext;
549
    }
550

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

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

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

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

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

    
621
    /**
622
     * JspStarter for embedded ServletContextHandlers
623
     *
624
     * This is added as a bean that is a jetty LifeCycle on the ServletContextHandler.
625
     * This bean's doStart method will be called as the ServletContextHandler starts,
626
     * and will call the ServletContainerInitializer for the jsp engine.
627
     *
628
     */
629
    public static class JspStarter extends AbstractLifeCycle implements ServletContextHandler.ServletContainerInitializerCaller {
630
      JettyJasperInitializer sci;
631
      ServletContextHandler context;
632

    
633
      public JspStarter (ServletContextHandler context) {
634
        this.sci = new JettyJasperInitializer();
635
        this.context = context;
636
        this.context.setAttribute("org.apache.tomcat.JarScanner", new StandardJarScanner());
637
      }
638

    
639
      @Override
640
      protected void doStart() throws Exception
641
      {
642
        ClassLoader old = Thread.currentThread().getContextClassLoader();
643
        Thread.currentThread().setContextClassLoader(context.getClassLoader());
644
        try
645
        {
646
          sci.onStartup(null, context.getServletContext());
647
          super.doStart();
648
        }
649
        finally
650
        {
651
          Thread.currentThread().setContextClassLoader(old);
652
        }
653
      }
654
    }
655

    
656

    
657
    private void verifySystemResources() {
658

    
659
        OsChecker osChecker = new OsChecker();
660
        if(osChecker.isLinux()){
661
            try {
662
                Process p = Runtime.getRuntime().exec(new String[] { "bash", "-c", "ulimit -n" });
663
                BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
664
                String line;
665
                StringBuilder response = new StringBuilder();
666
                     while ((line = in.readLine()) != null) {
667
                         response.append(line);
668
                     }
669
               logger.info("OS Limit (Linux): maximum number of open files: " + response);
670
            } catch (IOException e) {
671
                // TODO Auto-generated catch block
672
                e.printStackTrace();
673
            }
674
        } else {
675
            logger.info("verifySystemResources only implemented for linux");
676
        }
677
    }
678

    
679

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

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

    
713
        instance.setStatus(Status.initializing);
714
        logger.info("preparing WebAppContext for '"+ conf.getInstanceName() + "'");
715
        WebAppContext cdmWebappContext = new WebAppContext();
716

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

    
723

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

    
732
        cdmWebappContext.setInitParameter(SharedAttributes.ATTRIBUTE_DATASOURCE_NAME, conf.getInstanceName());
733
        cdmWebappContext.setInitParameter(SharedAttributes.ATTRIBUTE_JDBC_JNDI_NAME, conf.getJdbcJndiName());
734
        if(cmdLine.hasOption(FORCE_SCHEMA_UPDATE.getOpt())){
735
            cdmWebappContext.setInitParameter(SharedAttributes.ATTRIBUTE_FORCE_SCHEMA_UPDATE, "true");
736
        }
737
        setWebApp(cdmWebappContext, getCdmRemoteWebAppFile());
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
                try {
753
                    jdk8MemleakFixInstance(classLoader, instance);
754
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
755
                        | IllegalArgumentException | InvocationTargetException | NoSuchMethodException
756
                        | SecurityException e) {
757
                    logger.error("Cannot apply jdk8MemleakFix to instance " + instance, e);
758
                }
759
            } else {
760
                throw new RuntimeException("Classpath cdm-webapp for missing while running cdm-webapp from source folder. Please supplied cdm-server option '-" +  WEBAPP_CLASSPATH.getOpt() +"");
761
            }
762
            cdmWebappContext.setClassLoader(classLoader);
763
        }
764

    
765
        contexts.addHandler(loggingConfigurator.configureWebApp(cdmWebappContext, instance));
766
        instance.setWebAppContext(cdmWebappContext);
767
        cdmWebappContext.addLifeCycleListener(instance);
768
        instance.setStatus(Status.stopped);
769

    
770
        return cdmWebappContext;
771
    }
772

    
773
    /**
774
     * @param conf
775
     * @return
776
     */
777
    public String constructContextPath(Configuration conf) {
778

    
779
        return "/" + contextPathPrefix + conf.getInstanceName();
780
    }
781

    
782
    /**
783
     * Removes the given instance from the contexts. If the instance is running
784
     * at the time of calling this method it will be stopped first.
785
     * The JndiDataSource and the webapplicationContext will be released and removed.
786
     *
787
     * @param instance the instance to be removed
788
     *
789
     * @throws Exception in case stopping the instance fails
790
     */
791
    public void removeCdmInstanceContext(CdmInstance instance) throws Exception {
792

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

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

    
834
    private void updateServerRunMode() {
835
        String webappPathNormalized = cdmRemoteWebAppFile.getAbsolutePath().replace('\\', '/');
836
        isRunningFromSource =  webappPathNormalized.endsWith("src/main/webapp");
837
        isRunningfromTargetFolder = webappPathNormalized.endsWith("cdm-webapp/target/cdmserver");
838
        isRunningFromWarFile = !(isRunningFromSource || isRunningfromTargetFolder);
839
    }
840

    
841

    
842
    public Server getServer() {
843
        return server;
844
    }
845

    
846
    public ContextHandler getContextFor(Configuration conf) {
847
        return getContextFor(constructContextPath(conf));
848
    }
849

    
850
    public ContextHandler getContextFor(String contextPath) {
851
        for( Handler handler : contexts.getHandlers()){
852
            if(handler instanceof ContextHandler){
853
                if(((ContextHandler)handler).getContextPath().equals(contextPath)){
854
                    return (ContextHandler)handler;
855
                }
856
            }
857
        }
858
        return null;
859
    }
860

    
861
    public ContextHandlerCollection getContexts() {
862
        return contexts;
863
    }
864

    
865
    /**
866
     * @return a File object pointing to the location of the cdm-webapp
867
     */
868
    public File getCdmRemoteWebAppFile(){
869
        if(cdmRemoteWebAppFile == null){
870
            throw new RuntimeException("Invalid order of action. Server not yet started. The startServer() method must be called first. ");
871
        }
872
        return cdmRemoteWebAppFile;
873
    }
874
}
(2-2/6)