Project

General

Profile

Download (28.5 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.LOG_DIR;
18
import static eu.etaxonomy.cdm.server.CommandOptions.WEBAPP;
19
import static eu.etaxonomy.cdm.server.CommandOptions.WIN32SERVICE;
20

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

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

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

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

    
63

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

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

    
89

    
90

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

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

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

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

    
115
        }
116

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

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

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

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

    
143

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

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

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

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

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

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

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

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

    
177

    
178
    // memory requirements
179
    private static final int KB = 1024;
180
    private static final long MB = 1024 * KB;
181
    public static final long PERM_GEN_SPACE_PER_INSTANCE = 55 * MB;
182
    public static final long HEAP_PER_INSTANCE = 130 * MB;
183
    public static final long PERM_GEN_SPACE_CDMSERVER = 19 * MB;
184
    public static final long HEAP_CDMSERVER = 15 * MB;
185

    
186

    
187

    
188
    private List<CdmInstanceProperties> configAndStatusList = null;
189

    
190
    public List<CdmInstanceProperties> getConfigAndStatus() {
191
        return configAndStatusList;
192
    }
193

    
194
    private File cdmRemoteWebAppFile = null;
195
    private File defaultWebAppFile = null;
196

    
197
    private String logPath = null;
198

    
199
    private Server server = null;
200
    private ContextHandlerCollection contexts = new ContextHandlerCollection();
201

    
202
    private CommandLine cmdLine;
203

    
204
    /* thread save singleton implementation */
205

    
206
    private static Bootloader instance = new Bootloader();
207

    
208
    private Bootloader() {}
209

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

    
217
    /* end of singleton implementation */
218

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

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

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

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

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

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

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

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

    
304

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

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

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

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

    
332

    
333
        File warFile = new File(TMP_PATH, warName + "-" + WAR_POSTFIX);
334
        logger.info("Extracting " + warFileName + " to " + warFile + " ...");
335

    
336
        writeStreamTo(resource.openStream(), new FileOutputStream(warFile), 8 * KB);
337

    
338
        logger.info("Extracted " + warFileName);
339
        return warFile;
340
    }
341

    
342

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

    
351
        Bootloader bootloader = Bootloader.getBootloader();
352

    
353
        bootloader.parseCommandOptions(args);
354

    
355
        bootloader.startServer();
356
    }
357

    
358

    
359

    
360
    public void startServer() throws IOException,
361
            FileNotFoundException, Exception, InterruptedException {
362

    
363

    
364
        if(cmdLine.hasOption(LOG_DIR.getOpt())){
365
            logPath = cmdLine.getOptionValue(LOG_DIR.getOpt());
366
        } else {
367
            logPath = LOG_PATH;
368
        }
369

    
370

    
371
        //assure LOG_PATH exists
372
        File logPathFile = new File(logPath);
373
        if(!logPathFile.exists()){
374
            FileUtils.forceMkdir(new File(logPath));
375
        }
376

    
377
        //append logger
378
        configureFileLogger();
379

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

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

    
396

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

    
409
             } else {
410
                defaultWebAppFile = extractWar(DEFAULT_WEBAPP_WAR_NAME);
411
             }
412
         } else {
413
             // read version number
414
             String version = readCdmRemoteVersion();
415

    
416
             cdmRemoteWebAppFile = extractWar(CDMLIB_REMOTE_WEBAPP + "-" + version);
417
             defaultWebAppFile = extractWar(DEFAULT_WEBAPP_WAR_NAME);
418
         }
419

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

    
432
         if(cmdLine.hasOption(DATASOURCES_FILE.getOpt())){
433
             logger.error(DATASOURCES_FILE.getOpt() + " NOT JET IMPLEMENTED!!!");
434
         }
435

    
436
        loadDataSources();
437

    
438
        verifyMemoryRequirements();
439

    
440
        verifySystemResources();
441

    
442

    
443
        server = new Server(httpPort);
444

    
445
        // JMX support
446
        if(cmdLine.hasOption(JMX.getOpt())){
447
            logger.info("adding JMX support ...");
448
            MBeanContainer mBeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
449
            server.getContainer().addEventListener(mBeanContainer);
450
            mBeanContainer.addBean(Log.getLog());
451
            mBeanContainer.start();
452
        }
