Project

General

Profile

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

    
11
package eu.etaxonomy.cdm.server;
12

    
13
import static eu.etaxonomy.cdm.server.CommandOptions.DATASOURCES_FILE;
14
import static eu.etaxonomy.cdm.server.CommandOptions.HELP;
15
import static eu.etaxonomy.cdm.server.CommandOptions.HTTP_PORT;
16
import static eu.etaxonomy.cdm.server.CommandOptions.JMX;
17
import static eu.etaxonomy.cdm.server.CommandOptions.WEBAPP;
18
import static eu.etaxonomy.cdm.server.CommandOptions.WIN32SERVICE;
19
import static eu.etaxonomy.cdm.server.CommandOptions.LOG_DIR;
20

    
21
import java.io.File;
22
import java.io.FileNotFoundException;
23
import java.io.FileOutputStream;
24
import java.io.IOException;
25
import java.io.InputStream;
26
import java.io.OutputStream;
27
import java.lang.management.ManagementFactory;
28
import java.lang.reflect.InvocationTargetException;
29
import java.net.URL;
30
import java.sql.Connection;
31
import java.sql.SQLException;
32
import java.util.List;
33
import java.util.Properties;
34
import java.util.Set;
35

    
36
import javax.naming.NamingException;
37
import javax.sql.DataSource;
38

    
39
import org.apache.commons.cli.CommandLine;
40
import org.apache.commons.cli.CommandLineParser;
41
import org.apache.commons.cli.GnuParser;
42
import org.apache.commons.cli.HelpFormatter;
43
import org.apache.commons.cli.ParseException;
44
import org.apache.commons.io.FileUtils;
45
import org.apache.log4j.Logger;
46
import org.apache.log4j.PatternLayout;
47
import org.apache.log4j.RollingFileAppender;
48
import org.eclipse.jetty.jmx.MBeanContainer;
49
import org.eclipse.jetty.security.HashLoginService;
50
import org.eclipse.jetty.server.Server;
51
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
52
import org.eclipse.jetty.server.handler.ContextHandler.Context;
53
import org.eclipse.jetty.util.component.LifeCycle;
54
import org.eclipse.jetty.util.component.LifeCycle.Listener;
55
import org.eclipse.jetty.util.log.Log;
56
import org.eclipse.jetty.webapp.WebAppClassLoader;
57
import org.eclipse.jetty.webapp.WebAppContext;
58

    
59
import eu.etaxonomy.cdm.server.CdmInstanceProperties.Status;
60
import eu.etaxonomy.cdm.server.win32service.Win32Service;
61

    
62

    
63
/**
64
 * A bootstrap class for starting Jetty Runner using an embedded war.
65
 *
66
 * Recommended start options for the java virtual machine:
67
 * <pre>
68
 * -Xmx1024M
69
 *
70
 * -XX:PermSize=128m
71
 * -XX:MaxPermSize=192m
72
 *
73
 * -XX:+UseConcMarkSweepGC
74
 * -XX:+CMSClassUnloadingEnabled
75
 * -XX:+CMSPermGenSweepingEnabled
76
 * </pre>
77
 *
78
 * @version $Revision$
79
 */
