diff --git a/core/src/main/java/org/kohsuke/stapler/AbstractTearOff.java b/core/src/main/java/org/kohsuke/stapler/AbstractTearOff.java index 1a5837568c..110ab8577d 100644 --- a/core/src/main/java/org/kohsuke/stapler/AbstractTearOff.java +++ b/core/src/main/java/org/kohsuke/stapler/AbstractTearOff.java @@ -23,8 +23,16 @@ package org.kohsuke.stapler; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; -import java.util.Collection; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Partial default implementation of tear-off class, for convenience of derived classes. @@ -34,9 +42,22 @@ * @author Kohsuke Kawaguchi */ public abstract class AbstractTearOff extends CachingScriptLoader { + + private static final Logger LOGGER = Logger.getLogger(AbstractTearOff.class.getName()); + protected final MetaClass owner; protected final CLT classLoader; + private static final class ExpirableCacheHit { + private final long timestamp; + private final S script; + ExpirableCacheHit(long timestamp, S script) { + this.timestamp = timestamp; + this.script = script; + } + } + private final Cache> cachedScripts = CacheBuilder.newBuilder().softValues().build(); + protected AbstractTearOff(MetaClass owner, Class cltClass) { this.owner = owner; if(owner.classLoader!=null) @@ -87,12 +108,75 @@ public S resolveScript(String name) throws E { if(name.lastIndexOf('/') cached = cachedScripts.getIfPresent(res); + if (cached == null) { + S script; + if (LOGGER.isLoggable(Level.FINE)) { + long start = System.nanoTime(); + try { + script = parseScript(res); + } finally { + LOGGER.log(Level.FINE, "cache miss; took {0}ms to parse {1}", new Object[] {(System.nanoTime() - start) / 1_000_000, res}); + } + } else { + LOGGER.log(Level.FINE, "cache miss on {0}", res); + script = parseScript(res); + } + cachedScripts.put(res, new ExpirableCacheHit<>(timestamp, script)); + return script; + } else if (timestamp == cached.timestamp) { + LOGGER.log(Level.FINE, "cache hit on {0}", res); + return cached.script; + } else { + LOGGER.log(Level.FINE, "expired cache hit on {0}", res); + S script = parseScript(res); + cachedScripts.put(res, new ExpirableCacheHit<>(timestamp, script)); + return script; + } + } + } + } else { + LOGGER.log(Level.FINE, "standard CachingScriptLoader logic applies to {0}", res); + return parseScript(res); + } + } return null; } + private static final Pattern JAR_URL = Pattern.compile("jar:(file:.+)!/.*"); + private static File fileOf(URL res) { + try { + switch (res.getProtocol()) { + case "file": + return new File(res.toURI()); + case "jar": + Matcher m = JAR_URL.matcher(res.toString()); + if (m.matches()) { + return new File(new URI(m.group(1))); + } else { + return null; + } + default: + return null; + } + } catch (URISyntaxException | IllegalArgumentException x) { + return null; // caching is a best effort + } + } + protected final S loadScript(String name) throws E { S s = resolveScript(name); if (s!=null) return s;