453

    
454
        if(cmdLine.hasOption(WIN32SERVICE.getOpt())){
455
            Win32Service win32Service = new Win32Service();
456
            win32Service.setServer(server);
457
            server.setStopAtShutdown(true);
458
            server.addBean(win32Service);
459
        }
460

    
461
        // add servelet contexts
462

    
463

    
464
        //
465
        // 1. default context
466
        //
467
        logger.info("preparing default WebAppContext");
468
        WebAppContext defaultWebappContext = new WebAppContext();
469

    
470
        setWebApp(defaultWebappContext, defaultWebAppFile);
471
        defaultWebappContext.setContextPath("/");
472
        defaultWebappContext.setTempDirectory(DEFAULT_WEBAPP_TEMP_FOLDER);
473

    
474
        // configure security context
475
        // see for reference * http://docs.codehaus.org/display/JETTY/Realms
476
        //                   * http://wiki.eclipse.org/Jetty/Starting/Porting_to_Jetty_7
477
        HashLoginService loginService = new HashLoginService();
478
        loginService.setConfig(USERHOME_CDM_LIBRARY_PATH + REALM_PROPERTIES_FILE);
479
        defaultWebappContext.getSecurityHandler().setLoginService(loginService);
480

    
481
        // Important:
482
        // the defaultWebappContext MUST USE the super classloader
483
        // otherwise the status page (index.jsp) might not work
484
        defaultWebappContext.setClassLoader(this.getClass().getClassLoader());
485
        contexts.addHandler(defaultWebappContext);
486

    
487
        //
488
        // 2. cdm server contexts
489
        //
490
        server.addLifeCycleListener(new LifeCycle.Listener(){
491

    
492
            @Override
493
            public void lifeCycleFailure(LifeCycle event, Throwable cause) {
494
                logger.error("Jetty LifeCycleFailure", cause);
495
            }
496

    
497
            @Override
498
            public void lifeCycleStarted(LifeCycle event) {
499
                logger.info("cdmserver has started, now adding CDM server contexts");
500
                try {
501
                    addCdmServerContexts(true);
502
                } catch (IOException e1) {
503
                    logger.error(e1);
504
                }
505
            }
506

    
507
            @Override
508
            public void lifeCycleStarting(LifeCycle event) {
509
            }
510

    
511
            @Override
512
            public void lifeCycleStopped(LifeCycle event) {
513
            }
514

    
515
            @Override
516
            public void lifeCycleStopping(LifeCycle event) {
517
            }
518

    
519
            });
520

    
521

    
522
        logger.info("setting contexts ...");
523
        server.setHandler(contexts);
524
        logger.info("starting jetty ...");
525
//        try {
526

    
527
            server.start();
528

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

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

    
552
    public String readCdmRemoteVersion() throws IOException {
553
        InputStream versionInStream = Bootloader.class.getClassLoader().getResourceAsStream(VERSION_PROPERTIES_FILE);
554
         Properties versionProperties = new Properties();
555
         versionProperties.load(versionInStream);
556
         String version = versionProperties.getProperty(CDMLIB_REMOTE_WEBAPP_VERSION);
557
        return version;
558
    }
559

    
560
    /**
561
     *
562
     */
563
    private void verifyMemoryRequirements() {
564

    
565
        verifyMemoryRequirement("PermGenSpace", PERM_GEN_SPACE_CDMSERVER, PERM_GEN_SPACE_PER_INSTANCE, JvmManager.getPermGenSpaceUsage().getMax());
566
        verifyMemoryRequirement("HeapSpace", HEAP_CDMSERVER, HEAP_PER_INSTANCE, JvmManager.getHeapMemoryUsage().getMax());
567

    
568
    }
569

    
570
    private void verifySystemResources() {
571

    
572
        OsChecker osChecker = new OsChecker();
573
        if(osChecker.isLinux()){
574
            try {
575
                Process p = Runtime.getRuntime().exec(new String[] { "bash", "-c", "ulimit -n" });
576
                BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
577
                String line;
578
                StringBuilder response = new StringBuilder();
579
                     while ((line = in.readLine()) != null) {
580
                         response.append(line);
581
                     }
582
               logger.info("OS Limit (Linux): maximum number of open files: " + response);
583
            } catch (IOException e) {
584
                // TODO Auto-generated catch block
585
                e.printStackTrace();
586
            }
587
        } else {
588
            logger.info("verifySystemResources only implemented for linux");
589
        }
590
    }
