This is an automated email from the ASF dual-hosted git repository.
nihaljain pushed a commit to branch branch-3
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/branch-3 by this push:
new 4c8f1612521 HBASE-29226 Migrate to jetty 12 with EE8 and bump java
servlet to 4.0.1 (#7233) (#6783)
4c8f1612521 is described below
commit 4c8f1612521820462dde231f25be1a0bf13e29c6
Author: Nihal Jain <[email protected]>
AuthorDate: Fri Aug 22 15:52:53 2025 +0530
HBASE-29226 Migrate to jetty 12 with EE8 and bump java servlet to 4.0.1
(#7233) (#6783)
Please find a detailed summary of all changes as below:
**Dependencies**
- Replaced `hbase-shaded-jetty` i.e. Jetty 9 with
`hbase-shaded-jetty-12-plus-core` and `hbase-shaded-jetty-12-plus-ee8` i.e.
Jetty 12 EE8
- Upgrade `servlet.api.version` to `4.0.1`
- Upgrade `tomcat.jasper.version` to `9.0.108`
**Code Adjustments**
- Modified import statements to use `org.eclipse.jetty.ee8` etc. as per
Jetty 12
- Replace `HandlerCollection` with `Handler.Sequence`
- Refer
https://jetty.org/docs/jetty/12/programming-guide/migration/11-to-12.html#api-changes-handler-sequence
- Adjust request log handler.
- Refer
https://jetty.org/docs/jetty/12/programming-guide/migration/11-to-12.html#api-changes-handler-requestlog
- Adjust error handlers' set logic based on available methods
- Remove usage of deprecated method `setResourceBase()` with
`setBaseResourceAsPath()`. Also, passing `logDir` path in canonical format as
otherwise, we get `404` in case we have `\..\` in static path.
- Refer https://github.com/jetty/jetty.project/issues/12958
- Remove `MultiException` with `ExceptionUtil.MultiException` in
`HttpServer.java`.
- Modify assignment: `webServer.getHandlers()` with new return type:
`Array` to `List`
- Replaced `Constraint` with `ServletConstraint`
- Added `LogLevelExceptionUtils.java` to handle HTML error response for log
levels as `connection.getResponseMessage()` now returns error code as string in
message and hence causes test failure otherwise
- Allow `javax` as xsd files at `javax/servlet/resources` come via
`org.apache.hbase.thirdparty:hbase-shaded-jetty-12-ee8 `
**Test Adjustments**
- Update `checkBindAddress()` to call `server.start()` else
`testBindAddress()` fails with NPE. Verified doing same without Jetty 12 works
as well.
- Replace `JSON.parse()` with `new JSON().fromJSON()` and `JSON.toString()`
with `new JSON().toJSON()`
- Update app name from `".."` to `""` as it fails with
`IllegalArgumentException` otherwise
- Update `MockHttpApiRule` where we replace `AbstractHandler` with
`Handler.Abstract` and adjust code based on new interface.
- Refer
https://jetty.org/docs/jetty/12/programming-guide/migration/11-to-12.html#handler-request-content-apis
- Deleted `webdefaults.xml`. Need to see if still needed.
- Added necessary compliance rules for URI validation, required by tests
and possibly users as well. Add note for what fails if these rules are not
there in tests.
- Refer
https://github.com/jetty/jetty.project/issues/11890#issuecomment-2156449534
**References**
- Umbrealla at HBASE-29224
- Discussion at
https://lists.apache.org/thread/bkrfm705kqd3bqzyvo7jv46t6p64x2n5
- Dependent thirdparty change at
https://github.com/apache/hbase-thirdparty/commit/fd76b1eec91d40c97c59a2a36e8f430ce05d6788
Signed-off-by: Dávid Paksy <[email protected]>
Signed-off-by: Istvan Toth <[email protected]>
---
hbase-http/pom.xml | 6 +-
.../hadoop/hbase/http/AdminAuthorizedServlet.java | 2 +-
.../org/apache/hadoop/hbase/http/HttpServer.java | 53 +-
.../apache/hadoop/hbase/http/HttpServerUtil.java | 12 +-
.../org/apache/hadoop/hbase/http/InfoServer.java | 2 +-
.../hadoop/hbase/http/ProfileOutputServlet.java | 2 +-
.../org/apache/hadoop/hbase/http/log/LogLevel.java | 7 +-
.../hbase/http/log/LogLevelExceptionUtils.java | 100 ++++
.../apache/hadoop/hbase/http/TestHttpServer.java | 5 +-
.../hadoop/hbase/http/conf/TestConfServlet.java | 2 +-
.../apache/hadoop/hbase/http/log/TestLogLevel.java | 5 +-
.../hadoop/hbase/http/resource/JerseyResource.java | 2 +-
.../org/apache/hadoop/hbase/MockHttpApiRule.java | 65 ++-
.../org/apache/hadoop/hbase/rest/RESTServer.java | 24 +-
.../hadoop/hbase/rest/TestGetAndPutResource.java | 5 +
.../hadoop/hbase/rest/client/TestRemoteTable.java | 25 +
.../org/apache/hadoop/hbase/master/HMaster.java | 4 +-
.../resources/ensure-jars-have-correct-contents.sh | 2 +
.../shaded/org/eclipse/jetty/webapp/webdefault.xml | 550 ---------------------
.../resources/ensure-jars-have-correct-contents.sh | 2 +
.../apache/hadoop/hbase/thrift/ThriftServer.java | 4 +-
pom.xml | 13 +-
22 files changed, 261 insertions(+), 631 deletions(-)
diff --git a/hbase-http/pom.xml b/hbase-http/pom.xml
index 60288f49519..98f7c4e1a06 100644
--- a/hbase-http/pom.xml
+++ b/hbase-http/pom.xml
@@ -78,7 +78,11 @@
</dependency>
<dependency>
<groupId>org.apache.hbase.thirdparty</groupId>
- <artifactId>hbase-shaded-jetty</artifactId>
+ <artifactId>hbase-shaded-jetty-12-plus-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.hbase.thirdparty</groupId>
+ <artifactId>hbase-shaded-jetty-12-plus-ee8</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hbase.thirdparty</groupId>
diff --git
a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/AdminAuthorizedServlet.java
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/AdminAuthorizedServlet.java
index 2ad09b5ae5c..1ecd75532ce 100644
---
a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/AdminAuthorizedServlet.java
+++
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/AdminAuthorizedServlet.java
@@ -24,7 +24,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.yetus.audience.InterfaceStability;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.DefaultServlet;
+import
org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.DefaultServlet;
/**
* General servlet which is admin-authorized.
diff --git
a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
index 6012b24ec54..fe2a9a48c21 100644
--- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
+++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
@@ -72,6 +72,12 @@ import org.slf4j.LoggerFactory;
import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
+import
org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.DefaultServlet;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.FilterHolder;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.FilterMapping;
+import
org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.ServletContextHandler;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.ServletHolder;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.webapp.WebAppContext;
import org.apache.hbase.thirdparty.org.eclipse.jetty.http.HttpVersion;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Handler;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.HttpConfiguration;
@@ -84,18 +90,10 @@ import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.SslConnectionFactory
import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.SymlinkAllowedResourceAliasChecker;
import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.handler.ContextHandlerCollection;
import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.handler.ErrorHandler;
-import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.handler.HandlerCollection;
-import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.handler.RequestLogHandler;
import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.handler.gzip.GzipHandler;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.DefaultServlet;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.FilterHolder;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.FilterMapping;
-import
org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.ServletContextHandler;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.ServletHolder;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.util.MultiException;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.util.ExceptionUtil;
import
org.apache.hbase.thirdparty.org.eclipse.jetty.util.ssl.SslContextFactory;
import
org.apache.hbase.thirdparty.org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.webapp.WebAppContext;
import org.apache.hbase.thirdparty.org.glassfish.jersey.server.ResourceConfig;
import
org.apache.hbase.thirdparty.org.glassfish.jersey.servlet.ServletContainer;
@@ -654,23 +652,21 @@ public class HttpServer implements FilterContainer {
Preconditions.checkNotNull(webAppContext);
- HandlerCollection handlerCollection = new HandlerCollection();
+ Handler.Sequence handlers = new Handler.Sequence();
ContextHandlerCollection contexts = new ContextHandlerCollection();
RequestLog requestLog = HttpRequestLog.getRequestLog(name);
if (requestLog != null) {
- RequestLogHandler requestLogHandler = new RequestLogHandler();
- requestLogHandler.setRequestLog(requestLog);
- handlerCollection.addHandler(requestLogHandler);
+ webServer.setRequestLog(requestLog);
}
final String appDir = getWebAppsPath(name);
- handlerCollection.addHandler(contexts);
- handlerCollection.addHandler(webAppContext);
+ handlers.addHandler(contexts);
+ handlers.addHandler(webAppContext);
- webServer.setHandler(handlerCollection);
+ webServer.setHandler(handlers);
webAppContext.setAttribute(ADMINS_ACL, adminsAcl);
@@ -715,8 +711,9 @@ public class HttpServer implements FilterContainer {
// Check if disable stack trace property is configured
if (!conf.getBoolean(HTTP_UI_SHOW_STACKTRACE_KEY, true)) {
// Disable stack traces for server errors in UI
- webServer.setErrorHandler(new ErrorHandler());
- webServer.getErrorHandler().setShowStacks(false);
+ ErrorHandler errorHandler = new ErrorHandler();
+ errorHandler.setShowStacks(false);
+ webServer.setErrorHandler(errorHandler);
// Disable stack traces for web app errors in UI
webAppContext.getErrorHandler().setShowStacks(false);
}
@@ -841,7 +838,8 @@ public class HttpServer implements FilterContainer {
if
(context.getAliasChecks().stream().anyMatch(aliasCheckerClass::isInstance)) {
LOG.debug("{} is already part of alias check list",
aliasCheckerClass.getName());
} else {
- context.addAliasCheck(new SymlinkAllowedResourceAliasChecker(context));
+ context
+ .addAliasCheck(new
SymlinkAllowedResourceAliasChecker(context.getCoreContextHandler()));
LOG.debug("{} added to the alias check list",
aliasCheckerClass.getName());
}
LOG.info("Serving aliases allowed for /logs context");
@@ -1258,14 +1256,14 @@ public class HttpServer implements FilterContainer {
} catch (IOException ex) {
LOG.info("HttpServer.start() threw a non Bind IOException", ex);
throw ex;
- } catch (MultiException ex) {
- LOG.info("HttpServer.start() threw a MultiException", ex);
+ } catch (Exception ex) {
+ LOG.info("HttpServer.start() threw a Exception", ex);
throw ex;
}
// Make sure there is no handler failures.
- Handler[] handlers = webServer.getHandlers();
- for (int i = 0; i < handlers.length; i++) {
- if (handlers[i].isFailed()) {
+ List<Handler> handlers = webServer.getHandlers();
+ for (Handler handler : handlers) {
+ if (handler.isFailed()) {
throw new IOException("Problem in starting http server. Server
handlers failed");
}
}
@@ -1335,7 +1333,7 @@ public class HttpServer implements FilterContainer {
* stop the server
*/
public void stop() throws Exception {
- MultiException exception = null;
+ ExceptionUtil.MultiException exception = null;
for (ListenerInfo li : listeners) {
if (!li.isManaged) {
continue;
@@ -1372,9 +1370,10 @@ public class HttpServer implements FilterContainer {
}
- private MultiException addMultiException(MultiException exception, Exception
e) {
+ private ExceptionUtil.MultiException
addMultiException(ExceptionUtil.MultiException exception,
+ Exception e) {
if (exception == null) {
- exception = new MultiException();
+ exception = new ExceptionUtil.MultiException();
}
exception.add(e);
return exception;
diff --git
a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServerUtil.java
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServerUtil.java
index ecfb32742fd..0f1c9707d1c 100644
--- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServerUtil.java
+++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServerUtil.java
@@ -22,11 +22,11 @@ import javax.servlet.DispatcherType;
import org.apache.hadoop.conf.Configuration;
import org.apache.yetus.audience.InterfaceAudience;
-import
org.apache.hbase.thirdparty.org.eclipse.jetty.security.ConstraintMapping;
-import
org.apache.hbase.thirdparty.org.eclipse.jetty.security.ConstraintSecurityHandler;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.FilterHolder;
-import
org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.ServletContextHandler;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.util.security.Constraint;
+import
org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.nested.ServletConstraint;
+import
org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.security.ConstraintMapping;
+import
org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.security.ConstraintSecurityHandler;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.FilterHolder;
+import
org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.ServletContextHandler;
/**
* HttpServer utility.
@@ -43,7 +43,7 @@ public final class HttpServerUtil {
*/
public static void constrainHttpMethods(ServletContextHandler ctxHandler,
boolean allowOptionsMethod) {
- Constraint c = new Constraint();
+ ServletConstraint c = new ServletConstraint();
c.setAuthenticate(true);
ConstraintMapping cmt = new ConstraintMapping();
diff --git
a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/InfoServer.java
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/InfoServer.java
index ea73be808f0..5a09315ed77 100644
--- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/InfoServer.java
+++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/InfoServer.java
@@ -29,7 +29,7 @@ import org.apache.hadoop.security.authorize.AccessControlList;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.hbase.thirdparty.com.google.common.net.HostAndPort;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.ServletHolder;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.ServletHolder;
/**
* Create a Jetty embedded server to answer http requests. The primary goal is
to serve up status
diff --git
a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProfileOutputServlet.java
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProfileOutputServlet.java
index d92e7d009f6..12d0c462504 100644
---
a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProfileOutputServlet.java
+++
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProfileOutputServlet.java
@@ -27,7 +27,7 @@ import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.DefaultServlet;
+import
org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.DefaultServlet;
/**
* Servlet to serve files generated by {@link ProfileServlet}
diff --git
a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/log/LogLevel.java
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/log/LogLevel.java
index a9e8fa7cbe1..915f7e29918 100644
--- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/log/LogLevel.java
+++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/log/LogLevel.java
@@ -41,7 +41,6 @@ import org.apache.hadoop.hbase.logging.Log4jUtils;
import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
import org.apache.hadoop.security.authentication.client.KerberosAuthenticator;
import org.apache.hadoop.security.ssl.SSLFactory;
-import org.apache.hadoop.util.HttpExceptionUtils;
import org.apache.hadoop.util.ServletUtil;
import org.apache.hadoop.util.Tool;
import org.apache.yetus.audience.InterfaceAudience;
@@ -267,7 +266,11 @@ public final class LogLevel {
HttpURLConnection connection = connect(url);
- HttpExceptionUtils.validateResponse(connection, 200);
+ // We now use the validateResponse method of hbase to handle for HTML
response,
+ // as with Jetty 12: getResponseMessage() returns "Precondition Failed"
vs
+ // "Modification of logger
protected.org.apache.hadoop.hbase.http.log.TestLogLevel is
+ // disallowed in configuration" in Jetty 9
+ LogLevelExceptionUtils.validateResponse(connection, 200);
// read from the servlet
diff --git
a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/log/LogLevelExceptionUtils.java
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/log/LogLevelExceptionUtils.java
new file mode 100644
index 00000000000..2776c9b4313
--- /dev/null
+++
b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/log/LogLevelExceptionUtils.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.http.log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * HTTP utility class to help propagate server side exception in log level
servlet to the client
+ * over HTTP (HTML payload) It parses HTTP client connections and recreates
the exception.
+ */
[email protected]
+public class LogLevelExceptionUtils {
+
+ private static void throwEx(Throwable ex) {
+ LogLevelExceptionUtils.<RuntimeException> throwException(ex);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <E extends Throwable> void throwException(Throwable ex)
throws E {
+ throw (E) ex;
+ }
+
+ /**
+ * Validates the status of an <code>HttpURLConnection</code> against an
expected HTTP status code.
+ * If the current status code is not the expected one it throws an exception
with a detail message
+ * using Server side error messages if available.
+ * <p>
+ * <b>NOTE: This is an adapted version of the original method in
HttpServerUtil.java of Hadoop,
+ * but we handle for HTML response.
+ * @param conn the <code>HttpURLConnection</code>.
+ * @param expectedStatus the expected HTTP status code.
+ * @throws IOException thrown if the current status code does not match the
expected one.
+ */
+ @SuppressWarnings("unchecked")
+ public static void validateResponse(HttpURLConnection conn, int
expectedStatus)
+ throws IOException {
+ if (conn.getResponseCode() != expectedStatus) {
+ Exception toThrow = null;
+
+ try (InputStream es = conn.getErrorStream()) {
+ if (es != null) {
+ try (InputStreamReader isr = new InputStreamReader(es,
StandardCharsets.UTF_8);
+ BufferedReader reader = new BufferedReader(isr)) {
+ final String errorAsHtml =
reader.lines().collect(Collectors.joining("\n"));
+
+ final String status = extractValue(errorAsHtml,
"<th>STATUS:</th><td>(\\d+)</td>");
+ final String message = extractValue(errorAsHtml,
"<th>MESSAGE:</th><td>([^<]+)</td>");
+ final String uri = extractValue(errorAsHtml,
"<th>URI:</th><td>([^<]+)</td>");
+ final String exception = extractValue(errorAsHtml,
"<title>([^<]+)</title>");
+
+ toThrow = new IOException(
+ String.format("HTTP status [%s], message [%s], URL [%s],
exception [%s]", status,
+ message, uri, exception));
+ }
+ }
+ } catch (Exception ex) {
+ toThrow =
+ new IOException(String.format("HTTP status [%d], message [%s], URL
[%s], exception [%s]",
+ conn.getResponseCode(), conn.getResponseMessage(), conn.getURL(),
ex), ex);
+ }
+ if (toThrow != null) {
+ throwEx(toThrow);
+ }
+ }
+ }
+
+ private static String extractValue(String html, String regex) {
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(html);
+ if (matcher.find()) {
+ return matcher.group(1);
+ }
+ return null;
+ }
+
+}
diff --git
a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestHttpServer.java
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestHttpServer.java
index ad9c9d3a067..72466392827 100644
--- a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestHttpServer.java
+++ b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestHttpServer.java
@@ -507,7 +507,7 @@ public class TestHttpServer extends
HttpServerFunctionalTest {
@SuppressWarnings("unchecked")
private static Map<String, Object> parse(String jsonString) {
- return (Map<String, Object>) JSON.parse(jsonString);
+ return (Map<String, Object>) new JSON().fromJSON(jsonString);
}
@Test
@@ -615,6 +615,9 @@ public class TestHttpServer extends
HttpServerFunctionalTest {
ServerConnector listener = server.getServerConnectors().get(0);
assertEquals(port, listener.getPort());
+ // We are doing this as otherwise testBindAddress fails, not sure how we
were even starting
+ // server in jetty 9 without this call
+ server.start();
// verify hostname is what was given
server.openListeners();
assertEquals(host, server.getConnectorAddress(0).getHostName());
diff --git
a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/conf/TestConfServlet.java
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/conf/TestConfServlet.java
index fe202906e9c..dc14be96d40 100644
---
a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/conf/TestConfServlet.java
+++
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/conf/TestConfServlet.java
@@ -73,7 +73,7 @@ public class TestConfServlet {
Set<String> programSet = new HashSet<>();
programSet.add("programatically");
programSet.add("programmatically");
- Object parsed = JSON.parse(json);
+ Object parsed = new JSON().fromJSON(json);
Object[] properties = ((Map<String, Object[]>) parsed).get("properties");
for (Object o : properties) {
Map<String, Object> propertyInfo = (Map<String, Object>) o;
diff --git
a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/log/TestLogLevel.java
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/log/TestLogLevel.java
index bc45a629555..04452f613ba 100644
---
a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/log/TestLogLevel.java
+++
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/log/TestLogLevel.java
@@ -235,7 +235,10 @@ public class TestLogLevel {
* @throws Exception if unable to create or start a Jetty server
*/
private HttpServer createServer(String protocol, boolean isSpnego) throws
Exception {
- HttpServer.Builder builder = new HttpServer.Builder().setName("..")
+ // Changed to "" as ".." moves it a steps back in path because the path is
relative to the
+ // current working directory. throws "java.lang.IllegalArgumentException:
Base Resource is not
+ // valid: hbase-http/target/test-classes/static" as it is not able to find
the static folder.
+ HttpServer.Builder builder = new HttpServer.Builder().setName("")
.addEndpoint(new URI(protocol +
"://localhost:0")).setFindPort(true).setConf(serverConf);
if (isSpnego) {
// Set up server Kerberos credentials.
diff --git
a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/resource/JerseyResource.java
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/resource/JerseyResource.java
index 89d71b403af..4e97dcfae3f 100644
---
a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/resource/JerseyResource.java
+++
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/resource/JerseyResource.java
@@ -54,7 +54,7 @@ public class JerseyResource {
final Map<String, Object> m = new TreeMap<>();
m.put(PATH, path);
m.put(OP, op);
- final String js = JSON.toString(m);
+ final String js = new JSON().toJSON(m);
return Response.ok(js).type(MediaType.APPLICATION_JSON).build();
}
}
diff --git
a/hbase-it/src/test/java/org/apache/hadoop/hbase/MockHttpApiRule.java
b/hbase-it/src/test/java/org/apache/hadoop/hbase/MockHttpApiRule.java
index 5817d071f02..be50b313725 100644
--- a/hbase-it/src/test/java/org/apache/hadoop/hbase/MockHttpApiRule.java
+++ b/hbase-it/src/test/java/org/apache/hadoop/hbase/MockHttpApiRule.java
@@ -17,28 +17,31 @@
*/
package org.apache.hadoop.hbase;
-import java.io.IOException;
-import java.io.PrintWriter;
import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;
-import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.rules.ExternalResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.HttpHeaders;
import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.server.CustomRequestLog;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Handler;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Request;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.RequestLog;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Response;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Server;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.ServerConnector;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Slf4jRequestLog;
-import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.handler.AbstractHandler;
+import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.Slf4jRequestLogWriter;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.util.Callback;
import org.apache.hbase.thirdparty.org.eclipse.jetty.util.RegexSet;
/**
@@ -55,7 +58,7 @@ public class MockHttpApiRule extends ExternalResource {
* Register a callback handler for the specified path target.
*/
public MockHttpApiRule addRegistration(final String pathRegex,
- final BiConsumer<String, HttpServletResponse> responder) {
+ final BiConsumer<String, Response> responder) {
handler.register(pathRegex, responder);
return this;
}
@@ -65,16 +68,21 @@ public class MockHttpApiRule extends ExternalResource {
*/
public MockHttpApiRule registerOk(final String pathRegex, final String
responseBody) {
return addRegistration(pathRegex, (target, resp) -> {
- try {
- resp.setStatus(HttpServletResponse.SC_OK);
- resp.setCharacterEncoding("UTF-8");
- resp.setContentType(MediaType.APPLICATION_JSON_TYPE.toString());
- final PrintWriter writer = resp.getWriter();
- writer.write(responseBody);
- writer.flush();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ resp.setStatus(HttpServletResponse.SC_OK);
+ resp.getHeaders().put(HttpHeaders.CONTENT_ENCODING, "UTF-8");
+ resp.getHeaders().put(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_TYPE.toString());
+ ByteBuffer content =
ByteBuffer.wrap(responseBody.getBytes(StandardCharsets.UTF_8));
+ resp.write(true, content, new Callback() {
+ @Override
+ public void succeeded() {
+ // nothing to do
+ }
+
+ @Override
+ public void failed(Throwable x) {
+ throw new RuntimeException(x);
+ }
+ });
});
}
@@ -115,20 +123,18 @@ public class MockHttpApiRule extends ExternalResource {
}
private static RequestLog buildRequestLog() {
- final Slf4jRequestLog requestLog = new Slf4jRequestLog();
- requestLog.setLoggerName(LOG.getName() + ".RequestLog");
- requestLog.setExtended(true);
- return requestLog;
+ Slf4jRequestLogWriter writer = new Slf4jRequestLogWriter();
+ writer.setLoggerName(LOG.getName() + ".RequestLog");
+ return new CustomRequestLog(writer, CustomRequestLog.EXTENDED_NCSA_FORMAT);
}
- private static class MockHandler extends AbstractHandler {
+ private static class MockHandler extends Handler.Abstract {
private final ReadWriteLock responseMappingLock = new
ReentrantReadWriteLock();
- private final Map<String, BiConsumer<String, HttpServletResponse>>
responseMapping =
- new HashMap<>();
+ private final Map<String, BiConsumer<String, Response>> responseMapping =
new HashMap<>();
private final RegexSet regexSet = new RegexSet();
- void register(final String pathRegex, final BiConsumer<String,
HttpServletResponse> responder) {
+ void register(final String pathRegex, final BiConsumer<String, Response>
responder) {
LOG.debug("Registering responder to '{}'", pathRegex);
responseMappingLock.writeLock().lock();
try {
@@ -151,20 +157,25 @@ public class MockHttpApiRule extends ExternalResource {
}
@Override
- public void handle(final String target, final Request baseRequest,
- final HttpServletRequest request, final HttpServletResponse response) {
+ public boolean handle(Request request, Response response, Callback
callback) throws Exception {
+ String target = request.getHttpURI().getPath();
responseMappingLock.readLock().lock();
try {
if (!regexSet.matches(target)) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
- return;
+ callback.succeeded();
+ return true;
}
responseMapping.entrySet().stream().filter(e ->
Pattern.matches(e.getKey(), target))
.findAny().map(Map.Entry::getValue).orElseThrow(() ->
noMatchFound(target))
.accept(target, response);
+ callback.succeeded();
+ } catch (Exception e) {
+ callback.failed(e);
} finally {
responseMappingLock.readLock().unlock();
}
+ return true;
}
private static RuntimeException noMatchFound(final String target) {
diff --git
a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java
b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java
index 760f2ca8b41..666c994d692 100644
--- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java
+++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java
@@ -23,8 +23,10 @@ import java.lang.management.ManagementFactory;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.EnumSet;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import javax.servlet.DispatcherType;
import org.apache.commons.lang3.ArrayUtils;
@@ -57,7 +59,11 @@ import
org.apache.hbase.thirdparty.org.apache.commons.cli.HelpFormatter;
import org.apache.hbase.thirdparty.org.apache.commons.cli.Options;
import org.apache.hbase.thirdparty.org.apache.commons.cli.ParseException;
import org.apache.hbase.thirdparty.org.apache.commons.cli.PosixParser;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.FilterHolder;
+import
org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.ServletContextHandler;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.ServletHolder;
import org.apache.hbase.thirdparty.org.eclipse.jetty.http.HttpVersion;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.http.UriCompliance;
import org.apache.hbase.thirdparty.org.eclipse.jetty.jmx.MBeanContainer;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.HttpConfiguration;
import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.HttpConnectionFactory;
@@ -65,9 +71,6 @@ import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.SecureRequestCustomi
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Server;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.ServerConnector;
import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.SslConnectionFactory;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.FilterHolder;
-import
org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.ServletContextHandler;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.ServletHolder;
import
org.apache.hbase.thirdparty.org.eclipse.jetty.util.ssl.SslContextFactory;
import
org.apache.hbase.thirdparty.org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.apache.hbase.thirdparty.org.glassfish.jersey.server.ResourceConfig;
@@ -287,6 +290,13 @@ public class RESTServer implements Constants {
httpConfig.setSendServerVersion(false);
httpConfig.setSendDateHeader(false);
+ // In Jetty 12, ambiguous path separators, suspicious path characters, and
ambiguous empty
+ // segments are considered violations of the URI specification and hence
are not allowed.
+ // Refer to
https://github.com/jetty/jetty.project/issues/11890#issuecomment-2156449534
+ // We must set a URI compliance to allow for this violation so that client
requests are not
+ // automatically rejected. Our rest endpoints rely on this behavior to
handle encoded uri paths.
+ setUriComplianceRules(httpConfig);
+
ServerConnector serverConnector;
boolean isSecure = false;
if (conf.getBoolean(REST_SSL_ENABLED, false)) {
@@ -400,6 +410,14 @@ public class RESTServer implements Constants {
server.start();
}
+ private static void setUriComplianceRules(HttpConfiguration httpConfig) {
+ Set<UriCompliance.Violation> complianceViolationSet = new HashSet<>();
+
complianceViolationSet.add(UriCompliance.Violation.AMBIGUOUS_PATH_SEPARATOR);
+
complianceViolationSet.add(UriCompliance.Violation.SUSPICIOUS_PATH_CHARACTERS);
+
complianceViolationSet.add(UriCompliance.Violation.AMBIGUOUS_EMPTY_SEGMENT);
+ httpConfig.setUriCompliance(UriCompliance.from(complianceViolationSet));
+ }
+
private static String getHostName(Configuration conf) throws
UnknownHostException {
return Strings.domainNamePointerToHostName(DNS.getDefaultHost(
conf.get(REST_DNS_INTERFACE, "default"), conf.get(REST_DNS_NAMESERVER,
"default")));
diff --git
a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestGetAndPutResource.java
b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestGetAndPutResource.java
index b20baea9df8..21e948e3cfc 100644
---
a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestGetAndPutResource.java
+++
b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestGetAndPutResource.java
@@ -321,6 +321,11 @@ public class TestGetAndPutResource extends RowResourceBase
{
@Test
public void testURLEncodedKey() throws IOException, JAXBException {
+ // Requires UriCompliance.Violation.AMBIGUOUS_PATH_SEPARATOR
+ // Otherwise fails with "400: Ambiguous URI path separator"
+ // In this test, request url resolves to
"/TestRowResource/http%3A%2F%2Fexample.com%2Ffoo/a:1"
+ // and is considered ambiguous by Jetty 12.
+ // Basically we are having a URL encoded string as row key here!
String urlKey = "http://example.com/foo";
StringBuilder path = new StringBuilder();
path.append('/');
diff --git
a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/client/TestRemoteTable.java
b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/client/TestRemoteTable.java
index fcbe7c7ca02..c464a51813e 100644
---
a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/client/TestRemoteTable.java
+++
b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/client/TestRemoteTable.java
@@ -164,6 +164,12 @@ public class TestRemoteTable {
@Test
public void testGet() throws IOException {
+ // Requires UriCompliance.Violation.SUSPICIOUS_PATH_CHARACTERS
+ // Otherwise fails with "400: Suspicious Path Character"
+ // In this test, the request path resolves to
+ //
"/TestRemoteTable_-./testrow1%7C%22%5C%5E%7B%7D%01%02%03%04%05%06%07%08%09%0B%0C/"
+ // and is considered suspicious by the Jetty 12.
+ // Basically ROW_1 contains invalid URL characters here.
Get get = new Get(ROW_1);
Result result = remoteTable.get(get);
byte[] value1 = result.getValue(COLUMN_1, QUALIFIER_1);
@@ -264,6 +270,9 @@ public class TestRemoteTable {
@Test
public void testMultiGet() throws Exception {
+ // In case of multi gets, the request path resolves to
+ //
"/TestRemoteTable_-./multiget/?row=testrow1%7C%22%5C%5E&row=testrow2%7C%22%5C%5E%&v=3"
+ // and hence is not considered suspicious by the Jetty 12.
ArrayList<Get> gets = new ArrayList<>(2);
gets.add(new Get(ROW_1));
gets.add(new Get(ROW_2));
@@ -303,6 +312,8 @@ public class TestRemoteTable {
@Test
public void testPut() throws IOException {
+ // Requires UriCompliance.Violation.SUSPICIOUS_PATH_CHARACTERS
+ // Otherwise fails with "400: Suspicious Path Character"
Put put = new Put(ROW_3);
put.addColumn(COLUMN_1, QUALIFIER_1, VALUE_1);
remoteTable.put(put);
@@ -348,6 +359,13 @@ public class TestRemoteTable {
@Test
public void testDelete() throws IOException {
+ // Requires UriCompliance.Violation.SUSPICIOUS_PATH_CHARACTERS for put,
+ // otherwise fails with "400: Suspicious Path Character"
+ // This example is considered suspicious by the Jetty 12 due to reasons
same as shown in
+ // testGet()
+
+ // Also, requires UriCompliance.Violation.AMBIGUOUS_EMPTY_SEGMENT
+ // Otherwise fails with "400: Ambiguous URI empty segment"
Put put = new Put(ROW_3);
put.addColumn(COLUMN_1, QUALIFIER_1, VALUE_1);
put.addColumn(COLUMN_2, QUALIFIER_2, VALUE_2);
@@ -387,6 +405,9 @@ public class TestRemoteTable {
assertTrue(Bytes.equals(VALUE_1, value1));
assertNull(value2);
+ // This leads to path which resolves to
+ //
"/TestRemoteTable_-./testrow3%7C%22%5C%5E%7B%7D%01%02%03%04%05%06%07%08%09%0B%0C//1"
+ // causing "400: Ambiguous URI empty segment" error with Jetty 12.
delete = new Delete(ROW_3);
delete.setTimestamp(1L);
remoteTable.delete(delete);
@@ -493,6 +514,10 @@ public class TestRemoteTable {
@Test
public void testCheckAndDelete() throws IOException {
+ // Requires UriCompliance.Violation.SUSPICIOUS_PATH_CHARACTERS
+ // Otherwise fails with "400: Suspicious Path Character"
+ // This example is considered suspicious by the Jetty 12 due to reasons
same as shown in
+ // testGet()
Get get = new Get(ROW_1);
Result result = remoteTable.get(get);
byte[] value1 = result.getValue(COLUMN_1, QUALIFIER_1);
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
index 19f58ebe6ad..317f571ba8a 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
@@ -287,10 +287,10 @@ import
org.apache.hbase.thirdparty.com.google.common.io.Closeables;
import org.apache.hbase.thirdparty.com.google.gson.JsonParseException;
import org.apache.hbase.thirdparty.com.google.protobuf.Descriptors;
import org.apache.hbase.thirdparty.com.google.protobuf.Service;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.ServletHolder;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.webapp.WebAppContext;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Server;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.ServerConnector;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.ServletHolder;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.webapp.WebAppContext;
import org.apache.hbase.thirdparty.org.glassfish.jersey.server.ResourceConfig;
import
org.apache.hbase.thirdparty.org.glassfish.jersey.servlet.ServletContainer;
diff --git
a/hbase-shaded/hbase-shaded-check-invariants/src/test/resources/ensure-jars-have-correct-contents.sh
b/hbase-shaded/hbase-shaded-check-invariants/src/test/resources/ensure-jars-have-correct-contents.sh
index 1d1350712b1..8b63889f195 100644
---
a/hbase-shaded/hbase-shaded-check-invariants/src/test/resources/ensure-jars-have-correct-contents.sh
+++
b/hbase-shaded/hbase-shaded-check-invariants/src/test/resources/ensure-jars-have-correct-contents.sh
@@ -98,6 +98,8 @@ allowed_expr+="|^about.html$"
allowed_expr+="|^jetty-dir.css$"
# Coming from Guava, see
https://github.com/google/guava/commit/2cc8c5eddb587db3ac12dacdd5563e79a4681ec4
allowed_expr+="|^org/jspecify/$|^org/jspecify/annotations/$|^org/jspecify/annotations/.*\.class$"
+# Required by jetty 12 on ee8
+allowed_expr="(|^javax/$)"
if [ -n "${allow_hadoop}" ]; then
# * classes in packages that start with org.apache.hadoop, which by
diff --git
a/hbase-shaded/hbase-shaded-testing-util/src/main/resources/org/apache/hadoop/hbase/shaded/org/eclipse/jetty/webapp/webdefault.xml
b/hbase-shaded/hbase-shaded-testing-util/src/main/resources/org/apache/hadoop/hbase/shaded/org/eclipse/jetty/webapp/webdefault.xml
deleted file mode 100644
index 8f10b517eb5..00000000000
---
a/hbase-shaded/hbase-shaded-testing-util/src/main/resources/org/apache/hadoop/hbase/shaded/org/eclipse/jetty/webapp/webdefault.xml
+++ /dev/null
@@ -1,550 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!--
-Copyright (C) Jetty Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-
-<web-app
- xmlns="http://xmlns.jcp.org/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
- metadata-complete="false"
- version="3.1">
-
- <!-- =====================================================================
-->
- <!-- This file contains the default descriptor for web applications.
-->
- <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-->
- <!-- The intent of this descriptor is to include jetty specific or common
-->
- <!-- configuration for all webapps. If a context has a webdefault.xml
-->
- <!-- descriptor, it is applied before the context's own web.xml file
-->
- <!--
-->
- <!-- A context may be assigned a default descriptor by calling
-->
- <!-- WebAppContext.setDefaultsDescriptor(String).
-->
- <!--
-->
- <!-- This file is present in the jetty-webapp.jar, and is used as the
-->
- <!-- defaults descriptor if no other is explicitly set on a context.
-->
- <!--
-->
- <!-- A copy of this file is also placed into the $JETTY_HOME/etc dir of
-->
- <!-- the distribution, and is referenced by some of the other xml files,
-->
- <!-- eg the jetty-deploy.xml file.
-->
- <!-- =====================================================================
-->
-
- <description>
- Default web.xml file.
- This file is applied to a Web application before it's own WEB_INF/web.xml
file
- </description>
-
- <!-- ==================================================================== -->
- <!-- Removes static references to beans from javax.el.BeanELResolver to -->
- <!-- ensure webapp classloader can be released on undeploy -->
- <!-- ==================================================================== -->
- <listener>
-
<listener-class>org.apache.hadoop.hbase.shaded.org.eclipse.jetty.servlet.listener.ELContextCleaner</listener-class>
- </listener>
-
- <!-- ==================================================================== -->
- <!-- Removes static cache of Methods from java.beans.Introspector to -->
- <!-- ensure webapp classloader can be released on undeploy -->
- <!-- ==================================================================== -->
- <listener>
-
<listener-class>org.apache.hadoop.hbase.shaded.org.eclipse.jetty.servlet.listener.IntrospectorCleaner</listener-class>
- </listener>
-
-
- <!-- ==================================================================== -->
- <!-- Context params to control Session Cookies -->
- <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
- <!--
- UNCOMMENT TO ACTIVATE
- <context-param>
- <param-name>org.eclipse.jetty.servlet.SessionDomain</param-name>
- <param-value>127.0.0.1</param-value>
- </context-param>
- <context-param>
- <param-name>org.eclipse.jetty.servlet.SessionPath</param-name>
- <param-value>/</param-value>
- </context-param>
- <context-param>
- <param-name>org.eclipse.jetty.servlet.MaxAge</param-name>
- <param-value>-1</param-value>
- </context-param>
- -->
-
- <!-- ==================================================================== -->
- <!-- The default servlet. -->
- <!-- This servlet, normally mapped to /, provides the handling for static -->
- <!-- content, OPTIONS and TRACE methods for the context. -->
- <!-- The following initParameters are supported: -->
- <!--
- * acceptRanges If true, range requests and responses are
- * supported
- *
- * dirAllowed If true, directory listings are returned if no
- * welcome file is found. Else 403 Forbidden.
- *
- * welcomeServlets If true, attempt to dispatch to welcome files
- * that are servlets, but only after no matching static
- * resources could be found. If false, then a welcome
- * file must exist on disk. If "exact", then exact
- * servlet matches are supported without an existing file.
- * Default is true.
- *
- * This must be false if you want directory listings,
- * but have index.jsp in your welcome file list.
- *
- * redirectWelcome If true, welcome files are redirected rather than
- * forwarded to.
- *
- * gzip If set to true, then static content will be served as
- * gzip content encoded if a matching resource is
- * found ending with ".gz"
- *
- * resourceBase Set to replace the context resource base
- *
- * resourceCache If set, this is a context attribute name, which the
servlet
- * will use to look for a shared ResourceCache instance.
- *
- * relativeResourceBase
- * Set with a pathname relative to the base of the
- * servlet context root. Useful for only serving static
content out
- * of only specific subdirectories.
- *
- * pathInfoOnly If true, only the path info will be applied to the
resourceBase
- *
- * stylesheet Set with the location of an optional stylesheet that
will be used
- * to decorate the directory listing html.
- *
- * aliases If True, aliases of resources are allowed (eg. symbolic
- * links and caps variations). May bypass security
constraints.
- *
- * etags If True, weak etags will be generated and handled.
- *
- * maxCacheSize The maximum total size of the cache or 0 for no cache.
- * maxCachedFileSize The maximum size of a file to cache
- * maxCachedFiles The maximum number of files to cache
- *
- * useFileMappedBuffer
- * If set to true, it will use mapped file buffers to serve
static content
- * when using an NIO connector. Setting this value to false
means that
- * a direct buffer will be used instead of a mapped file
buffer.
- * This file sets the value to true.
- *
- * cacheControl If set, all static content will have this value set as
the cache-control
- * header.
- *
- -->
- <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
- <servlet>
- <servlet-name>default</servlet-name>
-
<servlet-class>org.apache.hadoop.hbase.shaded.org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
- <init-param>
- <param-name>aliases</param-name>
- <param-value>false</param-value>
- </init-param>
- <init-param>
- <param-name>acceptRanges</param-name>
- <param-value>true</param-value>
- </init-param>
- <init-param>
- <param-name>dirAllowed</param-name>
- <param-value>true</param-value>
- </init-param>
- <init-param>
- <param-name>welcomeServlets</param-name>
- <param-value>false</param-value>
- </init-param>
- <init-param>
- <param-name>redirectWelcome</param-name>
- <param-value>false</param-value>
- </init-param>
- <init-param>
- <param-name>maxCacheSize</param-name>
- <param-value>256000000</param-value>
- </init-param>
- <init-param>
- <param-name>maxCachedFileSize</param-name>
- <param-value>200000000</param-value>
- </init-param>
- <init-param>
- <param-name>maxCachedFiles</param-name>
- <param-value>2048</param-value>
- </init-param>
- <init-param>
- <param-name>gzip</param-name>
- <param-value>false</param-value>
- </init-param>
- <init-param>
- <param-name>etags</param-name>
- <param-value>false</param-value>
- </init-param>
- <init-param>
- <param-name>useFileMappedBuffer</param-name>
- <param-value>true</param-value>
- </init-param>
- <!--
- <init-param>
- <param-name>resourceCache</param-name>
- <param-value>resourceCache</param-value>
- </init-param>
- -->
- <!--
- <init-param>
- <param-name>cacheControl</param-name>
- <param-value>max-age=3600,public</param-value>
- </init-param>
- -->
- <load-on-startup>0</load-on-startup>
- </servlet>
-
- <servlet-mapping>
- <servlet-name>default</servlet-name>
- <url-pattern>/</url-pattern>
- </servlet-mapping>
-
-
- <!-- ==================================================================== -->
- <!-- JSP Servlet -->
- <!-- This is the jasper JSP servlet. -->
- <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
- <!-- The JSP page compiler and execution servlet, which is the mechanism -->
- <!-- used by the jsp container to support JSP pages. Traditionally, -->
- <!-- this servlet is mapped to URL pattern "*.jsp". This servlet -->
- <!-- supports the following initialization parameters (default values -->
- <!-- are in square brackets): -->
- <!-- -->
- <!-- checkInterval If development is false and reloading is true, -->
- <!-- background compiles are enabled. checkInterval -->
- <!-- is the time in seconds between checks to see -->
- <!-- if a JSP page needs to be recompiled. [300] -->
- <!-- -->
- <!-- compiler Which compiler Ant should use to compile JSP -->
- <!-- pages. See the Ant documentation for more -->
- <!-- information. [javac] -->
- <!-- -->
- <!-- classdebuginfo Should the class file be compiled with -->
- <!-- debugging information? [true] -->
- <!-- -->
- <!-- classpath What class path should I use while compiling -->
- <!-- generated servlets? [Created dynamically -->
- <!-- based on the current web application] -->
- <!-- Set to ? to make the container explicitly set -->
- <!-- this parameter. -->
- <!-- -->
- <!-- development Is Jasper used in development mode (will check -->
- <!-- for JSP modification on every access)? [true] -->
- <!-- -->
- <!-- enablePooling Determines whether tag handler pooling is -->
- <!-- enabled [true] -->
- <!-- -->
- <!-- fork Tell Ant to fork compiles of JSP pages so that -->
- <!-- a separate JVM is used for JSP page compiles -->
- <!-- from the one Tomcat is running in. [true] -->
- <!-- -->
- <!-- ieClassId The class-id value to be sent to Internet -->
- <!-- Explorer when using <jsp:plugin> tags. -->
- <!-- [clsid:8AD9C840-044E-11D1-B3E9-00805F499D93] -->
- <!-- -->
- <!-- javaEncoding Java file encoding to use for generating java -->
- <!-- source files. [UTF-8] -->
- <!-- -->
- <!-- keepgenerated Should we keep the generated Java source code -->
- <!-- for each page instead of deleting it? [true] -->
- <!-- -->
- <!-- logVerbosityLevel The level of detailed messages to be produced -->
- <!-- by this servlet. Increasing levels cause the -->
- <!-- generation of more messages. Valid values are -->
- <!-- FATAL, ERROR, WARNING, INFORMATION, and DEBUG. -->
- <!-- [WARNING] -->
- <!-- -->
- <!-- mappedfile Should we generate static content with one -->
- <!-- print statement per input line, to ease -->
- <!-- debugging? [false] -->
- <!-- -->
- <!-- -->
- <!-- reloading Should Jasper check for modified JSPs? [true] -->
- <!-- -->
- <!-- suppressSmap Should the generation of SMAP info for JSR45 -->
- <!-- debugging be suppressed? [false] -->
- <!-- -->
- <!-- dumpSmap Should the SMAP info for JSR45 debugging be -->
- <!-- dumped to a file? [false] -->
- <!-- False if suppressSmap is true -->
- <!-- -->
- <!-- scratchdir What scratch directory should we use when -->
- <!-- compiling JSP pages? [default work directory -->
- <!-- for the current web application] -->
- <!-- -->
- <!-- tagpoolMaxSize The maximum tag handler pool size [5] -->
- <!-- -->
- <!-- xpoweredBy Determines whether X-Powered-By response -->
- <!-- header is added by generated servlet [false] -->
- <!-- -->
- <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
- <servlet id="jsp">
- <servlet-name>jsp</servlet-name>
-
<servlet-class>org.apache.hadoop.hbase.shaded.org.eclipse.jetty.jsp.JettyJspServlet</servlet-class>
- <init-param>
- <param-name>logVerbosityLevel</param-name>
- <param-value>DEBUG</param-value>
- </init-param>
- <init-param>
- <param-name>fork</param-name>
- <param-value>false</param-value>
- </init-param>
- <init-param>
- <param-name>xpoweredBy</param-name>
- <param-value>false</param-value>
- </init-param>
- <init-param>
- <param-name>compilerTargetVM</param-name>
- <param-value>1.7</param-value>
- </init-param>
- <init-param>
- <param-name>compilerSourceVM</param-name>
- <param-value>1.7</param-value>
- </init-param>
- <!--
- <init-param>
- <param-name>classpath</param-name>
- <param-value>?</param-value>
- </init-param>
- -->
- <load-on-startup>0</load-on-startup>
- </servlet>
-
- <servlet-mapping>
- <servlet-name>jsp</servlet-name>
- <url-pattern>*.jsp</url-pattern>
- <url-pattern>*.jspf</url-pattern>
- <url-pattern>*.jspx</url-pattern>
- <url-pattern>*.xsp</url-pattern>
- <url-pattern>*.JSP</url-pattern>
- <url-pattern>*.JSPF</url-pattern>
- <url-pattern>*.JSPX</url-pattern>
- <url-pattern>*.XSP</url-pattern>
- </servlet-mapping>
-
-
- <!-- ==================================================================== -->
- <!-- Default session configuration -->
- <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
- <session-config>
- <session-timeout>30</session-timeout>
- </session-config>
-
- <!-- ==================================================================== -->
- <!-- Default MIME mappings -->
- <!-- The default MIME mappings are provided by the mime.properties -->
- <!-- resource in the jetty-http.jar file. Additional or modified -->
- <!-- mappings may be specified here -->
- <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
- <!-- UNCOMMENT TO ACTIVATE
- <mime-mapping>
- <extension>mysuffix</extension>
- <mime-type>mymime/type</mime-type>
- </mime-mapping>
- -->
-
- <!-- ==================================================================== -->
- <!-- Default welcome files -->
- <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
- <welcome-file-list>
- <welcome-file>index.html</welcome-file>
- <welcome-file>index.htm</welcome-file>
- <welcome-file>index.jsp</welcome-file>
- </welcome-file-list>
-
- <!-- ==================================================================== -->
- <!-- Default locale encodings -->
- <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
- <locale-encoding-mapping-list>
- <locale-encoding-mapping>
- <locale>ar</locale>
- <encoding>ISO-8859-6</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>be</locale>
- <encoding>ISO-8859-5</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>bg</locale>
- <encoding>ISO-8859-5</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>ca</locale>
- <encoding>ISO-8859-1</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>cs</locale>
- <encoding>ISO-8859-2</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>da</locale>
- <encoding>ISO-8859-1</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>de</locale>
- <encoding>ISO-8859-1</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>el</locale>
- <encoding>ISO-8859-7</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>en</locale>
- <encoding>ISO-8859-1</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>es</locale>
- <encoding>ISO-8859-1</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>et</locale>
- <encoding>ISO-8859-1</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>fi</locale>
- <encoding>ISO-8859-1</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>fr</locale>
- <encoding>ISO-8859-1</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>hr</locale>
- <encoding>ISO-8859-2</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>hu</locale>
- <encoding>ISO-8859-2</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>is</locale>
- <encoding>ISO-8859-1</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>it</locale>
- <encoding>ISO-8859-1</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>iw</locale>
- <encoding>ISO-8859-8</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>ja</locale>
- <encoding>Shift_JIS</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>ko</locale>
- <encoding>EUC-KR</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>lt</locale>
- <encoding>ISO-8859-2</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>lv</locale>
- <encoding>ISO-8859-2</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>mk</locale>
- <encoding>ISO-8859-5</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>nl</locale>
- <encoding>ISO-8859-1</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>no</locale>
- <encoding>ISO-8859-1</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>pl</locale>
- <encoding>ISO-8859-2</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>pt</locale>
- <encoding>ISO-8859-1</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>ro</locale>
- <encoding>ISO-8859-2</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>ru</locale>
- <encoding>ISO-8859-5</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>sh</locale>
- <encoding>ISO-8859-5</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>sk</locale>
- <encoding>ISO-8859-2</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>sl</locale>
- <encoding>ISO-8859-2</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>sq</locale>
- <encoding>ISO-8859-2</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>sr</locale>
- <encoding>ISO-8859-5</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>sv</locale>
- <encoding>ISO-8859-1</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>tr</locale>
- <encoding>ISO-8859-9</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>uk</locale>
- <encoding>ISO-8859-5</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>zh</locale>
- <encoding>GB2312</encoding>
- </locale-encoding-mapping>
- <locale-encoding-mapping>
- <locale>zh_TW</locale>
- <encoding>Big5</encoding>
- </locale-encoding-mapping>
- </locale-encoding-mapping-list>
-
- <!-- ==================================================================== -->
- <!-- Disable TRACE method with security constraint -->
- <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
- <security-constraint>
- <web-resource-collection>
- <web-resource-name>Disable TRACE</web-resource-name>
- <url-pattern>/</url-pattern>
- <http-method>TRACE</http-method>
- </web-resource-collection>
- <auth-constraint/>
- </security-constraint>
- <security-constraint>
- <web-resource-collection>
- <web-resource-name>Enable everything but TRACE</web-resource-name>
- <url-pattern>/</url-pattern>
- <http-method-omission>TRACE</http-method-omission>
- </web-resource-collection>
- </security-constraint>
-
-</web-app>
diff --git
a/hbase-shaded/hbase-shaded-with-hadoop-check-invariants/src/test/resources/ensure-jars-have-correct-contents.sh
b/hbase-shaded/hbase-shaded-with-hadoop-check-invariants/src/test/resources/ensure-jars-have-correct-contents.sh
index 1d1350712b1..8b63889f195 100644
---
a/hbase-shaded/hbase-shaded-with-hadoop-check-invariants/src/test/resources/ensure-jars-have-correct-contents.sh
+++
b/hbase-shaded/hbase-shaded-with-hadoop-check-invariants/src/test/resources/ensure-jars-have-correct-contents.sh
@@ -98,6 +98,8 @@ allowed_expr+="|^about.html$"
allowed_expr+="|^jetty-dir.css$"
# Coming from Guava, see
https://github.com/google/guava/commit/2cc8c5eddb587db3ac12dacdd5563e79a4681ec4
allowed_expr+="|^org/jspecify/$|^org/jspecify/annotations/$|^org/jspecify/annotations/.*\.class$"
+# Required by jetty 12 on ee8
+allowed_expr="(|^javax/$)"
if [ -n "${allow_hadoop}" ]; then
# * classes in packages that start with org.apache.hadoop, which by
diff --git
a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift/ThriftServer.java
b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift/ThriftServer.java
index 7f2d3744029..a0370aab0fb 100644
---
a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift/ThriftServer.java
+++
b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift/ThriftServer.java
@@ -144,6 +144,8 @@ import
org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLineParser;
import org.apache.hbase.thirdparty.org.apache.commons.cli.DefaultParser;
import org.apache.hbase.thirdparty.org.apache.commons.cli.HelpFormatter;
import org.apache.hbase.thirdparty.org.apache.commons.cli.Options;
+import
org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.ServletContextHandler;
+import org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.ServletHolder;
import org.apache.hbase.thirdparty.org.eclipse.jetty.http.HttpVersion;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.HttpConfiguration;
import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.HttpConnectionFactory;
@@ -151,8 +153,6 @@ import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.SecureRequestCustomi
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Server;
import org.apache.hbase.thirdparty.org.eclipse.jetty.server.ServerConnector;
import
org.apache.hbase.thirdparty.org.eclipse.jetty.server.SslConnectionFactory;
-import
org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.ServletContextHandler;
-import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.ServletHolder;
import
org.apache.hbase.thirdparty.org.eclipse.jetty.util.ssl.SslContextFactory;
import
org.apache.hbase.thirdparty.org.eclipse.jetty.util.thread.QueuedThreadPool;
diff --git a/pom.xml b/pom.xml
index 7fe0f2e7976..368fba739f2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -839,9 +839,9 @@
<jackson.version>2.19.2</jackson.version>
<jackson.databind.version>2.19.2</jackson.databind.version>
<jaxb-api.version>2.3.1</jaxb-api.version>
- <servlet.api.version>3.1.0</servlet.api.version>
+ <servlet.api.version>4.0.1</servlet.api.version>
<wx.rs.api.version>2.1.1</wx.rs.api.version>
- <tomcat.jasper.version>9.0.104</tomcat.jasper.version>
+ <tomcat.jasper.version>9.0.108</tomcat.jasper.version>
<jruby.version>9.4.12.1</jruby.version>
<junit.version>4.13.2</junit.version>
<hamcrest.version>1.3</hamcrest.version>
@@ -919,7 +919,7 @@
<!-- for.exclusion version are NOT for direct dependencies. To use the
provided
scope to transitively exclude some transitive dependencies, we need to
specify
some existing version to for maven. -->
- <tomcat.version.for.exclusion>9.0.93</tomcat.version.for.exclusion>
+
<tomcat.version.for.exclusion>${tomcat.jasper.version}</tomcat.version.for.exclusion>
<!-- Coverage properties -->
<jacoco.version>0.8.8</jacoco.version>
<jacocoArgLine/>
@@ -1790,7 +1790,12 @@
</dependency>
<dependency>
<groupId>org.apache.hbase.thirdparty</groupId>
- <artifactId>hbase-shaded-jetty</artifactId>
+ <artifactId>hbase-shaded-jetty-12-plus-core</artifactId>
+ <version>${hbase-thirdparty.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.hbase.thirdparty</groupId>
+ <artifactId>hbase-shaded-jetty-12-plus-ee8</artifactId>
<version>${hbase-thirdparty.version}</version>
</dependency>
<dependency>