diff --git a/tomee/tomee-catalina/src/main/java/org/apache/catalina/startup/OpenEJBContextConfig.java b/tomee/tomee-catalina/src/main/java/org/apache/catalina/startup/OpenEJBContextConfig.java index 079dc4f092a..70d230bf6c6 100644 --- a/tomee/tomee-catalina/src/main/java/org/apache/catalina/startup/OpenEJBContextConfig.java +++ b/tomee/tomee-catalina/src/main/java/org/apache/catalina/startup/OpenEJBContextConfig.java @@ -109,6 +109,13 @@ public class OpenEJBContextConfig extends ContextConfig { // since we store all classes in WEB-INF we will do it only once so use this boolean to avoid multiple processing private Collection webInfClassesAnnotationsProcessed = new ArrayList<>(1); + // isIncluded() resolves the same module roots and the same web resource once + // per candidate class, and every resolution is a filesystem syscall. Both + // mappings are stable for the lifetime of a deploy, and this config instance + // is per context, so they are memoised here rather than recomputed. + private final Map canonicalFiles = new HashMap<>(); + private final Map urlAsFile = new HashMap<>(); + public OpenEJBContextConfig(final TomcatWebAppBuilder.StandardContextInfo standardContextInfo) { logger.debug("OpenEJBContextConfig({0})", standardContextInfo.toString()); info = standardContextInfo; @@ -565,7 +572,17 @@ protected void processAnnotationsWebResource(final WebResource webResource, final Map javaClassCache) { final WebAppInfo webAppInfo = info.get(); if (webAppInfo != null && FileResource.class.isInstance(webResource)) { + // Tomcat calls this for every web resource, but once every module has been processed + // there is nothing left to match against and resolving the resource is wasted work. + if (allWebAnnotationsProcessed(webAppInfo)) { + return; + } + final File file = new File(FileResource.class.cast(webResource).getCanonicalPath()); + // the path already comes from getCanonicalPath(), so record it as its own canonical + // form instead of letting isIncluded() resolve it a second time + canonicalFiles.putIfAbsent(file.getPath(), file); + for (final ClassListInfo info : webAppInfo.webAnnotatedClasses) { if (webInfClassesAnnotationsProcessed.contains(info.name)) { continue; @@ -724,19 +741,8 @@ protected void processAnnotationWebServlet(final String className, final Annotat } private boolean isIncluded(final File root, final File clazz) { - File file; - try { // symb links - file = root.getCanonicalFile(); - } catch (final IOException e) { - file = root; - } - - File current; - try { // symb links and windows long home names - current = clazz.getCanonicalFile(); - } catch (final IOException e) { - current = clazz; - } + final File file = canonical(root); // symb links + File current = canonical(clazz); // symb links and windows long home names while (current != null && current.exists()) { if (current.equals(file)) { final File parent = current.getParentFile(); @@ -751,7 +757,38 @@ private boolean isIncluded(final File root, final File clazz) { } private boolean isIncludedIn(final String filePath, final File classAsFile) throws MalformedURLException { - return isIncluded(URLs.toFile(new URL(filePath)), classAsFile); + File root = urlAsFile.get(filePath); + if (root == null) { + root = URLs.toFile(new URL(filePath)); + urlAsFile.put(filePath, root); + } + return isIncluded(root, classAsFile); + } + + private boolean allWebAnnotationsProcessed(final WebAppInfo webAppInfo) { + for (final ClassListInfo classes : webAppInfo.webAnnotatedClasses) { + if (!webInfClassesAnnotationsProcessed.contains(classes.name)) { + return false; + } + } + return true; + } + + private File canonical(final File file) { + final String key = file.getPath(); + final File cached = canonicalFiles.get(key); + if (cached != null) { + return cached; + } + + File resolved; + try { + resolved = file.getCanonicalFile(); + } catch (final IOException e) { + resolved = file; + } + canonicalFiles.put(key, resolved); + return resolved; } }