/* * Copyright 2010-2012 VMware and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springsource.loaded; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import org.springsource.loaded.agent.SpringPlugin; /** * Encapsulates configurable elements - these are set (to values other than the defaults) in TypeRegistry when the * system property springloaded configuration is processed. Some of the options are only used by testcases to make the * testcases easier to write and more straightforward. * * @author Andy Clement * @since 0.5.0 */ public class GlobalConfiguration { private static Logger log = Logger.getLogger(GlobalConfiguration.class.getName()); /** * Are references to fields being modified - covering both the GETS/SETS and the reflective references. */ public final static boolean fieldRewriting = true; public static boolean catchersOn = true; /** * If active, SpringLoaded will be trying to watch for types changing on the file system once they have been made * reloadable. */ public static boolean fileSystemMonitoring = false; /** * Global control for loadtime logging */ public static boolean logging = false; /** * verbose mode can trigger extra messages. Enable with 'verbose=true' */ public static boolean verboseMode = false; /** * asserts mode will trigger extra checking (performance impact but confirms correctness) */ public static boolean assertsMode = false; /** * Can be turned on to enable users to determine the decision process around why something is not reloadable. */ public static boolean explainMode = false; /** * Once a type is found to be reloadable or not (based on whether it is accessible as a .class file on the disk * rather than packaged in a jar), that decision is remembered and all types from the same package are treated in * the same way without repeating the costly lookup. This option enables that behaviour to be turned OFF and then * you can have some files in a package that are in a jar and are not reloadable and some types that are in the same * package but on disk that will be reloadable. */ public static boolean allowSplitPackages = false; /** * Global control for runtime logging */ public static boolean isRuntimeLogging = false; public static boolean callsideRewritingOn = true; /** * Allows a cache to be cleaned up as the agent starts (effectively starting with a new cache, if 'caching' is true) */ public static boolean cleanCache = false; /** * Determine whether on disk caching will be used. */ public static boolean isCaching = false; public static boolean investigateSystemClassReflection = false; public static boolean rewriteAllSystemClasses = false; /** * A well known profile (e.g. grails) can tweak a lot of the default options in a particular way. */ public static String profile = null; /** * The base directory in which to create any cache (.slcache folder). If null then user.home will be used. */ public static String cacheDir = null; public final static boolean logNonInterceptedReflectiveCalls = false; /** * Holds a list of fully qualified paths to jars that should be 'watched' for changes. Types within these jars will * be made reloadable. Set via option 'watchJars' which takes a colon separated list of jars. */ public static String[] jarsToWatch = null; /** * Global control for checking assertions */ public final static boolean isProfiling = false; public static boolean directlyDefineTypes = true; public final static boolean interceptReflection = true; // max number of values before we prevent them being reloaded (the clinit rewrite blows the codesize limit) public static int enumLimit = 1000; public static boolean reloadMessages = false;// can be forced on for testing /** * When a reload is attempted, if this is true it will be checked to confirm it is allowed and does not violate the * supported reloadable changes that can be made to a type. */ public static boolean verifyReloads = true; /** * When classes are dumped by Utils.dump() this specifies where. A null value will cause us to dump into the default * temp folder. */ public static String dumpFolder = null; /** * Global configuration properties set based on the value of system property 'springloaded'. If null then not yet * initialized (and a call to initializeFromSystemProperty()) is needed. If settings are truely once per VM, they * are set directly in GlobalConfiguration whereas if they may be overridden on a per classloader level, they are * set in this properties object and may be overridden by the springloaded.properties files accessible through each * classloader. */ public static Properties globalConfigurationProperties; /** * List of slashed classnames for types we should 'dump' during processing (for debugging purposes). */ public static List<String> classesToDump; public static int maxClassDefinitions = 100; /** * List of dotted classnames representing classnames of plugins that should be loaded. */ public static List<String> pluginClassnameList; public final static boolean debugplugins; private static void printUsage() { System.out.println("SpringLoaded"); System.out.println("============"); System.out.println(); System.out.println("Usage: java -noverify -javaagent:<pathto>/springloaded.jar"); System.out.println("Optionally specify configuration through -Dspringloaded=<options>"); System.out.println("<options> is a ';' separated list of directives or name=value options"); System.out.println("Example: -Dspringloaded=verbose;cacheDir=/tmp"); System.out.println(); System.out.println("Directives:"); System.out.println(" ? - print this usage text"); System.out.println(" verbose - the reloader will log important lifecycle events"); System.out.println("Options:"); System.exit(0); } /** * Look for a springloaded system property and initialize the 'default system wide' configuration based upon it. * Support configuration options: * <ul> * <li><tt>info</tt> - print usage information on the options * <li><tt>verbose</tt> - this directive causes SpringLoaded to report on decisions it is making. * </ul> */ static { globalConfigurationProperties = new Properties(); boolean debugPlugins = false; try { boolean specifiedCaching = false; String value = System.getProperty("springloaded"); // value is a ';' separated list of configuration options which either may be name=value settings or directives (just a name) if (value != null) { StringTokenizer st = new StringTokenizer(value, ";"); while (st.hasMoreTokens()) { String kv = st.nextToken(); int equals = kv.indexOf('='); if (equals != -1) { // key=value String key = kv.substring(0, equals); // Supported settings: // dump=XX,YYY,ZZZ // - this option lists classes for which we should dump the bytecode, names are dotted if (key.equals("dump")) { String classList = kv.substring(equals + 1); StringTokenizer clSt = new StringTokenizer(classList, ","); classesToDump = new ArrayList<String>(); while (clSt.hasMoreTokens()) { classesToDump.add(clSt.nextToken().replace('.', '/')); } if (isRuntimeLogging && log.isLoggable(Level.INFO)) { log.info("configuration: dumping: " + classesToDump); } // } else if (key.equals("interceptReflection")) { // global setting // interceptReflection = kv.substring(equals + 1).equalsIgnoreCase("true"); // if (isRuntimeLogging && log.isLoggable(Level.INFO)) { // log.info("configuration: interceptReflection = " + interceptReflection); // } } else if (key.equals("cleanCache")) { cleanCache = kv.substring(equals + 1).equalsIgnoreCase("true"); } else if (key.equals("caching")) { specifiedCaching = true; isCaching = kv.substring(equals + 1).equalsIgnoreCase("true"); } else if (key.equals("allowSplitPackages")) { allowSplitPackages = kv.substring(equals + 1).equalsIgnoreCase("true"); } else if (key.equals("debugplugins")) { debugPlugins = true; } else if (key.equals("enumlimit")) { enumLimit = toInt(kv.substring(equals + 1), enumLimit); } else if (key.equals("profile")) { profile = kv.substring(equals + 1); } else if (key.equals("cacheDir")) { cacheDir = kv.substring(equals + 1); } else if (key.equals("callsideRewritingOn")) { // global setting callsideRewritingOn = kv.substring(equals + 1).equalsIgnoreCase("true"); if (isRuntimeLogging && log.isLoggable(Level.INFO)) { log.info("configuration: callsideRewritingOn = " + callsideRewritingOn); } // } else if (key.equals("logNonInterceptedReflectiveCalls")) { // global setting // logNonInterceptedReflectiveCalls = kv.substring(equals + 1).equalsIgnoreCase("true"); // if (isRuntimeLogging && log.isLoggable(Level.INFO)) { // log.info("configuration: logNonInterceptedReflectiveCalls = " + logNonInterceptedReflectiveCalls); // } } else if (key.equals("verifyReloads")) { // global setting verifyReloads = kv.substring(equals + 1).equalsIgnoreCase("true"); if (isRuntimeLogging && log.isLoggable(Level.INFO)) { log.info("configuration: verifyReloads = " + verifyReloads); } } else if (key.equals("dumpFolder")) { // global setting dumpFolder = kv.substring(equals + 1); if (isRuntimeLogging && log.isLoggable(Level.INFO)) { log.info("configuration: dumpFolder = " + dumpFolder); } } else if (key.equals("watchJars")) { if (isRuntimeLogging && log.isLoggable(Level.INFO)) { log.info("Watching jars: " + kv.substring(equals + 1)); } jarsToWatch = kv.substring(equals + 1).split(":"); } else if (key.equals("maxClassDefinitions")) { try { maxClassDefinitions = Integer.parseInt(kv.substring(equals + 1)); if (isRuntimeLogging && log.isLoggable(Level.INFO)) { log.info("configuration: maxClassDefinitions = " + maxClassDefinitions); } } catch (NumberFormatException nfe) { System.err.println("ERROR: unable to parse " + kv.substring(equals + 1) + " as a integer"); } } else if (key.equals("logging")) { GlobalConfiguration.isRuntimeLogging = kv.substring(equals + 1).equalsIgnoreCase("true"); GlobalConfiguration.logging = kv.substring(equals + 1).equalsIgnoreCase("true"); System.out.println("Spring-Loaded logging = (" + GlobalConfiguration.isRuntimeLogging + "," + GlobalConfiguration.logging + ")"); } else if (key.equals("verbose")) { verboseMode = kv.substring(equals + 1).equalsIgnoreCase("true"); reloadMessages = verboseMode; } else if (key.equals("asserts")) { assertsMode = kv.substring(equals + 1).equalsIgnoreCase("true"); } else if (key.equals("rebasePaths")) { // value is a series of "a=b,c=d,e=f" indicating from and to globalConfigurationProperties.put("rebasePaths", kv.substring(equals + 1)); } else if (key.equals("inclusions")) { globalConfigurationProperties.put("inclusions", kv.substring(equals + 1)); } else if (key.equals("exclusions")) { globalConfigurationProperties.put("exclusions", kv.substring(equals + 1)); } else if (key.equals("plugins")) { // plugins=com.myplugin.Plugin,com.somethingelse.SomeOtherPlugin String pluginList = kv.substring(equals + 1); StringTokenizer pluginListTokenizer = new StringTokenizer(pluginList, ","); pluginClassnameList = new ArrayList<String>(); while (pluginListTokenizer.hasMoreTokens()) { pluginClassnameList.add(pluginListTokenizer.nextToken()); } } } else { if (kv.equals("?")) { printUsage(); } else if (kv.equals("verbose")) { Log.log("[verbose mode on] Full configuration is:" + value); verboseMode = true; reloadMessages = true; } else if (kv.equals("investigateSystemClassReflection")) { investigateSystemClassReflection = true; } else if (kv.equals("rewriteAllSystemClasses")) { rewriteAllSystemClasses = true; } else if (kv.equals("asserts")) { Log.log("[asserts mode on] Will verify system coherence"); assertsMode = true; } else if (kv.equals("explain")) { Log.log("[explain mode on] Reporting on the decision making process within SpringLoaded"); explainMode = true; } } } } // Profile support. A profile is a shortcut for configuring a bunch of options if (profile != null) { if (profile.equals("grails")) { // Configure options based on a grails profile // turn on caching if we have a cacheDir set or can put one in the .grails folder under user.home if (cacheDir == null) { try { String userhome = System.getProperty("user.home"); if (userhome != null) { cacheDir = new StringBuilder(userhome).append(File.separator).append( ".grails").toString(); new File(cacheDir).mkdir(); } } catch (Throwable t) { System.err.println( "looks like user.home is not set, or cannot write to it: cannot create cache."); t.printStackTrace(System.err); } } if (!specifiedCaching) { if (cacheDir != null) { isCaching = true; } } if (pluginClassnameList == null) { pluginClassnameList = new ArrayList<String>(); } pluginClassnameList.add("org.springsource.loaded.SystemPropertyConfiguredIsReloadableTypePlugin"); // turn off the 3.0 reloading, for now (just because it hasn't been tested) SpringPlugin.support305 = false; } } else { if (isCaching) { if (cacheDir == null) { try { String userhome = System.getProperty("user.home"); if (userhome != null) { cacheDir = userhome; } } catch (Throwable t) { System.err.println("looks like user.home is not set: cannot create cache."); t.printStackTrace(System.err); } } } } if (isCaching) { // Ensure cache folder exists try { File cacheDirFile = new File(cacheDir); if (!cacheDirFile.exists()) { boolean created = cacheDirFile.mkdirs(); if (!created) { System.err.println("Caching deactivated: failed to create cache directory: " + cacheDir); isCaching = false; } } else { if (!cacheDirFile.isDirectory()) { System.err.println( "Caching deactivated: unable to use specified cache area, it is not a directory: " + cacheDirFile); isCaching = false; } } } catch (Exception e) { System.err.println("Unexpected problem creating specified cachedir: " + cacheDir); e.printStackTrace(); } } // Alternative route for specifying values value = System.getProperty("springloaded.enumlimit"); if (value != null) { enumLimit = toInt(value, enumLimit); } } catch (Throwable t) { System.err.println("Unexpected problem reading global configuration setting:" + t.toString()); t.printStackTrace(); } debugplugins = debugPlugins; } private static int toInt(String value, int defaultValue) { try { return Integer.parseInt(value); } catch (NumberFormatException nfe) { return defaultValue; } } public final static boolean isJava18orHigher; public static boolean InTestMode = false; static { String version = System.getProperty("java.version"); if (version.startsWith("1.8")) { isJava18orHigher = true; } else { isJava18orHigher = false; } } }