diff --git a/springloaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java b/springloaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java index 7f3df61..8fec68f 100644 --- a/springloaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java +++ b/springloaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java @@ -27,8 +27,8 @@ import org.springsource.loaded.agent.SpringPlugin; /** - * Captures configurable elements - these are set (to values other than the defaults) in TypeRegistry when the system property - * springloaded.configuration is processed. It is possible to tweak them during testcases to simplify what is being tested - the + * Encapsulates configurable elements - these are set (to values other than the defaults) in TypeRegistry when the system property + * springloaded configuration is processed. It is possible to tweak them during testcases to simplify what is being tested - the * test should reset them to their original values on completion. * * @author Andy Clement @@ -135,21 +135,37 @@ public class GlobalConfiguration { 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:/springloaded.jar"); + System.out.println("Optionally specify configuration through -Dspringloaded="); + System.out.println(" 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: + *
    + *
  • info - print usage information on the options + *
  • verbose - this directive causes SpringLoaded to report on decisions it is making. + *
*/ static { - // classesToDump = new ArrayList(); - // classesToDump.add("Demo"); globalConfigurationProperties = new Properties(); - // Load global configuration boolean debugPlugins = false; try { boolean specifiedCaching = false; String value = System.getProperty("springloaded"); - if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) { - log.finest("GlobalConfiguration: being configured from '" + value + "'"); - } // 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, ";"); @@ -224,8 +240,8 @@ public class GlobalConfiguration { System.out.println("Spring-Loaded logging = (" + GlobalConfiguration.isRuntimeLogging + "," + GlobalConfiguration.logging + ")"); } else if (key.equals("verbose")) { - GlobalConfiguration.verboseMode = kv.substring(equals + 1).equalsIgnoreCase("true"); - GlobalConfiguration.reloadMessages = verboseMode; + verboseMode = kv.substring(equals + 1).equalsIgnoreCase("true"); + reloadMessages = verboseMode; } 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)); @@ -243,7 +259,13 @@ public class GlobalConfiguration { } } } else { - // directive + if (kv.equals("?")) { + printUsage(); + } + else if (kv.equals("verbose")) { + Log.log("verbose mode on, configuration is:"+value); + verboseMode = true; + } } } } diff --git a/springloaded/src/main/java/org/springsource/loaded/IsReloadableTypePlugin.java b/springloaded/src/main/java/org/springsource/loaded/IsReloadableTypePlugin.java index 49d63d9..eb41188 100644 --- a/springloaded/src/main/java/org/springsource/loaded/IsReloadableTypePlugin.java +++ b/springloaded/src/main/java/org/springsource/loaded/IsReloadableTypePlugin.java @@ -29,10 +29,11 @@ import org.springsource.loaded.agent.ReloadDecision; public interface IsReloadableTypePlugin extends Plugin { /** + * @param typeRegistry * @param typename slashed type name (e.g. java/lang/String) * @param protectionDomain * @param bytes the classfile data */ - ReloadDecision shouldBeMadeReloadable(String typename, ProtectionDomain protectionDomain, byte[] bytes); + ReloadDecision shouldBeMadeReloadable(TypeRegistry typeRegistry, String typename, ProtectionDomain protectionDomain, byte[] bytes); } diff --git a/springloaded/src/main/java/org/springsource/loaded/Log.java b/springloaded/src/main/java/org/springsource/loaded/Log.java new file mode 100644 index 0000000..0cee064 --- /dev/null +++ b/springloaded/src/main/java/org/springsource/loaded/Log.java @@ -0,0 +1,16 @@ +package org.springsource.loaded; + +/** + * Minimal support for logging messages, avoids all dependencies it can because it will be loaded very early by the VM and + * we don't want to introduce unnecessarily complex classloading. + * + * @author Andy Clement + * @since 1.1.5 + */ +public class Log { + + public static void log(String message) { + System.out.println("SL: "+message); + } + +} diff --git a/springloaded/src/main/java/org/springsource/loaded/SystemPropertyConfiguredIsReloadableTypePlugin.java b/springloaded/src/main/java/org/springsource/loaded/SystemPropertyConfiguredIsReloadableTypePlugin.java index ec614f2..f94bc5a 100644 --- a/springloaded/src/main/java/org/springsource/loaded/SystemPropertyConfiguredIsReloadableTypePlugin.java +++ b/springloaded/src/main/java/org/springsource/loaded/SystemPropertyConfiguredIsReloadableTypePlugin.java @@ -61,7 +61,7 @@ public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloada private String mostRecentReloadableDirs = null; // TODO need try/catch protection when calling plugins, in case of bad ones - public ReloadDecision shouldBeMadeReloadable(String typename, ProtectionDomain protectionDomain, byte[] bytes) { + public ReloadDecision shouldBeMadeReloadable(TypeRegistry typeRegistry, String typename, ProtectionDomain protectionDomain, byte[] bytes) { if (debug) { System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: entered, for typename " + typename); } @@ -106,6 +106,21 @@ public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloada // System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " does not have a codeSource"); // } } else { + // May have to do something special for CGLIB types + // These will have a type name of something like: grails/plugin/springsecurity/SpringSecurityService$$EnhancerByCGLIB$$8f956be2 + // But a codesource location of file:/Users/aclement/.m2/repository/org/springframework/spring-core/3.2.5.RELEASE/spring-core-3.2.5.RELEASE.jar + int cglibIndex = typename.indexOf("ByCGLIB$$"); // catches fastclass too + if (cglibIndex != -1) { + String originalType = typename.substring(0, typename.indexOf("$$")); // assuming first $$ is good enough + while (typeRegistry != null) { + ReloadableType originalReloadable = typeRegistry.getReloadableType(originalType); + if (originalReloadable != null) { + return ReloadDecision.YES; + } + typeRegistry = typeRegistry.getParentRegistry(); + } + } + if (debug) { System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " codeSource.getLocation() is " + codeSource.getLocation()); diff --git a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java index 53a2b87..03c257e 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java @@ -696,8 +696,8 @@ public class TypeRegistry { // } for (IsReloadableTypePlugin plugin : SpringLoadedPreProcessor.getIsReloadableTypePlugins()) { - ReloadDecision decision = plugin.shouldBeMadeReloadable(slashedName, protectionDomain, bytes); - if (decision == ReloadDecision.YES) { + ReloadDecision decision = plugin.shouldBeMadeReloadable(this,slashedName, protectionDomain, bytes); + if (decision == ReloadDecision.YES) { return true; } else if (decision == ReloadDecision.NO) { return false; diff --git a/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java b/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java index 94d7b36..fa75cf9 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java @@ -33,6 +33,7 @@ import org.springsource.loaded.Constants; import org.springsource.loaded.GlobalConfiguration; import org.springsource.loaded.IsReloadableTypePlugin; import org.springsource.loaded.LoadtimeInstrumentationPlugin; +import org.springsource.loaded.Log; import org.springsource.loaded.Plugin; import org.springsource.loaded.ReloadableType; import org.springsource.loaded.SystemClassReflectionRewriter; @@ -158,7 +159,14 @@ public class SpringLoadedPreProcessor implements Constants { // 3. If NO, and nothing in this classloader might be, return the original bytes // 4. If YES, make the type reloadable (including rewriting call sites) - if (typeRegistry.isReloadableTypeName(slashedClassName, protectionDomain, bytes)) { + boolean isReloadableTypeName = typeRegistry.isReloadableTypeName(slashedClassName, protectionDomain, bytes); + + // logging causes a ClassCircularity problem when reporting on: + // SL: Type 'org/codehaus/groovy/grails/cli/logging/GrailsConsolePrintStream' is not being made reloadable +// if (GlobalConfiguration.verboseMode && isReloadableTypeName) { +// Log.log("Type '"+slashedClassName+"' is preliminarily being considered a reloadable type"); +// } + if (isReloadableTypeName) { if (!firstReloadableTypeHit) { firstReloadableTypeHit = true; // TODO move into the ctor for ReloadableType so that it can't block loading @@ -186,39 +194,66 @@ public class SpringLoadedPreProcessor implements Constants { if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { log.info("Appears to be a CGLIB type, checking if type " + originalType + " is reloadable"); } - if (typeRegistry.isReloadableTypeName(originalType)) { - if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { - log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName - + " reloadable"); + TypeRegistry currentRegistry = typeRegistry; + while (currentRegistry != null) { + ReloadableType originalReloadable = currentRegistry.getReloadableType(originalType); + if (originalReloadable != null) { + makeReloadableAnyway = true; + break; } - makeReloadableAnyway = true; + currentRegistry = currentRegistry.getParentRegistry(); } +// if (typeRegistry.isReloadableTypeName(originalType)) { +// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { +// log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName +// + " reloadable"); +// } +// makeReloadableAnyway = true; +// } } - int cglibIndex2 = slashedClassName.indexOf("$$FastClassByCGLIB"); + int cglibIndex2 = makeReloadableAnyway?-1:slashedClassName.indexOf("$$FastClassByCGLIB"); if (cglibIndex2 != -1) { String originalType = slashedClassName.substring(0, cglibIndex2); if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { log.info("Appears to be a CGLIB FastClass type, checking if type " + originalType + " is reloadable"); } - if (typeRegistry.isReloadableTypeName(originalType)) { - if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { - log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName - + " reloadable"); + TypeRegistry currentRegistry = typeRegistry; + while (currentRegistry != null) { + ReloadableType originalReloadable = currentRegistry.getReloadableType(originalType); + if (originalReloadable != null) { + makeReloadableAnyway = true; + break; } - makeReloadableAnyway = true; + currentRegistry = currentRegistry.getParentRegistry(); } +// if (typeRegistry.isReloadableTypeName(originalType)) { +// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { +// log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName +// + " reloadable"); +// } +// makeReloadableAnyway = true; +// } } - int proxyIndex = slashedClassName.indexOf("$Proxy"); + int proxyIndex = makeReloadableAnyway?-1:slashedClassName.indexOf("$Proxy"); if (proxyIndex == 0 || (proxyIndex > 0 && slashedClassName.charAt(proxyIndex - 1) == '/')) { // Determine if the interfaces being implemented are reloadable String[] interfacesImplemented = Utils.discoverInterfaces(bytes); if (interfacesImplemented != null) { for (int i = 0; i < interfacesImplemented.length; i++) { - if (typeRegistry.isReloadableTypeName(interfacesImplemented[i])) { - makeReloadableAnyway = true; + TypeRegistry currentRegistry = typeRegistry; + while (currentRegistry != null) { + ReloadableType originalReloadable = currentRegistry.getReloadableType(interfacesImplemented[i]); + if (originalReloadable != null) { + makeReloadableAnyway = true; + break; + } + currentRegistry = currentRegistry.getParentRegistry(); } +// if (typeRegistry.isReloadableTypeName(interfacesImplemented[i])) { +// makeReloadableAnyway = true; +// } } } } @@ -234,8 +269,8 @@ public class SpringLoadedPreProcessor implements Constants { if (!makeReloadableAnyway) { // can't watch it for updates (it comes from a jar perhaps) so just rewrite call sites and return - if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { - log.info("can't watch " + slashedClassName + ": not making it reloadable"); + if (GlobalConfiguration.verboseMode) { + Log.log("Cannot watch "+slashedClassName+": not making it reloadable"); } if (needsClientSideRewriting(slashedClassName)) { bytes = typeRegistry.methodCallRewriteUseCacheIfAvailable(slashedClassName, bytes);