diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules.adoc
index a9207b77b636..03ef41e8645a 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules.adoc
@@ -128,7 +128,7 @@ Appends the value to the existing value.
This is useful to append a value to properties that accept a comma separated list of values, for example:
+
----
-jetty.webapp.addServerClasses+=,com.acme
+jetty.webapp.addProtectedClasses+=,com.acme
----
+
// TODO: check what happens if the property is empty and +=,value is done: is the comma stripped? If so add a sentence about this.
diff --git a/jetty-core/jetty-ee/pom.xml b/jetty-core/jetty-ee/pom.xml
new file mode 100644
index 000000000000..8ff96f24bd78
--- /dev/null
+++ b/jetty-core/jetty-ee/pom.xml
@@ -0,0 +1,61 @@
+
+
+
+ 4.0.0
+
+ org.eclipse.jetty
+ jetty-core
+ 12.0.9-SNAPSHOT
+
+ jetty-ee
+ Core :: EE Common
+
+
+ ${project.groupId}.ee
+ org.eclipse.jetty.ee.*
+
+
+
+
+ org.eclipse.jetty
+ jetty-server
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.eclipse.jetty
+ jetty-slf4j-impl
+ test
+
+
+ org.eclipse.jetty.tests
+ jetty-test-multipart
+ ${project.version}
+ test
+
+
+ org.eclipse.jetty.toolchain
+ jetty-test-helper
+ test
+
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ true
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ @{argLine} ${jetty.surefire.argLine} --add-reads org.eclipse.jetty.ee=org.eclipse.jetty.logging
+
+
+
+
+
diff --git a/jetty-core/jetty-ee/src/main/config/etc/jetty-ee-webapp.xml b/jetty-core/jetty-ee/src/main/config/etc/jetty-ee-webapp.xml
new file mode 100644
index 000000000000..0ba8f34ec729
--- /dev/null
+++ b/jetty-core/jetty-ee/src/main/config/etc/jetty-ee-webapp.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jetty-core/jetty-ee/src/main/config/modules/ee-webapp.mod b/jetty-core/jetty-ee/src/main/config/modules/ee-webapp.mod
new file mode 100644
index 000000000000..db2420cf4f29
--- /dev/null
+++ b/jetty-core/jetty-ee/src/main/config/modules/ee-webapp.mod
@@ -0,0 +1,31 @@
+# DO NOT EDIT THIS FILE - See: https://eclipse.dev/jetty/documentation/
+
+[description]
+# tag::description[]
+This module provide common configuration of Java Servlet web applications over all environments.
+# end::description[]
+
+[xml]
+etc/jetty-ee-webapp.xml
+
+[lib]
+lib/jetty-ee-${jetty.version}.jar
+
+[ini-template]
+# tag::ini-template[]
+## Add to the server wide default jars and packages protected or hidden from webapps.
+## Protected (aka System) classes cannot be overridden by a webapp.
+## Hidden (aka Server) classes cannot be seen by a webapp
+## Lists of patterns are comma separated and may be either:
+## + a qualified classname e.g. 'com.acme.Foo'
+## + a package name e.g. 'net.example.'
+## + a jar file e.g. '${jetty.base.uri}/lib/dependency.jar'
+## + a directory of jars,resource or classes e.g. '${jetty.base.uri}/resources'
+## + A pattern preceded with a '-' is an exclusion, all other patterns are inclusions
+##
+## The +=, operator appends to a CSV list with a comma as needed.
+##
+#jetty.server.addProtectedClasses+=,org.example.
+#jetty.server.addHiddenClasses+=,org.example.
+# end::ini-template[]
+
diff --git a/jetty-core/jetty-ee/src/main/java/module-info.java b/jetty-core/jetty-ee/src/main/java/module-info.java
new file mode 100644
index 000000000000..90f777dc5720
--- /dev/null
+++ b/jetty-core/jetty-ee/src/main/java/module-info.java
@@ -0,0 +1,22 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+module org.eclipse.jetty.ee
+{
+ requires org.slf4j;
+
+ requires transitive org.eclipse.jetty.util;
+ requires transitive org.eclipse.jetty.server;
+
+ exports org.eclipse.jetty.ee;
+}
diff --git a/jetty-core/jetty-ee/src/main/java/org/eclipse/jetty/ee/WebAppClassLoading.java b/jetty-core/jetty-ee/src/main/java/org/eclipse/jetty/ee/WebAppClassLoading.java
new file mode 100644
index 000000000000..eaf13e52a64a
--- /dev/null
+++ b/jetty-core/jetty-ee/src/main/java/org/eclipse/jetty/ee/WebAppClassLoading.java
@@ -0,0 +1,214 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.ee;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.ClassMatcher;
+import org.eclipse.jetty.util.component.Environment;
+
+/**
+ * Common attributes and methods for configuring the {@link ClassLoader Class loading} of web application:
+ *
+ *
Protected (a.k.a. System) classes are classes typically provided by the JVM, that cannot be replaced by the
+ * web application, and they are always loaded via the environment or system classloader. They are visible but
+ * protected.
+ *
Hidden (a.k.a. Server) classes are those used to implement the Server and are not made available to the
+ * web application. They are hidden from the web application {@link ClassLoader}.
+ *
+ *
These protections are set to reasonable defaults {@link #DEFAULT_PROTECTED_CLASSES} and {@link #DEFAULT_HIDDEN_CLASSES},
+ * which may be programmatically configured and will affect the defaults applied to all web applications in the same JVM.
+ *
+ *
+ * The defaults applied by a specific {@link Server} can be configured using {@link #addProtectedClasses(Server, String...)} and
+ * {@link #addHiddenClasses(Server, String...)}. Alternately the {@link Server} attributes {@link #PROTECTED_CLASSES_ATTRIBUTE}
+ * and {@link #HIDDEN_CLASSES_ATTRIBUTE} may be used to direct set a {@link ClassMatcher} to use for all web applications
+ * within the server instance.
+ *
+ *
+ * The defaults applied by a specific {@link Environment} can be configured using {@link #addProtectedClasses(Environment, String...)} and
+ * {@link #addHiddenClasses(Environment, String...)}. Alternately the {@link Environment} attributes {@link #PROTECTED_CLASSES_ATTRIBUTE}
+ * and {@link #HIDDEN_CLASSES_ATTRIBUTE} may be used to direct set a {@link ClassMatcher} to use for all web applications
+ * within the server instance.
+ *
+ *
+ * Ultimately, the configurations set by this class only affects the defaults applied to each web application
+ * {@link org.eclipse.jetty.server.handler.ContextHandler Context} and the {@link ClassMatcher} fields of the web applications
+ * can be directly access to configure a specific context.
+ *
+ */
+public class WebAppClassLoading
+{
+ public static final String PROTECTED_CLASSES_ATTRIBUTE = "org.eclipse.jetty.webapp.systemClasses";
+ public static final String HIDDEN_CLASSES_ATTRIBUTE = "org.eclipse.jetty.webapp.serverClasses";
+
+ /**
+ * The default protected (system) classes used by a web application, which will be applied to the {@link ClassMatcher}s created
+ * by {@link #getProtectedClasses(Environment)}.
+ */
+ public static final ClassMatcher DEFAULT_PROTECTED_CLASSES = new ClassMatcher(
+ "java.", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
+ "javax.", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
+ "jakarta.", // Jakarta classes (per servlet spec v5.0 / Section 15.2.1)
+ "org.xml.", // javax.xml
+ "org.w3c." // javax.xml
+ );
+
+ /**
+ * The default hidden (server) classes used by a web application, which can be applied to the {@link ClassMatcher}s created
+ * by {@link #getHiddenClasses(Environment)}.
+ */
+ public static final ClassMatcher DEFAULT_HIDDEN_CLASSES = new ClassMatcher(
+ "org.eclipse.jetty." // hide jetty classes
+ );
+
+ /**
+ * Get the default protected (system) classes for a {@link Server}
+ * @param server The {@link Server} for the defaults
+ * @return The default protected (system) classes for the {@link Server}, which will be empty if not previously configured.
+ */
+ public static ClassMatcher getProtectedClasses(Server server)
+ {
+ return getClassMatcher(server, PROTECTED_CLASSES_ATTRIBUTE, null);
+ }
+
+ /**
+ * Get the default protected (system) classes for an {@link Environment}
+ * @param environment The {@link Server} for the defaults
+ * @return The default protected (system) classes for the {@link Environment}, which will be the {@link #DEFAULT_PROTECTED_CLASSES} if not previously configured.
+ */
+ public static ClassMatcher getProtectedClasses(Environment environment)
+ {
+ return getClassMatcher(environment, PROTECTED_CLASSES_ATTRIBUTE, DEFAULT_PROTECTED_CLASSES);
+ }
+
+ /**
+ * Add a protected (system) Class pattern to use for all WebAppContexts.
+ * @param patterns the patterns to use
+ */
+ public static void addProtectedClasses(String... patterns)
+ {
+ DEFAULT_PROTECTED_CLASSES.add(patterns);
+ }
+
+ /**
+ * Add a protected (system) Class pattern to use for all WebAppContexts of a given {@link Server}.
+ * @param attributes The {@link Attributes} instance to add classes to
+ * @param patterns the patterns to use
+ */
+ public static void addProtectedClasses(Attributes attributes, String... patterns)
+ {
+ if (patterns != null && patterns.length > 0)
+ getClassMatcher(attributes, PROTECTED_CLASSES_ATTRIBUTE, null).add(patterns);
+ }
+
+ /**
+ * Add a protected (system) Class pattern to use for all WebAppContexts of a given {@link Server}.
+ * @param server The {@link Server} instance to add classes to
+ * @param patterns the patterns to use
+ */
+ public static void addProtectedClasses(Server server, String... patterns)
+ {
+ if (patterns != null && patterns.length > 0)
+ getClassMatcher(server, PROTECTED_CLASSES_ATTRIBUTE, null).add(patterns);
+ }
+
+ /**
+ * Add a protected (system) Class pattern to use for WebAppContexts of a given environment.
+ * @param environment The {@link Environment} instance to add classes to
+ * @param patterns the patterns to use
+ */
+ public static void addProtectedClasses(Environment environment, String... patterns)
+ {
+ if (patterns != null && patterns.length > 0)
+ getClassMatcher(environment, PROTECTED_CLASSES_ATTRIBUTE, DEFAULT_PROTECTED_CLASSES).add(patterns);
+ }
+
+ /**
+ * Get the default hidden (server) classes for a {@link Server}
+ * @param server The {@link Server} for the defaults
+ * @return The default hidden (server) classes for the {@link Server}, which will be empty if not previously configured.
+ *
+ */
+ public static ClassMatcher getHiddenClasses(Server server)
+ {
+ return getClassMatcher(server, HIDDEN_CLASSES_ATTRIBUTE, null);
+ }
+
+ /**
+ * Get the default hidden (server) classes for an {@link Environment}
+ * @param environment The {@link Server} for the defaults
+ * @return The default hidden (server) classes for the {@link Environment}, which will be {@link #DEFAULT_PROTECTED_CLASSES} if not previously configured.
+ */
+ public static ClassMatcher getHiddenClasses(Environment environment)
+ {
+ return getClassMatcher(environment, HIDDEN_CLASSES_ATTRIBUTE, DEFAULT_HIDDEN_CLASSES);
+ }
+
+ /**
+ * Add a hidden (server) Class pattern to use for all WebAppContexts of a given {@link Server}.
+ * @param patterns the patterns to use
+ */
+ public static void addHiddenClasses(String... patterns)
+ {
+ DEFAULT_HIDDEN_CLASSES.add(patterns);
+ }
+
+ /**
+ * Add a hidden (server) Class pattern to use for all WebAppContexts of a given {@link Server}.
+ * @param attributes The {@link Attributes} instance to add classes to
+ * @param patterns the patterns to use
+ */
+ @Deprecated (forRemoval = true)
+ public static void addHiddenClasses(Attributes attributes, String... patterns)
+ {
+ if (patterns != null && patterns.length > 0)
+ getClassMatcher(attributes, HIDDEN_CLASSES_ATTRIBUTE, null).add(patterns);
+ }
+
+ /**
+ * Add a hidden (server) Class pattern to use for all WebAppContexts of a given {@link Server}.
+ * @param server The {@link Server} instance to add classes to
+ * @param patterns the patterns to use
+ */
+ public static void addHiddenClasses(Server server, String... patterns)
+ {
+ if (patterns != null && patterns.length > 0)
+ getClassMatcher(server, HIDDEN_CLASSES_ATTRIBUTE, null).add(patterns);
+ }
+
+ /**
+ * Add a hidden (server) Class pattern to use for all ee9 WebAppContexts.
+ * @param environment The {@link Environment} instance to add classes to
+ * @param patterns the patterns to use
+ */
+ public static void addHiddenClasses(Environment environment, String... patterns)
+ {
+ if (patterns != null && patterns.length > 0)
+ getClassMatcher(environment, HIDDEN_CLASSES_ATTRIBUTE, DEFAULT_HIDDEN_CLASSES).add(patterns);
+ }
+
+ private static ClassMatcher getClassMatcher(Attributes attributes, String attribute, ClassMatcher defaultPatterns)
+ {
+ Object existing = attributes.getAttribute(attribute);
+ if (existing instanceof ClassMatcher cm)
+ return cm;
+
+ ClassMatcher classMatcher = (existing instanceof String[] stringArray)
+ ? new ClassMatcher(stringArray) : new ClassMatcher(defaultPatterns);
+ attributes.setAttribute(attribute, classMatcher);
+ return classMatcher;
+ }
+
+}
diff --git a/jetty-core/jetty-ee/src/test/java/orge/eclipse/jetty/ee/WebAppClassLoadingTest.java b/jetty-core/jetty-ee/src/test/java/orge/eclipse/jetty/ee/WebAppClassLoadingTest.java
new file mode 100644
index 000000000000..cf4299986066
--- /dev/null
+++ b/jetty-core/jetty-ee/src/test/java/orge/eclipse/jetty/ee/WebAppClassLoadingTest.java
@@ -0,0 +1,222 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package orge.eclipse.jetty.ee;
+
+import java.util.Arrays;
+
+import org.eclipse.jetty.ee.WebAppClassLoading;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ClassMatcher;
+import org.eclipse.jetty.util.component.Environment;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItemInArray;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
+
+public class WebAppClassLoadingTest
+{
+ @BeforeEach
+ public void beforeEach()
+ {
+ Environment.ensure("Test");
+ }
+
+ @AfterEach
+ public void afterEach()
+ {
+ Environment.ensure("Test").clearAttributes();
+ }
+
+ @Test
+ public void testServerDefaults()
+ {
+ Server server = new Server();
+ ClassMatcher protect = WebAppClassLoading.getProtectedClasses(server);
+ assertThat(protect.size(), is(0));
+ assertThat(server.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
+ ClassMatcher hide = WebAppClassLoading.getHiddenClasses(server);
+ assertThat(hide.size(), is(0));
+ assertThat(server.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
+ }
+
+ @Test
+ public void testServerAttributeDefaults()
+ {
+ Server server = new Server();
+ ClassMatcher protect = new ClassMatcher("org.protect.");
+ server.setAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE, protect);
+ ClassMatcher hide = new ClassMatcher("org.hide.");
+ server.setAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE, hide);
+
+ assertThat(WebAppClassLoading.getProtectedClasses(server), sameInstance(protect));
+ assertThat(WebAppClassLoading.getHiddenClasses(server), sameInstance(hide));
+ }
+
+ @Test
+ public void testServerStringAttributeDefaults()
+ {
+ Server server = new Server();
+ server.setAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE, new String[] {"org.protect."});
+ server.setAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE, new String[] {"org.hide."});
+
+ ClassMatcher protect = WebAppClassLoading.getProtectedClasses(server);
+ assertThat(protect.size(), is(1));
+ assertThat(Arrays.asList(protect.getPatterns()), contains("org.protect."));
+ assertThat(server.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
+ ClassMatcher hide = WebAppClassLoading.getHiddenClasses(server);
+ assertThat(hide.size(), is(1));
+ assertThat(Arrays.asList(hide.getPatterns()), contains("org.hide."));
+ assertThat(server.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
+ }
+
+ @Test
+ public void testServerProgrammaticDefaults()
+ {
+ Server server = new Server();
+ WebAppClassLoading.addProtectedClasses(server, "org.protect.");
+ WebAppClassLoading.addHiddenClasses(server, "org.hide.");
+
+ ClassMatcher protect = WebAppClassLoading.getProtectedClasses(server);
+ assertThat(protect.size(), is(1));
+ assertThat(Arrays.asList(protect.getPatterns()), contains("org.protect."));
+ assertThat(server.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
+ ClassMatcher hide = WebAppClassLoading.getHiddenClasses(server);
+ assertThat(hide.size(), is(1));
+ assertThat(Arrays.asList(hide.getPatterns()), contains("org.hide."));
+ assertThat(server.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
+ }
+
+ @Test
+ public void testServerAddPatterns()
+ {
+ Server server = new Server();
+ ClassMatcher protect = WebAppClassLoading.getProtectedClasses(server);
+ ClassMatcher hide = WebAppClassLoading.getHiddenClasses(server);
+
+ assertThat(protect.size(), is(0));
+ assertThat(hide.size(), is(0));
+
+ WebAppClassLoading.addProtectedClasses(server, "org.protect.", "com.protect.");
+ WebAppClassLoading.addHiddenClasses(server, "org.hide.", "com.hide.");
+
+ assertThat(protect.size(), is(2));
+ assertThat(Arrays.asList(protect.getPatterns()), containsInAnyOrder("org.protect.", "com.protect."));
+ assertThat(server.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
+
+ assertThat(hide.size(), is(2));
+ assertThat(Arrays.asList(hide.getPatterns()), containsInAnyOrder("org.hide.", "com.hide."));
+ assertThat(server.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
+ }
+
+ @Test
+ public void testEnvironmentDefaults()
+ {
+ Environment environment = Environment.get("Test");
+ ClassMatcher protect = WebAppClassLoading.getProtectedClasses(environment);
+ assertThat(protect, equalTo(WebAppClassLoading.DEFAULT_PROTECTED_CLASSES));
+ assertThat(environment.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
+ ClassMatcher hide = WebAppClassLoading.getHiddenClasses(environment);
+ assertThat(hide, equalTo(WebAppClassLoading.DEFAULT_HIDDEN_CLASSES));
+ assertThat(environment.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
+ }
+
+ @Test
+ public void testEnvironmentAttributeDefaults()
+ {
+ Environment environment = Environment.get("Test");
+ ClassMatcher protect = new ClassMatcher("org.protect.");
+ environment.setAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE, protect);
+ ClassMatcher hide = new ClassMatcher("org.hide.");
+ environment.setAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE, hide);
+
+ assertThat(WebAppClassLoading.getProtectedClasses(environment), sameInstance(protect));
+ assertThat(WebAppClassLoading.getHiddenClasses(environment), sameInstance(hide));
+ }
+
+ @Test
+ public void testEnvironmentStringAttributeDefaults()
+ {
+ Environment environment = Environment.get("Test");
+ environment.setAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE, new String[] {"org.protect."});
+ environment.setAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE, new String[] {"org.hide."});
+
+ ClassMatcher protect = WebAppClassLoading.getProtectedClasses(environment);
+ assertThat(protect.size(), is(1));
+ assertThat(Arrays.asList(protect.getPatterns()), contains("org.protect."));
+ assertThat(environment.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
+ ClassMatcher hide = WebAppClassLoading.getHiddenClasses(environment);
+ assertThat(hide.size(), is(1));
+ assertThat(Arrays.asList(hide.getPatterns()), contains("org.hide."));
+ assertThat(environment.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
+ }
+
+ @Test
+ public void testEnvironmentProgrammaticDefaults()
+ {
+ Environment environment = Environment.get("Test");
+ WebAppClassLoading.addProtectedClasses(environment, "org.protect.");
+ WebAppClassLoading.addHiddenClasses(environment, "org.hide.");
+
+ ClassMatcher protect = WebAppClassLoading.getProtectedClasses(environment);
+ ClassMatcher hide = WebAppClassLoading.getHiddenClasses(environment);
+
+ assertThat(protect.size(), is(WebAppClassLoading.DEFAULT_PROTECTED_CLASSES.size() + 1));
+ assertThat(protect.getPatterns(), hasItemInArray("org.protect."));
+ for (String pattern : WebAppClassLoading.DEFAULT_PROTECTED_CLASSES)
+ assertThat(protect.getPatterns(), hasItemInArray(pattern));
+ assertThat(environment.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
+
+ assertThat(hide.size(), is(WebAppClassLoading.DEFAULT_HIDDEN_CLASSES.size() + 1));
+ assertThat(hide.getPatterns(), hasItemInArray("org.hide."));
+ for (String pattern : WebAppClassLoading.DEFAULT_HIDDEN_CLASSES)
+ assertThat(hide.getPatterns(), hasItemInArray(pattern));
+ assertThat(environment.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
+ }
+
+ @Test
+ public void testEnvironmentAddPatterns()
+ {
+ Environment environment = Environment.get("Test");
+ ClassMatcher protect = WebAppClassLoading.getProtectedClasses(environment);
+ ClassMatcher hide = WebAppClassLoading.getHiddenClasses(environment);
+
+ assertThat(protect, equalTo(WebAppClassLoading.DEFAULT_PROTECTED_CLASSES));
+ assertThat(hide, equalTo(WebAppClassLoading.DEFAULT_HIDDEN_CLASSES));
+
+ WebAppClassLoading.addProtectedClasses(environment, "org.protect.", "com.protect.");
+ WebAppClassLoading.addHiddenClasses(environment, "org.hide.", "com.hide.");
+
+ assertThat(protect.size(), is(WebAppClassLoading.DEFAULT_PROTECTED_CLASSES.size() + 2));
+ assertThat(protect.getPatterns(), hasItemInArray("org.protect."));
+ assertThat(protect.getPatterns(), hasItemInArray("com.protect."));
+ for (String pattern : WebAppClassLoading.DEFAULT_PROTECTED_CLASSES)
+ assertThat(protect.getPatterns(), hasItemInArray(pattern));
+ assertThat(environment.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
+
+ assertThat(hide.size(), is(WebAppClassLoading.DEFAULT_HIDDEN_CLASSES.size() + 2));
+ assertThat(hide.getPatterns(), hasItemInArray("org.hide."));
+ assertThat(hide.getPatterns(), hasItemInArray("com.hide."));
+ for (String pattern : WebAppClassLoading.DEFAULT_HIDDEN_CLASSES)
+ assertThat(hide.getPatterns(), hasItemInArray(pattern));
+ assertThat(environment.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
+ }
+
+}
diff --git a/jetty-core/jetty-keystore/src/main/config/modules/test-keystore.mod b/jetty-core/jetty-keystore/src/main/config/modules/test-keystore.mod
index 0ff1996dbfed..1c1b98fb3dc0 100644
--- a/jetty-core/jetty-keystore/src/main/config/modules/test-keystore.mod
+++ b/jetty-core/jetty-keystore/src/main/config/modules/test-keystore.mod
@@ -25,7 +25,7 @@ etc/jetty-test-keystore.xml
[ini]
bouncycastle.version?=@bouncycastle.version@
-jetty.webapp.addServerClasses+=,${jetty.base.uri}/lib/bouncycastle/
+jetty.webapp.addHiddenClasses+=,${jetty.base.uri}/lib/bouncycastle/
jetty.sslContext.keyStorePath?=etc/test-keystore.p12
jetty.sslContext.keyStoreType?=PKCS12
jetty.sslContext.keyStorePassword?=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
diff --git a/jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/SessionData.java b/jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/SessionData.java
index a5445c76ccac..c33682cb4327 100644
--- a/jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/SessionData.java
+++ b/jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/SessionData.java
@@ -89,7 +89,7 @@ else if (contextLoader instanceof ClassVisibilityChecker)
//Clazz not loaded by context classloader, but ask if loadable by context classloader,
//because preferable to use context classloader if possible (eg for deep structures).
ClassVisibilityChecker checker = (ClassVisibilityChecker)(contextLoader);
- isContextLoader = (checker.isSystemClass(clazz) && !(checker.isServerClass(clazz)));
+ isContextLoader = (checker.isProtectedClass(clazz) && !(checker.isHiddenClass(clazz)));
}
else
{
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ClassMatcher.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ClassMatcher.java
new file mode 100644
index 000000000000..2fe9fe754c8d
--- /dev/null
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ClassMatcher.java
@@ -0,0 +1,810 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * A matcher for classes based on package and/or location and/or module/
+ *
+ * Performs pattern matching of a class against a set of pattern entries.
+ * A class pattern is a string of one of the forms:
+ *
'org.package.SomeClass' will match a specific class
+ *
'org.package.' will match a specific package hierarchy
+ *
'org.package.SomeClass$NestedClass ' will match a nested class exactly otherwise.
+ * Nested classes are matched by their containing class. (eg. org.example.MyClass
+ * matches org.example.MyClass$AnyNestedClass)
+ *
'file:///some/location/' - A file system directory from which
+ * the class was loaded
+ *
'file:///some/location.jar' - The URI of a jar file from which
+ * the class was loaded
+ *
'jrt:/modulename' - A Java9 module name
+ *
Any of the above patterns preceded by '-' will exclude rather than include the match.
+ *
+ * When class is initialized from a classpath pattern string, entries
+ * in this string should be separated by ':' (semicolon) or ',' (comma).
+ */
+
+public class ClassMatcher extends AbstractSet
+{
+ public static class Entry
+ {
+ private final String _pattern;
+ private final String _name;
+ private final boolean _inclusive;
+
+ protected Entry(String name, boolean inclusive)
+ {
+ _name = name;
+ _inclusive = inclusive;
+ _pattern = inclusive ? _name : ("-" + _name);
+ }
+
+ public String getPattern()
+ {
+ return _pattern;
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ @Override
+ public String toString()
+ {
+ return _pattern;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return _pattern.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ return (o instanceof Entry) && _pattern.equals(((Entry)o)._pattern);
+ }
+
+ public boolean isInclusive()
+ {
+ return _inclusive;
+ }
+ }
+
+ private static class PackageEntry extends Entry
+ {
+ protected PackageEntry(String name, boolean inclusive)
+ {
+ super(name, inclusive);
+ }
+ }
+
+ private static class ClassEntry extends Entry
+ {
+ protected ClassEntry(String name, boolean inclusive)
+ {
+ super(name, inclusive);
+ }
+ }
+
+ private static class LocationEntry extends Entry
+ {
+ private final Path _path;
+
+ protected LocationEntry(String name, boolean inclusive)
+ {
+ super(name, inclusive);
+ URI uri = URI.create(name);
+ if (!uri.isAbsolute() && !"file".equalsIgnoreCase(uri.getScheme()))
+ throw new IllegalArgumentException("Not a valid file URI: " + name);
+
+ _path = Paths.get(uri);
+ }
+
+ public Path getPath()
+ {
+ return _path;
+ }
+ }
+
+ private static class ModuleEntry extends Entry
+ {
+ private final String _module;
+
+ protected ModuleEntry(String name, boolean inclusive)
+ {
+ super(name, inclusive);
+ if (!getName().startsWith("jrt:"))
+ throw new IllegalArgumentException(name);
+ _module = getName().split("/")[1];
+ }
+
+ public String getModule()
+ {
+ return _module;
+ }
+ }
+
+ public static class ByPackage extends AbstractSet implements Predicate
+ {
+ private final Index.Mutable _entries = new Index.Builder()
+ .caseSensitive(true)
+ .mutable()
+ .build();
+
+ @Override
+ public boolean test(String name)
+ {
+ return _entries.getBest(name) != null;
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ return _entries.keySet().stream().map(_entries::get).iterator();
+ }
+
+ @Override
+ public int size()
+ {
+ return _entries.size();
+ }
+
+ @Override
+ public boolean isEmpty()
+ {
+ return _entries.isEmpty();
+ }
+
+ @Override
+ public boolean add(Entry entry)
+ {
+ String name = entry.getName();
+ if (entry instanceof ClassEntry)
+ name += "$";
+ else if (!(entry instanceof PackageEntry))
+ throw new IllegalArgumentException(entry.toString());
+ else if (".".equals(name))
+ name = "";
+
+ if (_entries.get(name) != null)
+ return false;
+
+ return _entries.put(name, entry);
+ }
+
+ @Override
+ public boolean remove(Object entry)
+ {
+ if (!(entry instanceof Entry))
+ return false;
+
+ return _entries.remove(((Entry)entry).getName()) != null;
+ }
+
+ @Override
+ public void clear()
+ {
+ _entries.clear();
+ }
+ }
+
+ public static class ByClass extends HashSet implements Predicate
+ {
+ private final Map _entries = new HashMap<>();
+
+ @Override
+ public boolean test(String name)
+ {
+ return _entries.containsKey(name);
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ return _entries.values().iterator();
+ }
+
+ @Override
+ public int size()
+ {
+ return _entries.size();
+ }
+
+ @Override
+ public boolean add(Entry entry)
+ {
+ if (!(entry instanceof ClassEntry))
+ throw new IllegalArgumentException(entry.toString());
+ return _entries.put(entry.getName(), entry) == null;
+ }
+
+ @Override
+ public boolean remove(Object entry)
+ {
+ if (!(entry instanceof Entry))
+ return false;
+
+ return _entries.remove(((Entry)entry).getName()) != null;
+ }
+ }
+
+ public static class ByPackageOrName extends AbstractSet implements Predicate
+ {
+ private final ByClass _byClass = new ByClass();
+ private final ByPackage _byPackage = new ByPackage();
+
+ @Override
+ public boolean test(String name)
+ {
+ return _byPackage.test(name) || _byClass.test(name);
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ // by package contains all entries (classes are also $ packages).
+ return _byPackage.iterator();
+ }
+
+ @Override
+ public int size()
+ {
+ return _byPackage.size();
+ }
+
+ @Override
+ public boolean add(Entry entry)
+ {
+ if (entry instanceof PackageEntry)
+ return _byPackage.add(entry);
+
+ if (entry instanceof ClassEntry)
+ {
+ // Add class name to packages also as classes act
+ // as packages for nested classes.
+ boolean added = _byPackage.add(entry);
+ added = _byClass.add(entry) || added;
+ return added;
+ }
+
+ throw new IllegalArgumentException();
+ }
+
+ @Override
+ public boolean remove(Object o)
+ {
+ if (!(o instanceof Entry))
+ return false;
+
+ boolean removedPackage = _byPackage.remove(o);
+ boolean removedClass = _byClass.remove(o);
+
+ return removedPackage || removedClass;
+ }
+
+ @Override
+ public void clear()
+ {
+ _byPackage.clear();
+ _byClass.clear();
+ }
+ }
+
+ public static class ByLocation extends HashSet implements Predicate
+ {
+ @Override
+ public boolean test(URI uri)
+ {
+ if ((uri == null) || (!uri.isAbsolute()))
+ return false;
+ if (!uri.getScheme().equals("file"))
+ return false;
+ Path path = Paths.get(uri);
+
+ for (Entry entry : this)
+ {
+ if (!(entry instanceof LocationEntry))
+ throw new IllegalStateException();
+
+ Path entryPath = ((LocationEntry)entry).getPath();
+
+ if (Files.isDirectory(entryPath))
+ {
+ if (path.startsWith(entryPath))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ try
+ {
+ if (Files.isSameFile(path, entryPath))
+ {
+ return true;
+ }
+ }
+ catch (IOException ignore)
+ {
+ // this means there is a FileSystem issue preventing comparison.
+ // Use old technique
+ if (path.equals(entryPath))
+ {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ public static class ByModule extends HashSet implements Predicate
+ {
+ private final Index.Mutable _entries = new Index.Builder()
+ .caseSensitive(true)
+ .mutable()
+ .build();
+
+ @Override
+ public boolean test(URI uri)
+ {
+ if ((uri == null) || (!uri.isAbsolute()))
+ return false;
+ if (!uri.getScheme().equalsIgnoreCase("jrt"))
+ return false;
+ String module = uri.getPath();
+ int end = module.indexOf('/', 1);
+ if (end < 1)
+ end = module.length();
+ return _entries.get(module, 1, end - 1) != null;
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ return _entries.keySet().stream().map(_entries::get).iterator();
+ }
+
+ @Override
+ public int size()
+ {
+ return _entries.size();
+ }
+
+ @Override
+ public boolean add(Entry entry)
+ {
+ if (!(entry instanceof ModuleEntry))
+ throw new IllegalArgumentException(entry.toString());
+ String module = ((ModuleEntry)entry).getModule();
+
+ if (_entries.get(module) != null)
+ return false;
+ _entries.put(module, entry);
+ return true;
+ }
+
+ @Override
+ public boolean remove(Object entry)
+ {
+ if (!(entry instanceof Entry))
+ return false;
+
+ return _entries.remove(((Entry)entry).getName()) != null;
+ }
+ }
+
+ public static class ByLocationOrModule extends AbstractSet implements Predicate
+ {
+ private final ByLocation _byLocation = new ByLocation();
+ private final ByModule _byModule = new ByModule();
+
+ @Override
+ public boolean test(URI name)
+ {
+ if ((name == null) || (!name.isAbsolute()))
+ return false;
+ return _byLocation.test(name) || _byModule.test(name);
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ Set entries = new HashSet<>();
+ entries.addAll(_byLocation);
+ entries.addAll(_byModule);
+ return entries.iterator();
+ }
+
+ @Override
+ public int size()
+ {
+ return _byLocation.size() + _byModule.size();
+ }
+
+ @Override
+ public boolean add(Entry entry)
+ {
+ if (entry instanceof LocationEntry)
+ return _byLocation.add(entry);
+ if (entry instanceof ModuleEntry)
+ return _byModule.add(entry);
+
+ throw new IllegalArgumentException(entry.toString());
+ }
+
+ @Override
+ public boolean remove(Object o)
+ {
+ if (o instanceof LocationEntry)
+ return _byLocation.remove(o);
+ if (o instanceof ModuleEntry)
+ return _byModule.remove(o);
+ return false;
+ }
+
+ @Override
+ public void clear()
+ {
+ _byLocation.clear();
+ _byModule.clear();
+ }
+ }
+
+ protected final Map _entries;
+ protected final IncludeExcludeSet _patterns;
+ protected final IncludeExcludeSet _locations;
+
+ protected ClassMatcher(Map entries, IncludeExcludeSet patterns, IncludeExcludeSet locations)
+ {
+ _entries = entries;
+ _patterns = patterns == null ? new IncludeExcludeSet<>(ByPackageOrName.class) : patterns;
+ _locations = locations == null ? new IncludeExcludeSet<>(ByLocationOrModule.class) : locations;
+ }
+
+ private ClassMatcher(Map entries)
+ {
+ this(entries, null, null);
+ }
+
+ public ClassMatcher()
+ {
+ this(new HashMap<>());
+ }
+
+ public ClassMatcher(ClassMatcher patterns)
+ {
+ this(new HashMap<>());
+ if (patterns != null)
+ setAll(patterns.getPatterns());
+ }
+
+ public ClassMatcher(String... patterns)
+ {
+ this(new HashMap<>());
+ if (patterns != null && patterns.length > 0)
+ setAll(patterns);
+ }
+
+ public ClassMatcher(String pattern)
+ {
+ this(new HashMap<>());
+ add(pattern);
+ }
+
+ public ClassMatcher asImmutable()
+ {
+ return new ClassMatcher(Map.copyOf(_entries),
+ _patterns.asImmutable(),
+ _locations.asImmutable());
+ }
+
+ public boolean include(String name)
+ {
+ if (name == null)
+ return false;
+ return add(newEntry(name, true));
+ }
+
+ public boolean include(String... name)
+ {
+ boolean added = false;
+ for (String n : name)
+ {
+ if (n != null)
+ added = add(newEntry(n, true)) || added;
+ }
+ return added;
+ }
+
+ public boolean exclude(String name)
+ {
+ if (name == null)
+ return false;
+ return add(newEntry(name, false));
+ }
+
+ public boolean exclude(String... name)
+ {
+ boolean added = false;
+ for (String n : name)
+ {
+ if (n != null)
+ added = add(newEntry(n, false)) || added;
+ }
+ return added;
+ }
+
+ @Override
+ public boolean add(String pattern)
+ {
+ if (pattern == null)
+ return false;
+ return add(newEntry(pattern));
+ }
+
+ public boolean add(String... pattern)
+ {
+ boolean added = false;
+ for (String p : pattern)
+ {
+ if (p != null)
+ added = add(newEntry(p)) || added;
+ }
+ return added;
+ }
+
+ protected boolean add(Entry entry)
+ {
+ if (_entries.containsKey(entry.getPattern()))
+ return false;
+ _entries.put(entry.getPattern(), entry);
+
+ if (entry instanceof LocationEntry || entry instanceof ModuleEntry)
+ {
+ if (entry.isInclusive())
+ _locations.include(entry);
+ else
+ _locations.exclude(entry);
+ }
+ else
+ {
+ if (entry.isInclusive())
+ _patterns.include(entry);
+ else
+ _patterns.exclude(entry);
+ }
+ return true;
+ }
+
+ protected Entry newEntry(String pattern)
+ {
+ if (pattern.startsWith("-"))
+ return newEntry(pattern.substring(1), false);
+ return newEntry(pattern, true);
+ }
+
+ protected Entry newEntry(String name, boolean inclusive)
+ {
+ if (name.startsWith("-"))
+ throw new IllegalStateException(name);
+ if (name.startsWith("file:"))
+ return new LocationEntry(name, inclusive);
+ if (name.startsWith("jrt:"))
+ return new ModuleEntry(name, inclusive);
+ if (name.endsWith("."))
+ return new PackageEntry(name, inclusive);
+ return new ClassEntry(name, inclusive);
+ }
+
+ @Override
+ public boolean remove(Object o)
+ {
+ if (!(o instanceof String pattern))
+ return false;
+
+ Entry entry = _entries.remove(pattern);
+ if (entry == null)
+ return false;
+
+ List saved = new ArrayList<>(_entries.values());
+ clear();
+ for (Entry e : saved)
+ {
+ add(e);
+ }
+ return true;
+ }
+
+ @Override
+ public void clear()
+ {
+ _entries.clear();
+ _patterns.clear();
+ _locations.clear();
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ return _entries.keySet().iterator();
+ }
+
+ @Override
+ public int size()
+ {
+ return _entries.size();
+ }
+
+ /**
+ * Initialize the matcher by parsing each classpath pattern in an array
+ *
+ * @param classes array of classpath patterns
+ */
+ private void setAll(String[] classes)
+ {
+ _entries.clear();
+ addAll(classes);
+ }
+
+ /**
+ * Add array of classpath patterns.
+ * @param classes array of classpath patterns
+ */
+ private void addAll(String[] classes)
+ {
+ if (classes != null)
+ addAll(Arrays.asList(classes));
+ }
+
+ /**
+ * @return array of classpath patterns
+ */
+ public String[] getPatterns()
+ {
+ return toArray(new String[0]);
+ }
+
+ /**
+ * @return array of inclusive classpath patterns
+ */
+ public String[] getInclusions()
+ {
+ return _entries.values().stream().filter(Entry::isInclusive).map(Entry::getName).toArray(String[]::new);
+ }
+
+ /**
+ * @return array of excluded classpath patterns (without '-' prefix)
+ */
+ public String[] getExclusions()
+ {
+ return _entries.values().stream().filter(e -> !e.isInclusive()).map(Entry::getName).toArray(String[]::new);
+ }
+
+ /**
+ * Match the class name against the pattern
+ *
+ * @param name name of the class to match
+ * @return true if class matches the pattern
+ */
+ public boolean match(String name)
+ {
+ return _patterns.test(name);
+ }
+
+ /**
+ * Match the class name against the pattern
+ *
+ * @param clazz A class to try to match
+ * @return true if class matches the pattern
+ */
+ public boolean match(Class> clazz)
+ {
+ try
+ {
+ return combine(_patterns, clazz.getName(), _locations, () -> TypeUtil.getLocationOfClass(clazz));
+ }
+ catch (Exception ignored)
+ {
+ }
+ return false;
+ }
+
+ public boolean match(String name, URL url)
+ {
+ if (url == null)
+ return false;
+
+ // Strip class suffix for name matching
+ if (name.endsWith(".class"))
+ name = name.substring(0, name.length() - 6);
+
+ // Treat path elements as packages for name matching
+ name = StringUtil.replace(name, '/', '.');
+
+ return combine(_patterns, name, _locations, () ->
+ {
+ try
+ {
+ return URIUtil.unwrapContainer(url.toURI());
+ }
+ catch (URISyntaxException ignored)
+ {
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Match a class against inclusions and exclusions by name and location.
+ * Name based checks are performed before location checks. For a class to match,
+ * it must not be excluded by either name or location, and must either be explicitly
+ * included, or for there to be no inclusions. In the case where the location
+ * of the class is null, it will match if it is included by name, or
+ * if there are no location exclusions.
+ *
+ * @param names configured inclusions and exclusions by name
+ * @param name the name to check
+ * @param locations configured inclusions and exclusions by location
+ * @param location the location of the class (can be null)
+ * @return true if the class is not excluded but is included, or there are
+ * no inclusions. False otherwise.
+ */
+ static boolean combine(IncludeExcludeSet names, String name, IncludeExcludeSet locations, Supplier location)
+ {
+ // check the name set
+ Boolean byName = names.isIncludedAndNotExcluded(name);
+
+ // If we excluded by name, then no match
+ if (Boolean.FALSE == byName)
+ return false;
+
+ // check the location set
+ URI uri = location.get();
+ Boolean byLocation = uri == null ? null : locations.isIncludedAndNotExcluded(uri);
+
+ // If we excluded by location or couldn't check location exclusion, then no match
+ if (Boolean.FALSE == byLocation || (locations.hasExcludes() && uri == null))
+ return false;
+
+ // If there are includes, then we must be included to match.
+ if (names.hasIncludes() || locations.hasIncludes())
+ return byName == Boolean.TRUE || byLocation == Boolean.TRUE;
+
+ // Otherwise there are no includes and it was not excluded, so match
+ return true;
+ }
+}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ClassVisibilityChecker.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ClassVisibilityChecker.java
index 3167d9b82d35..12bfaec2322f 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ClassVisibilityChecker.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ClassVisibilityChecker.java
@@ -14,13 +14,33 @@
package org.eclipse.jetty.util;
/**
- * ClassVisibilityChecker
- *
* Interface to be implemented by classes capable of checking class visibility
* for a context.
*/
public interface ClassVisibilityChecker
{
+ /**
+ * Is the class a Protected (System) Class.
+ * A System class is a class that is visible to a webapplication,
+ * but that cannot be overridden by the contents of WEB-INF/lib or
+ * WEB-INF/classes
+ *
+ * @param clazz The fully qualified name of the class.
+ * @return True if the class is a system class.
+ */
+ boolean isProtectedClass(Class> clazz);
+
+ /**
+ * Is the class a Hidden (Server) Class.
+ * A Server class is a class that is part of the implementation of
+ * the server and is NIT visible to a webapplication. The web
+ * application may provide it's own implementation of the class,
+ * to be loaded from WEB-INF/lib or WEB-INF/classes
+ *
+ * @param clazz The fully qualified name of the class.
+ * @return True if the class is a server class.
+ */
+ boolean isHiddenClass(Class> clazz);
/**
* Is the class a System Class.
@@ -30,8 +50,13 @@ public interface ClassVisibilityChecker
*
* @param clazz The fully qualified name of the class.
* @return True if the class is a system class.
+ * @deprecated use {@link #isProtectedClass(Class)}
*/
- boolean isSystemClass(Class> clazz);
+ @Deprecated (forRemoval = true, since = "12.0.9")
+ default boolean isSystemClass(Class> clazz)
+ {
+ return isProtectedClass(clazz);
+ }
/**
* Is the class a Server Class.
@@ -42,6 +67,11 @@ public interface ClassVisibilityChecker
*
* @param clazz The fully qualified name of the class.
* @return True if the class is a server class.
+ * @deprecated use {@link #isHiddenClass(Class)}
*/
- boolean isServerClass(Class> clazz);
+ @Deprecated (forRemoval = true, since = "12.0.9")
+ default boolean isServerClass(Class> clazz)
+ {
+ return isHiddenClass(clazz);
+ }
}
diff --git a/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/ClassMatcherTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/ClassMatcherTest.java
similarity index 97%
rename from jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/ClassMatcherTest.java
rename to jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/ClassMatcherTest.java
index 069b4df78ab8..e29c994f0112 100644
--- a/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/ClassMatcherTest.java
+++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/ClassMatcherTest.java
@@ -11,17 +11,15 @@
// ========================================================================
//
-package org.eclipse.jetty.ee10.webapp;
+package org.eclipse.jetty.util;
import java.net.URI;
import java.util.Arrays;
import java.util.function.Supplier;
-import org.eclipse.jetty.ee10.webapp.ClassMatcher.ByLocationOrModule;
-import org.eclipse.jetty.ee10.webapp.ClassMatcher.ByPackageOrName;
-import org.eclipse.jetty.ee10.webapp.ClassMatcher.Entry;
-import org.eclipse.jetty.util.IncludeExcludeSet;
-import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.ClassMatcher.ByLocationOrModule;
+import org.eclipse.jetty.util.ClassMatcher.ByPackageOrName;
+import org.eclipse.jetty.util.ClassMatcher.Entry;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
diff --git a/jetty-core/pom.xml b/jetty-core/pom.xml
index 7790a54c5e83..97f9ff5401c3 100644
--- a/jetty-core/pom.xml
+++ b/jetty-core/pom.xml
@@ -17,6 +17,7 @@
jetty-clientjetty-demosjetty-deploy
+ jetty-eejetty-fcgijetty-httpjetty-http-spi
diff --git a/jetty-ee10/jetty-ee10-annotations/src/main/java/org/eclipse/jetty/ee10/annotations/ResourceAnnotationHandler.java b/jetty-ee10/jetty-ee10-annotations/src/main/java/org/eclipse/jetty/ee10/annotations/ResourceAnnotationHandler.java
index ca442da47c1d..78cebbd08bac 100644
--- a/jetty-ee10/jetty-ee10-annotations/src/main/java/org/eclipse/jetty/ee10/annotations/ResourceAnnotationHandler.java
+++ b/jetty-ee10/jetty-ee10-annotations/src/main/java/org/eclipse/jetty/ee10/annotations/ResourceAnnotationHandler.java
@@ -158,7 +158,7 @@ public void handleField(Class> clazz, Field field)
//try environment scope next
if (!bound)
- bound = NamingEntryUtil.bindToENC(ServletContextHandler.__environment.getName(), name, mappedName);
+ bound = NamingEntryUtil.bindToENC(ServletContextHandler.ENVIRONMENT.getName(), name, mappedName);
//try Server scope next
if (!bound)
@@ -313,7 +313,7 @@ public void handleMethod(Class> clazz, Method method)
//try the environment's scope
if (!bound)
- bound = NamingEntryUtil.bindToENC(ServletContextHandler.__environment.getName(), name, mappedName);
+ bound = NamingEntryUtil.bindToENC(ServletContextHandler.ENVIRONMENT.getName(), name, mappedName);
//try the server's scope
if (!bound)
diff --git a/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-jetty-webapp/src/main/webapp/WEB-INF/jetty-web.xml b/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-jetty-webapp/src/main/webapp/WEB-INF/jetty-web.xml
index 94c1864b1c35..5a92efcbfaf9 100644
--- a/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-jetty-webapp/src/main/webapp/WEB-INF/jetty-web.xml
+++ b/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-jetty-webapp/src/main/webapp/WEB-INF/jetty-web.xml
@@ -9,13 +9,13 @@ org.eclipse.jetty.ee10.servlet.WebApplicationContext object
-->
-
+
- org.eclipse.jetty.util.
- org.eclipse.jetty.ee10.servlets.
-
+ org.eclipse.jetty.util.
+ org.eclipse.jetty.ee10.servlets.
+
diff --git a/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-proxy-webapp/src/test/java/org/eclipse/jetty/ee10/demos/ProxyWebAppTest.java b/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-proxy-webapp/src/test/java/org/eclipse/jetty/ee10/demos/ProxyWebAppTest.java
index 63562373118a..79390bd4e4b0 100644
--- a/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-proxy-webapp/src/test/java/org/eclipse/jetty/ee10/demos/ProxyWebAppTest.java
+++ b/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-proxy-webapp/src/test/java/org/eclipse/jetty/ee10/demos/ProxyWebAppTest.java
@@ -54,7 +54,7 @@ public void setup() throws Exception
// This is a pieced together WebApp.
// We don't have a valid WEB-INF/lib to rely on at this point.
// So, open up server classes here, for purposes of this testcase.
- webapp.getServerClassMatcher().add("-org.eclipse.jetty.ee10.proxy.");
+ webapp.getHiddenClassMatcher().add("-org.eclipse.jetty.ee10.proxy.");
webapp.setWar(MavenTestingUtils.getProjectDirPath("src/main/webapp").toString());
webapp.setExtraClasspath(MavenTestingUtils.getTargetPath().resolve("test-classes").toString());
server.setHandler(webapp);
diff --git a/jetty-ee10/jetty-ee10-maven-plugin/src/it/jetty-cdi-start-forked/src/main/jetty/jetty-context.xml b/jetty-ee10/jetty-ee10-maven-plugin/src/it/jetty-cdi-start-forked/src/main/jetty/jetty-context.xml
index effcf47f4cf7..9214a93f9c36 100644
--- a/jetty-ee10/jetty-ee10-maven-plugin/src/it/jetty-cdi-start-forked/src/main/jetty/jetty-context.xml
+++ b/jetty-ee10/jetty-ee10-maven-plugin/src/it/jetty-cdi-start-forked/src/main/jetty/jetty-context.xml
@@ -6,7 +6,7 @@
-
+ -org.eclipse.jetty.util.Decorator
diff --git a/jetty-ee10/jetty-ee10-osgi/test-jetty-ee10-osgi/src/test/java/org/eclipse/jetty/ee10/osgi/test/TestOSGiUtil.java b/jetty-ee10/jetty-ee10-osgi/test-jetty-ee10-osgi/src/test/java/org/eclipse/jetty/ee10/osgi/test/TestOSGiUtil.java
index ba27aba40888..971e14827bc3 100644
--- a/jetty-ee10/jetty-ee10-osgi/test-jetty-ee10-osgi/src/test/java/org/eclipse/jetty/ee10/osgi/test/TestOSGiUtil.java
+++ b/jetty-ee10/jetty-ee10-osgi/test-jetty-ee10-osgi/src/test/java/org/eclipse/jetty/ee10/osgi/test/TestOSGiUtil.java
@@ -234,6 +234,7 @@ public static void coreJettyDependencies(List