Move ClassMatcher to core to have a consistent fix for addServerClasses in all environments (#11566)

* Issue #11514 - Cleanup `jetty.webapp.addServerClasses` property behavior for ee10/ee9/ee8

* Fix test

* Merging patterns (default -> env -> config)

* Moved ClassMatcher to util

* Adding more deprecations

* Changing XML demos/tests to use new getter names

* rollback xml changes in ee9/ee8

---------

Co-authored-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
Co-authored-by: Jan Bartel <janb@webtide.com>
This commit is contained in:
Greg Wilkins 2024-04-12 09:31:47 +02:00 committed by GitHub
parent 83526f77d3
commit 30bee710f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 1987 additions and 2149 deletions

View File

@ -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.

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-core</artifactId>
<version>12.0.9-SNAPSHOT</version>
</parent>
<artifactId>jetty-ee</artifactId>
<name>Core :: EE Common</name>
<properties>
<bundle-symbolic-name>${project.groupId}.ee</bundle-symbolic-name>
<spotbugs.onlyAnalyze>org.eclipse.jetty.ee.*</spotbugs.onlyAnalyze>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>jetty-test-multipart</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>@{argLine} ${jetty.surefire.argLine} --add-reads org.eclipse.jetty.ee=org.eclipse.jetty.logging</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call class="org.eclipse.jetty.ee.WebAppClassLoading" name="addProtectedClasses">
<Arg><Ref refid="Server"/></Arg>
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.server.addProtectedClasses"/></Arg>
</Call>
</Arg>
</Call>
<Call class="org.eclipse.jetty.ee.WebAppClassLoading" name="addHiddenClasses">
<Arg><Ref refid="Server"/></Arg>
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.server.addHiddenClasses"/></Arg>
</Call>
</Arg>
</Call>
</Configure>

View File

@ -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[]

View File

@ -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;
}

View File

@ -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:
* <ul>
* <li>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.</li>
* <li>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}.</li>
* </ul>
* <p>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.
*
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
*/
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;
}
}

View File

@ -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));
}
}

View File

@ -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

View File