80
public final class Bootloader {
81
    /**
82
     *
83
     */
84
    private static final String VERSION_PROPERTIES_FILE = "version.properties";
85

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

    
88

    
89

    
90
    /**
91
     * @author a.kohlbecker
92
     * @date 03.02.2011
93
     *
94
     */
95
    private class WebAppContextListener implements Listener {
96

    
97
        WebAppContext cdmWebappContext;
98
        /**
99
         * @param cdmWebappContext
100
         */
101
        public WebAppContextListener(WebAppContext cdmWebappContext) {
102
            this.cdmWebappContext = cdmWebappContext;
103
        }
104

    
105
        @Override
106
        public void lifeCycleStopping(LifeCycle event) {
107
            logger.info("lifeCycleStopping");
108
        }
109

    
110
        @Override
111
        public void lifeCycleStopped(LifeCycle event) {
112
            logger.info("lifeCycleStopped");
113

    
114
        }
115

    
116
        @Override
117
        public void lifeCycleStarting(LifeCycle event) {
118
            logger.info("lifeCycleStarting");
119
        }
120

    
121
        @SuppressWarnings("unchecked")
122
        @Override
123
        public void lifeCycleStarted(LifeCycle event) {
124
            logger.info("lifeCycleStarted");
125

    
126
            List<String> messages = getServletContextAttribute(cdmWebappContext, ATTRIBUTE_ERROR_MESSAGES, List.class);
127
            String dataSourceName = getServletContextAttribute(cdmWebappContext, ATTRIBUTE_DATASOURCE_NAME, String.class);
128

    
129
            if(messages != null && dataSourceName != null){
130
                CdmInstanceProperties configAndStatus = findConfigAndStatusFor(dataSourceName);
131
                configAndStatus.getProblems().addAll(messages);
132
                configAndStatus.setStatus(Status.error);
133
                try {
134
                    logger.warn("Stopping context '" + dataSourceName + "' due to errors reported in ServletContext");
135
                    cdmWebappContext.stop();
136
                } catch (Exception e) {
137
                    logger.error(e);
138
                }
139
            }
140
        }
141

    
142

    
143
        @Override
144
        public void lifeCycleFailure(LifeCycle event, Throwable cause) {
145
            logger.error("lifeCycleFailure");
146
        }
147
    }
148

    
149
    private static final Logger logger = Logger.getLogger(Bootloader.class);
150

    
151
    private static final String DATASOURCE_BEANDEF_FILE = "datasources.xml";
152
    private static final String REALM_PROPERTIES_FILE = "cdm-server-realm.properties";
153

    
154
    private static final String USERHOME_CDM_LIBRARY_PATH = System.getProperty("user.home")+File.separator+".cdmLibrary"+File.separator;
155
    private static final String TMP_PATH = USERHOME_CDM_LIBRARY_PATH + "server" + File.separator;
156
    private static final String LOG_PATH = USERHOME_CDM_LIBRARY_PATH + "log" + File.separator;
157

    
158
    private static final String APPLICATION_NAME = "CDM Server";
159
    private static final String WAR_POSTFIX = ".war";
160

    
161
    private static final String CDMLIB_REMOTE_WEBAPP = "cdmlib-remote-webapp";
162
    private static final String CDMLIB_REMOTE_WEBAPP_VERSION = "cdmlib-remote-webapp.version";
163

    
164
    private static final String DEFAULT_WEBAPP_WAR_NAME = "default-webapp";
165
    private static final File DEFAULT_WEBAPP_TEMP_FOLDER = new File(TMP_PATH + DEFAULT_WEBAPP_WAR_NAME);
166
    private static final File CDM_WEBAPP_TEMP_FOLDER = new File(TMP_PATH + CDMLIB_REMOTE_WEBAPP);
167

    
168
    private static final String ATTRIBUTE_JDBC_JNDI_NAME = "cdm.jdbcJndiName";
169
    private static final String ATTRIBUTE_DATASOURCE_NAME = "cdm.datasource";
170
    private static final String ATTRIBUTE_CDM_LOGFILE = "cdm.logfile";
171
    /**
172
     * same as in eu.etaxonomy.cdm.remote.config.DataSourceConfigurer
173
     */
174
    private static final String ATTRIBUTE_ERROR_MESSAGES = "cdm.errorMessages";
175

    
176

    
177
    // memory requirements
178
    private static final long MB = 1024 * 1024;
179
    private static final long PERM_GEN_SPACE_PER_INSTANCE = 64 * MB;
180
    private static final long HEAP_PER_INSTANCE = 150 * MB;
181

    
182
    private static final int KB = 1024;
183

    
184

    
185
    private Set<CdmInstanceProperties> configAndStatusSet = null;
186

    
187
    public Set<CdmInstanceProperties> getConfigAndStatus() {
188
        return configAndStatusSet;
189
    }
190

    
191
    private File cdmRemoteWebAppFile = null;
192
    private File defaultWebAppFile = null;
193

    
194
    private String logPath = null;
195

    
196
    private Server server = null;
197
    private ContextHandlerCollection contexts = new ContextHandlerCollection();
198

    
199
    private CommandLine cmdLine;
200

    
201
    /* thread save singleton implementation */
202

    
203
    private static Bootloader instance = new Bootloader();
204

    
205
    private Bootloader() {}
206

    
207
    /**
208
     * @return the thread save singleton instance of the Bootloader
209
     */
210
    public synchronized static Bootloader getBootloader(){
211
        return instance;
212
    }
213

    
214
    /* end of singleton implementation */
215

    
216
    private Set<CdmInstanceProperties> loadDataSources(){
217
        if(configAndStatusSet == null){
218
            File datasourcesFile = new File(USERHOME_CDM_LIBRARY_PATH, DATASOURCE_BEANDEF_FILE);
219
            configAndStatusSet = DataSourcePropertyParser.parseDataSourceConfigs(datasourcesFile);
220
            logger.info("cdm server instance names loaded: "+ configAndStatusSet.toString());
221
        }
222
        return configAndStatusSet;
223
    }
224

    
225
    public int writeStreamTo(final InputStream input, final OutputStream output, int bufferSize) throws IOException {
226
        int available = Math.min(input.available(), 256 * KB);
227
        byte[] buffer = new byte[Math.max(bufferSize, available)];
228
        int answer = 0;
229
        int count = input.read(buffer);
230
        while (count >= 0) {
231
            output.write(buffer, 0, count);
232
            answer += count;
233
            count = input.read(buffer);
234
        }
235
        return answer;
236
    }
237

    
238
    private boolean bindJndiDataSource(CdmInstanceProperties conf) {
239
        try {
240
            Class<DataSource> dsCass = (Class<DataSource>) Thread.currentThread().getContextClassLoader().loadClass("com.mchange.v2.c3p0.ComboPooledDataSource");
241
            DataSource datasource = dsCass.newInstance();
242
            dsCass.getMethod("setDriverClass", new Class[] {String.class}).invoke(datasource, new Object[] {conf.getDriverClass()});
243
            dsCass.getMethod("setJdbcUrl", new Class[] {String.class}).invoke(datasource, new Object[] {conf.getUrl()});
244
            dsCass.getMethod("setUser", new Class[] {String.class}).invoke(datasource, new Object[] {conf.getUsername()});
245
            dsCass.getMethod("setPassword", new Class[] {String.class}).invoke(datasource, new Object[] {conf.getPassword()});
246

    
247
            Connection connection = null;
248
            String sqlerror = null;
249
            try {
250
                connection = datasource.getConnection();
251
                connection.close();
252
            } catch (SQLException e) {
253
                sqlerror = e.getMessage() + "["+ e.getSQLState() + "]";
254
                conf.getProblems().add(sqlerror);
255
                if(connection !=  null){
256
                    try {connection.close();} catch (SQLException e1) { /* IGNORE */ }
257
                }
258
                logger.error(conf.toString() + " has problem : "+ sqlerror );
259
            }
260

    
261
            if(!conf.hasProblems()){
262
                logger.info("binding jndi datasource at " + conf.getJdbcJndiName() + " with "+conf.getUsername() +"@"+ conf.getUrl());
263
                org.eclipse.jetty.plus.jndi.Resource jdbcResource = new org.eclipse.jetty.plus.jndi.Resource(conf.getJdbcJndiName(), datasource);
264
                return true;
265
            }
266

    
267
        } catch (IllegalArgumentException e) {
268
            logger.error(e);
269
            e.printStackTrace();
270
        } catch (SecurityException e) {
271
            logger.error(e);
272
        } catch (ClassNotFoundException e) {
273
            logger.error(e);
274
        } catch (InstantiationException e) {
275
            logger.error(e);
276
        } catch (IllegalAccessException e) {
277
            logger.error(e);
278
        } catch (InvocationTargetException e) {
279
            logger.error(e);
280
        } catch (NoSuchMethodException e) {
281
            logger.error(e);
282
        } catch (NamingException e) {
283
            logger.error(e);
284
        }
285
        return false;
286
    }
287

    
288
    public void parseCommandOptions(String[] args) throws ParseException {
289
        CommandLineParser parser = new GnuParser();
290
        cmdLine = parser.parse( CommandOptions.getOptions(), args );
291

    
292
         // print the help message
293
         if(cmdLine.hasOption(HELP.getOpt())){
294
             HelpFormatter formatter = new HelpFormatter();
295
             formatter.setWidth(200);
296
             formatter.printHelp( "java .. ", CommandOptions.getOptions() );
297
             System.exit(0);
298
         }
299
    }
300

    
301

    
302
    private File extractWar(String warName) throws IOException, FileNotFoundException {
303
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
304
        String warFileName = warName + WAR_POSTFIX;
305

    
306
        // 1. find in classpath
307
        URL resource = classLoader.getResource(warFileName);
308
        if (resource == null) {
309
            logger.error("Could not find the " + warFileName + " on classpath!");
310

    
311
            File pomxml = new File("pom.xml");
312
            if(pomxml.exists()){
313
                // 2. try finding in target folder of maven project
314
                File warFile = new File("target" + File.separator + warFileName);
315
                logger.debug("looging for war file at " + warFile.getAbsolutePath());
316
                if (warFile.canRead()) {
317
                    resource = warFile.toURI().toURL();
318
                } else {
319
                    logger.error("Also could not find the " + warFileName + " in maven project, try excuting 'mvn install'");
320
                }
321
            }
322
        }
323

    
324
        if (resource == null) {
325
            // no way finding the war file :-(
326
            System.exit(1);
327
        }
328

    
329

    
330
        File warFile = new File(TMP_PATH, warName + "-" + WAR_POSTFIX);
331
        logger.info("Extracting " + warFileName + " to " + warFile + " ...");
332

    
333
        writeStreamTo(resource.openStream(), new FileOutputStream(warFile), 8 * KB);
334

    
335
        logger.info("Extracted " + warFileName);
336
        return warFile;
337
    }
338

    
339

    
340
    /**
341
     * MAIN METHOD
342
     *
343
     * @param args
344
     * @throws Exception
345
     */
346
    public static void main(String[] args) throws Exception {
347

    
348
        Bootloader bootloader = Bootloader.getBootloader();
349

    
350
        bootloader.parseCommandOptions(args);
351

    
352
        bootloader.startServer();
353
    }
354

    
355

    
356

    
357
    public void startServer() throws IOException,
358
            FileNotFoundException, Exception, InterruptedException {
359

    
360
        if(cmdLine.hasOption(LOG_DIR.getOpt())){
361
            logPath = cmdLine.getOptionValue(LOG_DIR.getOpt());
362
        } else {
363
            logPath = LOG_PATH;
364
        }
365

    
366

    
367
        //assure LOG_PATH exists
368
        File logPathFile = new File(logPath);
369
        if(!logPathFile.exists()){
370
            FileUtils.forceMkdir(new File(logPath));
371
        }
372

    
373
        //append logger
374
        configureFileLogger();
375

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

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

    
392

    
393
         // WARFILE
394
         if(cmdLine.hasOption(WEBAPP.getOpt())){
395
             cdmRemoteWebAppFile = new File(cmdLine.getOptionValue(WEBAPP.getOpt()));
396
             if(cdmRemoteWebAppFile.isDirectory()){
397
                 logger.info("using user defined web application folder: " + cdmRemoteWebAppFile.getAbsolutePath());
398
             } else {
399
                 logger.info("using user defined warfile: " + cdmRemoteWebAppFile.getAbsolutePath());
400
             }
401
             if(isRunningFromCdmRemoteWebAppSource()){
402
                 //TODO check if all local paths are valid !!!!
403
                defaultWebAppFile = new File("./src/main/webapp");
404

    
405
             } else {
406
                defaultWebAppFile = extractWar(DEFAULT_WEBAPP_WAR_NAME);
407
             }
408
         } else {
409
             // read version number
410
             String version = readCdmRemoteVersion();
411

    
412
             cdmRemoteWebAppFile = extractWar(CDMLIB_REMOTE_WEBAPP + "-" + version);
413
             defaultWebAppFile = extractWar(DEFAULT_WEBAPP_WAR_NAME);
414
         }
415

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

    
428
         if(cmdLine.hasOption(DATASOURCES_FILE.getOpt())){
429
             logger.error(DATASOURCES_FILE.getOpt() + " NOT JET IMPLEMENTED!!!");
430
         }
431

    
432
        loadDataSources();
433

    
434
        verifyMemoryRequirements();
435

    
436

    
437
        server = new Server(httpPort);
438

    
439
        // JMX support
440
        if(cmdLine.hasOption(JMX.getOpt())){
441
            logger.info("adding JMX support ...");
442
            MBeanContainer mBeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
443
            server.getContainer().addEventListener(mBeanContainer);
444
            mBeanContainer.addBean(Log.getLog());
445
            mBeanContainer.start();
446
        }
447

    
448
        if(cmdLine.hasOption(WIN32SERVICE.getOpt())){
449
            Win32Service win32Service = new Win32Service();
450
            win32Service.setServer(server);
451
            server.setStopAtShutdown(true);
452
            server.addBean(win32Service);
453
        }
454

    
455
        // add servelet contexts
456

    
457

    
458
        //
459
        // 1. default context
460
        //
461
        logger.info("preparing default WebAppContext");
462
        WebAppContext defaultWebappContext = new WebAppContext();
463

    
464
        setWebApp(defaultWebappContext, defaultWebAppFile);
465
        defaultWebappContext.setContextPath("/");
466
        defaultWebappContext.setTempDirectory(DEFAULT_WEBAPP_TEMP_FOLDER);
467

    
468
        // configure security context
469
        // see for reference * http://docs.codehaus.org/display/JETTY/Realms
470
        //                   * http://wiki.eclipse.org/Jetty/Starting/Porting_to_Jetty_7
471
        HashLoginService loginService = new HashLoginService();
472
        loginService.setConfig(USERHOME_CDM_LIBRARY_PATH + REALM_PROPERTIES_FILE);
473
        defaultWebappContext.getSecurityHandler().setLoginService(loginService);
474

    
475
        // Important:
476
        // the defaultWebappContext MUST USE the super classloader
477
        // otherwise the status page (index.jsp) might not work
478
        defaultWebappContext.setClassLoader(this.getClass().getClassLoader());
479
        contexts.addHandler(defaultWebappContext);
480

    
481
        //
482
        // 2. cdm server contexts
483
        //
484
        server.addLifeCycleListener(new LifeCycle.Listener(){
485

    
486
            @Override
487
            public void lifeCycleFailure(LifeCycle event, Throwable cause) {
488
                logger.error("Jetty LifeCycleFailure", cause);
489
            }
490

    
491
            @Override
492
            public void lifeCycleStarted(LifeCycle event) {
493
                logger.info("cdmserver has started, now adding CDM server contexts");
494
                try {
495
                    addCdmServerContexts(true);
496
                } catch (IOException e1) {
497
                    logger.error(e1);
498
                }
499
            }
500

    
501
            @Override
502
            public void lifeCycleStarting(LifeCycle event) {
503
            }
504

    
505
            @Override
506
            public void lifeCycleStopped(LifeCycle event) {
507
            }
508

    
509
            @Override
510
            public void lifeCycleStopping(LifeCycle event) {
511
            }
512

    
513
            });
514

    
515

    
516
        logger.info("setting contexts ...");
517
        server.setHandler(contexts);
518
        logger.info("starting jetty ...");
519
//        try {
520

    
521
            server.start();
522

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

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

    
546
    public String readCdmRemoteVersion() throws IOException {
547
        InputStream versionInStream = Bootloader.class.getClassLoader().getResourceAsStream(VERSION_PROPERTIES_FILE);
548
         Properties versionProperties = new Properties();
549
         versionProperties.load(versionInStream);
550
         String version = versionProperties.getProperty(CDMLIB_REMOTE_WEBAPP_VERSION);
551
        return version;
552
    }
553

    
554
    /**
555
     *
556
     */
557
    private void verifyMemoryRequirements() {
558

    
559
        verifyMemoryRequirement("PermGenSpace", PERM_GEN_SPACE_PER_INSTANCE, JvmManager.getPermGenSpaceUsage().getMax());
560
        verifyMemoryRequirement("HeapSpace", HEAP_PER_INSTANCE, JvmManager.getHeapMemoryUsage().getMax());
561

    
562
    }
563

    
564
    private void verifyMemoryRequirement(String memoryName, long requiredSpacePerIntance, long availableSpace) {
565

    
566

    
567
        long requiredSpace = configAndStatusSet.size() * requiredSpacePerIntance;
568

    
569
        if(requiredSpace > availableSpace){
570

    
571
            String message = memoryName + " ("
572
                + (availableSpace / MB)
573
                + "MB) insufficient for "
574
                + configAndStatusSet.size()
575
                + " instances. Increase " + memoryName + " to "
576
                + (requiredSpace / MB)
577
                + "MB";
578
                ;
579
            logger.error(message + " => disabling some instances!!!");
580

    
581
            // disabling some instances
582
            int i=0;
583
            for(CdmInstanceProperties instanceProps : configAndStatusSet){
584
                i++;
585
                if(i * requiredSpacePerIntance > availableSpace){
586
                    instanceProps.setStatus(Status.disabled);
587
                    instanceProps.getProblems().add("Disbled due to: " + message);
588
                }
589
            }
590
        }
591
    }
592

    
593
    /**
594
     * Configures and adds a {@link RollingFileAppender} to the root logger
595
     *
596
     * The log files of the cdm-remote instances are configured by the
597
     * {@link eu.etaxonomy.cdm.remote.config.LoggingConfigurer}
598
     */
599
    private void configureFileLogger() {
600

    
601
        PatternLayout layout = new PatternLayout("%d %p [%c] - %m%n");
602
        try {
603
            String logFile = logPath + File.separator + "cdmserver.log";
604
            RollingFileAppender appender = new RollingFileAppender(layout, logFile);
605
            appender.setMaxBackupIndex(3);
606
            appender.setMaxFileSize("2MB");
607
            Logger.getRootLogger().addAppender(appender);
608
            logger.info("logging to :" + logFile);
609
        } catch (IOException e) {
610
            logger.error("Creating RollingFileAppender failed:", e);
611
        }
612
    }
613

    
614
    private void addCdmServerContexts(boolean austostart) throws IOException {
615

    
616
        for(CdmInstanceProperties conf : configAndStatusSet){
617

    
618
            if(!conf.isEnabled()){
619
                logger.info(conf.getDataSourceName() + " is disabled => skipping");
620
                continue;
621
            }
622
            conf.setStatus(CdmInstanceProperties.Status.initializing);
623
            logger.info("preparing WebAppContext for '"+ conf.getDataSourceName() + "'");
624
            WebAppContext cdmWebappContext = new WebAppContext();
625

    
626
            cdmWebappContext.setContextPath("/"+conf.getDataSourceName());
627
            cdmWebappContext.setTempDirectory(CDM_WEBAPP_TEMP_FOLDER);
628

    
629
            if(!bindJndiDataSource(conf)){
630
                // a problem with the datasource occurred skip this webapp
631
                cdmWebappContext = null;
632
                logger.error("a problem with the datasource occurred -> skipping /" + conf.getDataSourceName());
633
                conf.setStatus(CdmInstanceProperties.Status.error);
634
                continue;
635
            }
636

    
637
            cdmWebappContext.setAttribute(ATTRIBUTE_DATASOURCE_NAME, conf.getDataSourceName());
638
            cdmWebappContext.setAttribute(ATTRIBUTE_JDBC_JNDI_NAME, conf.getJdbcJndiName());
639
            setWebApp(cdmWebappContext, cdmRemoteWebAppFile);
640

    
641
            cdmWebappContext.setAttribute(ATTRIBUTE_CDM_LOGFILE,
642
                    logPath + File.separator + "cdm-"
643
                            + conf.getDataSourceName() + ".log");
644

    
645
            if(cdmRemoteWebAppFile.isDirectory() && isRunningFromCdmRemoteWebAppSource()){
646

    
647
                /*
648
                 * when running the webapp from {projectpath} src/main/webapp we
649
                 * must assure that each web application is using it's own
650
                 * classloader thus we tell the WebAppClassLoader where the
651
                 * dependencies of the webapplication can be found. Otherwise
652
                 * the system classloader would load these resources.
653
                 */
654
                logger.info("Running webapp from source folder, thus adding java.class.path to WebAppClassLoader");
655

    
656
                WebAppClassLoader classLoader = new WebAppClassLoader(cdmWebappContext);
657

    
658
                String classPath = System.getProperty("java.class.path");
659
                classLoader.addClassPath(classPath);
660
                cdmWebappContext.setClassLoader(classLoader);
661
            }
662

    
663
            cdmWebappContext.addLifeCycleListener(new WebAppContextListener(cdmWebappContext));
664
            contexts.addHandler(cdmWebappContext);
665

    
666
            if(austostart){
667
                try {
668
                    conf.setStatus(CdmInstanceProperties.Status.starting);
669
                    cdmWebappContext.start();
670
                    if(!conf.getStatus().equals(Status.error)){
671
                        conf.setStatus(CdmInstanceProperties.Status.started);
672
                    }
673
                } catch (Exception e) {
674
                    logger.error("Could not start " + cdmWebappContext.getContextPath());
675
                    conf.setStatus(CdmInstanceProperties.Status.error);
676
                }
677
            }
678

    
679
        }
680
    }
681

    
682
    /**
683
     * @param context
684
     * @param webApplicationResource
685
     */
686
    private void setWebApp(WebAppContext context, File webApplicationResource) {
687
        if(webApplicationResource.isDirectory()){
688
            context.setResourceBase(webApplicationResource.getAbsolutePath());
689
            logger.debug("setting directory " + webApplicationResource.getAbsolutePath() + " as webapplication");
690
        } else {
691
            context.setWar(webApplicationResource.getAbsolutePath());
692
            logger.debug("setting war file " + webApplicationResource.getAbsolutePath() + " as webapplication");
693
        }
694
    }
695

    
696
    /**
697
     * @return
698
     */
699
    private boolean isRunningFromCdmRemoteWebAppSource() {
700
        String webappPathNormalized = cdmRemoteWebAppFile.getAbsolutePath().replace('\\', '/');
701
        return webappPathNormalized.endsWith("src/main/webapp") || webappPathNormalized.endsWith("cdmlib-remote-webapp/target/cdmserver");
702
    }
703

    
704
    /**
705
     * @param dataSourceName
706
     * @return
707
     */
708
    private CdmInstanceProperties findConfigAndStatusFor(String dataSourceName){
709
        for(CdmInstanceProperties props : configAndStatusSet){
710
            if(props.getDataSourceName().equals(dataSourceName)){
711
                return props;
712
            }
713
        }
714
        return null;
715
    }
716

    
717
    /**
718
     * @param <T>
719
     * @param webAppContext
720
     * @param attributeName
721
     * @param type
722
     * @return
723
     */
724
    @SuppressWarnings("unchecked")
725
    private <T> T getServletContextAttribute(WebAppContext webAppContext, String attributeName, Class<T> type) {
726

    
727
        Context servletContext = webAppContext.getServletContext();
728
        Object value = servletContext.getAttribute(attributeName);
729
        if( value != null && type.isAssignableFrom(value.getClass())){
730

    
731
        }
732
        return (T) value;
733
    }
734

    
735
    public Server getServer() {
736
        return server;
737
    }
738
}
(1-1/5)