001/* 002 * #%L 003 * Netarchivesuite - common 004 * %% 005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library, 006 * the National Library of France and the Austrian National Library. 007 * %% 008 * This program is free software: you can redistribute it and/or modify 009 * it under the terms of the GNU Lesser General Public License as 010 * published by the Free Software Foundation, either version 2.1 of the 011 * License, or (at your option) any later version. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Lesser Public License for more details. 017 * 018 * You should have received a copy of the GNU General Lesser Public 019 * License along with this program. If not, see 020 * <http://www.gnu.org/licenses/lgpl-2.1.html>. 021 * #L% 022 */ 023package dk.netarkivet.common.utils; 024 025import java.io.File; 026import java.lang.reflect.Method; 027import java.lang.reflect.Modifier; 028 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032import dk.netarkivet.common.CommonSettings; 033import dk.netarkivet.common.Constants; 034import dk.netarkivet.common.exceptions.ArgumentNotValid; 035import dk.netarkivet.common.exceptions.PermissionDenied; 036import dk.netarkivet.common.lifecycle.LifeCycleComponent; 037import dk.netarkivet.common.management.MBeanConnectorCreator; 038 039/** 040 * This class provides functionality for starting applications. 041 */ 042public abstract class ApplicationUtils { 043 044 /** logger for this class. */ 045 private static final Logger log = LoggerFactory.getLogger(ApplicationUtils.class); 046 047 /** System.exit() value for the case where wrong arguments are given. */ 048 public static final int WRONG_ARGUMENTS = 1; 049 050 /** 051 * System.exit() value for the case where the application does not have a factory method. 052 */ 053 public static final int NO_FACTORY_METHOD = 2; 054 055 /** 056 * System.exit() value for the case where the application class could not be instantiated. 057 */ 058 public static final int EXCEPTION_WHILE_INSTANTIATING = 3; 059 060 /** 061 * System.exit() value for the case where the Shutdown Hook for the application could not be added. 062 */ 063 public static final int EXCEPTION_WHEN_ADDING_SHUTDOWN_HOOK = 4; 064 065 /** 066 * System.exit() value for the case where the management registration for the application could not be started. 067 */ 068 public static final int EXCEPTION_WHEN_ADDING_MANAGEMENT = 6; 069 070 /** 071 * Helper class that prints a String to STDOUT, and logs it at INFO level at the same time. 072 * 073 * @param s the given string. 074 */ 075 private static void logAndPrint(String s) { 076 System.out.println(s); 077 log.info(s); 078 } 079 080 /** 081 * Helper class for logging an exception (at level fatal) and printing it to STDOUT at the same time. Also invokes 082 * the error notifier. 083 * 084 * @param s a given String. 085 * @param t a given Exception. 086 */ 087 private static void logExceptionAndPrint(String s, Throwable t) { 088 System.out.println(s); 089 t.printStackTrace(); 090 log.error(s, t); 091 NotificationsFactory.getInstance().notify(s, NotificationType.ERROR, t); 092 } 093 094 /** 095 * Checks that the arguments for a class are empty. Exits the JVM with error code 1 if the arguments are not empty. 096 * 097 * @param args the argument array. 098 */ 099 private static void checkArgs(String[] args) { 100 if (showVersion(args)) { 101 logAndPrint("NetarchiveSuite " + dk.netarkivet.common.Constants.getVersionString()); 102 System.exit(0); 103 } 104 if (args.length > 0) { 105 logAndPrint("This application takes no arguments"); 106 System.exit(WRONG_ARGUMENTS); 107 } 108 } 109 110 /** 111 * Should we show the version of NetarchiveSuite. 112 * 113 * @param args commandline arguments to NetarchiveSuite 114 * @return true, if we should show the version of NetarchiveSuite; otherwise false 115 */ 116 private static boolean showVersion(String[] args) { 117 if (args.length == 1 && (args[0].equals("-v") || args[0].equals("--version"))) { 118 return true; 119 } 120 return false; 121 } 122 123 /** 124 * Starts up an application. The applications class must: (i) Have a static getInstance() method which returns a an 125 * instance of itself. (ii) Implement CleanupIF. If the class cannot be started and a shutdown hook added, the JVM 126 * exits with a return code depending on the problem: 1 means wrong arguments 2 means no factory method exists for 127 * class 3 means couldn't instantiate class 4 means couldn't add shutdown hook 5 means couldn't add liveness logger 128 * 6 means couldn't add remote management 129 * 130 * @param c The class to be started. 131 * @param args The arguments to the application (should be empty). 132 */ 133 @SuppressWarnings({"rawtypes", "unchecked"}) 134 public static void startApp(Class c, String[] args) { 135 String appName = c.getName(); 136 Settings.set(CommonSettings.APPLICATION_NAME, appName); 137 logAndPrint("Starting " + appName + "\n" + Constants.getVersionString()); 138 logAndPrint("Java VM: " + System.getProperty("java.version")); 139 logAndPrint("java.home: " + System.getProperty("java.home")); 140 logAndPrint("Working dir: " + System.getProperty("user.dir")); 141 log.info("Using settings files '{}'", StringUtils.conjoin(File.pathSeparator, Settings.getSettingsFiles())); 142 checkArgs(args); 143 dirMustExist(FileUtils.getTempDir()); 144 Method factoryMethod = null; 145 CleanupIF instance = null; 146 // Start the remote management connector 147 try { 148 MBeanConnectorCreator.exposeJMXMBeanServer(); 149 log.trace("Added remote management for {}", appName); 150 } catch (Throwable e) { 151 logExceptionAndPrint("Could not add remote management for class " + appName, e); 152 System.exit(EXCEPTION_WHEN_ADDING_MANAGEMENT); 153 } 154 // Get the factory method 155 try { 156 factoryMethod = c.getMethod("getInstance", (Class[]) null); 157 int modifier = factoryMethod.getModifiers(); 158 if (!Modifier.isStatic(modifier)) { 159 throw new Exception("getInstance is not static"); 160 } 161 logAndPrint(appName + " Running"); 162 } catch (Throwable e) { 163 logExceptionAndPrint("Class " + appName + " does not have required factory method", e); 164 System.exit(NO_FACTORY_METHOD); 165 } 166 // Invoke the factory method 167 try { 168 log.trace("Invoking factory method."); 169 instance = (CleanupIF) factoryMethod.invoke(null, (Object[]) null); 170 log.trace("Factory method invoked."); 171 } catch (Throwable e) { 172 logExceptionAndPrint("Could not start class " + appName, e); 173 System.exit(EXCEPTION_WHILE_INSTANTIATING); 174 } 175 // Add the shutdown hook 176 try { 177 log.trace("Adding shutdown hook for " + appName); 178 Runtime.getRuntime().addShutdownHook((new CleanupHook(instance))); 179 log.trace("Added shutdown hook for " + appName); 180 } catch (Throwable e) { 181 logExceptionAndPrint("Could not add shutdown hook for class " + appName, e); 182 System.exit(EXCEPTION_WHEN_ADDING_SHUTDOWN_HOOK); 183 } 184 } 185 186 /** 187 * Starts up an LifeCycleComponent. 188 * 189 * @param component The component to start. 190 */ 191 public static void startApp(LifeCycleComponent component) { 192 ArgumentNotValid.checkNotNull(component, "LifeCycleComponent component"); 193 194 String appName = component.getClass().getName(); 195 Settings.set(CommonSettings.APPLICATION_NAME, appName); 196 logAndPrint("Starting " + appName + "\n" + Constants.getVersionString()); 197 log.info("Using settings files '{}'", StringUtils.conjoin(File.pathSeparator, Settings.getSettingsFiles())); 198 dirMustExist(FileUtils.getTempDir()); 199 // Start the remote management connector 200 try { 201 MBeanConnectorCreator.exposeJMXMBeanServer(); 202 log.trace("Added remote management for {}", appName); 203 } catch (Throwable e) { 204 logExceptionAndPrint("Could not add remote management for class " + appName, e); 205 System.exit(EXCEPTION_WHEN_ADDING_MANAGEMENT); 206 } 207 208 component.start(); 209 logAndPrint(appName + " Running"); 210 211 // Add the shutdown hook 212 try { 213 log.trace("Adding shutdown hook for {}", appName); 214 Runtime.getRuntime().addShutdownHook((new ShutdownHook(component))); 215 log.trace("Added shutdown hook for {}", appName); 216 } catch (Throwable e) { 217 logExceptionAndPrint("Could not add shutdown hook for class " + appName, e); 218 System.exit(EXCEPTION_WHEN_ADDING_SHUTDOWN_HOOK); 219 } 220 } 221 222 /** 223 * Ensure that a directory is available and writable. Will warn if the directory doesn't already exist (it ought to 224 * be created by the install script) and throws a PermissionDenied exception if the directory cannot be created. 225 * 226 * @param dir A File object denoting a directory. 227 * @throws PermissionDenied if the directory doesn't exist and cannot be created/written to, or if the File object 228 * indicates an existing non-directory. 229 */ 230 public static void dirMustExist(File dir) { 231 ArgumentNotValid.checkNotNull(dir, "File dir"); 232 if (FileUtils.createDir(dir)) { 233 log.warn("Non-existing directory created '{}'", dir); 234 } 235 } 236 237}