diff --git a/History.md b/History.md
index 7892f691..3e8a6c9d 100644
--- a/History.md
+++ b/History.md
@@ -1,3 +1,7 @@
+## 2.0.0 (UNRELEASED)
+
+- Support Jakarta Servlet API 5.0 (JEE 9)
+
## 1.3.0 (UNRELEASED)
- Support Javax Servlet API 4.0 (JEE 8)
diff --git a/README.md b/README.md
index 5bb322ac..ace32d0d 100644
--- a/README.md
+++ b/README.md
@@ -291,7 +291,7 @@ Or the equivalent of doing `bundle exec rackup ...` if you're using Bundler :
## Logging
JRuby-Rack sets up a delegate logger for Rails that sends logging output to
-`javax.servlet.ServletContext#log` by default. If you wish to use a different
+`jakarta.servlet.ServletContext#log` by default. If you wish to use a different
logging system, configure `jruby.rack.logging` as follows:
- `servlet_context` (default): Sends log messages to the servlet context.
diff --git a/pom.xml b/pom.xml
index 9c557ea0..e49ed6b6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
org.jruby.rackjruby-rack
- 1.3.0-SNAPSHOT
+ 2.0.0-SNAPSHOTJRuby-Rackhttps://github.com/jruby/jruby-rack/
@@ -91,13 +91,13 @@
jakarta.servletjakarta.servlet-api
- 4.0.4
+ 5.0.0providedjakarta.servlet.jspjakarta.servlet.jsp-api
- 2.3.6
+ 3.0.0provided
@@ -136,16 +136,10 @@
${spring.version}test
-
- org.springframework
- spring-test
- ${spring.version}
- test
- jakarta.eljakarta.el-api
- 3.0.3
+ 4.0.0test
diff --git a/src/main/java/org/jruby/rack/AbstractFilter.java b/src/main/java/org/jruby/rack/AbstractFilter.java
index acc98c5d..c4ac7f59 100644
--- a/src/main/java/org/jruby/rack/AbstractFilter.java
+++ b/src/main/java/org/jruby/rack/AbstractFilter.java
@@ -8,14 +8,14 @@
package org.jruby.rack;
import java.io.IOException;
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import org.jruby.rack.servlet.RequestCapture;
import org.jruby.rack.servlet.ResponseCapture;
import org.jruby.rack.servlet.ServletRackEnvironment;
diff --git a/src/main/java/org/jruby/rack/AbstractServlet.java b/src/main/java/org/jruby/rack/AbstractServlet.java
index e52b37c3..fc4d1e9e 100644
--- a/src/main/java/org/jruby/rack/AbstractServlet.java
+++ b/src/main/java/org/jruby/rack/AbstractServlet.java
@@ -8,13 +8,13 @@
package org.jruby.rack;
import java.io.IOException;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import org.jruby.rack.servlet.ServletRackEnvironment;
import org.jruby.rack.servlet.ServletRackResponseEnvironment;
diff --git a/src/main/java/org/jruby/rack/RackDispatcher.java b/src/main/java/org/jruby/rack/RackDispatcher.java
index b187bcc1..38e1a573 100644
--- a/src/main/java/org/jruby/rack/RackDispatcher.java
+++ b/src/main/java/org/jruby/rack/RackDispatcher.java
@@ -8,7 +8,8 @@
package org.jruby.rack;
import java.io.IOException;
-import javax.servlet.ServletException;
+
+import jakarta.servlet.ServletException;
/**
*
diff --git a/src/main/java/org/jruby/rack/RackEnvironment.java b/src/main/java/org/jruby/rack/RackEnvironment.java
index 9d95fbb4..03c1dbf5 100644
--- a/src/main/java/org/jruby/rack/RackEnvironment.java
+++ b/src/main/java/org/jruby/rack/RackEnvironment.java
@@ -13,7 +13,7 @@
/**
* Represent a Rack environment (that will most likely by wrapping a
- * {@link javax.servlet.http.HttpServletRequest}).
+ * {@link jakarta.servlet.http.HttpServletRequest}).
* Allows Rack applications to be loaded outside of JEE servlet environments.
*
* @see org.jruby.rack.servlet.ServletRackEnvironment
@@ -37,7 +37,7 @@ public interface RackEnvironment {
// The following methods are specific to the rack environment
/**
- * @see javax.servlet.ServletRequest#getInputStream()
+ * @see jakarta.servlet.ServletRequest#getInputStream()
* @return the input as a stream
* @throws IOException if there's an IO exception
*/
@@ -53,107 +53,107 @@ public interface RackEnvironment {
// The following methods are usually inherited from the servlet request
/**
- * @see javax.servlet.http.HttpServletRequest#getPathInfo()
+ * @see jakarta.servlet.http.HttpServletRequest#getPathInfo()
* @return the request path info
*/
String getPathInfo();
/**
* Request URI should include the query string if available.
- * @see javax.servlet.http.HttpServletRequest#getRequestURI()
+ * @see jakarta.servlet.http.HttpServletRequest#getRequestURI()
* @return the request URI
*/
String getRequestURI();
/**
- * @see javax.servlet.http.HttpServletRequest#getAttributeNames()
+ * @see jakarta.servlet.http.HttpServletRequest#getAttributeNames()
* @return an enumeration of all attribute names
*/
Enumeration getAttributeNames();
/**
- * @see javax.servlet.http.HttpServletRequest#getAttribute(String)
+ * @see jakarta.servlet.http.HttpServletRequest#getAttribute(String)
* @param key the attribute key
* @return the attribute value
*/
Object getAttribute(String key);
/**
- * @see javax.servlet.http.HttpServletRequest#setAttribute(String, Object)
+ * @see jakarta.servlet.http.HttpServletRequest#setAttribute(String, Object)
* @param key the key
* @param value the value
*/
void setAttribute(String key, Object value);
/**
- * @see javax.servlet.http.HttpServletRequest#getHeaderNames()
+ * @see jakarta.servlet.http.HttpServletRequest#getHeaderNames()
* @return an enumeration of all header names
*/
Enumeration getHeaderNames();
/**
- * @see javax.servlet.http.HttpServletRequest#getHeader(String)
+ * @see jakarta.servlet.http.HttpServletRequest#getHeader(String)
* @param name the header name
* @return the header value
*/
String getHeader(String name);
/**
- * @see javax.servlet.http.HttpServletRequest#getScheme()
+ * @see jakarta.servlet.http.HttpServletRequest#getScheme()
* @return the request scheme
*/
String getScheme();
/**
- * @see javax.servlet.http.HttpServletRequest#getContentType()
+ * @see jakarta.servlet.http.HttpServletRequest#getContentType()
* @return the content type
*/
String getContentType();
/**
- * @see javax.servlet.http.HttpServletRequest#getContentLength()
+ * @see jakarta.servlet.http.HttpServletRequest#getContentLength()
* @return the content length
*/
int getContentLength();
/**
- * @see javax.servlet.http.HttpServletRequest#getMethod()
+ * @see jakarta.servlet.http.HttpServletRequest#getMethod()
* @return the request method
*/
String getMethod();
/**
- * @see javax.servlet.http.HttpServletRequest#getQueryString()
+ * @see jakarta.servlet.http.HttpServletRequest#getQueryString()
* @return the query string
*/
String getQueryString();
/**
- * @see javax.servlet.http.HttpServletRequest#getServerName()
+ * @see jakarta.servlet.http.HttpServletRequest#getServerName()
* @return the server name
*/
String getServerName();
/**
- * @see javax.servlet.http.HttpServletRequest#getServerPort()
+ * @see jakarta.servlet.http.HttpServletRequest#getServerPort()
* @return the server port
*/
int getServerPort();
/**
- * @see javax.servlet.ServletRequest#getRemoteHost()
+ * @see jakarta.servlet.ServletRequest#getRemoteHost()
* @return the remote host
*/
String getRemoteHost();
/**
- * @see javax.servlet.ServletRequest#getRemoteAddr()
+ * @see jakarta.servlet.ServletRequest#getRemoteAddr()
* @return the remote address
*/
String getRemoteAddr();
/**
- * @see javax.servlet.http.HttpServletRequest#getRemoteUser()
+ * @see jakarta.servlet.http.HttpServletRequest#getRemoteUser()
* @return the remote user
*/
String getRemoteUser();
diff --git a/src/main/java/org/jruby/rack/RackFilter.java b/src/main/java/org/jruby/rack/RackFilter.java
index b4494cac..1df1f910 100644
--- a/src/main/java/org/jruby/rack/RackFilter.java
+++ b/src/main/java/org/jruby/rack/RackFilter.java
@@ -9,13 +9,13 @@
import java.io.IOException;
import java.net.MalformedURLException;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
import org.jruby.rack.servlet.RequestCapture;
import org.jruby.rack.servlet.ResponseCapture;
import org.jruby.rack.servlet.ServletRackContext;
diff --git a/src/main/java/org/jruby/rack/RackResponseEnvironment.java b/src/main/java/org/jruby/rack/RackResponseEnvironment.java
index 48da1b58..57eb093a 100644
--- a/src/main/java/org/jruby/rack/RackResponseEnvironment.java
+++ b/src/main/java/org/jruby/rack/RackResponseEnvironment.java
@@ -16,8 +16,8 @@
* handle and return the Rack response) interface.
* It is likely to be (only) implemented as a HTTP servlet response.
*
- * @see javax.servlet.ServletResponse
- * @see javax.servlet.http.HttpServletResponse
+ * @see jakarta.servlet.ServletResponse
+ * @see jakarta.servlet.http.HttpServletResponse
* @see RackResponse
*
* @author nicksieger
@@ -26,30 +26,30 @@ public interface RackResponseEnvironment {
/**
* @return whether the underlying response has been committed.
- * @see javax.servlet.ServletResponse#isCommitted()
+ * @see jakarta.servlet.ServletResponse#isCommitted()
*/
boolean isCommitted();
/**
* Reset the response (buffer) so we can begin a new response.
- * @see javax.servlet.ServletResponse#reset()
+ * @see jakarta.servlet.ServletResponse#reset()
*/
void reset();
/**
- * @see javax.servlet.ServletResponse#setContentType(String)
+ * @see jakarta.servlet.ServletResponse#setContentType(String)
* @param type the content type
*/
void setContentType(String type) ;
/**
- * @see javax.servlet.ServletResponse#setContentLength(int)
+ * @see jakarta.servlet.ServletResponse#setContentLength(int)
* @param length the content length
*/
void setContentLength(int length) ;
/**
- * @see javax.servlet.ServletResponse#setCharacterEncoding(String)
+ * @see jakarta.servlet.ServletResponse#setCharacterEncoding(String)
* @param charset the charset
*/
void setCharacterEncoding(String charset) ;
@@ -107,14 +107,14 @@ public interface RackResponseEnvironment {
void sendError(int code) throws IOException ;
/**
- * @see javax.servlet.ServletResponse#getOutputStream()
+ * @see jakarta.servlet.ServletResponse#getOutputStream()
* @return the output stream
* @throws IOException if there's an IO exception
*/
OutputStream getOutputStream() throws IOException ;
/**
- * @see javax.servlet.ServletResponse#getWriter()
+ * @see jakarta.servlet.ServletResponse#getWriter()
* @return the writer
* @throws IOException if there's an IO exception
*/
diff --git a/src/main/java/org/jruby/rack/RackServlet.java b/src/main/java/org/jruby/rack/RackServlet.java
index e5bf4944..a67c78ff 100644
--- a/src/main/java/org/jruby/rack/RackServlet.java
+++ b/src/main/java/org/jruby/rack/RackServlet.java
@@ -7,7 +7,7 @@
package org.jruby.rack;
-import javax.servlet.ServletConfig;
+import jakarta.servlet.ServletConfig;
@SuppressWarnings("serial")
public class RackServlet extends AbstractServlet {
diff --git a/src/main/java/org/jruby/rack/RackServletContextListener.java b/src/main/java/org/jruby/rack/RackServletContextListener.java
index 2fee5092..df470f5d 100644
--- a/src/main/java/org/jruby/rack/RackServletContextListener.java
+++ b/src/main/java/org/jruby/rack/RackServletContextListener.java
@@ -7,10 +7,9 @@
package org.jruby.rack;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletContextEvent;
+import jakarta.servlet.ServletContextListener;
import org.jruby.rack.servlet.DefaultServletRackContext;
import org.jruby.rack.servlet.ServletRackConfig;
import org.jruby.rack.servlet.ServletRackContext;
diff --git a/src/main/java/org/jruby/rack/RackTag.java b/src/main/java/org/jruby/rack/RackTag.java
index 73b4d8e1..30ae25a1 100644
--- a/src/main/java/org/jruby/rack/RackTag.java
+++ b/src/main/java/org/jruby/rack/RackTag.java
@@ -7,12 +7,11 @@
package org.jruby.rack;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.jsp.JspException;
-import javax.servlet.jsp.tagext.TagSupport;
-
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.jsp.JspException;
+import jakarta.servlet.jsp.tagext.TagSupport;
import org.jruby.rack.servlet.ServletRackEnvironment;
@SuppressWarnings("serial")
diff --git a/src/main/java/org/jruby/rack/UnmappedRackFilter.java b/src/main/java/org/jruby/rack/UnmappedRackFilter.java
index da64da12..b2f9f4bb 100644
--- a/src/main/java/org/jruby/rack/UnmappedRackFilter.java
+++ b/src/main/java/org/jruby/rack/UnmappedRackFilter.java
@@ -14,11 +14,11 @@
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletResponse;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletResponse;
import org.jruby.rack.servlet.RequestCapture;
import org.jruby.rack.servlet.ResponseCapture;
diff --git a/src/main/java/org/jruby/rack/embed/Servlet.java b/src/main/java/org/jruby/rack/embed/Servlet.java
index 69974537..1f0ad34c 100644
--- a/src/main/java/org/jruby/rack/embed/Servlet.java
+++ b/src/main/java/org/jruby/rack/embed/Servlet.java
@@ -7,8 +7,7 @@
*/
package org.jruby.rack.embed;
-import javax.servlet.ServletConfig;
-
+import jakarta.servlet.ServletConfig;
import org.jruby.rack.AbstractServlet;
import org.jruby.rack.RackContext;
import org.jruby.rack.RackDispatcher;
diff --git a/src/main/java/org/jruby/rack/ext/Logger.java b/src/main/java/org/jruby/rack/ext/Logger.java
index 6d63e90c..a96e570c 100644
--- a/src/main/java/org/jruby/rack/ext/Logger.java
+++ b/src/main/java/org/jruby/rack/ext/Logger.java
@@ -23,8 +23,7 @@
*/
package org.jruby.rack.ext;
-import javax.servlet.ServletContext;
-
+import jakarta.servlet.ServletContext;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyException;
diff --git a/src/main/java/org/jruby/rack/logging/CommonsLoggingLogger.java b/src/main/java/org/jruby/rack/logging/CommonsLoggingLogger.java
index 458aecc2..c7299e8c 100644
--- a/src/main/java/org/jruby/rack/logging/CommonsLoggingLogger.java
+++ b/src/main/java/org/jruby/rack/logging/CommonsLoggingLogger.java
@@ -33,7 +33,7 @@ public void setLoggerName(String loggerName) {
@Override
public boolean isEnabled(Level level) {
- if ( level == null ) return logger.isInfoEnabled(); // TODO ???!
+ if ( level == null ) return logger.isInfoEnabled();
switch ( level ) {
case DEBUG: return logger.isDebugEnabled();
case INFO: return logger.isInfoEnabled();
diff --git a/src/main/java/org/jruby/rack/logging/ServletContextLogger.java b/src/main/java/org/jruby/rack/logging/ServletContextLogger.java
index 9ea6a3bc..3c02b668 100644
--- a/src/main/java/org/jruby/rack/logging/ServletContextLogger.java
+++ b/src/main/java/org/jruby/rack/logging/ServletContextLogger.java
@@ -7,8 +7,7 @@
*/
package org.jruby.rack.logging;
-import javax.servlet.ServletContext;
-
+import jakarta.servlet.ServletContext;
import org.jruby.rack.RackLogger;
public class ServletContextLogger extends RackLogger.Base {
diff --git a/src/main/java/org/jruby/rack/servlet/DefaultServletRackContext.java b/src/main/java/org/jruby/rack/servlet/DefaultServletRackContext.java
index e1077566..1053cd89 100644
--- a/src/main/java/org/jruby/rack/servlet/DefaultServletRackContext.java
+++ b/src/main/java/org/jruby/rack/servlet/DefaultServletRackContext.java
@@ -14,17 +14,17 @@
import java.util.EventListener;
import java.util.Map;
import java.util.Set;
-import javax.servlet.Filter;
-import javax.servlet.FilterRegistration;
-import javax.servlet.RequestDispatcher;
-import javax.servlet.Servlet;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRegistration;
-import javax.servlet.SessionCookieConfig;
-import javax.servlet.SessionTrackingMode;
-import javax.servlet.descriptor.JspConfigDescriptor;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterRegistration;
+import jakarta.servlet.RequestDispatcher;
+import jakarta.servlet.Servlet;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRegistration;
+import jakarta.servlet.SessionCookieConfig;
+import jakarta.servlet.SessionTrackingMode;
+import jakarta.servlet.descriptor.JspConfigDescriptor;
import org.jruby.rack.RackApplicationFactory;
import org.jruby.rack.RackConfig;
import org.jruby.rack.RackLogger;
@@ -198,13 +198,12 @@ public String getServletContextName() {
return context.getServletContextName();
}
+ // RackLogger
@Override @Deprecated
public void log(Exception e, String msg) {
logger.log(msg, e);
}
- // RackLogger
-
@Override
public boolean isEnabled(Level level) {
return logger.isEnabled(level);
diff --git a/src/main/java/org/jruby/rack/servlet/HttpUtils.java b/src/main/java/org/jruby/rack/servlet/HttpUtils.java
new file mode 100644
index 00000000..edd7ad71
--- /dev/null
+++ b/src/main/java/org/jruby/rack/servlet/HttpUtils.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 1997-2018 Oracle and/or its affiliates and others.
+ * All rights reserved.
+ * Copyright 2004 The Apache Software Foundation
+ *
+ * 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.
+ */
+
+package org.jruby.rack.servlet;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * @deprecated As of Java(tm) Servlet API 2.3. These methods were only useful with the default encoding and have been
+ * moved to the request interfaces.
+ * @implNote Propagated to jruby-rack from old Javax Servlet API <= 4.0.
+ *
+ */
+@Deprecated
+public class HttpUtils {
+
+ private HttpUtils() {}
+
+ /**
+ * Parses a query string passed from the client to the server and builds a HashMap object with
+ * key-value pairs. The query string should be in the form of a string packaged by the GET or POST method, that is,
+ * it should have key-value pairs in the form key=value, with each pair separated from the next by a &
+ * character.
+ *
+ *
+ * A key can appear more than once in the query string with different values. However, the key appears only once in
+ * the HashMap, with its value being an array of strings containing the multiple values sent by the query string.
+ *
+ *
+ * The keys and values in the HashMap are stored in their decoded form, so any + characters are converted to
+ * spaces, and characters sent in hexadecimal notation (like %xx) are converted to ASCII characters.
+ *
+ * @param s a string containing the query to be parsed
+ *
+ * @return a Map object built from the parsed key-value pairs
+ *
+ * @exception IllegalArgumentException if the query string is invalid
+ */
+ public static Map parseQueryString(String s) {
+
+ String valArray[];
+
+ if (s == null) {
+ throw new IllegalArgumentException();
+ }
+
+ Map ht = new HashMap<>();
+ StringBuilder sb = new StringBuilder();
+ StringTokenizer st = new StringTokenizer(s, "&");
+ while (st.hasMoreTokens()) {
+ String pair = st.nextToken();
+ int pos = pair.indexOf('=');
+ if (pos == -1) {
+ // XXX
+ // should give more detail about the illegal argument
+ throw new IllegalArgumentException();
+ }
+ String key = parseName(pair.substring(0, pos), sb);
+ String val = parseName(pair.substring(pos + 1), sb);
+ if (ht.containsKey(key)) {
+ String oldVals[] = ht.get(key);
+ valArray = new String[oldVals.length + 1];
+ System.arraycopy(oldVals, 0, valArray, 0, oldVals.length);
+ valArray[oldVals.length] = val;
+ } else {
+ valArray = new String[1];
+ valArray[0] = val;
+ }
+ ht.put(key, valArray);
+ }
+
+ return ht;
+ }
+
+ /*
+ * Parse a name in the query string.
+ */
+ private static String parseName(String s, StringBuilder sb) {
+ sb.setLength(0);
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '+':
+ sb.append(' ');
+ break;
+ case '%':
+ try {
+ sb.append((char) Integer.parseInt(s.substring(i + 1, i + 3), 16));
+ i += 2;
+ } catch (NumberFormatException e) {
+ // XXX
+ // need to be more specific about illegal arg
+ throw new IllegalArgumentException();
+ } catch (StringIndexOutOfBoundsException e) {
+ String rest = s.substring(i);
+ sb.append(rest);
+ if (rest.length() == 2)
+ i++;
+ }
+
+ break;
+ default:
+ sb.append(c);
+ break;
+ }
+ }
+
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/org/jruby/rack/servlet/RequestCapture.java b/src/main/java/org/jruby/rack/servlet/RequestCapture.java
index 598c36f2..816ed203 100644
--- a/src/main/java/org/jruby/rack/servlet/RequestCapture.java
+++ b/src/main/java/org/jruby/rack/servlet/RequestCapture.java
@@ -16,9 +16,10 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
-import javax.servlet.ServletInputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
+
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
/**
* Request wrapper passed to filter chain.
diff --git a/src/main/java/org/jruby/rack/servlet/ResponseCapture.java b/src/main/java/org/jruby/rack/servlet/ResponseCapture.java
index 73b3efb6..7224d5d3 100644
--- a/src/main/java/org/jruby/rack/servlet/ResponseCapture.java
+++ b/src/main/java/org/jruby/rack/servlet/ResponseCapture.java
@@ -12,11 +12,12 @@
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Collections;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.WriteListener;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
/**
* Response wrapper passed to filter chain.
@@ -80,15 +81,6 @@ public void setStatus(int status) {
}
}
- @SuppressWarnings("deprecation")
- @Override
- @Deprecated
- public void setStatus(int status, String message) {
- if ( handleStatus(status, false) ) {
- super.setStatus(status, message);
- }
- }
-
@Override
public void sendError(int status) throws IOException {
if ( handleStatus(status, true) ) {
diff --git a/src/main/java/org/jruby/rack/servlet/RewindableInputStream.java b/src/main/java/org/jruby/rack/servlet/RewindableInputStream.java
index c20aa38d..7367b7b8 100644
--- a/src/main/java/org/jruby/rack/servlet/RewindableInputStream.java
+++ b/src/main/java/org/jruby/rack/servlet/RewindableInputStream.java
@@ -12,8 +12,9 @@
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
-import javax.servlet.ReadListener;
-import javax.servlet.ServletInputStream;
+
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
/**
* Originally inspired by Kirk's RewindableInputStream ...
diff --git a/src/main/java/org/jruby/rack/servlet/ServletRackConfig.java b/src/main/java/org/jruby/rack/servlet/ServletRackConfig.java
index 3fa00530..87a3e377 100644
--- a/src/main/java/org/jruby/rack/servlet/ServletRackConfig.java
+++ b/src/main/java/org/jruby/rack/servlet/ServletRackConfig.java
@@ -7,8 +7,7 @@
package org.jruby.rack.servlet;
-import javax.servlet.ServletContext;
-
+import jakarta.servlet.ServletContext;
import org.jruby.rack.DefaultRackConfig;
import org.jruby.rack.RackLogger;
import org.jruby.rack.logging.ServletContextLogger;
diff --git a/src/main/java/org/jruby/rack/servlet/ServletRackContext.java b/src/main/java/org/jruby/rack/servlet/ServletRackContext.java
index bb67a335..f9a39d28 100644
--- a/src/main/java/org/jruby/rack/servlet/ServletRackContext.java
+++ b/src/main/java/org/jruby/rack/servlet/ServletRackContext.java
@@ -7,8 +7,7 @@
package org.jruby.rack.servlet;
-import javax.servlet.ServletContext;
-
+import jakarta.servlet.ServletContext;
import org.jruby.rack.RackApplicationFactory;
import org.jruby.rack.RackContext;
diff --git a/src/main/java/org/jruby/rack/servlet/ServletRackEnvironment.java b/src/main/java/org/jruby/rack/servlet/ServletRackEnvironment.java
index 7725a80c..86a2ea45 100644
--- a/src/main/java/org/jruby/rack/servlet/ServletRackEnvironment.java
+++ b/src/main/java/org/jruby/rack/servlet/ServletRackEnvironment.java
@@ -8,11 +8,11 @@
package org.jruby.rack.servlet;
import java.io.IOException;
-import javax.servlet.ServletInputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.http.HttpServletResponse;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import jakarta.servlet.http.HttpServletResponse;
import org.jruby.rack.RackContext;
import org.jruby.rack.RackEnvironment;
import org.jruby.rack.ext.Input;
diff --git a/src/main/java/org/jruby/rack/servlet/ServletRackIncludedResponse.java b/src/main/java/org/jruby/rack/servlet/ServletRackIncludedResponse.java
index 545de948..b53cc67c 100644
--- a/src/main/java/org/jruby/rack/servlet/ServletRackIncludedResponse.java
+++ b/src/main/java/org/jruby/rack/servlet/ServletRackIncludedResponse.java
@@ -13,11 +13,12 @@
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.ServletResponse;
-import javax.servlet.WriteListener;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
/**
* Response wrapper used to buffer the output of a server-side include.
diff --git a/src/main/java/org/jruby/rack/servlet/ServletRackResponseEnvironment.java b/src/main/java/org/jruby/rack/servlet/ServletRackResponseEnvironment.java
index a071d7f3..e24a880a 100644
--- a/src/main/java/org/jruby/rack/servlet/ServletRackResponseEnvironment.java
+++ b/src/main/java/org/jruby/rack/servlet/ServletRackResponseEnvironment.java
@@ -8,9 +8,9 @@
package org.jruby.rack.servlet;
import java.io.IOException;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
import org.jruby.rack.DefaultErrorApplication;
import org.jruby.rack.RackResponse;
import org.jruby.rack.RackResponseEnvironment;
diff --git a/src/main/ruby/jruby/rack/servlet_ext.rb b/src/main/ruby/jruby/rack/servlet_ext.rb
index 4df3b62e..bb1e2e41 100644
--- a/src/main/ruby/jruby/rack/servlet_ext.rb
+++ b/src/main/ruby/jruby/rack/servlet_ext.rb
@@ -9,7 +9,7 @@
# Ruby-friendly extensions to the Servlet API.
-module Java::JavaxServlet::ServletContext
+module Java::JakartaServlet::ServletContext
# Fetch an attribute from the servlet context.
def [](key)
getAttribute(key.to_s)
@@ -38,7 +38,7 @@ def each
end
end
-module Java::JavaxServlet::ServletRequest
+module Java::JakartaServlet::ServletRequest
# Fetch an attribute from the servlet request.
def [](key)
getAttribute(key.to_s)
@@ -67,7 +67,7 @@ def each
end
end
-module Java::JavaxServletHttp::HttpSession
+module Java::JakartaServletHttp::HttpSession
# Fetch an attribute from the session.
def [](key)
getAttribute(key.to_s)
diff --git a/src/main/ruby/jruby/rack/version.rb b/src/main/ruby/jruby/rack/version.rb
index d77aa9b6..f3f34c54 100644
--- a/src/main/ruby/jruby/rack/version.rb
+++ b/src/main/ruby/jruby/rack/version.rb
@@ -8,6 +8,6 @@
module JRuby
module Rack
- VERSION = '1.3.0.SNAPSHOT'
+ VERSION = '2.0.0.SNAPSHOT'
end
end
diff --git a/src/main/ruby/rack/handler/servlet/default_env.rb b/src/main/ruby/rack/handler/servlet/default_env.rb
index bf6786be..0ecd202e 100644
--- a/src/main/ruby/rack/handler/servlet/default_env.rb
+++ b/src/main/ruby/rack/handler/servlet/default_env.rb
@@ -231,14 +231,14 @@ def load_variable(env, key)
def load_builtin(env, key)
case key
- when 'rack.version' then env[key] = ::Rack::VERSION
- when 'rack.multithread' then env[key] = true
- when 'rack.multiprocess' then env[key] = false
- when 'rack.run_once' then env[key] = false
- when 'rack.hijack?' then env[key] = false
- when 'rack.input' then
+ when 'rack.version' then env[key] = ::Rack::VERSION
+ when 'rack.multithread' then env[key] = true
+ when 'rack.multiprocess' then env[key] = false
+ when 'rack.run_once' then env[key] = false
+ when 'rack.hijack?' then env[key] = false
+ when 'rack.input' then
env[key] = @servlet_env ? JRuby::Rack::Input.new(@servlet_env) : nil
- when 'rack.errors' then context = rack_context
+ when 'rack.errors' then context = rack_context
env[key] = context ? JRuby::Rack::ServletLog.new(context) : nil
when 'rack.url_scheme'
env[key] = scheme = @servlet_env ? @servlet_env.getScheme : nil
@@ -246,9 +246,9 @@ def load_builtin(env, key)
scheme
when 'java.servlet_request' then env[key] = servlet_request
when 'java.servlet_response' then env[key] = servlet_response
- when 'java.servlet_context' then env[key] = servlet_context
- when 'jruby.rack.context' then env[key] = rack_context
- when 'jruby.rack.version' then env[key] = JRuby::Rack::VERSION
+ when 'java.servlet_context' then env[key] = @servlet_env.servlet_context
+ when 'jruby.rack.context' then env[key] = rack_context
+ when 'jruby.rack.version' then env[key] = JRuby::Rack::VERSION
else
nil
end
@@ -274,20 +274,6 @@ def servlet_response
@servlet_env.respond_to?(:response) ? @servlet_env.response : @servlet_env
end
- def servlet_context
- if @servlet_env.respond_to?(:servlet_context) # @since Servlet 3.0
- @servlet_env.servlet_context # ServletRequest#getServletContext()
- else
- if @servlet_env.respond_to?(:context) &&
- @servlet_env.context.is_a?(javax.servlet.ServletContext)
- @servlet_env.context
- else
- JRuby::Rack.context ||
- ( servlet_request ? servlet_request.servlet_context : nil )
- end
- end
- end
-
TRANSIENT_KEYS = [ 'rack.input', 'rack.errors',
'java.servlet_request', 'java.servlet_response',
'java.servlet_context', 'jruby.rack.context'
diff --git a/src/main/ruby/rack/handler/servlet/servlet_env.rb b/src/main/ruby/rack/handler/servlet/servlet_env.rb
index efc469d4..0a605b03 100644
--- a/src/main/ruby/rack/handler/servlet/servlet_env.rb
+++ b/src/main/ruby/rack/handler/servlet/servlet_env.rb
@@ -58,7 +58,7 @@ def load_env_key(env, key)
end
# Load parameters into the (Rack) env from the Servlet API.
- # using javax.servlet.http.HttpServletRequest#getParameterMap
+ # using Java::JakartaServletHttp::HttpServletRequest#getParameterMap
def load_parameters
get_only = ! POST_PARAM_METHODS.include?( @servlet_env.getMethod )
# we only need to really do this for POSTs but we'll handle all
@@ -153,7 +153,7 @@ def store_parameter(key, val, hash)
COOKIE_HASH = "rack.request.cookie_hash".freeze
# Load cookies into the (Rack) env from the Servlet API.
- # using javax.servlet.http.HttpServletRequest#getCookies
+ # using Java::JakartaServletHttp::HttpServletRequest#getCookies
def load_cookies
cookie_hash = {}
(@servlet_env.getCookies || []).each do |cookie|
@@ -184,7 +184,7 @@ def query_values(key)
end
def parse_query_string
- Java::JavaxServletHttp::HttpUtils.parseQueryString(query_string)
+ Java::OrgJrubyRackServlet::HttpUtils.parseQueryString(query_string)
end
def mark_parameter_error(msg)
diff --git a/src/spec/java/org/jruby/rack/fake/FakeJspWriter.java b/src/spec/java/org/jruby/rack/fake/FakeJspWriter.java
index d35f9518..a9f8e960 100644
--- a/src/spec/java/org/jruby/rack/fake/FakeJspWriter.java
+++ b/src/spec/java/org/jruby/rack/fake/FakeJspWriter.java
@@ -8,7 +8,8 @@
package org.jruby.rack.fake;
import java.io.IOException;
-import javax.servlet.jsp.JspWriter;
+
+import jakarta.servlet.jsp.JspWriter;
/**
* Currently only used as a mock for testing.
diff --git a/src/spec/java/org/jruby/rack/fake/FakePageContext.java b/src/spec/java/org/jruby/rack/fake/FakePageContext.java
index 65c08749..a2425d50 100644
--- a/src/spec/java/org/jruby/rack/fake/FakePageContext.java
+++ b/src/spec/java/org/jruby/rack/fake/FakePageContext.java
@@ -9,30 +9,30 @@
import java.io.IOException;
import java.util.Enumeration;
-import javax.el.ELContext;
-import javax.servlet.Servlet;
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import javax.servlet.jsp.JspWriter;
-import javax.servlet.jsp.PageContext;
-import javax.servlet.jsp.el.ExpressionEvaluator;
-import javax.servlet.jsp.el.VariableResolver;
+
+import jakarta.el.ELContext;
+import jakarta.servlet.Servlet;
+import jakarta.servlet.ServletConfig;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+import jakarta.servlet.jsp.JspWriter;
+import jakarta.servlet.jsp.PageContext;
+import jakarta.servlet.jsp.el.ExpressionEvaluator;
+import jakarta.servlet.jsp.el.VariableResolver;
/**
* Currently only used as a mock for testing.
*/
-@SuppressWarnings("deprecation")
public class FakePageContext extends PageContext {
- private ServletContext context;
- private ServletRequest request;
- private ServletResponse response;
- private JspWriter out;
+ private final ServletContext context;
+ private final ServletRequest request;
+ private final ServletResponse response;
+ private final JspWriter out;
public FakePageContext(ServletContext context, HttpServletRequest request, HttpServletResponse response, JspWriter out) {
this.context = context;
@@ -160,12 +160,12 @@ public void include(String arg0, boolean arg1) throws ServletException, IOExcept
throw new UnsupportedOperationException("Not supported yet.");
}
- @Override
+ @Override @Deprecated
public ExpressionEvaluator getExpressionEvaluator() {
throw new UnsupportedOperationException("Not supported yet.");
}
- @Override
+ @Override @Deprecated
public VariableResolver getVariableResolver() {
throw new UnsupportedOperationException("Not supported yet.");
}
diff --git a/src/spec/java/org/jruby/rack/mock/RackLoggingMockServletContext.java b/src/spec/java/org/jruby/rack/mock/RackLoggingMockServletContext.java
index b937c51b..46a0cff7 100644
--- a/src/spec/java/org/jruby/rack/mock/RackLoggingMockServletContext.java
+++ b/src/spec/java/org/jruby/rack/mock/RackLoggingMockServletContext.java
@@ -53,12 +53,6 @@ public void log(String message) {
logger.log(message);
}
- @Override
- @SuppressWarnings("deprecation")
- public void log(Exception ex, String message) {
- logger.log(message, ex);
- }
-
@Override
public void log(String message, Throwable ex) {
logger.log(message, ex);
diff --git a/src/spec/java/org/jruby/rack/mock/fail/FailingHttpServletResponse.java b/src/spec/java/org/jruby/rack/mock/fail/FailingHttpServletResponse.java
index 64986d01..4c4ce08d 100644
--- a/src/spec/java/org/jruby/rack/mock/fail/FailingHttpServletResponse.java
+++ b/src/spec/java/org/jruby/rack/mock/fail/FailingHttpServletResponse.java
@@ -24,8 +24,8 @@
package org.jruby.rack.mock.fail;
import java.io.IOException;
-import javax.servlet.ServletOutputStream;
+import jakarta.servlet.ServletOutputStream;
import org.springframework.mock.web.MockHttpServletResponse;
/**
diff --git a/src/spec/java/org/springframework/mock/web/DelegatingServletInputStream.java b/src/spec/java/org/springframework/mock/web/DelegatingServletInputStream.java
new file mode 100644
index 00000000..6b5ffb9b
--- /dev/null
+++ b/src/spec/java/org/springframework/mock/web/DelegatingServletInputStream.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2002-2018 the original author or 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
+ *
+ * https://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.springframework.mock.web;
+
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import org.springframework.util.Assert;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Delegating implementation of {@link jakarta.servlet.ServletInputStream}.
+ *
+ *
Used by {@link MockHttpServletRequest}; typically not directly
+ * used for testing application controllers.
+ *
+ * @author Juergen Hoeller
+ * @since 1.0.2
+ * @see MockHttpServletRequest
+ * @implNote Source copied into jruby-rack from Spring Test 5.3.39 and minimally changed to support Jakarta Servlet API 5,
+ * while still compiling to Java 8, which is not supported by Spring's support in 6.0+ (targets only Java 17+).
+ */
+public class DelegatingServletInputStream extends ServletInputStream {
+
+ private final InputStream sourceStream;
+
+ private boolean finished = false;
+
+
+ /**
+ * Create a DelegatingServletInputStream for the given source stream.
+ * @param sourceStream the source stream (never {@code null})
+ */
+ public DelegatingServletInputStream(InputStream sourceStream) {
+ Assert.notNull(sourceStream, "Source InputStream must not be null");
+ this.sourceStream = sourceStream;
+ }
+
+ /**
+ * Return the underlying source stream (never {@code null}).
+ */
+ public final InputStream getSourceStream() {
+ return this.sourceStream;
+ }
+
+
+ @Override
+ public int read() throws IOException {
+ int data = this.sourceStream.read();
+ if (data == -1) {
+ this.finished = true;
+ }
+ return data;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return this.sourceStream.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ this.sourceStream.close();
+ }
+
+ @Override
+ public boolean isFinished() {
+ return this.finished;
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/spec/java/org/springframework/mock/web/DelegatingServletOutputStream.java b/src/spec/java/org/springframework/mock/web/DelegatingServletOutputStream.java
new file mode 100644
index 00000000..0c0aa114
--- /dev/null
+++ b/src/spec/java/org/springframework/mock/web/DelegatingServletOutputStream.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2002-2016 the original author or 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
+ *
+ * https://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.springframework.mock.web;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import org.springframework.util.Assert;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Delegating implementation of {@link jakarta.servlet.ServletOutputStream}.
+ *
+ *
Used by {@link MockHttpServletResponse}; typically not directly
+ * used for testing application controllers.
+ *
+ * @author Juergen Hoeller
+ * @since 1.0.2
+ * @see MockHttpServletResponse
+ * @implNote Source copied into jruby-rack from Spring Test 5.3.39 and minimally changed to support Jakarta Servlet API 5,
+ * while still compiling to Java 8, which is not supported by Spring's support in 6.0+ (targets only Java 17+).
+ */
+public class DelegatingServletOutputStream extends ServletOutputStream {
+
+ private final OutputStream targetStream;
+
+
+ /**
+ * Create a DelegatingServletOutputStream for the given target stream.
+ * @param targetStream the target stream (never {@code null})
+ */
+ public DelegatingServletOutputStream(OutputStream targetStream) {
+ Assert.notNull(targetStream, "Target OutputStream must not be null");
+ this.targetStream = targetStream;
+ }
+
+ /**
+ * Return the underlying target stream (never {@code null}).
+ */
+ public final OutputStream getTargetStream() {
+ return this.targetStream;
+ }
+
+
+ @Override
+ public void write(int b) throws IOException {
+ this.targetStream.write(b);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ super.flush();
+ this.targetStream.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ this.targetStream.close();
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setWriteListener(WriteListener writeListener) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/spec/java/org/springframework/mock/web/HeaderValueHolder.java b/src/spec/java/org/springframework/mock/web/HeaderValueHolder.java
new file mode 100644
index 00000000..60751cdb
--- /dev/null
+++ b/src/spec/java/org/springframework/mock/web/HeaderValueHolder.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2021 the original author or 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
+ *
+ * https://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.springframework.mock.web;
+
+import org.springframework.lang.Nullable;
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+
+/**
+ * Internal helper class that serves as a value holder for request headers.
+ *
+ * @author Juergen Hoeller
+ * @author Rick Evans
+ * @since 2.0.1
+ * @implNote Source copied into jruby-rack from Spring Test 5.3.39 and minimally changed to support Jakarta Servlet API 5,
+ * while still compiling to Java 8, which is not supported by Spring's support in 6.0+ (targets only Java 17+).
+ */
+class HeaderValueHolder {
+
+ private final List