591

    
592
    private void verifyMemoryRequirement(String memoryName, long requiredSpaceServer, long requiredSpacePerInstance, long availableSpace) {
593

    
594

    
595
        long recommendedMinimumSpace = recommendedMinimumSpace(requiredSpaceServer, requiredSpacePerInstance, null);
596

    
597
        if(recommendedMinimumSpace > availableSpace){
598

    
599
            String message = memoryName + " ("
600
                + (availableSpace / MB)
601
                + "MB) insufficient for "
602
                + configAndStatusList.size()
603
                + " instances. Increase " + memoryName + " to "
604
                + (recommendedMinimumSpace / MB)
605
                + "MB";
606
                ;
607
            logger.error(message + " => disabling some instances!!!");
608

    
609
            // disabling some instances
610
            int i=0;
611
            for(CdmInstanceProperties instanceProps : configAndStatusList){
612
                i++;
613
                if(recommendedMinimumSpace(requiredSpaceServer, requiredSpacePerInstance, i)  > availableSpace){
614
                    instanceProps.setStatus(Status.disabled);
615
                    instanceProps.getProblems().add("Disabled due to: " + message);
616
                }
617
            }
618
        }
619
    }
620

    
621
    /**
622
     * @param requiredServerSpace
623
     * @param requiredSpacePerIntance
624
     * @param numOfInstances may be null, the total number of instances found in the current configuration is used in this case.
625
     * @return
626
     */
627
    public long recommendedMinimumSpace(long requiredServerSpace, long requiredSpacePerIntance, Integer numOfInstances) {
628
        if(numOfInstances == null){
629
            numOfInstances = configAndStatusList.size();
630
        }
631
        return (numOfInstances * requiredSpacePerIntance) + requiredServerSpace;
632
    }
633

    
634
    /**
635
     * Configures and adds a {@link RollingFileAppender} to the root logger
636
     *
637
     * The log files of the cdm-remote instances are configured by the
638
     * {@link eu.etaxonomy.cdm.remote.config.LoggingConfigurer}
639
     */
640
    private void configureFileLogger() {
641

    
642
        PatternLayout layout = new PatternLayout("%d %p [%c] - %m%n");
643
        try {
644
            String logFile = logPath + File.separator + "cdmserver.log";
645
            RollingFileAppender appender = new RollingFileAppender(layout, logFile);
646
            appender.setMaxBackupIndex(3);
647
            appender.setMaxFileSize("2MB");
648
            Logger.getRootLogger().addAppender(appender);
649
            logger.info("logging to :" + logFile);
650
        } catch (IOException e) {
651
            logger.error("Creating RollingFileAppender failed:", e);
652
        }
653
    }