@ -89,7 +89,7 @@ public class SessionData implements Serializable
//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
{

View File

@ -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/
* <p>
* Performs pattern matching of a class against a set of pattern entries.
* A class pattern is a string of one of the forms:<ul>
* <li>'org.package.SomeClass' will match a specific class
* <li>'org.package.' will match a specific package hierarchy
* <li>'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)
* <li>'file:///some/location/' - A file system directory from which
* the class was loaded
* <li>'file:///some/location.jar' - The URI of a jar file from which
* the class was loaded
* <li>'jrt:/modulename' - A Java9 module name</li>
* <li>Any of the above patterns preceded by '-' will exclude rather than include the match.
* </ul>
* 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<String>
{
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<Entry> implements Predicate<String>
{
private final Index.Mutable<Entry> _entries = new Index.Builder<Entry>()
.caseSensitive(true)
.mutable()
.build();
@Override
public boolean test(String name)
{
return _entries.getBest(name) != null;
}
@Override
public Iterator<Entry> 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<Entry> implements Predicate<String>
{
private final Map<String, Entry> _entries = new HashMap<>();
@Override
public boolean test(String name)
{
return _entries.containsKey(name);
}
@Override
public Iterator<Entry> 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<Entry> implements Predicate<String>
{
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<Entry> 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<Entry> implements Predicate<URI>
{
@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<Entry> implements Predicate<URI>
{
private final Index.Mutable<Entry> _entries = new Index.Builder<Entry>()
.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<Entry> 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<Entry> implements Predicate<URI>
{
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<Entry> iterator()
{
Set<Entry> 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<String, Entry> _entries;
protected final IncludeExcludeSet<Entry, String> _patterns;
protected final IncludeExcludeSet<Entry, URI> _locations;
protected ClassMatcher(Map<String, Entry> entries, IncludeExcludeSet<Entry, String> patterns, IncludeExcludeSet<Entry, URI> locations)
{
_entries = entries;
_patterns = patterns == null ? new IncludeExcludeSet<>(ByPackageOrName.class) : patterns;
_locations = locations == null ? new IncludeExcludeSet<>(ByLocationOrModule.class) : locations;
}
private ClassMatcher(Map<String, Entry> 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<Entry> 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<String> 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<Entry, String> names, String name, IncludeExcludeSet<Entry, URI> locations, Supplier<URI> 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;
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -17,6 +17,7 @@
<module>jetty-client</module>
<module>jetty-demos</module>
<module>jetty-deploy</module>
<module>jetty-ee</module>
<module>jetty-fcgi</module>
<module>jetty-http</module>
<module>jetty-http-spi</module>

View File

@ -158,7 +158,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
//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 class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
//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)

View File

@ -9,13 +9,13 @@ org.eclipse.jetty.ee10.servlet.WebApplicationContext object
-->
<Configure class="org.eclipse.jetty.ee10.webapp.WebAppContext">
<Get name="ServerClassMatcher">
<Get name="HiddenClassMatcher">
<Call name="exclude">
<Arg>
<Array type="String">
<Item>org.eclipse.jetty.util.</Item>
<Item>org.eclipse.jetty.ee10.servlets.</Item>
</Array>
<Item>org.eclipse.jetty.util.</Item>
<Item>org.eclipse.jetty.ee10.servlets.</Item>
</Array>
</Arg>
</Call>
</Get>

View File

@ -54,7 +54,7 @@ public class ProxyWebAppTest
// 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);

View File

@ -6,7 +6,7 @@
<Configure class="org.eclipse.jetty.ee10.webapp.WebAppContext">
<Call name="addServerClassMatcher">
<Arg>
<New class="org.eclipse.jetty.ee10.webapp.ClassMatcher">
<New class="org.eclipse.jetty.util.ClassMatcher">
<Arg>
<Array type="java.lang.String">
<Item>-org.eclipse.jetty.util.Decorator</Item>

View File

@ -234,6 +234,7 @@ public class TestOSGiUtil
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-jndi").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-osgi").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-client").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-ee").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty.ee10").artifactId("jetty-ee10-servlet").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty.ee10").artifactId("jetty-ee10-webapp").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty.ee10").artifactId("jetty-ee10-servlets").versionAsInProject().start());

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.ee10.plus.webapp;
import java.util.Map;
import java.util.Set;
import javax.naming.Context;
import javax.naming.InitialContext;
@ -208,8 +207,8 @@ public class EnvConfiguration extends AbstractConfiguration
LOG.debug("Binding env entries from the server scope");
doBindings(envCtx, context.getServer());
LOG.debug("Binding env entries from environment {} scope", ServletContextHandler.__environment.getName());
doBindings(envCtx, ServletContextHandler.__environment.getName());
LOG.debug("Binding env entries from environment {} scope", ServletContextHandler.ENVIRONMENT.getName());
doBindings(envCtx, ServletContextHandler.ENVIRONMENT.getName());
LOG.debug("Binding env entries from the context scope");
doBindings(envCtx, context);

View File

@ -83,13 +83,13 @@ public class PlusConfiguration extends AbstractConfiguration
{
try
{
Transaction.bindTransactionToENC(ServletContextHandler.__environment.getName());
Transaction.bindTransactionToENC(ServletContextHandler.ENVIRONMENT.getName());
}
catch (NameNotFoundException e)
{
try
{
org.eclipse.jetty.plus.jndi.Transaction.bindTransactionToENC(ServletContextHandler.__environment.getName());
org.eclipse.jetty.plus.jndi.Transaction.bindTransactionToENC(ServletContextHandler.ENVIRONMENT.getName());
}
catch (NameNotFoundException x)
{

View File

@ -129,7 +129,7 @@ public class PlusDescriptorProcessorTest
context.setConfigurations(new Configuration[]{new PlusConfiguration(), new EnvConfiguration()});
context.preConfigure();
context.setClassLoader(new WebAppClassLoader(Thread.currentThread().getContextClassLoader(), context));
context.getServerClassMatcher().exclude("org.eclipse.jetty.ee10.plus.webapp."); //need visbility of the TestInjections class
context.getHiddenClassMatcher().exclude("org.eclipse.jetty.ee10.plus.webapp."); //need visbility of the TestInjections class
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(context.getClassLoader());
Context icontext = new InitialContext();

View File

@ -89,7 +89,7 @@ public class TestQuickStart
WebAppContext webapp = new WebAppContext();
webapp.setBaseResourceAsPath(testDir.toPath());
webapp.addConfiguration(new QuickStartConfiguration());
webapp.getServerClassMatcher().exclude("org.eclipse.jetty.ee10.quickstart.");
webapp.getHiddenClassMatcher().exclude("org.eclipse.jetty.ee10.quickstart.");
webapp.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART);
//add in the servlet
webapp.getServletHandler().addServlet(fooHolder);
@ -139,7 +139,7 @@ public class TestQuickStart
webapp.addConfiguration(new QuickStartConfiguration());
webapp.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART);
webapp.setBaseResourceAsPath(testDir.toPath());
webapp.getServerClassMatcher().exclude("org.eclipse.jetty.ee10.quickstart.");
webapp.getHiddenClassMatcher().exclude("org.eclipse.jetty.ee10.quickstart.");
server.setHandler(webapp);
server.setDryRun(false);
@ -180,7 +180,7 @@ public class TestQuickStart
webapp.addConfiguration(new QuickStartConfiguration());
webapp.setAttribute(QuickStartConfiguration.MODE, QuickStartConfiguration.Mode.QUICKSTART);
webapp.setBaseResourceAsPath(testDir.toPath());
webapp.getServerClassMatcher().exclude("org.eclipse.jetty.ee10.quickstart.");
webapp.getHiddenClassMatcher().exclude("org.eclipse.jetty.ee10.quickstart.");
server.setHandler(webapp);
server.setDryRun(false);
@ -255,7 +255,7 @@ public class TestQuickStart
//a freshly applied context xml
quickstart = new WebAppContext();
//need visibility of FooServlet, FooFilter, FooContextListener when we quickstart
quickstart.getServerClassMatcher().exclude("org.eclipse.jetty.ee10.quickstart.");
quickstart.getHiddenClassMatcher().exclude("org.eclipse.jetty.ee10.quickstart.");
quickstart.addConfiguration(new QuickStartConfiguration());
quickstart.setWar(testDir.toURI().toURL().toExternalForm());
quickstart.setDescriptor(MavenTestingUtils.getTestResourceFile("web.xml").getAbsolutePath());

View File

@ -131,7 +131,12 @@ import static jakarta.servlet.ServletContext.TEMPDIR;
public class ServletContextHandler extends ContextHandler
{
private static final Logger LOG = LoggerFactory.getLogger(ServletContextHandler.class);
public static final Environment __environment = Environment.ensure("ee10");
public static final Environment ENVIRONMENT = Environment.ensure("ee10");
/**
* @deprecated Use {@link ServletContextHandler#ENVIRONMENT} instead.
*/
@Deprecated(since = "12.0.9", forRemoval = true)
public static final Environment __environment = ENVIRONMENT;
public static final Class<?>[] SERVLET_LISTENER_TYPES =
{
ServletContextListener.class,

View File

@ -29,7 +29,6 @@ import org.eclipse.jetty.ee10.webapp.WebAppContext;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@ -204,8 +203,8 @@ public class EmbeddedWeldTest
webapp.addServletContainerInitializer(new org.jboss.weld.environment.servlet.EnhancedListener());
String pkg = EmbeddedWeldTest.class.getPackage().getName();
webapp.getServerClassMatcher().add("-" + pkg + ".");
webapp.getSystemClassMatcher().add(pkg + ".");
webapp.getHiddenClassMatcher().add("-" + pkg + ".");
webapp.getProtectedClassMatcher().add(pkg + ".");
webapp.addServlet(GreetingsServlet.class, "/greet");
webapp.addFilter(MyFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
@ -240,8 +239,8 @@ public class EmbeddedWeldTest
// This is ugly but needed for maven for testing in a overlaid war pom
String pkg = EmbeddedWeldTest.class.getPackage().getName();
webapp.getServerClassMatcher().add("-" + pkg + ".");
webapp.getSystemClassMatcher().add(pkg + ".");
webapp.getHiddenClassMatcher().add("-" + pkg + ".");
webapp.getProtectedClassMatcher().add(pkg + ".");
webapp.getServletHandler().addListener(new ListenerHolder(MyContextListener.class));
webapp.addFilter(MyFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));

View File

@ -3,11 +3,11 @@
<Configure class="org.eclipse.jetty.ee10.webapp.WebAppContext">
<Set name="contextPath">/rfc2616-webapp</Set>
<Set name="war"><Property name="test.webapps" default="target/webapps" />/ee10-test-rfc2616.war</Set>
<Get name="systemClassMatcher">
<Get name="ProtectedClassMatcher">
<Call name="add"><Arg>org.slf4j.</Arg></Call>
<Call name="add"><Arg>org.eclipse.jetty.logging.</Arg></Call>
</Get>
<Get name="serverClassMatcher">
<Get name="HiddenClassMatcher">
<Call name="add"><Arg>-org.slf4j.</Arg></Call>
<Call name="add"><Arg>-org.eclipse.jetty.logging.</Arg></Call>
</Get>

View File

@ -17,6 +17,10 @@
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-ee</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-session</artifactId>

View File

@ -2,22 +2,21 @@
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call class="org.eclipse.jetty.ee10.webapp.WebAppContext" name="addSystemClasses">
<Arg><Ref refid="Server"/></Arg>
<Call class="org.eclipse.jetty.ee.WebAppClassLoading" name="addProtectedClasses">
<Arg><Ref refid="Environment"/></Arg>
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.webapp.addSystemClasses"/></Arg>
<Arg><Property name="jetty.webapp.addProtectedClasses" deprecated="jetty.webapp.addSystemClasses"/></Arg>
</Call>
</Arg>
</Call>
<Call class="org.eclipse.jetty.ee10.webapp.WebAppContext" name="addServerClasses">
<Arg><Ref refid="Server"/></Arg>
<Call class="org.eclipse.jetty.ee.WebAppClassLoading" name="addHiddenClasses">
<Arg><Ref refid="Environment"/></Arg>
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.webapp.addServerClasses"/></Arg>
<Arg><Property name="jetty.webapp.addHiddenClasses" deprecated="jetty.webapp.addServerClasses"/></Arg>
</Call>
</Arg>
</Call>
</Configure>

View File

@ -9,6 +9,7 @@ This module enables deployment of Java Servlet web applications.
ee10
[depend]
ee-webapp
ee10-servlet
ee10-security
@ -20,9 +21,9 @@ lib/jetty-ee10-webapp-${jetty.version}.jar
[ini-template]
# tag::ini-template[]
## Add to the server wide default jars and packages protected or hidden from webapps.
## System classes are protected and cannot be overridden by a webapp.
## Server classes are hidden and cannot be seen by a webapp
## Add to the environment 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.'
@ -32,8 +33,8 @@ lib/jetty-ee10-webapp-${jetty.version}.jar
##
## The +=, operator appends to a CSV list with a comma as needed.
##
#jetty.webapp.addSystemClasses+=,org.example.
#jetty.webapp.addServerClasses+=,org.example.
#jetty.webapp.addProtectedClasses+=,org.example.
#jetty.webapp.addHiddenClasses+=,org.example.
# end::ini-template[]
[ini]

View File

@ -33,6 +33,7 @@ module org.eclipse.jetty.ee10.webapp
requires transitive org.eclipse.jetty.session;
requires transitive org.eclipse.jetty.ee10.servlet;
requires transitive org.eclipse.jetty.xml;
requires transitive org.eclipse.jetty.ee;
exports org.eclipse.jetty.ee10.webapp;

View File

@ -13,804 +13,53 @@
package org.eclipse.jetty.ee10.webapp;
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;
import org.eclipse.jetty.util.IncludeExcludeSet;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.URIUtil;
/**
* A matcher for classes based on package and/or location and/or module/
* <p>
* Performs pattern matching of a class against a set of pattern entries.
* A class pattern is a string of one of the forms:<ul>
* <li>'org.package.SomeClass' will match a specific class
* <li>'org.package.' will match a specific package hierarchy
* <li>'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)
* <li>'file:///some/location/' - A file system directory from which
* the class was loaded
* <li>'file:///some/location.jar' - The URI of a jar file from which
* the class was loaded
* <li>'jrt:/modulename' - A Java9 module name</li>
* <li>Any of the above patterns preceded by '-' will exclude rather than include the match.
* </ul>
* When class is initialized from a classpath pattern string, entries
* in this string should be separated by ':' (semicolon) or ',' (comma).
* @deprecated Use org.eclipse.jetty.util.ClassMatcher
*/
public class ClassMatcher extends AbstractSet<String>
@Deprecated(since = "12.0.8", forRemoval = true)
public class ClassMatcher extends org.eclipse.jetty.util.ClassMatcher
{
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<Entry> implements Predicate<String>
{
private final Index.Mutable<Entry> _entries = new Index.Builder<Entry>()
.caseSensitive(true)
.mutable()
.build();
@Override
public boolean test(String name)
{
return _entries.getBest(name) != null;
}
@Override
public Iterator<Entry> 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<Entry> implements Predicate<String>
{
private final Map<String, Entry> _entries = new HashMap<>();
@Override
public boolean test(String name)
{
return _entries.containsKey(name);
}
@Override
public Iterator<Entry> 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<Entry> implements Predicate<String>
{
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<Entry> 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<Entry> implements Predicate<URI>
{
@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<Entry> implements Predicate<URI>
{
private final Index.Mutable<Entry> _entries = new Index.Builder<Entry>()
.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<Entry> 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<Entry> implements Predicate<URI>
{
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<Entry> iterator()
{
Set<Entry> 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();
}
}
private final Map<String, Entry> _entries;
private final IncludeExcludeSet<Entry, String> _patterns;
private final IncludeExcludeSet<Entry, URI> _locations;
private ClassMatcher(Map<String, Entry> entries, IncludeExcludeSet<Entry, String> patterns, IncludeExcludeSet<Entry, URI> locations)
{
_entries = entries;
_patterns = patterns == null ? new IncludeExcludeSet<>(ByPackageOrName.class) : patterns;
_locations = locations == null ? new IncludeExcludeSet<>(ByLocationOrModule.class) : locations;
}
private ClassMatcher(Map<String, Entry> entries)
{
this(entries, null, null);
}
public ClassMatcher()
{
this(new HashMap<>());
super();
}
public ClassMatcher(ClassMatcher patterns)
{
this(new HashMap<>());
if (patterns != null)
setAll(patterns.getPatterns());
super(patterns);
}
public ClassMatcher(org.eclipse.jetty.util.ClassMatcher patterns)
{
super(patterns);
}
public ClassMatcher(String... patterns)
{
this(new HashMap<>());
if (patterns != null && patterns.length > 0)
setAll(patterns);
super(patterns);
}
public ClassMatcher(String pattern)
{
this(new HashMap<>());
add(pattern);
super(pattern);
}
protected ClassMatcher(Map<String, Entry> entries, IncludeExcludeSet<Entry, String> patterns, IncludeExcludeSet<Entry, URI> locations)
{
super(entries, patterns, locations);
}
@Override
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<Entry> 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<String> 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<Entry, String> names, String name, IncludeExcludeSet<Entry, URI> locations, Supplier<URI> 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;
}
}

View File

@ -60,8 +60,8 @@ import org.slf4j.LoggerFactory;
* parent loader. Java2 compliant loading, where the parent loader
* always has priority, can be selected with the
* {@link WebAppContext#setParentLoaderPriority(boolean)}
* method and influenced with {@link WebAppContext#isServerClass(Class)} and
* {@link WebAppContext#isSystemClass(Class)}.
* method and influenced with {@link WebAppContext#isHiddenClass(Class)} and
* {@link WebAppContext#isProtectedClass(Class)}.
* <p>
* If no parent class loader is provided, then the current thread
* context classloader will be used. If that is null then the
@ -115,9 +115,9 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
List<Resource> getExtraClasspath();
boolean isServerResource(String name, URL parentUrl);
boolean isHiddenResource(String name, URL parentUrl);
boolean isSystemResource(String name, URL webappUrl);
boolean isProtectedResource(String name, URL webappUrl);
}
/**
@ -302,7 +302,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
while (urls != null && urls.hasMoreElements())
{
URL url = urls.nextElement();
if (Boolean.TRUE.equals(__loadServerClasses.get()) || !_context.isServerResource(name, url))
if (Boolean.TRUE.equals(__loadServerClasses.get()) || !_context.isHiddenResource(name, url))
fromParent.add(url);
}
@ -310,7 +310,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
while (urls != null && urls.hasMoreElements())
{
URL url = urls.nextElement();
if (!_context.isSystemResource(name, url) || fromParent.isEmpty())
if (!_context.isProtectedResource(name, url) || fromParent.isEmpty())
fromWebapp.add(url);
}
@ -351,7 +351,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
// return if we have a url the webapp is allowed to see
if (parentUrl != null &&
(Boolean.TRUE.equals(__loadServerClasses.get()) ||
!_context.isServerResource(name, parentUrl)))
!_context.isHiddenResource(name, parentUrl)))
resource = parentUrl;
else
{
@ -370,7 +370,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
{
URL webappUrl = this.findResource(name);
if (webappUrl != null && !_context.isSystemResource(name, webappUrl))
if (webappUrl != null && !_context.isProtectedResource(name, webappUrl))
resource = webappUrl;
else
{
@ -379,7 +379,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
URL parentUrl = _parent.getResource(name);
if (parentUrl != null &&
(Boolean.TRUE.equals(__loadServerClasses.get()) ||
!_context.isServerResource(name, parentUrl)))
!_context.isHiddenResource(name, parentUrl)))
resource = parentUrl;
// We couldn't find a parent resource, so OK to return a webapp one if it exists
// and we just couldn't see it before
@ -425,7 +425,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
throw new ClassNotFoundException("Bad ClassLoader: returned null for loadClass(" + name + ")");
// If the webapp is allowed to see this class
if (Boolean.TRUE.equals(__loadServerClasses.get()) || !_context.isServerClass(parentClass))
if (Boolean.TRUE.equals(__loadServerClasses.get()) || !_context.isHiddenClass(parentClass))
{
return parentClass;
}
@ -473,7 +473,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
{
parentClass = _parent.loadClass(name);
// If the webapp is allowed to see this class
if (Boolean.TRUE.equals(__loadServerClasses.get()) || !_context.isServerClass(parentClass))
if (Boolean.TRUE.equals(__loadServerClasses.get()) || !_context.isHiddenClass(parentClass))
{
return parentClass;
}
@ -524,7 +524,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
String path = TypeUtil.toClassReference(name);
URL webappUrl = findResource(path);
if (webappUrl != null && (!checkSystemResource || !_context.isSystemResource(name, webappUrl)))
if (webappUrl != null && (!checkSystemResource || !_context.isProtectedResource(name, webappUrl)))
{
webappClass = this.foundClass(name, webappUrl);
@ -605,14 +605,14 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
}
@Override
public boolean isSystemClass(Class<?> clazz)
public boolean isProtectedClass(Class<?> clazz)
{
return _context.isSystemClass(clazz);
return _context.isProtectedClass(clazz);
}
@Override
public boolean isServerClass(Class<?> clazz)
public boolean isHiddenClass(Class<?> clazz)
{
return _context.isServerClass(clazz);
return _context.isHiddenClass(clazz);
}
}

View File

@ -36,6 +36,7 @@ import jakarta.servlet.http.HttpSessionAttributeListener;
import jakarta.servlet.http.HttpSessionBindingListener;
import jakarta.servlet.http.HttpSessionIdListener;
import jakarta.servlet.http.HttpSessionListener;
import org.eclipse.jetty.ee.WebAppClassLoading;
import org.eclipse.jetty.ee10.servlet.ErrorHandler;
import org.eclipse.jetty.ee10.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
@ -49,6 +50,7 @@ import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Deployable;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.ClassMatcher;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
@ -80,32 +82,35 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
static final Logger LOG = LoggerFactory.getLogger(WebAppContext.class);
public static final String WEB_DEFAULTS_XML = "org/eclipse/jetty/ee10/webapp/webdefault-ee10.xml";
public static final String SERVER_SYS_CLASSES = "org.eclipse.jetty.webapp.systemClasses";
public static final String SERVER_SRV_CLASSES = "org.eclipse.jetty.webapp.serverClasses";
/**
* @deprecated use {@link WebAppClassLoading#PROTECTED_CLASSES_ATTRIBUTE} instead.
*/
@Deprecated(forRemoval = true, since = "12.0.9")
public static final String SERVER_SYS_CLASSES = WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE;
/**
* @deprecated use {@link WebAppClassLoading#HIDDEN_CLASSES_ATTRIBUTE} instead.
*/
@Deprecated(forRemoval = true, since = "12.0.9")
public static final String SERVER_SRV_CLASSES = WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE;
private static String[] __dftProtectedTargets = {"/WEB-INF", "/META-INF"};
private static final String[] __dftProtectedTargets = {"/WEB-INF", "/META-INF"};
// System classes are classes that cannot be replaced by
// the web application, and they are *always* loaded via
// system classloader.
public static final ClassMatcher __dftSystemClasses = 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
);
/**
* @deprecated use {@link WebAppClassLoading#DEFAULT_PROTECTED_CLASSES}
*/
@Deprecated (forRemoval = true, since = "12.0.9")
public static final org.eclipse.jetty.ee10.webapp.ClassMatcher __dftSystemClasses =
new org.eclipse.jetty.ee10.webapp.ClassMatcher(WebAppClassLoading.DEFAULT_PROTECTED_CLASSES);
// Server classes are classes that are hidden from being
// loaded by the web application using system classloader,
// so if web application needs to load any of such classes,
// it has to include them in its distribution.
public static final ClassMatcher __dftServerClasses = new ClassMatcher(
"org.eclipse.jetty." // hide jetty classes
);
/**
* @deprecated use {@link WebAppClassLoading#DEFAULT_HIDDEN_CLASSES}
*/
@Deprecated (forRemoval = true, since = "12.0.9")
public static final org.eclipse.jetty.ee10.webapp.ClassMatcher __dftServerClasses =
new org.eclipse.jetty.ee10.webapp.ClassMatcher(WebAppClassLoading.DEFAULT_HIDDEN_CLASSES);
private final ClassMatcher _systemClasses = new ClassMatcher(__dftSystemClasses);
private final ClassMatcher _serverClasses = new ClassMatcher(__dftServerClasses);
private final ClassMatcher _protectedClasses = new ClassMatcher(WebAppClassLoading.getProtectedClasses(ServletContextHandler.ENVIRONMENT));
private final ClassMatcher _hiddenClasses = new ClassMatcher(WebAppClassLoading.getHiddenClasses(ServletContextHandler.ENVIRONMENT));
private Configurations _configurations;
private String _defaultsDescriptor = WEB_DEFAULTS_XML;
@ -420,7 +425,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
// Add the known server class inclusions for all known configurations
for (Configuration configuration : Configurations.getKnown())
{
_serverClasses.include(configuration.getServerClasses().getInclusions());
_hiddenClasses.include(configuration.getServerClasses().getInclusions());
}
// Setup Configuration classes for this webapp!
@ -428,8 +433,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
_configurations.sort();
for (Configuration configuration : _configurations)
{
_systemClasses.add(configuration.getSystemClasses().getPatterns());
_serverClasses.exclude(configuration.getServerClasses().getExclusions());
_protectedClasses.add(configuration.getSystemClasses().getPatterns());
_hiddenClasses.exclude(configuration.getServerClasses().getExclusions());
}
// Configure classloader
@ -484,7 +489,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
protected void doStart() throws Exception
{
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(__environment.getClassLoader());
Thread.currentThread().setContextClassLoader(ServletContextHandler.ENVIRONMENT.getClassLoader());
try
{
_metadata.setAllowDuplicateFragmentNames(isAllowDuplicateFragmentNames());
@ -615,107 +620,217 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
}
/**
* Set the server classes patterns.
* Set the hidden (aka server) classes patterns.
* <p>
* Server classes/packages are classes used to implement the server and are hidden
* These classes/packages are used to implement the server and are hiddenClasses
* from the context. If the context needs to load these classes, it must have its
* own copy of them in WEB-INF/lib or WEB-INF/classes.
*
* @param serverClasses the server classes pattern
* @param hiddenClasses the server classes pattern
*/
public void setServerClassMatcher(ClassMatcher serverClasses)
public void setHiddenClassMatcher(ClassMatcher hiddenClasses)
{
_serverClasses.clear();
_serverClasses.add(serverClasses.getPatterns());
_hiddenClasses.clear();
_hiddenClasses.add(hiddenClasses.getPatterns());
}
/**
* Set the system classes patterns.
* Set the protected (aka system) classes patterns.
* <p>
* System classes/packages are classes provided by the JVM and that
* These classes/packages are provided by the JVM and
* cannot be replaced by classes of the same name from WEB-INF,
* regardless of the value of {@link #setParentLoaderPriority(boolean)}.
*
* @param systemClasses the system classes pattern
* @param protectedClasses the system classes pattern
*/
public void setSystemClassMatcher(ClassMatcher systemClasses)
public void setProtectedClassMatcher(ClassMatcher protectedClasses)
{
_systemClasses.clear();
_systemClasses.add(systemClasses.getPatterns());
_protectedClasses.clear();
_protectedClasses.add(protectedClasses.getPatterns());
}
/**
* Add a ClassMatcher for server classes by combining with
* Add a ClassMatcher for hidden (server) classes by combining with
* any existing matcher.
*
* @param serverClasses The class matcher of patterns to add to the server ClassMatcher
* @param hiddenClasses The class matcher of patterns to add to the server ClassMatcher
*/
public void addServerClassMatcher(ClassMatcher serverClasses)
public void addHiddenClassMatcher(ClassMatcher hiddenClasses)
{
_serverClasses.add(serverClasses.getPatterns());
_hiddenClasses.add(hiddenClasses.getPatterns());
}
/**
* Add a ClassMatcher for system classes by combining with
* Add a ClassMatcher for protected (system) classes by combining with
* any existing matcher.
*
* @param systemClasses The class matcher of patterns to add to the system ClassMatcher
* @param protectedClasses The class matcher of patterns to add to the system ClassMatcher
*/
public void addSystemClassMatcher(ClassMatcher systemClasses)
public void addProtectedClassMatcher(ClassMatcher protectedClasses)
{
_systemClasses.add(systemClasses.getPatterns());
_protectedClasses.add(protectedClasses.getPatterns());
}
/**
* @return The ClassMatcher used to match System (protected) classes
*/
public ClassMatcher getSystemClassMatcher()
public ClassMatcher getProtectedClassMatcher()
{
return _systemClasses;
return _protectedClasses;
}
/**
* @return The ClassMatcher used to match Server (hidden) classes
* @return The ClassMatcher used to match Server (hiddenClasses) classes
*/
public ClassMatcher getServerClassMatcher()
public ClassMatcher getHiddenClassMatcher()
{
return _serverClasses;
return _hiddenClasses;
}
@ManagedAttribute(value = "classes and packages protected by context classloader", readonly = true)
public String[] getProtectedClasses()
{
return _protectedClasses.getPatterns();
}
@ManagedAttribute(value = "classes and packages hiddenClasses by the context classloader", readonly = true)
public String[] getHiddenClasses()
{
return _hiddenClasses.getPatterns();
}
@Override
public boolean isHiddenClass(Class<?> clazz)
{
return _hiddenClasses.match(clazz);
}
@Override
public boolean isProtectedClass(Class<?> clazz)
{
return _protectedClasses.match(clazz);
}
@Override
public boolean isHiddenResource(String name, URL url)
{
return _hiddenClasses.match(name, url);
}
@Override
public boolean isProtectedResource(String name, URL url)
{
return _protectedClasses.match(name, url);
}
/**
* @deprecated use {@link #setHiddenClassMatcher(ClassMatcher)}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public void setServerClassMatcher(ClassMatcher serverClasses)
{
_hiddenClasses.clear();
_hiddenClasses.add(serverClasses.getPatterns());
}
/**
* @deprecated use {@link #setProtectedClassMatcher(ClassMatcher)}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public void setSystemClassMatcher(ClassMatcher systemClasses)
{
_protectedClasses.clear();
_protectedClasses.add(systemClasses.getPatterns());
}
/**
* @deprecated use {@link #addHiddenClassMatcher(ClassMatcher)}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public void addServerClassMatcher(ClassMatcher serverClasses)
{
_hiddenClasses.add(serverClasses.getPatterns());
}
/**
* @deprecated use {@link #addProtectedClassMatcher(ClassMatcher)}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public void addSystemClassMatcher(ClassMatcher systemClasses)
{
_protectedClasses.add(systemClasses.getPatterns());
}
/**
* @deprecated use {@link #getProtectedClassMatcher()}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public ClassMatcher getSystemClassMatcher()
{
return _protectedClasses;
}
/**
* @deprecated use {@link #getHiddenClassMatcher()}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public ClassMatcher getServerClassMatcher()
{
return _hiddenClasses;
}
/**
* @deprecated use {@link #getProtectedClasses()}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public String[] getSystemClasses()
{
return _systemClasses.getPatterns();
return _protectedClasses.getPatterns();
}
@ManagedAttribute(value = "classes and packages hidden by the context classloader", readonly = true)
/**
* @deprecated use {@link #getHiddenClasses()}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public String[] getServerClasses()
{
return _serverClasses.getPatterns();
return _hiddenClasses.getPatterns();
}
@Override
/**
* @deprecated use {@link #isHiddenClass(Class)}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public boolean isServerClass(Class<?> clazz)
{
return _serverClasses.match(clazz);
return _hiddenClasses.match(clazz);
}
@Override
/**
* @deprecated use {@link #isProtectedClass(Class)}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public boolean isSystemClass(Class<?> clazz)
{
return _systemClasses.match(clazz);
return _protectedClasses.match(clazz);
}
@Override
/**
* @deprecated use {@link #isHiddenResource(String, URL)}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public boolean isServerResource(String name, URL url)
{
return _serverClasses.match(name, url);
return _hiddenClasses.match(name, url);
}
@Override
/**
* @deprecated use {@link #isProtectedResource(String, URL)}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public boolean isSystemResource(String name, URL url)
{
return _systemClasses.match(name, url);
return _protectedClasses.match(name, url);
}
@Override
@ -724,23 +839,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
super.setServer(server);
if (server != null)
{
if (__dftSystemClasses.equals(_systemClasses))
{
Object systemClasses = server.getAttribute(SERVER_SYS_CLASSES);
if (systemClasses instanceof String[])
systemClasses = new ClassMatcher((String[])systemClasses);
if (systemClasses instanceof ClassMatcher)
_systemClasses.add(((ClassMatcher)systemClasses).getPatterns());
}
if (__dftServerClasses.equals(_serverClasses))
{
Object serverClasses = server.getAttribute(SERVER_SRV_CLASSES);
if (serverClasses instanceof String[])
serverClasses = new ClassMatcher((String[])serverClasses);
if (serverClasses instanceof ClassMatcher)
_serverClasses.add(((ClassMatcher)serverClasses).getPatterns());
}
_protectedClasses.add(WebAppClassLoading.getProtectedClasses(server).getPatterns());
_hiddenClasses.add(WebAppClassLoading.getHiddenClasses(server).getPatterns());
}
}
@ -863,16 +963,16 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
public void dump(Appendable out, String indent) throws IOException
{
List<String> systemClasses = null;
if (_systemClasses != null)
if (_protectedClasses != null)
{
systemClasses = new ArrayList<>(_systemClasses);
systemClasses = new ArrayList<>(_protectedClasses);
Collections.sort(systemClasses);
}
List<String> serverClasses = null;
if (_serverClasses != null)
if (_hiddenClasses != null)
{
serverClasses = new ArrayList<>(_serverClasses);
serverClasses = new ArrayList<>(_hiddenClasses);
Collections.sort(serverClasses);
}
@ -1411,38 +1511,31 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
return _metadata;
}
public static void addServerClasses(Server server, String... pattern)
/**
* Add a Server Class pattern to use for all ee9 WebAppContexts.
* @param server The {@link Server} instance to add classes to
* @param patterns the patterns to use
* @see #getHiddenClassMatcher()
* @see #getHiddenClasses()
* @deprecated use {@link WebAppClassLoading#addProtectedClasses(Server, String...)}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public static void addServerClasses(Server server, String... patterns)
{
addClasses(__dftServerClasses, SERVER_SRV_CLASSES, server, pattern);
WebAppClassLoading.addHiddenClasses(server, patterns);
}
public static void addSystemClasses(Server server, String... pattern)
/**
* Add a System Class pattern to use for all ee9 WebAppContexts.
* @param server The {@link Server} instance to add classes to
* @param patterns the patterns to use
* @see #getProtectedClassMatcher()
* @see #getProtectedClasses()
* @deprecated use {@link WebAppClassLoading#addHiddenClasses(Server, String...)}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public static void addSystemClasses(Server server, String... patterns)
{
addClasses(__dftSystemClasses, SERVER_SYS_CLASSES, server, pattern);
}
private static void addClasses(ClassMatcher matcher, String attribute, Server server, String... pattern)
{
if (pattern == null || pattern.length == 0)
return;
// look for a Server attribute with the list of System classes
// to apply to every web application. If not present, use our defaults.
Object o = server.getAttribute(attribute);
if (o instanceof ClassMatcher)
{
((ClassMatcher)o).add(pattern);
return;
}
String[] classes;
if (o instanceof String[])
classes = (String[])o;
else
classes = matcher.getPatterns();
int l = classes.length;
classes = Arrays.copyOf(classes, l + pattern.length);
System.arraycopy(pattern, 0, classes, l, pattern.length);
server.setAttribute(attribute, classes);
WebAppClassLoading.addProtectedClasses(server, patterns);
}
}

View File

@ -95,7 +95,7 @@ public class WebDescriptor extends Descriptor
public WebDescriptorParser(boolean validating) throws IOException
{
super(validating);
String catalogName = "catalog-%s.xml".formatted(ServletContextHandler.__environment.getName());
String catalogName = "catalog-%s.xml".formatted(ServletContextHandler.ENVIRONMENT.getName());
URL url = WebDescriptor.class.getResource(catalogName);
if (url == null)
throw new IllegalStateException("Catalog not found: %s/%s".formatted(WebDescriptor.class.getPackageName(), catalogName));

View File

@ -26,9 +26,9 @@ import java.util.List;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.ClassMatcher;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.FileSystemPool;
import org.eclipse.jetty.util.resource.Resource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -37,7 +37,6 @@ import org.junit.jupiter.api.Test;
import static org.eclipse.jetty.toolchain.test.ExtraMatchers.ordered;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
@ -211,11 +210,11 @@ public class WebAppClassLoaderTest
@Test
public void testExposedClassDeprecated() throws Exception
{
String[] oldSC = _context.getServerClasses();
String[] oldSC = _context.getHiddenClasses();
String[] newSC = new String[oldSC.length + 1];
newSC[0] = "-org.eclipse.jetty.ee10.webapp.Configuration";
System.arraycopy(oldSC, 0, newSC, 1, oldSC.length);
_context.setServerClassMatcher(new ClassMatcher(newSC));
_context.setHiddenClassMatcher(new ClassMatcher(newSC));
assertCanLoadClass("org.acme.webapp.ClassInJarA");
assertCanLoadClass("org.acme.webapp.ClassInJarB");
@ -228,7 +227,7 @@ public class WebAppClassLoaderTest
@Test
public void testExposedClass() throws Exception
{
_context.getServerClassMatcher().exclude("org.eclipse.jetty.ee10.webapp.Configuration");
_context.getHiddenClassMatcher().exclude("org.eclipse.jetty.ee10.webapp.Configuration");
assertCanLoadClass("org.acme.webapp.ClassInJarA");
assertCanLoadClass("org.acme.webapp.ClassInJarB");
@ -241,18 +240,18 @@ public class WebAppClassLoaderTest
@Test
public void testSystemServerClassDeprecated() throws Exception
{
String[] oldServC = _context.getServerClasses();
String[] oldServC = _context.getHiddenClasses();
String[] newServC = new String[oldServC.length + 1];
newServC[0] = "org.eclipse.jetty.ee10.webapp.Configuration";
System.arraycopy(oldServC, 0, newServC, 1, oldServC.length);
_context.setServerClassMatcher(new ClassMatcher(newServC));
_context.setHiddenClassMatcher(new ClassMatcher(newServC));
String[] oldSysC = _context.getSystemClasses();
String[] oldSysC = _context.getProtectedClasses();
String[] newSysC = new String[oldSysC.length + 1];
newSysC[0] = "org.eclipse.jetty.ee10.webapp.";
System.arraycopy(oldSysC, 0, newSysC, 1, oldSysC.length);
_context.setSystemClassMatcher(new ClassMatcher(newSysC));
_context.setProtectedClassMatcher(new ClassMatcher(newSysC));
assertCanLoadClass("org.acme.webapp.ClassInJarA");
assertCanLoadClass("org.acme.webapp.ClassInJarB");
@ -260,28 +259,28 @@ public class WebAppClassLoaderTest
assertCantLoadClass("org.eclipse.jetty.ee10.webapp.Configuration");
assertCantLoadClass("org.eclipse.jetty.ee10.webapp.JarScanner");
oldSysC = _context.getSystemClasses();
oldSysC = _context.getProtectedClasses();
newSysC = new String[oldSysC.length + 1];
newSysC[0] = "org.acme.webapp.ClassInJarA";
System.arraycopy(oldSysC, 0, newSysC, 1, oldSysC.length);
_context.setSystemClassMatcher(new ClassMatcher(newSysC));
_context.setProtectedClassMatcher(new ClassMatcher(newSysC));
assertCanLoadResource("org/acme/webapp/ClassInJarA.class");
_context.setSystemClassMatcher(new ClassMatcher(oldSysC));
_context.setProtectedClassMatcher(new ClassMatcher(oldSysC));
oldServC = _context.getServerClasses();
oldServC = _context.getHiddenClasses();
newServC = new String[oldServC.length + 1];
newServC[0] = "org.acme.webapp.ClassInJarA";
System.arraycopy(oldServC, 0, newServC, 1, oldServC.length);
_context.setServerClassMatcher(new ClassMatcher(newServC));
_context.setHiddenClassMatcher(new ClassMatcher(newServC));
assertCanLoadResource("org/acme/webapp/ClassInJarA.class");
}
@Test
public void testSystemServerClass() throws Exception
{
_context.getServerClassMatcher().add("org.eclipse.jetty.ee10.webapp.Configuration");
_context.getSystemClassMatcher().add("org.eclipse.jetty.ee10.webapp.");
_context.getHiddenClassMatcher().add("org.eclipse.jetty.ee10.webapp.Configuration");
_context.getProtectedClassMatcher().add("org.eclipse.jetty.ee10.webapp.");
assertCanLoadClass("org.acme.webapp.ClassInJarA");
assertCanLoadClass("org.acme.webapp.ClassInJarB");
@ -289,11 +288,11 @@ public class WebAppClassLoaderTest
assertCantLoadClass("org.eclipse.jetty.ee10.webapp.Configuration");
assertCantLoadClass("org.eclipse.jetty.ee10.webapp.JarScanner");
_context.getSystemClassMatcher().add("org.acme.webapp.ClassInJarA");
_context.getProtectedClassMatcher().add("org.acme.webapp.ClassInJarA");
assertCanLoadResource("org/acme/webapp/ClassInJarA.class");
_context.getSystemClassMatcher().remove("org.acme.webapp.ClassInJarA");
_context.getProtectedClassMatcher().remove("org.acme.webapp.ClassInJarA");
_context.getServerClassMatcher().add("org.acme.webapp.ClassInJarA");
_context.getHiddenClassMatcher().add("org.acme.webapp.ClassInJarA");
assertCanLoadResource("org/acme/webapp/ClassInJarA.class");
}
@ -340,11 +339,11 @@ public class WebAppClassLoaderTest
// assertEquals(0,resources.get(1).toString().indexOf("jar:file:"));
// assertEquals(-1,resources.get(2).toString().indexOf("test-classes"));
String[] oldServC = _context.getServerClasses();
String[] oldServC = _context.getHiddenClasses();
String[] newServC = new String[oldServC.length + 1];
newServC[0] = "org.acme.";
System.arraycopy(oldServC, 0, newServC, 1, oldServC.length);
_context.setServerClassMatcher(new ClassMatcher(newServC));
_context.setHiddenClassMatcher(new ClassMatcher(newServC));
_context.setParentLoaderPriority(true);
// dump(_context);
@ -361,12 +360,12 @@ public class WebAppClassLoaderTest
// assertEquals(0,resources.get(0).toString().indexOf("jar:file:"));
// assertEquals(0,resources.get(1).toString().indexOf("file:"));
_context.setServerClassMatcher(new ClassMatcher(oldServC));
String[] oldSysC = _context.getSystemClasses();
_context.setHiddenClassMatcher(new ClassMatcher(oldServC));
String[] oldSysC = _context.getProtectedClasses();
String[] newSysC = new String[oldSysC.length + 1];
newSysC[0] = "org.acme.";
System.arraycopy(oldSysC, 0, newSysC, 1, oldSysC.length);
_context.setSystemClassMatcher(new ClassMatcher(newSysC));
_context.setProtectedClassMatcher(new ClassMatcher(newSysC));
_context.setParentLoaderPriority(true);
// dump(_context);

View File

@ -36,6 +36,7 @@ import jakarta.servlet.GenericServlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.eclipse.jetty.ee.WebAppClassLoading;
import org.eclipse.jetty.ee10.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.http.HttpStatus;
@ -80,6 +81,7 @@ import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.either;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
@ -128,7 +130,7 @@ public class WebAppContextTest
* @param name the name of the war
* @return the Path of the generated war
*
* @throws Exception
* @throws Exception if the war could not be created
*/
private Path createWar(Path tempDir, String name) throws Exception
{
@ -936,4 +938,62 @@ public class WebAppContextTest
assertThat(handler.getServer(), sameInstance(server));
}
@Test
public void testAddServerClasses() throws Exception
{
Server server = newServer();
String testPattern = "org.eclipse.jetty.ee10.webapp.test.";
WebAppContext.addServerClasses(server, testPattern);
WebAppContext context = new WebAppContext();
context.setContextPath("/");
Path testPath = MavenPaths.targetTestDir("testAddServerClasses");
FS.ensureDirExists(testPath);
FS.ensureEmpty(testPath);
Path warPath = createWar(testPath, "test.war");
context.setBaseResource(context.getResourceFactory().newResource(warPath));
server.setHandler(context);
server.start();
List<String> serverClasses = List.of(context.getHiddenClasses());
assertThat("Should have environment specific test pattern", serverClasses, hasItem(testPattern));
assertThat("Should have pattern from defaults", serverClasses, hasItem("org.eclipse.jetty."));
assertThat("Should have pattern from JaasConfiguration", serverClasses, hasItem("-org.eclipse.jetty.security.jaas."));
for (String defaultServerClass: WebAppClassLoading.DEFAULT_HIDDEN_CLASSES)
assertThat("Should have default patterns", serverClasses, hasItem(defaultServerClass));
}
@Test
public void testAddSystemClasses() throws Exception
{
Server server = newServer();
String testPattern = "org.eclipse.jetty.ee10.webapp.test.";
WebAppContext.addSystemClasses(server, testPattern);
WebAppContext context = new WebAppContext();
context.setContextPath("/");
Path testPath = MavenPaths.targetTestDir("testAddServerClasses");
FS.ensureDirExists(testPath);
FS.ensureEmpty(testPath);
Path warPath = createWar(testPath, "test.war");
context.setBaseResource(context.getResourceFactory().newResource(warPath));
server.setHandler(context);
server.start();
List<String> systemClasses = List.of(context.getProtectedClasses());
assertThat("Should have environment specific test pattern", systemClasses, hasItem(testPattern));
assertThat("Should have pattern from defaults", systemClasses, hasItem("javax."));
assertThat("Should have pattern from defaults", systemClasses, hasItem("jakarta."));
assertThat("Should have pattern from JaasConfiguration", systemClasses, hasItem("org.eclipse.jetty.security.jaas."));
for (String defaultSystemClass: WebAppClassLoading.DEFAULT_PROTECTED_CLASSES)
assertThat("Should have default patterns", systemClasses, hasItem(defaultSystemClass));
}
}

View File

@ -6,7 +6,7 @@
<Configure class="org.eclipse.jetty.ee8.webapp.WebAppContext">
<Call name="addServerClassMatcher">
<Arg>
<New class="org.eclipse.jetty.ee8.webapp.ClassMatcher">
<New class="org.eclipse.jetty.util.ClassMatcher">
<Arg>
<Array type="java.lang.String">
<Item>-org.eclipse.jetty.util.Decorator</Item>

View File

@ -18,6 +18,10 @@
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-ee</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-xml</artifactId>

View File

@ -2,22 +2,21 @@
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://eclipse.dev/jetty/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call class="org.eclipse.jetty.ee8.webapp.WebAppContext" name="addSystemClasses">
<Call class="org.eclipse.jetty.ee.WebAppClassLoading" name="addProtectedClasses">
<Arg><Ref refid="Environment"/></Arg>
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.webapp.addSystemClasses"/></Arg>
<Arg><Property name="jetty.webapp.addProtectedClasses" deprecated="jetty.webapp.addSystemClasses"/></Arg>
</Call>
</Arg>
</Call>
<Call class="org.eclipse.jetty.ee8.webapp.WebAppContext" name="addServerClasses">
<Call class="org.eclipse.jetty.ee.WebAppClassLoading" name="addHiddenClasses">
<Arg><Ref refid="Environment"/></Arg>
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.webapp.addServerClasses"/></Arg>
<Arg><Property name="jetty.webapp.addHiddenClasses" deprecated="jetty.webapp.addServerClasses"/></Arg>
</Call>
</Arg>
</Call>
</Configure>

View File

@ -8,6 +8,7 @@ Without this, only Jetty-specific handlers may be deployed.
ee8
[depend]
ee-webapp
ee8-servlet
ee8-security
@ -18,9 +19,9 @@ etc/jetty-ee8-webapp.xml
lib/jetty-ee8-webapp-${jetty.version}.jar
[ini-template]
## Add to the server wide default jars and packages protected or hidden from webapps.
## System classes are protected and cannot be overridden by a webapp.
## Server classes are hidden and cannot be seen by a webapp
## Add to the environment wide default jars and packages protected or hidden from webapps.
## System (aka Protected) classes cannot be overridden by a webapp.
## Server (aka Hidden) 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.'
@ -30,8 +31,8 @@ lib/jetty-ee8-webapp-${jetty.version}.jar
##
## The +=, operator appends to a CSV list with a comma as needed.
##
#jetty.webapp.addSystemClasses+=,org.example.
#jetty.webapp.addServerClasses+=,org.example.
#jetty.webapp.addProtectedClasses+=,org.example.
#jetty.webapp.addHiddenClasses+=,org.example.
[ini]
contextHandlerClass=org.eclipse.jetty.ee8.webapp.WebAppContext

View File

@ -6,7 +6,7 @@
<Configure class="org.eclipse.jetty.ee9.webapp.WebAppContext">
<Call name="addServerClassMatcher">
<Arg>
<New class="org.eclipse.jetty.ee9.webapp.ClassMatcher">
<New class="org.eclipse.jetty.util.ClassMatcher">
<Arg>
<Array type="java.lang.String">
<Item>-org.eclipse.jetty.util.Decorator</Item>

View File

@ -232,6 +232,7 @@ public class TestOSGiUtil
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-jndi").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-osgi").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-client").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-ee").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty.ee9").artifactId("jetty-ee9-security").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty.ee9").artifactId("jetty-ee9-servlet").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty.ee9").artifactId("jetty-ee9-nested").versionAsInProject().start());

View File

@ -42,7 +42,6 @@ import org.eclipse.jetty.session.SessionDataStoreFactory;
import org.eclipse.jetty.session.test.TestSessionDataStoreFactory;
import org.eclipse.jetty.util.StringUtil;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;

View File

@ -17,6 +17,10 @@
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-ee</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-xml</artifactId>

View File

@ -2,22 +2,21 @@
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call class="org.eclipse.jetty.ee9.webapp.WebAppContext" name="addSystemClasses">
<Call class="org.eclipse.jetty.ee.WebAppClassLoading" name="addProtectedClasses">
<Arg><Ref refid="Environment"/></Arg>
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.webapp.addSystemClasses"/></Arg>
<Arg><Property name="jetty.webapp.addProtectedClasses" deprecated="jetty.webapp.addSystemClasses"/></Arg>
</Call>
</Arg>
</Call>
<Call class="org.eclipse.jetty.ee9.webapp.WebAppContext" name="addServerClasses">
<Call class="org.eclipse.jetty.ee.WebAppClassLoading" name="addHiddenClasses">
<Arg><Ref refid="Environment"/></Arg>
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.webapp.addServerClasses"/></Arg>
<Arg><Property name="jetty.webapp.addHiddenClasses" deprecated="jetty.webapp.addServerClasses"/></Arg>
</Call>
</Arg>
</Call>
</Configure>

View File

@ -8,6 +8,7 @@ Without this, only Jetty-specific handlers may be deployed.
ee9
[depend]
ee-webapp
ee9-servlet
ee9-security
@ -18,9 +19,9 @@ etc/jetty-ee9-webapp.xml
lib/jetty-ee9-webapp-${jetty.version}.jar
[ini-template]
## Add to the server wide default jars and packages protected or hidden from webapps.
## System classes are protected and cannot be overridden by a webapp.
## Server classes are hidden and cannot be seen by a webapp
## Add to the environment wide default jars and packages protected or hidden from webapps.
## System (aka Protected) classes cannot be overridden by a webapp.
## Server (aka Hidden) 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.'
@ -30,8 +31,8 @@ lib/jetty-ee9-webapp-${jetty.version}.jar
##
## The +=, operator appends to a CSV list with a comma as needed.
##
#jetty.webapp.addSystemClasses+=,org.example.
#jetty.webapp.addServerClasses+=,org.example.
#jetty.webapp.addProtectedClasses+=,org.example.
#jetty.webapp.addHiddenClasses+=,org.example.
[ini]
contextHandlerClass?=org.eclipse.jetty.ee9.webapp.WebAppContext

View File

@ -32,6 +32,7 @@ module org.eclipse.jetty.ee9.webapp
requires transitive java.instrument;
requires transitive org.eclipse.jetty.ee9.servlet;
requires transitive org.eclipse.jetty.xml;
requires transitive org.eclipse.jetty.ee;
exports org.eclipse.jetty.ee9.webapp;

View File

@ -13,786 +13,53 @@
package org.eclipse.jetty.ee9.webapp;
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;
import org.eclipse.jetty.util.IncludeExcludeSet;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.URIUtil;
/**
* A matcher for classes based on package and/or location and/or module/
* <p>
* Performs pattern matching of a class against a set of pattern entries.
* A class pattern is a string of one of the forms:<ul>
* <li>'org.package.SomeClass' will match a specific class
* <li>'org.package.' will match a specific package hierarchy
* <li>'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)
* <li>'file:///some/location/' - A file system directory from which
* the class was loaded
* <li>'file:///some/location.jar' - The URI of a jar file from which
* the class was loaded
* <li>'jrt:/modulename' - A Java9 module name</li>
* <li>Any of the above patterns preceded by '-' will exclude rather than include the match.
* </ul>
* When class is initialized from a classpath pattern string, entries
* in this string should be separated by ':' (semicolon) or ',' (comma).
* @deprecated Use org.eclipse.jetty.util.ClassMatcher
*/
public class ClassMatcher extends AbstractSet<String>
@Deprecated(since = "12.0.8", forRemoval = true)
public class ClassMatcher extends org.eclipse.jetty.util.ClassMatcher
{
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<Entry> implements Predicate<String>
{
private final Index.Mutable<Entry> _entries = new Index.Builder<Entry>()
.caseSensitive(true)
.mutable()
.build();
@Override
public boolean test(String name)
{
return _entries.getBest(name) != null;
}
@Override
public Iterator<Entry> 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();
}
}
@SuppressWarnings("serial")
public static class ByClass extends HashSet<Entry> implements Predicate<String>
{
private final Map<String, Entry> _entries = new HashMap<>();
@Override
public boolean test(String name)
{
return _entries.containsKey(name);
}
@Override
public Iterator<Entry> 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<Entry> implements Predicate<String>
{
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<Entry> 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();
}
}
@SuppressWarnings("serial")
public static class ByLocation extends HashSet<Entry> implements Predicate<URI>
{
@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;
}
}
@SuppressWarnings("serial")
public static class ByModule extends HashSet<Entry> implements Predicate<URI>
{
private final Index.Mutable<Entry> _entries = new Index.Builder<Entry>()
.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<Entry> 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<Entry> implements Predicate<URI>
{
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<Entry> iterator()
{
Set<Entry> 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();
}
}
Map<String, Entry> _entries = new HashMap<>();
IncludeExcludeSet<Entry, String> _patterns = new IncludeExcludeSet<>(ByPackageOrName.class);
IncludeExcludeSet<Entry, URI> _locations = new IncludeExcludeSet<>(ByLocationOrModule.class);
public ClassMatcher()
{
super();
}
@SuppressWarnings("CopyConstructorMissesField")
public ClassMatcher(ClassMatcher patterns)
{
if (patterns != null)
setAll(patterns.getPatterns());
super(patterns);
}
public ClassMatcher(org.eclipse.jetty.util.ClassMatcher patterns)
{
super(patterns);
}
public ClassMatcher(String... patterns)
{
if (patterns != null && patterns.length > 0)
setAll(patterns);
super(patterns);
}
public ClassMatcher(String pattern)
{
add(pattern);
super(pattern);
}
public boolean include(String name)
protected ClassMatcher(Map<String, Entry> entries, IncludeExcludeSet<Entry, String> patterns, IncludeExcludeSet<Entry, URI> locations)
{
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;
super(entries, patterns, locations);
}
@Override
public boolean add(String pattern)
public ClassMatcher asImmutable()
{
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))
return false;
String pattern = (String)o;
Entry entry = _entries.remove(pattern);
if (entry == null)
return false;
List<Entry> 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<String> 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<Entry, String> names, String name, IncludeExcludeSet<Entry, URI> locations, Supplier<URI> 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;
return new ClassMatcher(Map.copyOf(_entries),
_patterns.asImmutable(),
_locations.asImmutable());
}
}

View File

@ -60,8 +60,8 @@ import org.slf4j.LoggerFactory;
* parent loader. Java2 compliant loading, where the parent loader
* always has priority, can be selected with the
* {@link WebAppContext#setParentLoaderPriority(boolean)}
* method and influenced with {@link WebAppContext#isServerClass(Class)} and
* {@link WebAppContext#isSystemClass(Class)}.
* method and influenced with {@link WebAppContext#isHiddenClass(Class)} and
* {@link WebAppContext#isProtectedClass(Class)}.
* <p>
* If no parent class loader is provided, then the current thread
* context classloader will be used. If that is null then the
@ -429,7 +429,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
throw new ClassNotFoundException("Bad ClassLoader: returned null for loadClass(" + name + ")");
// If the webapp is allowed to see this class
if (Boolean.TRUE.equals(__loadServerClasses.get()) || !_context.isServerClass(parentClass))
if (Boolean.TRUE.equals(__loadServerClasses.get()) || !_context.isHiddenClass(parentClass))
{
return parentClass;
}
@ -477,7 +477,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
{
parentClass = _parent.loadClass(name);
// If the webapp is allowed to see this class
if (Boolean.TRUE.equals(__loadServerClasses.get()) || !_context.isServerClass(parentClass))
if (Boolean.TRUE.equals(__loadServerClasses.get()) || !_context.isHiddenClass(parentClass))
{
return parentClass;
}
@ -609,14 +609,14 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
}
@Override
public boolean isSystemClass(Class<?> clazz)
public boolean isProtectedClass(Class<?> clazz)
{
return _context.isSystemClass(clazz);
return _context.isProtectedClass(clazz);
}
@Override
public boolean isServerClass(Class<?> clazz)
public boolean isHiddenClass(Class<?> clazz)
{
return _context.isServerClass(clazz);
return _context.isHiddenClass(clazz);
}
}

View File

@ -38,6 +38,7 @@ import jakarta.servlet.http.HttpSessionAttributeListener;
import jakarta.servlet.http.HttpSessionBindingListener;
import jakarta.servlet.http.HttpSessionIdListener;
import jakarta.servlet.http.HttpSessionListener;
import org.eclipse.jetty.ee.WebAppClassLoading;
import org.eclipse.jetty.ee9.nested.ContextHandler;
import org.eclipse.jetty.ee9.nested.ErrorHandler;
import org.eclipse.jetty.ee9.nested.HandlerWrapper;
@ -54,6 +55,7 @@ import org.eclipse.jetty.server.Deployable;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.ClassMatcher;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
@ -87,32 +89,35 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
public static final String WEB_DEFAULTS_XML = "org/eclipse/jetty/ee9/webapp/webdefault-ee9.xml";
public static final String ERROR_PAGE = "org.eclipse.jetty.server.error_page";
public static final String SERVER_SYS_CLASSES = "org.eclipse.jetty.ee9.webapp.systemClasses";
public static final String SERVER_SRV_CLASSES = "org.eclipse.jetty.ee9.webapp.serverClasses";
/**
* @deprecated use {@link WebAppClassLoading#PROTECTED_CLASSES_ATTRIBUTE} instead.
*/
@Deprecated(forRemoval = true, since = "12.0.9")
public static final String SERVER_SYS_CLASSES = WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE;
/**
* @deprecated use {@link WebAppClassLoading#HIDDEN_CLASSES_ATTRIBUTE} instead.
*/
@Deprecated(forRemoval = true, since = "12.0.9")
public static final String SERVER_SRV_CLASSES = WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE;
private static String[] __dftProtectedTargets = {"/WEB-INF", "/META-INF"};
private static final String[] __dftProtectedTargets = {"/WEB-INF", "/META-INF"};
// System classes are classes that cannot be replaced by
// the web application, and they are *always* loaded via
// system classloader.
public static final ClassMatcher __dftSystemClasses = 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
);
/**
* @deprecated use {@link WebAppClassLoading#DEFAULT_PROTECTED_CLASSES}
*/
@Deprecated(forRemoval = true, since = "12.0.9")
public static final org.eclipse.jetty.ee9.webapp.ClassMatcher __dftSystemClasses =
new org.eclipse.jetty.ee9.webapp.ClassMatcher(WebAppClassLoading.DEFAULT_PROTECTED_CLASSES);
// Server classes are classes that are hidden from being
// loaded by the web application using system classloader,
// so if web application needs to load any of such classes,
// it has to include them in its distribution.
public static final ClassMatcher __dftServerClasses = new ClassMatcher(
"org.eclipse.jetty." // hide jetty classes
);
/**
* @deprecated use {@link WebAppClassLoading#DEFAULT_HIDDEN_CLASSES}
*/
@Deprecated(forRemoval = true, since = "12.0.9")
public static final org.eclipse.jetty.ee9.webapp.ClassMatcher __dftServerClasses =
new org.eclipse.jetty.ee9.webapp.ClassMatcher(WebAppClassLoading.DEFAULT_HIDDEN_CLASSES);
private final ClassMatcher _systemClasses = new ClassMatcher(__dftSystemClasses);
private final ClassMatcher _serverClasses = new ClassMatcher(__dftServerClasses);
private final ClassMatcher _systemClasses = new ClassMatcher(WebAppClassLoading.getProtectedClasses(ServletContextHandler.ENVIRONMENT));
private final ClassMatcher _serverClasses = new ClassMatcher(WebAppClassLoading.getHiddenClasses(ServletContextHandler.ENVIRONMENT));
private Configurations _configurations;
private String _defaultsDescriptor = WEB_DEFAULTS_XML;
@ -746,13 +751,13 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
}
@Override
public boolean isServerClass(Class<?> clazz)
public boolean isHiddenClass(Class<?> clazz)
{
return _serverClasses.match(clazz);
}
@Override
public boolean isSystemClass(Class<?> clazz)
public boolean isProtectedClass(Class<?> clazz)
{
return _systemClasses.match(clazz);
}
@ -775,23 +780,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
super.setServer(server);
if (server != null)
{
if (__dftSystemClasses.equals(_systemClasses))
{
Object systemClasses = server.getAttribute(SERVER_SYS_CLASSES);
if (systemClasses instanceof String[])
systemClasses = new ClassMatcher((String[])systemClasses);
if (systemClasses instanceof ClassMatcher)
_systemClasses.add(((ClassMatcher)systemClasses).getPatterns());
}
if (__dftServerClasses.equals(_serverClasses))
{
Object serverClasses = server.getAttribute(SERVER_SRV_CLASSES);
if (serverClasses instanceof String[])
serverClasses = new ClassMatcher((String[])serverClasses);
if (serverClasses instanceof ClassMatcher)
_serverClasses.add(((ClassMatcher)serverClasses).getPatterns());
}
_systemClasses.add(WebAppClassLoading.getProtectedClasses(server).getPatterns());
_serverClasses.add(WebAppClassLoading.getHiddenClasses(server).getPatterns());
}
}
@ -1486,38 +1476,31 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
return _metadata;
}
public static void addServerClasses(Attributes attributes, String... pattern)
/**
* Add a Server Class pattern to use for all ee9 WebAppContexts.
* @param attributes The {@link Server} instance to add classes to
* @param patterns the patterns to use
* @see #getServerClassMatcher()
* @see #getServerClasses()
* @deprecated use {@link WebAppClassLoading#addProtectedClasses(Server, String...)}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public static void addServerClasses(Attributes attributes, String... patterns)
{
addClasses(__dftServerClasses, SERVER_SRV_CLASSES, attributes, pattern);
WebAppClassLoading.addHiddenClasses(attributes, patterns);
}
public static void addSystemClasses(Attributes attributes, String... pattern)
/**
* Add a System Class pattern to use for all ee9 WebAppContexts.
* @param attributes The {@link Server} instance to add classes to
* @param patterns the patterns to use
* @see #getSystemClassMatcher()
* @see #getSystemClasses()
* @deprecated use {@link WebAppClassLoading#addHiddenClasses(Server, String...)}
*/
@Deprecated(since = "12.0.8", forRemoval = true)
public static void addSystemClasses(Attributes attributes, String... patterns)
{
addClasses(__dftSystemClasses, SERVER_SYS_CLASSES, attributes, pattern);
}
private static void addClasses(ClassMatcher matcher, String attribute, Attributes attributes, String... pattern)
{
if (pattern == null || pattern.length == 0)
return;
// look for a Server attribute with the list of System classes
// to apply to every web application. If not present, use our defaults.
Object o = attributes.getAttribute(attribute);
if (o instanceof ClassMatcher)
{
((ClassMatcher)o).add(pattern);
return;
}
String[] classes;
if (o instanceof String[])
classes = (String[])o;
else
classes = matcher.getPatterns();
int l = classes.length;
classes = Arrays.copyOf(classes, l + pattern.length);
System.arraycopy(pattern, 0, classes, l, pattern.length);
attributes.setAttribute(attribute, classes);
WebAppClassLoading.addProtectedClasses(attributes, patterns);
}
}

View File

@ -1,307 +0,0 @@
//
// ========================================================================
// 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.ee9.webapp;
import java.net.URI;
import java.util.Arrays;
import java.util.function.Supplier;
import org.eclipse.jetty.ee9.webapp.ClassMatcher.ByLocationOrModule;
import org.eclipse.jetty.ee9.webapp.ClassMatcher.ByPackageOrName;
import org.eclipse.jetty.ee9.webapp.ClassMatcher.Entry;
import org.eclipse.jetty.util.IncludeExcludeSet;
import org.eclipse.jetty.util.TypeUtil;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ClassMatcherTest
{
private final ClassMatcher _pattern = new ClassMatcher();
protected static Supplier<URI> NULL_SUPPLIER = () -> null;
@BeforeEach
public void before()
{
_pattern.clear();
_pattern.add("org.package.");
_pattern.add("-org.excluded.");
_pattern.add("org.example.FooBar");
_pattern.add("-org.example.Excluded");
_pattern.addAll(Arrays.asList(
"-org.example.Nested$Minus",
"org.example.Nested",
"org.example.Nested$Something"));
assertThat(_pattern, Matchers.containsInAnyOrder(
"org.package.",
"-org.excluded.",
"org.example.FooBar",
"-org.example.Excluded",
"-org.example.Nested$Minus",
"org.example.Nested",
"org.example.Nested$Something"
));
}
@Test
public void testClassMatch()
{
assertTrue(_pattern.match("org.example.FooBar"));
assertTrue(_pattern.match("org.example.Nested"));
assertFalse(_pattern.match("org.example.Unknown"));
assertFalse(_pattern.match("org.example.FooBar.Unknown"));
}
@Test
public void testPackageMatch()
{
assertTrue(_pattern.match("org.package.Something"));
assertTrue(_pattern.match("org.package.other.Something"));
assertFalse(_pattern.match("org.example.Unknown"));
assertFalse(_pattern.match("org.example.FooBar.Unknown"));
assertFalse(_pattern.match("org.example.FooBarElse"));
}
@Test
public void testExplicitNestedMatch()
{
assertTrue(_pattern.match("org.example.Nested$Something"));
assertFalse(_pattern.match("org.example.Nested$Minus"));
assertTrue(_pattern.match("org.example.Nested$Other"));
}
@Test
public void testImplicitNestedMatch()
{
assertTrue(_pattern.match("org.example.FooBar$Other"));
assertTrue(_pattern.match("org.example.Nested$Other"));
}
@Test
public void testDoubledNested()
{
assertTrue(_pattern.match("org.example.Nested$Something$Else"));
assertFalse(_pattern.match("org.example.Nested$Minus$Else"));
}
@Test
public void testMatchAll()
{
_pattern.clear();
_pattern.add(".");
assertTrue(_pattern.match("org.example.Anything"));
assertTrue(_pattern.match("org.example.Anything$Else"));
}
@Test
public void testCopy()
{
ClassMatcher copy = new ClassMatcher(_pattern);
assertThat(copy.toString(), is(_pattern.toString()));
}
@Test
public void testMatchFundamentalExcludeSpecific()
{
_pattern.clear();
_pattern.add("jakarta.");
_pattern.add("-jakarta.ws.rs.", "-jakarta.inject.");
assertFalse(_pattern.match("org.example.Anything"));
assertTrue(_pattern.match("jakarta.servlet.HttpServlet"));
assertFalse(_pattern.match("jakarta.ws.rs.ProcessingException"));
}
@SuppressWarnings("restriction")
@Test
public void testIncludedLocations() throws Exception
{
// jar from JVM classloader
URI locString = TypeUtil.getLocationOfClass(String.class);
// a jar from maven repo jar
URI locJunit = TypeUtil.getLocationOfClass(Test.class);
// class file
URI locTest = TypeUtil.getLocationOfClass(ClassMatcherTest.class);
ClassMatcher pattern = new ClassMatcher();
pattern.include("something");
assertThat(pattern.match(String.class), is(false));
assertThat(pattern.match(Test.class), is(false));
assertThat(pattern.match(ClassMatcherTest.class), is(false));
// Add directory for both JVM classes
pattern.include(locString.toASCIIString());
// Add jar for individual class and classes directory
pattern.include(locJunit.toString(), locTest.toString());
assertThat(pattern.match(String.class), is(true));
assertThat(pattern.match(Test.class), is(true));
assertThat(pattern.match(ClassMatcherTest.class), is(true));
pattern.add("-java.lang.String");
assertThat(pattern.match(String.class), is(false));
assertThat(pattern.match(Test.class), is(true));
assertThat(pattern.match(ClassMatcherTest.class), is(true));
}
@SuppressWarnings("restriction")
@Test
public void testIncludedLocationsOrModule() throws Exception
{
// jar from JVM classloader
URI modString = TypeUtil.getLocationOfClass(String.class);
// System.err.println(modString);
// a jar from maven repo jar
URI locJunit = TypeUtil.getLocationOfClass(Test.class);
// System.err.println(locJunit);
// class file
URI locTest = TypeUtil.getLocationOfClass(ClassMatcherTest.class);
// System.err.println(locTest);
ClassMatcher pattern = new ClassMatcher();
pattern.include("something");
assertThat(pattern.match(String.class), is(false));
assertThat(pattern.match(Test.class), is(false));
assertThat(pattern.match(ClassMatcherTest.class), is(false));
// Add module for all JVM base classes
pattern.include("jrt:/java.base");
// Add jar for individual class and classes directory
pattern.include(locJunit.toString(), locTest.toString());
assertThat(pattern.match(String.class), is(true));
assertThat(pattern.match(Test.class), is(true));
assertThat(pattern.match(ClassMatcherTest.class), is(true));
pattern.add("-java.lang.String");
assertThat(pattern.match(String.class), is(false));
assertThat(pattern.match(Test.class), is(true));
assertThat(pattern.match(ClassMatcherTest.class), is(true));
}
@SuppressWarnings("restriction")
@Test
public void testExcludeLocationsOrModule() throws Exception
{
// jar from JVM classloader
URI modString = TypeUtil.getLocationOfClass(String.class);
assertNotNull(modString);
// a jar from maven repo jar
URI locJunit = TypeUtil.getLocationOfClass(Test.class);
assertNotNull(locJunit);
// class file
URI locTest = TypeUtil.getLocationOfClass(ClassMatcherTest.class);
assertNotNull(locTest);
ClassMatcher pattern = new ClassMatcher();
// include everything
pattern.include(".");
assertThat(pattern.match(String.class), is(true));
assertThat(pattern.match(Test.class), is(true));
assertThat(pattern.match(ClassMatcherTest.class), is(true));
// Add directory for both JVM classes
pattern.exclude("jrt:/java.base/");
// Add jar for individual class and classes directory
pattern.exclude(locJunit.toASCIIString(), locTest.toASCIIString());
assertThat(pattern.match(String.class), is(false));
assertThat(pattern.match(Test.class), is(false));
assertThat(pattern.match(ClassMatcherTest.class), is(false));
}
@Test
public void testWithNullLocation() throws Exception
{
ClassMatcher matcher = new ClassMatcher();
IncludeExcludeSet<Entry, String> names = new IncludeExcludeSet<>(ByPackageOrName.class);
IncludeExcludeSet<Entry, URI> locations = new IncludeExcludeSet<>(ByLocationOrModule.class);
//Test no name or location includes or excludes - should match
assertThat(ClassMatcher.combine(names, "a.b.c", locations, NULL_SUPPLIER), is(true));
names.include(matcher.newEntry("a.b.", true));
names.exclude(matcher.newEntry("d.e.", false));
//Test explicit include by name no locations - should match
assertThat(ClassMatcher.combine(names, "a.b.c", locations, NULL_SUPPLIER), is(true));
//Test explicit exclude by name no locations - should not match
assertThat(ClassMatcher.combine(names, "d.e.f", locations, NULL_SUPPLIER), is(false));
//Test include by name with location includes - should match
locations.include(matcher.newEntry("file:/foo/bar", true));
assertThat(ClassMatcher.combine(names, "a.b.c", locations, NULL_SUPPLIER), is(true));
//Test include by name but with location exclusions - should not match
locations.clear();
locations.exclude(matcher.newEntry("file:/high/low", false));
assertThat(ClassMatcher.combine(names, "a.b.c", locations, NULL_SUPPLIER), is(false));
//Test neither included or excluded by name, but with location exclusions - should not match
assertThat(ClassMatcher.combine(names, "g.b.r", locations, NULL_SUPPLIER), is(false));
//Test neither included nor excluded by name, but with location inclusions - should not match
locations.clear();
locations.include(matcher.newEntry("file:/foo/bar", true));
assertThat(ClassMatcher.combine(names, "g.b.r", locations, NULL_SUPPLIER), is(false));
}
@Test
public void testLarge()
{
ClassMatcher pattern = new ClassMatcher();
for (int i = 0; i < 500; i++)
{
assertTrue(pattern.add("n" + i + "." + Integer.toHexString(100 + i) + ".Name"));
}
for (int i = 0; i < 500; i++)
{
assertTrue(pattern.match("n" + i + "." + Integer.toHexString(100 + i) + ".Name"));
}
}
@Test
public void testJvmModule()
{
URI uri = TypeUtil.getLocationOfClass(String.class);
assertThat(uri, notNullValue());
assertThat(uri.getScheme(), is("jrt"));
assertThat(uri.getPath(), is("/java.base"));
}
}

View File

@ -26,9 +26,9 @@ import java.util.List;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.ClassMatcher;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.FileSystemPool;
import org.eclipse.jetty.util.resource.Resource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -37,7 +37,6 @@ import org.junit.jupiter.api.Test;
import static org.eclipse.jetty.toolchain.test.ExtraMatchers.ordered;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;

View File

@ -34,6 +34,7 @@ import jakarta.servlet.GenericServlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.eclipse.jetty.ee.WebAppClassLoading;
import org.eclipse.jetty.ee9.nested.ContextHandler;
import org.eclipse.jetty.ee9.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.ee9.servlet.ServletContextHandler;
@ -79,6 +80,7 @@ import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
@ -116,7 +118,7 @@ public class WebAppContextTest
* @param name the name of the war
* @return the Path of the generated war
*
* @throws Exception
* @throws Exception if the war cannot be created
*/
private Path createWar(Path tempDir, String name) throws Exception
{
@ -847,4 +849,63 @@ public class WebAppContextTest
extLibs = extLibs.toAbsolutePath();
assertThat("URL[0]", urls[0].toURI(), is(extLibs.toUri()));
}
@Test
public void testAddServerClasses() throws Exception
{
Server server = newServer();
String testPattern = "org.eclipse.jetty.ee9.webapp.test.";
WebAppContext.addServerClasses(server, testPattern);
WebAppContext context = new WebAppContext();
context.setContextPath("/");
Path testPath = MavenPaths.targetTestDir("testAddServerClasses");
FS.ensureDirExists(testPath);
FS.ensureEmpty(testPath);
Path warPath = createWar(testPath, "test.war");
context.setBaseResource(context.getResourceFactory().newResource(warPath));
server.setHandler(context);
server.start();
List<String> serverClasses = List.of(context.getServerClasses());
assertThat("Should have environment specific test pattern", serverClasses, hasItem(testPattern));
assertThat("Should have pattern from defaults", serverClasses, hasItem("org.eclipse.jetty."));
assertThat("Should have pattern from JaasConfiguration", serverClasses, hasItem("-org.eclipse.jetty.security.jaas."));
for (String defaultServerClass: WebAppClassLoading.DEFAULT_HIDDEN_CLASSES)
assertThat("Should have default patterns", serverClasses, hasItem(defaultServerClass));
}
@Test
public void testAddSystemClasses() throws Exception
{
Server server = newServer();
String testPattern = "org.eclipse.jetty.ee9.webapp.test.";
WebAppContext.addSystemClasses(server, testPattern);
WebAppContext context = new WebAppContext();
context.setContextPath("/");
Path testPath = MavenPaths.targetTestDir("testAddServerClasses");
FS.ensureDirExists(testPath);
FS.ensureEmpty(testPath);
Path warPath = createWar(testPath, "test.war");
context.setBaseResource(context.getResourceFactory().newResource(warPath));
server.setHandler(context);
server.start();
List<String> systemClasses = List.of(context.getSystemClasses());
assertThat("Should have environment specific test pattern", systemClasses, hasItem(testPattern));
assertThat("Should have pattern from defaults", systemClasses, hasItem("javax."));
assertThat("Should have pattern from defaults", systemClasses, hasItem("jakarta."));
assertThat("Should have pattern from JaasConfiguration", systemClasses, hasItem("org.eclipse.jetty.security.jaas."));
for (String defaultSystemClass : WebAppClassLoading.DEFAULT_PROTECTED_CLASSES)
{
assertThat("Should have default patterns", systemClasses, hasItem(defaultSystemClass));
}
}
}

View File

@ -39,6 +39,10 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-deploy</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-ee</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-hazelcast</artifactId>

View File

@ -21,5 +21,5 @@ basehome:modules/logging/jetty
lib/logging/jetty-slf4j-impl-${jetty.version}.jar
[ini]
jetty.webapp.addServerClasses+=,org.eclipse.jetty.logging.
jetty.webapp.addServerClasses+=,${jetty.home.uri}/lib/logging/
jetty.webapp.addHiddenClasses+=,org.eclipse.jetty.logging.
jetty.webapp.addHiddenClasses+=,${jetty.home.uri}/lib/logging/

View File

@ -31,7 +31,7 @@ lib/logging/log4j-${log4j.version}.jar
[ini]
log4j.version?=1.2.17
jetty.webapp.addServerClasses+=,org.apache.log4j.
jetty.webapp.addHiddenClasses+=,org.apache.log4j.
[license]

View File

@ -28,7 +28,7 @@ lib/logging/log4j-core-${log4j2.version}.jar
[ini]
log4j2.version?=@log4j2.version@
jetty.webapp.addServerClasses+=,org.apache.logging.log4j.
jetty.webapp.addHiddenClasses+=,org.apache.logging.log4j.
[license]
Log4j is released under the Apache 2.0 license.

View File

@ -25,7 +25,7 @@ lib/logging/logback-core-${logback.version}.jar
[ini]
logback.version?=@logback.version@
jetty.webapp.addServerClasses+=,ch.qos.logback.
jetty.webapp.addHiddenClasses+=,ch.qos.logback.
[license]
Logback: the reliable, generic, fast and flexible logging framework.

View File

@ -16,4 +16,4 @@ lib/logging/slf4j-api-${slf4j.version}.jar
[ini]
slf4j.version?=@slf4j.version@
jetty.webapp.addServerClasses+=,org.slf4j.
jetty.webapp.addHiddenClasses+=,org.slf4j.

View File

@ -17,4 +17,4 @@ http://www.apache.org/licenses/LICENSE-2.0.html
[ini]
## Hide the gcloud libraries from deployed webapps
jetty.webapp.addServerClasses+=,${jetty.base.uri}/lib/gcloud/
jetty.webapp.addHiddenClasses+=,${jetty.base.uri}/lib/gcloud/

View File

@ -10,4 +10,4 @@ internal
[ini]
## Hide the infinispan libraries from deployed webapps
jetty.webapp.addServerClasses+=,${jetty.base.uri}/lib/infinispan/
jetty.webapp.addHiddenClasses+=,${jetty.base.uri}/lib/infinispan/

View File

@ -9,4 +9,4 @@ infinispan
[ini]
## Hide the infinispan libraries from deployed webapps
jetty.webapp.addServerClasses+=,${jetty.base.uri}/lib/infinispan/
jetty.webapp.addHiddenClasses+=,${jetty.base.uri}/lib/infinispan/

View File

@ -101,7 +101,7 @@ public class DemoModulesTests extends AbstractJettyHomeTest
@ParameterizedTest
@MethodSource("provideEnvironmentsToTest")
public void testDemoAddServerClasses(String env) throws Exception
public void testDemoAddHiddenClasses(String env) throws Exception
{
Path jettyBase = newTestJettyBaseDirectory();
String jettyVersion = System.getProperty("jettyVersion");
@ -124,14 +124,14 @@ public class DemoModulesTests extends AbstractJettyHomeTest
assertTrue(runListConfig.awaitFor(START_TIMEOUT, TimeUnit.SECONDS));
assertEquals(0, runListConfig.getExitValue(), "Exit value");
// Example of what we expect
// jetty.webapp.addServerClasses = org.eclipse.jetty.logging.,${jetty.home.uri}/lib/logging/,org.slf4j.,${jetty.base.uri}/lib/bouncycastle/
String addServerKey = " jetty.webapp.addServerClasses = ";
// jetty.webapp.addHiddenClasses = org.eclipse.jetty.logging.,${jetty.home.uri}/lib/logging/,org.slf4j.,${jetty.base.uri}/lib/bouncycastle/
String addServerKey = " jetty.webapp.addHiddenClasses = ";
String addServerClasses = runListConfig.getLogs().stream()
.filter(s -> s.startsWith(addServerKey))
.findFirst()
.orElseThrow(() ->
new NoSuchElementException("Unable to find [" + addServerKey + "]"));
assertThat("'jetty.webapp.addServerClasses' entry count",
assertThat("'jetty.webapp.addHiddenClasses' entry count",
addServerClasses.split(",").length,
greaterThan(1));
}