654

    
655
    private void addCdmServerContexts(boolean austostart) throws IOException {
656

    
657
        for(CdmInstanceProperties conf : configAndStatusList){
658

    
659
            if(!conf.isEnabled()){
660
                logger.info(conf.getDataSourceName() + " is disabled due to JVM memory limitations => skipping");
661
                continue;
662
            }
663
            conf.setStatus(CdmInstanceProperties.Status.initializing);
664
            logger.info("preparing WebAppContext for '"+ conf.getDataSourceName() + "'");
665
            WebAppContext cdmWebappContext = new WebAppContext();
666

    
667
            cdmWebappContext.setContextPath("/"+conf.getDataSourceName());
668
            cdmWebappContext.setTempDirectory(CDM_WEBAPP_TEMP_FOLDER);
669

    
670
            if(!bindJndiDataSource(conf)){
671
                // a problem with the datasource occurred skip this webapp
672
                cdmWebappContext = null;
673
                logger.error("a problem with the datasource occurred -> skipping /" + conf.getDataSourceName());
674
                conf.setStatus(CdmInstanceProperties.Status.error);
675
                continue;
676
            }
677

    
678
            cdmWebappContext.setAttribute(ATTRIBUTE_DATASOURCE_NAME, conf.getDataSourceName());
679
            cdmWebappContext.setAttribute(ATTRIBUTE_JDBC_JNDI_NAME, conf.getJdbcJndiName());
680
            setWebApp(cdmWebappContext, cdmRemoteWebAppFile);
681

    
682
            cdmWebappContext.setAttribute(ATTRIBUTE_CDM_LOGFILE,
683
                    logPath + File.separator + "cdm-"
684
                            + conf.getDataSourceName() + ".log");
685

    
686
            if(cdmRemoteWebAppFile.isDirectory() && isRunningFromCdmRemoteWebAppSource()){
687

    
688
                /*
689
                 * when running the webapp from {projectpath} src/main/webapp we
690
                 * must assure that each web application is using it's own
691
                 * classloader thus we tell the WebAppClassLoader where the
692
                 * dependencies of the webapplication can be found. Otherwise
693
                 * the system classloader would load these resources.
694
                 */
695
                logger.info("Running webapp from source folder, thus adding java.class.path to WebAppClassLoader");
696

    
697
                WebAppClassLoader classLoader = new WebAppClassLoader(cdmWebappContext);
698

    
699
                String classPath = System.getProperty("java.class.path");
700
                classLoader.addClassPath(classPath);
701
                cdmWebappContext.setClassLoader(classLoader);
702
            }
703

    
704
            cdmWebappContext.addLifeCycleListener(new WebAppContextListener(cdmWebappContext));
705
            contexts.addHandler(cdmWebappContext);
706

    
707
            if(austostart){
708
                try {
709
                    conf.setStatus(CdmInstanceProperties.Status.starting);
710
                    cdmWebappContext.start();
711
                    if(!conf.getStatus().equals(Status.error)){
712
                        conf.setStatus(CdmInstanceProperties.Status.started);
713
                    }
714
                } catch (Exception e) {
715
                    logger.error("Could not start " + cdmWebappContext.getContextPath());
716
                    conf.setStatus(CdmInstanceProperties.Status.error);
717
                }
718
            }
719

    
720
        }
721
    }
722

    
723
    /**
724
     * @param context
725
     * @param webApplicationResource
726
     */
727
    private void setWebApp(WebAppContext context, File webApplicationResource) {
728
        if(webApplicationResource.isDirectory()){
729
            context.setResourceBase(webApplicationResource.getAbsolutePath());
730
            logger.debug("setting directory " + webApplicationResource.getAbsolutePath() + " as webapplication");
731
        } else {
732
            context.setWar(webApplicationResource.getAbsolutePath());
733
            logger.debug("setting war file " + webApplicationResource.getAbsolutePath() + " as webapplication");
734
        }
735
    }
736

    
737
    /**
738
     * @return
739
     */
740
    private boolean isRunningFromCdmRemoteWebAppSource() {
741
        String webappPathNormalized = cdmRemoteWebAppFile.getAbsolutePath().replace('\\', '/');
742
        return webappPathNormalized.endsWith("src/main/webapp") || webappPathNormalized.endsWith("cdmlib-remote-webapp/target/cdmserver");
743
    }
744

    
745
    /**
746
     * @param dataSourceName
747
     * @return
748
     */
749
    private CdmInstanceProperties findConfigAndStatusFor(String dataSourceName){
750
        for(CdmInstanceProperties props : configAndStatusList){
751
            if(props.getDataSourceName().equals(dataSourceName)){
752
                return props;
753
            }
754
        }
755
        return null;
756
    }
757

    
758
    /**
759
     * @param <T>
760
     * @param webAppContext
761
     * @param attributeName
762
     * @param type
763
     * @return
764
     */
765
    @SuppressWarnings("unchecked")
766
    private <T> T getServletContextAttribute(WebAppContext webAppContext, String attributeName, Class<T> type) {
767

    
768
        Context servletContext = webAppContext.getServletContext();
769
        Object value = servletContext.getAttribute(attributeName);
770
        if( value != null && type.isAssignableFrom(value.getClass())){
771

    
772
        }
773
        return (T) value;
774
    }
775

    
776
    public Server getServer() {
777
        return server;
778
    }
779
}
(1-1/6)