This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch merge/master-to-7xx-2024-04-07
in repository https://gitbox.apache.org/repos/asf/struts.git

commit e5e6145c361ec6e6c98d86f9aaeef7e4bdb14f30
Merge: 4939d3cef 0f6d5dbb2
Author: Lukasz Lenart <lukaszlen...@apache.org>
AuthorDate: Sun Apr 7 07:58:50 2024 +0200

    Merge remote-tracking branch 'origin/master' into 
merge/master-to-7xx-2024-04-07
    
    # Conflicts:
    #       pom.xml

 .asf.yaml                                          |   8 +-
 README.md                                          |   2 +-
 .../showcase/fileupload/FileUploadAction.java      |   5 +-
 .../MultipleFileUploadUsingArrayAction.java        |  82 ++++++-------
 .../MultipleFileUploadUsingListAction.java         |  88 +++++++-------
 .../providers/XmlDocConfigurationProvider.java     |   4 +-
 .../apache/struts2/action/UploadedFilesAware.java  |   3 +-
 .../org/apache/struts2/dispatcher/Dispatcher.java  |  13 ++-
 .../multipart/AbstractMultiPartRequest.java        |  11 +-
 .../multipart/JakartaMultiPartRequest.java         |   7 +-
 .../multipart/JakartaStreamMultiPartRequest.java   |   6 +-
 .../dispatcher/multipart/MultiPartRequest.java     |   2 +-
 .../multipart/MultiPartRequestWrapper.java         |   3 +-
 .../dispatcher/multipart/StrutsUploadedFile.java   |   4 +-
 .../struts2/dispatcher/multipart/UploadedFile.java |   4 +-
 .../interceptor/AbstractFileUploadInterceptor.java |   3 +-
 .../interceptor/ActionFileUploadInterceptor.java   |   7 +-
 .../struts2/interceptor/ApplicationAware.java}     |  16 +--
 .../struts2/interceptor/HttpParametersAware.java}  |  16 +--
 .../struts2/interceptor/ParameterAware.java}       |  20 ++--
 .../struts2/interceptor/PrincipalAware.java}       |  14 +--
 .../apache/struts2/interceptor/RequestAware.java}  |  23 +++-
 .../struts2/interceptor/ServletRequestAware.java}  |  16 +--
 .../struts2/interceptor/ServletResponseAware.java} |  16 +--
 .../apache/struts2/interceptor/SessionAware.java}  |  16 +--
 .../apache/struts2/util/ServletContextAware.java}  |  16 +--
 .../multipart/AbstractMultiPartRequestTest.java    |   8 +-
 .../multipart/JakartaMultiPartRequestTest.java     |   4 +-
 .../ActionFileUploadInterceptorTest.java           |  14 +--
 .../interceptor/FileUploadInterceptorTest.java     |  14 +--
 plugins/jasperreports/pom.xml                      |   2 +-
 .../portlet/interceptor/PortletContextAware.java   |  16 +--
 .../interceptor/PortletPreferencesAware.java       |  16 +--
 .../portlet/interceptor/PortletRequestAware.java   |  16 +--
 .../portlet/interceptor/PortletResponseAware.java  |  16 +--
 plugins/tiles/pom.xml                              |   3 -
 .../struts2/tiles/StrutsTilesContainerFactory.java |  28 +++--
 .../tiles/StrutsTilesContainerFactoryTest.java     | 128 +++++++++++++++++++++
 pom.xml                                            |  47 +++-----
 39 files changed, 436 insertions(+), 281 deletions(-)

diff --cc 
core/src/main/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequest.java
index 1c9148047,23d879ba4..630d2ca43
--- 
a/core/src/main/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequest.java
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequest.java
@@@ -46,10 -34,8 +46,10 @@@ import java.util.Map
   * Abstract class with some helper methods, it should be used
   * when starting development of another implementation of {@link 
MultiPartRequest}
   */
- public abstract class AbstractMultiPartRequest<T> implements MultiPartRequest 
{
+ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
  
 +    protected static final String 
STRUTS_MESSAGES_UPLOAD_ERROR_PARAMETER_TOO_LONG_KEY = 
"struts.messages.upload.error.parameter.too.long";
 +
      private static final Logger LOG = 
LogManager.getLogger(AbstractMultiPartRequest.class);
  
      /**
@@@ -98,14 -76,9 +98,14 @@@
      protected String defaultEncoding;
  
      /**
 -     * Localization to be used regarding errors.
 +     * Map between file fields and file data.
       */
-     protected Map<String, List<UploadedFile<T>>> uploadedFiles = new 
HashMap<>();
 -    protected Locale defaultLocale = Locale.ENGLISH;
++    protected Map<String, List<UploadedFile>> uploadedFiles = new HashMap<>();
 +
 +    /**
 +     * Map between non-file fields and values.
 +     */
 +    protected Map<String, List<String>> parameters = new HashMap<>();
  
      /**
       * @param bufferSize Sets the buffer size to be used.
@@@ -296,109 -169,4 +296,108 @@@
          return fileName;
      }
  
 +    protected String sanitizeNewlines(String before) {
 +        return before.replaceAll("\\R", "_");
 +    }
 +
 +    /* (non-Javadoc)
 +     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getErrors()
 +     */
 +    public List<LocalizedMessage> getErrors() {
 +        return errors;
 +    }
 +
 +    /* (non-Javadoc)
 +     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileParameterNames()
 +     */
 +    public Enumeration<String> getFileParameterNames() {
 +        return Collections.enumeration(uploadedFiles.keySet());
 +    }
 +
 +    /* (non-Javadoc)
 +     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getContentType(java.lang.String)
 +     */
 +    public String[] getContentType(String fieldName) {
 +        return uploadedFiles.getOrDefault(fieldName, 
Collections.emptyList()).stream()
 +                .map(UploadedFile::getContentType)
 +                .toArray(String[]::new);
 +    }
 +
 +    /* (non-Javadoc)
 +     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFile(java.lang.String)
 +     */
-     @SuppressWarnings("unchecked")
-     public UploadedFile<T>[] getFile(String fieldName) {
++    public UploadedFile[] getFile(String fieldName) {
 +        return uploadedFiles.getOrDefault(fieldName, Collections.emptyList())
 +                .toArray(UploadedFile[]::new);
 +    }
 +
 +    /* (non-Javadoc)
 +     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileNames(java.lang.String)
 +     */
 +    public String[] getFileNames(String fieldName) {
 +        return uploadedFiles.getOrDefault(fieldName, 
Collections.emptyList()).stream()
 +                .map(file -> getCanonicalName(file.getOriginalName()))
 +                .toArray(String[]::new);
 +    }
 +
 +    /* (non-Javadoc)
 +     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFilesystemName(java.lang.String)
 +     */
 +    public String[] getFilesystemName(String fieldName) {
 +        return uploadedFiles.getOrDefault(fieldName, 
Collections.emptyList()).stream()
 +                .map(UploadedFile::getAbsolutePath)
 +                .toArray(String[]::new);
 +    }
 +
 +    /* (non-Javadoc)
 +     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameter(java.lang.String)
 +     */
 +    public String getParameter(String name) {
 +        List<String> paramValue = parameters.getOrDefault(name, 
Collections.emptyList());
 +        if (!paramValue.isEmpty()) {
 +            return paramValue.get(0);
 +        }
 +
 +        return null;
 +    }
 +
 +    /* (non-Javadoc)
 +     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterNames()
 +     */
 +    public Enumeration<String> getParameterNames() {
 +        return Collections.enumeration(parameters.keySet());
 +    }
 +
 +    /* (non-Javadoc)
 +     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterValues(java.lang.String)
 +     */
 +    public String[] getParameterValues(String name) {
 +        return parameters.getOrDefault(name, Collections.emptyList())
 +                .toArray(String[]::new);
 +    }
 +
 +    /* (non-Javadoc)
 +     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#cleanUp()
 +     */
 +    public void cleanUp() {
 +        try {
 +            LOG.debug("Performing File Upload temporary storage cleanup.");
-             for (List<UploadedFile<T>> uploadedFileList : 
uploadedFiles.values()) {
-                 for (UploadedFile<T> uploadedFile : uploadedFileList) {
++            for (List<UploadedFile> uploadedFileList : 
uploadedFiles.values()) {
++                for (UploadedFile uploadedFile : uploadedFileList) {
 +                    if (uploadedFile.isFile()) {
 +                        LOG.debug("Deleting file: {}", 
uploadedFile.getName());
 +                        if (!uploadedFile.delete()) {
 +                            LOG.warn("There was a problem attempting to 
delete file: {}", uploadedFile.getName());
 +                        }
 +                    } else {
 +                        LOG.debug("File: {} already deleted", 
uploadedFile.getName());
 +                    }
 +                }
 +            }
 +        } finally {
 +            uploadedFiles = new HashMap<>();
 +            parameters = new HashMap<>();
 +        }
 +    }
 +
  }
diff --cc 
core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequest.java
index c71a1ee25,1c0f458a0..b3227cb64
--- 
a/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequest.java
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequest.java
@@@ -25,104 -29,344 +25,103 @@@ import org.apache.commons.fileupload2.j
  import org.apache.commons.lang3.StringUtils;
  import org.apache.logging.log4j.LogManager;
  import org.apache.logging.log4j.Logger;
 -import org.apache.struts2.dispatcher.LocalizedMessage;
  
 -import javax.servlet.http.HttpServletRequest;
--import java.io.File;
  import java.io.IOException;
 -import java.io.InputStream;
 -import java.io.UnsupportedEncodingException;
 +import java.nio.charset.Charset;
 +import java.nio.file.Path;
  import java.util.ArrayList;
 -import java.util.Collections;
 -import java.util.Enumeration;
 -import java.util.HashMap;
  import java.util.List;
 -import java.util.Map;
 -import java.util.Set;
  
  /**
 - * Multipart form data request adapter for Jakarta Commons Fileupload package.
 + * Multipart form data request adapter for Jakarta Commons FileUpload package.
   */
- public class JakartaMultiPartRequest extends AbstractMultiPartRequest<File> {
+ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
  
 -    static final Logger LOG = 
LogManager.getLogger(JakartaMultiPartRequest.class);
 +    private static final Logger LOG = 
LogManager.getLogger(JakartaMultiPartRequest.class);
  
 -    // maps parameter name -> List of FileItem objects
 -    protected Map<String, List<FileItem>> files = new HashMap<>();
 +    @Override
 +    protected void processUpload(HttpServletRequest request, String saveDir) 
throws IOException {
 +        Charset charset = readCharsetEncoding(request);
  
 -    // maps parameter name -> List of param values
 -    protected Map<String, List<String>> params = new HashMap<>();
 +        JakartaServletDiskFileUpload servletFileUpload =
 +                prepareServletFileUpload(charset, Path.of(saveDir));
  
 -    /**
 -     * Creates a new request wrapper to handle multi-part data using methods 
adapted from Jason Pell's
 -     * multipart classes (see class description).
 -     *
 -     * @param saveDir the directory to save off the file
 -     * @param request the request containing the multipart
 -     * @throws java.io.IOException is thrown if encoding fails.
 -     */
 -    public void parse(HttpServletRequest request, String saveDir) throws 
IOException {
 -        try {
 -            setLocale(request);
 -            processUpload(request, saveDir);
 -        } catch (FileUploadException e) {
 -            LOG.debug("Request exceeded size limit!", e);
 -            LocalizedMessage errorMessage;
 -            if (e instanceof FileUploadBase.SizeLimitExceededException) {
 -                FileUploadBase.SizeLimitExceededException ex = 
(FileUploadBase.SizeLimitExceededException) e;
 -                errorMessage = buildErrorMessage(e, new 
Object[]{ex.getPermittedSize(), ex.getActualSize()});
 -            } else if (e instanceof 
FileUploadBase.FileSizeLimitExceededException) {
 -                FileUploadBase.FileSizeLimitExceededException ex = 
(FileUploadBase.FileSizeLimitExceededException) e;
 -                errorMessage = buildErrorMessage(e, new 
Object[]{ex.getFileName(), ex.getPermittedSize(), ex.getActualSize()});
 -            } else if (e instanceof FileCountLimitExceededException) {
 -                FileCountLimitExceededException ex = 
(FileCountLimitExceededException) e;
 -                errorMessage = buildErrorMessage(e, new 
Object[]{ex.getLimit()});
 +        for (DiskFileItem item : servletFileUpload.parseRequest(request)) {
 +            LOG.debug(() -> "Processing a form field: " + 
sanitizeNewlines(item.getFieldName()));
 +            if (item.isFormField()) {
 +                processNormalFormField(item, charset);
              } else {
 -                errorMessage = buildErrorMessage(e, new Object[]{});
 +                LOG.debug(() -> "Processing a file: " + 
sanitizeNewlines(item.getFieldName()));
 +                processFileField(item);
              }
 -
 -            if (!errors.contains(errorMessage)) {
 -                errors.add(errorMessage);
 -            }
 -        } catch (Exception e) {
 -            LOG.debug("Unable to parse request", e);
 -            LocalizedMessage errorMessage = buildErrorMessage(e, new 
Object[]{});
 -            if (!errors.contains(errorMessage)) {
 -                errors.add(errorMessage);
 -            }
 -        }
 -    }
 -
 -    protected void processUpload(HttpServletRequest request, String saveDir) 
throws FileUploadException, UnsupportedEncodingException {
 -        if (ServletFileUpload.isMultipartContent(request)) {
 -            for (FileItem item : parseRequest(request, saveDir)) {
 -                LOG.debug("Found file item: [{}]", 
sanitizeNewlines(item.getFieldName()));
 -                if (item.isFormField()) {
 -                    processNormalFormField(item, 
request.getCharacterEncoding());
 -                } else {
 -                    processFileField(item);
 -                }
 -            }
 -        }
 -    }
 -
 -    protected void processFileField(FileItem item) {
 -        LOG.debug("Item is a file upload");
 -
 -        // Skip file uploads that don't have a file name - meaning that no 
file was selected.
 -        if (item.getName() == null || item.getName().trim().isEmpty()) {
 -            LOG.debug("No file has been uploaded for the field: {}", 
sanitizeNewlines(item.getFieldName()));
 -            return;
 -        }
 -
 -        List<FileItem> values;
 -        if (files.get(item.getFieldName()) != null) {
 -            values = files.get(item.getFieldName());
 -        } else {
 -            values = new ArrayList<>();
 -        }
 -
 -        values.add(item);
 -        files.put(item.getFieldName(), values);
 -    }
 -
 -    protected void processNormalFormField(FileItem item, String charset) 
throws UnsupportedEncodingException {
 -        try {
 -            LOG.debug("Item is a normal form field");
 -
 -            List<String> values;
 -            if (params.get(item.getFieldName()) != null) {
 -                values = params.get(item.getFieldName());
 -            } else {
 -                values = new ArrayList<>();
 -            }
 -
 -            long size = item.getSize();
 -            if (size > maxStringLength) {
 -                LOG.debug("Form field {} of size {} bytes exceeds limit of 
{}.", sanitizeNewlines(item.getFieldName()), size, maxStringLength);
 -                String errorKey = 
"struts.messages.upload.error.parameter.too.long";
 -                LocalizedMessage localizedMessage = new 
LocalizedMessage(this.getClass(), errorKey, null,
 -                        new Object[]{item.getFieldName(), maxStringLength, 
size});
 -                if (!errors.contains(localizedMessage)) {
 -                    errors.add(localizedMessage);
 -                }
 -                return;
 -            }
 -            if (size == 0) {
 -                values.add(StringUtils.EMPTY);
 -            } else if (charset == null) {
 -                values.add(item.getString()); // WW-633
 -            } else {
 -                values.add(item.getString(charset));
 -            }
 -            params.put(item.getFieldName(), values);
 -        } finally {
 -            item.delete();
          }
      }
  
 -    protected List<FileItem> parseRequest(HttpServletRequest servletRequest, 
String saveDir) throws FileUploadException {
 -        DiskFileItemFactory fac = createDiskFileItemFactory(saveDir);
 -        ServletFileUpload upload = createServletFileUpload(fac);
 -        return upload.parseRequest(createRequestContext(servletRequest));
 -    }
 +    protected JakartaServletDiskFileUpload createJakartaFileUpload(Charset 
charset, Path saveDir) {
 +        DiskFileItemFactory.Builder builder = DiskFileItemFactory.builder();
  
 -    protected ServletFileUpload createServletFileUpload(DiskFileItemFactory 
fac) {
 -        ServletFileUpload upload = new ServletFileUpload(fac);
 -        if (maxSize != null) {
 -            upload.setSizeMax(maxSize);
 -        }
 -        if (maxFiles != null) {
 -            upload.setFileCountMax(maxFiles);
 -        }
 -        if (maxFileSize != null) {
 -            upload.setFileSizeMax(maxFileSize);
 -        }
 -        return upload;
 -    }
 -
 -    protected DiskFileItemFactory createDiskFileItemFactory(String saveDir) {
 -        DiskFileItemFactory fac = new DiskFileItemFactory();
 -        // Make sure that the data is written to file, even if the file is 
empty.
 -        fac.setSizeThreshold(-1);
 -        if (saveDir != null) {
 -            fac.setRepository(new File(saveDir));
 -        }
 -        return fac;
 -    }
 +        LOG.debug("Using file save directory: {}", saveDir);
 +        builder.setPath(saveDir);
  
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileParameterNames()
 -     */
 -    public Enumeration<String> getFileParameterNames() {
 -        return Collections.enumeration(files.keySet());
 -    }
 -
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getContentType(java.lang.String)
 -     */
 -    public String[] getContentType(String fieldName) {
 -        List<FileItem> items = files.get(fieldName);
 +        LOG.debug("Sets minimal buffer size to always write file to disk");
 +        builder.setBufferSize(1);
  
 -        if (items == null) {
 -            return null;
 -        }
 -
 -        List<String> contentTypes = new ArrayList<>(items.size());
 -        for (FileItem fileItem : items) {
 -            contentTypes.add(fileItem.getContentType());
 -        }
 +        LOG.debug("Using charset: {}", charset);
 +        builder.setCharset(charset);
  
 -        return contentTypes.toArray(new String[0]);
 +        DiskFileItemFactory factory = builder.get();
 +        return new JakartaServletDiskFileUpload(factory);
      }
  
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFile(java.lang.String)
 -     */
 -    public UploadedFile[] getFile(String fieldName) {
 -        List<FileItem> items = files.get(fieldName);
 +    protected void processNormalFormField(DiskFileItem item, Charset charset) 
throws IOException {
 +        LOG.debug("Item: {} is a normal form field", item.getName());
  
 -        if (items == null) {
 -            return null;
 -        }
 -
 -        List<UploadedFile> fileList = new ArrayList<>(items.size());
 -        for (FileItem fileItem : items) {
 -            DiskFileItem diskFileItem = (DiskFileItem) fileItem;
 -            File storeLocation = diskFileItem.getStoreLocation();
 -
 -            // Ensure file exists even if it is empty.
 -            if (diskFileItem.getSize() == 0 && storeLocation != null && 
!storeLocation.exists()) {
 -                try {
 -                    storeLocation.createNewFile();
 -                } catch (IOException e) {
 -                    LOG.error("Cannot write uploaded empty file to disk: {}", 
storeLocation.getAbsolutePath(), e);
 -                }
 -            }
 -            UploadedFile uploadedFile = 
StrutsUploadedFile.Builder.create(storeLocation)
 -                .withContentType(fileItem.getContentType())
 -                .withOriginalName(fileItem.getName())
 -                .build();
 -            fileList.add(uploadedFile);
 +        List<String> values;
 +        String fieldName = item.getFieldName();
 +        if (parameters.get(fieldName) != null) {
 +            values = parameters.get(fieldName);
 +        } else {
 +            values = new ArrayList<>();
          }
  
 -        return fileList.toArray(new UploadedFile[0]);
 -    }
 -
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileNames(java.lang.String)
 -     */
 -    public String[] getFileNames(String fieldName) {
 -        List<FileItem> items = files.get(fieldName);
 -
 -        if (items == null) {
 -            return null;
 +        String fieldValue = item.getString(charset);
 +        if (exceedsMaxStringLength(fieldName, fieldValue)) {
 +            return;
          }
 -
 -        List<String> fileNames = new ArrayList<>(items.size());
 -        for (FileItem fileItem : items) {
 -            fileNames.add(getCanonicalName(fileItem.getName()));
 +        if (item.getSize() == 0) {
 +            values.add(StringUtils.EMPTY);
 +        } else {
 +            values.add(fieldValue);
          }
 -
 -        return fileNames.toArray(new String[0]);
 +        parameters.put(fieldName, values);
      }
  
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFilesystemName(java.lang.String)
 -     */
 -    public String[] getFilesystemName(String fieldName) {
 -        List<FileItem> items = files.get(fieldName);
 -
 -        if (items == null) {
 -            return null;
 -        }
 -
 -        List<String> fileNames = new ArrayList<>(items.size());
 -        for (FileItem fileItem : items) {
 -            fileNames.add(((DiskFileItem) 
fileItem).getStoreLocation().getName());
 +    protected void processFileField(DiskFileItem item) {
 +        // Skip file uploads that don't have a file name - meaning that no 
file was selected.
 +        if (item.getName() == null || item.getName().trim().isEmpty()) {
 +            LOG.debug(() -> "No file has been uploaded for the field: " + 
sanitizeNewlines(item.getFieldName()));
 +            return;
          }
  
-         List<UploadedFile<File>> values;
 -        return fileNames.toArray(new String[0]);
 -    }
 -
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameter(java.lang.String)
 -     */
 -    public String getParameter(String name) {
 -        List<String> v = params.get(name);
 -        if (v != null && !v.isEmpty()) {
 -            return v.get(0);
++        List<UploadedFile> values;
 +        if (uploadedFiles.get(item.getFieldName()) != null) {
 +            values = uploadedFiles.get(item.getFieldName());
 +        } else {
 +            values = new ArrayList<>();
          }
  
 -        return null;
 -    }
 -
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterNames()
 -     */
 -    public Enumeration<String> getParameterNames() {
 -        return Collections.enumeration(params.keySet());
 -    }
 -
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterValues(java.lang.String)
 -     */
 -    public String[] getParameterValues(String name) {
 -        List<String> v = params.get(name);
 -        if (v != null && !v.isEmpty()) {
 -            return v.toArray(new String[0]);
 +        if (item.isInMemory()) {
 +            LOG.warn("Storing uploaded files just in memory isn't supported 
currently, skipping file: {}!", item.getName());
 +        } else {
-             UploadedFile<File> uploadedFile = StrutsUploadedFile.Builder
++            UploadedFile uploadedFile = StrutsUploadedFile.Builder
 +                    .create(item.getPath().toFile())
 +                    .withOriginalName(item.getName())
 +                    .withContentType(item.getContentType())
 +                    .build();
 +            values.add(uploadedFile);
          }
  
 -        return null;
 +        uploadedFiles.put(item.getFieldName(), values);
      }
  
 -    /**
 -     * Creates a RequestContext needed by Jakarta Commons Upload.
 -     *
 -     * @param req the request.
 -     * @return a new request context.
 -     */
 -    protected RequestContext createRequestContext(final HttpServletRequest 
req) {
 -        return new RequestContext() {
 -            public String getCharacterEncoding() {
 -                return req.getCharacterEncoding();
 -            }
 -
 -            public String getContentType() {
 -                return req.getContentType();
 -            }
 -
 -            public int getContentLength() {
 -                return req.getContentLength();
 -            }
 -
 -            public InputStream getInputStream() throws IOException {
 -                InputStream in = req.getInputStream();
 -                if (in == null) {
 -                    throw new IOException("Missing content in the request");
 -                }
 -                return req.getInputStream();
 -            }
 -        };
 -    }
 -
 -    /* (non-Javadoc)
 -     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#cleanUp()
 -     */
 -    public void cleanUp() {
 -        Set<String> names = files.keySet();
 -        for (String name : names) {
 -            List<FileItem> items = files.get(name);
 -            for (FileItem item : items) {
 -                LOG.debug("Removing file {} {}", name, item);
 -                if (!item.isInMemory()) {
 -                    item.delete();
 -                }
 -            }
 -        }
 -    }
 -
 -    private String sanitizeNewlines(String before) {
 -        return before.replaceAll("[\n\r]", "_");
 -    }
  }
diff --cc 
core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaStreamMultiPartRequest.java
index c6457c605,428ae112f..df7b07e22
--- 
a/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaStreamMultiPartRequest.java
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaStreamMultiPartRequest.java
@@@ -50,105 -53,150 +50,105 @@@ import java.util.UUID
   *
   * @since 2.3.18
   */
- public class JakartaStreamMultiPartRequest extends 
AbstractMultiPartRequest<File> {
+ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
  
 -    static final Logger LOG = 
LogManager.getLogger(JakartaStreamMultiPartRequest.class);
 +    private static final Logger LOG = 
LogManager.getLogger(JakartaStreamMultiPartRequest.class);
  
      /**
 -     * Map between file fields and file data.
 -     */
 -    protected Map<String, List<FileInfo>> fileInfos = new HashMap<>();
 -
 -    /**
 -     * Map between non-file fields and values.
 -     */
 -    protected Map<String, List<String>> parameters = new HashMap<>();
 -
 -    /* (non-Javadoc)
 -     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#cleanUp()
 +     * Processes the upload.
 +     *
 +     * @param request the servlet request
 +     * @param saveDir location of the save dir
       */
 -    public void cleanUp() {
 -        LOG.debug("Performing File Upload temporary storage cleanup.");
 -        for (List<FileInfo> fileInfoList : fileInfos.values()) {
 -            for (FileInfo fileInfo : fileInfoList) {
 -                File file = fileInfo.getFile();
 -                LOG.debug("Deleting file '{}'.", file.getName());
 -                if (!file.delete()) {
 -                    LOG.warn("There was a problem attempting to delete file 
'{}'.", file.getName());
 -                }
 +    @Override
 +    protected void processUpload(HttpServletRequest request, String saveDir) 
throws IOException {
 +        Charset charset = readCharsetEncoding(request);
 +        Path location = Path.of(saveDir);
 +
 +        JakartaServletDiskFileUpload servletFileUpload =
 +                prepareServletFileUpload(charset, location);
 +
 +        LOG.debug("Using Jakarta Stream API to process request");
 +        servletFileUpload.getItemIterator(request).forEachRemaining(item -> {
 +            if (item.isFormField()) {
 +                LOG.debug(() -> "Processing a form field: " + 
sanitizeNewlines(item.getFieldName()));
 +                processFileItemAsFormField(item);
 +            } else {
 +                LOG.debug(() -> "Processing a file: " + 
sanitizeNewlines(item.getFieldName()));
 +                processFileItemAsFileField(item, location);
              }
 -        }
 +        });
      }
  
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getContentType(java.lang.String)
 -     */
 -    public String[] getContentType(String fieldName) {
 -        List<FileInfo> infos = fileInfos.get(fieldName);
 -        if (infos == null) {
 -            return null;
 -        }
 +    protected JakartaServletDiskFileUpload createJakartaFileUpload(Charset 
charset, Path location) {
 +        DiskFileItemFactory.Builder builder = DiskFileItemFactory.builder();
  
 -        List<String> types = new ArrayList<>(infos.size());
 -        for (FileInfo fileInfo : infos) {
 -            types.add(fileInfo.getContentType());
 -        }
 +        LOG.debug("Using file save directory: {}", location);
 +        builder.setPath(location);
  
 -        return types.toArray(new String[0]);
 -    }
 +        LOG.debug("Sets buffer size: {}", bufferSize);
 +        builder.setBufferSize(bufferSize);
  
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFile(java.lang.String)
 -     */
 -    public UploadedFile[] getFile(String fieldName) {
 -        List<FileInfo> infos = fileInfos.get(fieldName);
 -        if (infos == null) {
 -            return null;
 -        }
 +        LOG.debug("Using charset: {}", charset);
 +        builder.setCharset(charset);
  
 -        return infos.stream().map(fileInfo ->
 -            StrutsUploadedFile.Builder.create(fileInfo.getFile())
 -                .withContentType(fileInfo.contentType)
 -                .withOriginalName(fileInfo.originalName)
 -                .build()
 -        ).toArray(UploadedFile[]::new);
 +        DiskFileItemFactory factory = builder.get();
 +        return new JakartaServletDiskFileUpload(factory);
      }
  
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileNames(java.lang.String)
 -     */
 -    public String[] getFileNames(String fieldName) {
 -        List<FileInfo> infos = fileInfos.get(fieldName);
 -        if (infos == null) {
 -            return null;
 +    private String readStream(InputStream inputStream) throws IOException {
 +        ByteArrayOutputStream result = new ByteArrayOutputStream();
 +        byte[] buffer = new byte[1024];
 +        for (int length; (length = inputStream.read(buffer)) != -1; ) {
 +            result.write(buffer, 0, length);
          }
 -
 -        List<String> names = new ArrayList<>(infos.size());
 -        for (FileInfo fileInfo : infos) {
 -            names.add(getCanonicalName(fileInfo.getOriginalName()));
 -        }
 -
 -        return names.toArray(new String[0]);
 +        return result.toString(StandardCharsets.UTF_8);
      }
  
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileParameterNames()
 -     */
 -    public Enumeration<String> getFileParameterNames() {
 -        return Collections.enumeration(fileInfos.keySet());
 -    }
 -
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFilesystemName(java.lang.String)
 +    /**
 +     * Processes the FileItem as a normal form field.
 +     *
 +     * @param fileItemInput a form field item input
       */
 -    public String[] getFilesystemName(String fieldName) {
 -        List<FileInfo> infos = fileInfos.get(fieldName);
 -        if (infos == null) {
 -            return null;
 -        }
 +    protected void processFileItemAsFormField(FileItemInput fileItemInput) 
throws IOException {
 +        String fieldName = fileItemInput.getFieldName();
 +        String fieldValue = readStream(fileItemInput.getInputStream());
  
 -        List<String> names = new ArrayList<>(infos.size());
 -        for (FileInfo fileInfo : infos) {
 -            names.add(fileInfo.getFile().getName());
 +        if (exceedsMaxStringLength(fieldName, fieldValue)) {
 +            return;
          }
  
 -        return names.toArray(new String[0]);
 -    }
 -
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameter(java.lang.String)
 -     */
 -    public String getParameter(String name) {
 -        List<String> values = parameters.get(name);
 -        if (values != null && !values.isEmpty()) {
 -            return values.get(0);
 +        List<String> values;
 +        if (parameters.containsKey(fieldName)) {
 +            values = parameters.get(fieldName);
 +        } else {
 +            values = new ArrayList<>();
 +            parameters.put(fieldName, values);
          }
 -        return null;
 -    }
 -
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterNames()
 -     */
 -    public Enumeration<String> getParameterNames() {
 -        return Collections.enumeration(parameters.keySet());
 +        values.add(fieldValue);
      }
  
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterValues(java.lang.String)
 +    /**
 +     * @return actual size of already uploaded files
       */
 -    public String[] getParameterValues(String name) {
 -        List<String> values = parameters.get(name);
 -        if (values != null && !values.isEmpty()) {
 -            return values.toArray(new String[0]);
 -        }
 -        return null;
 +    protected Long actualSizeOfUploadedFiles() {
 +        return uploadedFiles.values().stream()
 +                .map(files -> 
files.stream().map(UploadedFile::length).reduce(0L, Long::sum))
 +                .reduce(0L, Long::sum);
      }
  
 -    /* (non-Javadoc)
 -     * @see 
org.apache.struts2.dispatcher.multipart.MultiPartRequest#parse(javax.servlet.http.HttpServletRequest,
 java.lang.String)
 -     */
 -    public void parse(HttpServletRequest request, String saveDir) throws 
IOException {
 -        try {
 -            setLocale(request);
 -            processUpload(request, saveDir);
 -        } catch (Exception e) {
 -            LOG.debug("Error occurred during parsing of multi part request", 
e);
 -            LocalizedMessage errorMessage = buildErrorMessage(e, new 
Object[]{});
 +    private boolean exceedsMaxFiles(FileItemInput fileItemInput) {
 +        if (maxFiles != null && maxFiles == uploadedFiles.size()) {
 +            if (LOG.isDebugEnabled()) {
 +                LOG.debug("Cannot accept another file: {} as it will exceed 
max files: {}",
 +                        sanitizeNewlines(fileItemInput.getName()), maxFiles);
 +            }
 +            LocalizedMessage errorMessage = buildErrorMessage(
 +                    FileUploadFileCountLimitException.class,
 +                    String.format("File %s exceeds allowed maximum number of 
files %s",
 +                            fileItemInput.getName(), maxFiles),
 +                    new Object[]{maxFiles, uploadedFiles.size()}
 +            );
              if (!errors.contains(errorMessage)) {
                  errors.add(errorMessage);
              }
@@@ -238,27 -410,76 +238,27 @@@
      }
  
      /**
 -     * Creates an internal <code>FileInfo</code> structure used to pass 
information
 -     * to the <code>FileUploadInterceptor</code> during the interceptor stack
 -     * invocation process.
 +     * Create {@link UploadedFile} abstraction over uploaded file
       *
 -     * @param itemStream file item stream
 -     * @param file       the file
 +     * @param fileItemInput file item stream
 +     * @param file          the file
       */
 -    protected void createFileInfoFromItemStream(FileItemStream itemStream, 
File file) {
 -        // gather attributes from file upload stream.
 -        String fileName = itemStream.getName();
 -        String fieldName = itemStream.getFieldName();
 -        // create internal structure
 -        FileInfo fileInfo = new FileInfo(file, itemStream.getContentType(), 
fileName);
 -        // append or create new entry.
 -        if (!fileInfos.containsKey(fieldName)) {
 -            List<FileInfo> infos = new ArrayList<>();
 -            infos.add(fileInfo);
 -            fileInfos.put(fieldName, infos);
 +    protected void createUploadedFile(FileItemInput fileItemInput, File file) 
{
 +        String fileName = fileItemInput.getName();
 +        String fieldName = fileItemInput.getFieldName();
 +
-         UploadedFile<File> uploadedFile = StrutsUploadedFile.Builder
++        UploadedFile uploadedFile = StrutsUploadedFile.Builder
 +                .create(file)
 +                .withOriginalName(fileName)
 +                .withContentType(fileItemInput.getContentType())
 +                .build();
 +
 +        if (uploadedFiles.containsKey(fieldName)) {
 +            uploadedFiles.get(fieldName).add(uploadedFile);
          } else {
-             List<UploadedFile<File>> infos = new ArrayList<>();
 -            fileInfos.get(fieldName).add(fileInfo);
 -        }
 -    }
 -
 -    /**
 -     * Internal data structure used to store a reference to information needed
 -     * to later pass post processing data to the 
<code>FileUploadInterceptor</code>.
 -     *
 -     * @since 7.0.0
 -     */
 -    public static class FileInfo implements Serializable {
 -
 -        private static final long serialVersionUID = 1083158552766906037L;
 -
 -        private final File file;
 -        private final String contentType;
 -        private final String originalName;
 -
 -        /**
 -         * Default constructor.
 -         *
 -         * @param file         the file
 -         * @param contentType  content type
 -         * @param originalName original file name
 -         */
 -        public FileInfo(File file, String contentType, String originalName) {
 -            this.file = file;
 -            this.contentType = contentType;
 -            this.originalName = originalName;
 -        }
 -
 -        /**
 -         * @return the file
 -         */
 -        public File getFile() {
 -            return file;
 -        }
 -
 -        /**
 -         * @return content type
 -         */
 -        public String getContentType() {
 -            return contentType;
 -        }
 -
 -        /**
 -         * @return original file name
 -         */
 -        public String getOriginalName() {
 -            return originalName;
++            List<UploadedFile> infos = new ArrayList<>();
 +            infos.add(uploadedFile);
 +            uploadedFiles.put(fieldName, infos);
          }
      }
  
diff --cc 
core/src/main/java/org/apache/struts2/dispatcher/multipart/MultiPartRequestWrapper.java
index 7ddadb8c5,d37eed143..532a5e45a
--- 
a/core/src/main/java/org/apache/struts2/dispatcher/multipart/MultiPartRequestWrapper.java
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/MultiPartRequestWrapper.java
@@@ -24,9 -24,7 +24,8 @@@ import org.apache.logging.log4j.Logger
  import org.apache.struts2.dispatcher.LocalizedMessage;
  import org.apache.struts2.dispatcher.StrutsRequestWrapper;
  
 -import javax.servlet.http.HttpServletRequest;
 +import jakarta.servlet.http.HttpServletRequest;
 +
- import java.io.File;
  import java.io.IOException;
  import java.util.*;
  
diff --cc 
core/src/main/java/org/apache/struts2/dispatcher/multipart/UploadedFile.java
index c48054765,ada27ff6c..ddebac122
--- 
a/core/src/main/java/org/apache/struts2/dispatcher/multipart/UploadedFile.java
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/UploadedFile.java
@@@ -21,48 -21,24 +21,48 @@@ package org.apache.struts2.dispatcher.m
  import java.io.Serializable;
  
  /**
 - * Virtual representation of a uploaded file used by {@link MultiPartRequest}
 + * Virtual representation of an uploaded file used by {@link MultiPartRequest}
   */
- public interface UploadedFile<T> extends Serializable {
+ public interface UploadedFile extends Serializable {
  
 +    /**
 +     * @return size of the content of file/stream/array
 +     */
      Long length();
  
 +    /**
 +     * @return a local name of the file
 +     */
      String getName();
  
 +    /**
 +     * @return original file name from upload source
 +     */
      String getOriginalName();
  
 +    /**
 +     * @return indicates if this is a real file or maybe just in-memory stream
 +     */
      boolean isFile();
  
 +    /**
 +     * @return removes a local copy of the uploaded file/stream
 +     */
      boolean delete();
  
 +    /**
 +     * @return an absolute path of the file if possible
 +     */
      String getAbsolutePath();
  
 +    /**
 +     * @return content of the upload file
 +     */
-     T getContent();
+     Object getContent();
  
 +    /**
 +     * @return content type of the uploaded file
 +     */
      String getContentType();
  
  }
diff --cc 
core/src/main/java/org/apache/struts2/interceptor/ActionFileUploadInterceptor.java
index 72fe043da,c42d125af..3cd0529ae
--- 
a/core/src/main/java/org/apache/struts2/interceptor/ActionFileUploadInterceptor.java
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/ActionFileUploadInterceptor.java
@@@ -28,7 -27,7 +28,6 @@@ import org.apache.struts2.action.Upload
  import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
  import org.apache.struts2.dispatcher.multipart.UploadedFile;
  
- import java.io.File;
 -import javax.servlet.http.HttpServletRequest;
  import java.util.ArrayList;
  import java.util.Enumeration;
  import java.util.List;
diff --cc core/src/main/java/org/apache/struts2/interceptor/RequestAware.java
index e24dc133d,822f9ca45..5e24e8dfb
--- a/core/src/main/java/org/apache/struts2/interceptor/RequestAware.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/RequestAware.java
@@@ -16,15 -16,26 +16,26 @@@
   * specific language governing permissions and limitations
   * under the License.
   */
- package org.apache.struts2.dispatcher.multipart;
+ package org.apache.struts2.interceptor;
  
- import java.io.File;
+ import org.apache.struts2.dispatcher.RequestMap;
  
- public class JakartaMultiPartRequestTest extends AbstractMultiPartRequestTest 
{
 -import javax.servlet.http.HttpServletRequest;
++import jakarta.servlet.http.HttpServletRequest;
+ import java.util.Map;
+ 
+ @Deprecated
+ public interface RequestAware extends ServletRequestAware {
+ 
+     @Override
+     default void setServletRequest(HttpServletRequest httpServletRequest) {
+         // default no-op
+     }
  
      @Override
-     protected AbstractMultiPartRequest<File> createMultipartRequest() {
-         return new JakartaMultiPartRequest();
+     default void withServletRequest(HttpServletRequest request) {
+         ServletRequestAware.super.withServletRequest(request);
+         setRequest(new RequestMap(request));
      }
  
- }
+     void setRequest(Map<String, Object> request);
+ }
diff --cc 
core/src/main/java/org/apache/struts2/interceptor/ServletRequestAware.java
index e24dc133d,f02ac90d1..8f059d479
--- a/core/src/main/java/org/apache/struts2/interceptor/ServletRequestAware.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/ServletRequestAware.java
@@@ -16,15 -16,17 +16,17 @@@
   * specific language governing permissions and limitations
   * under the License.
   */
- package org.apache.struts2.dispatcher.multipart;
+ package org.apache.struts2.interceptor;
  
- import java.io.File;
 -import javax.servlet.http.HttpServletRequest;
++import jakarta.servlet.http.HttpServletRequest;
  
- public class JakartaMultiPartRequestTest extends AbstractMultiPartRequestTest 
{
+ @Deprecated
+ public interface ServletRequestAware extends 
org.apache.struts2.action.ServletRequestAware {
+ 
+     void setServletRequest(HttpServletRequest httpServletRequest);
  
      @Override
-     protected AbstractMultiPartRequest<File> createMultipartRequest() {
-         return new JakartaMultiPartRequest();
+     default void withServletRequest(HttpServletRequest request) {
+         setServletRequest(request);
      }
- 
- }
+ }
diff --cc 
core/src/main/java/org/apache/struts2/interceptor/ServletResponseAware.java
index e24dc133d,d252621fe..de054878a
--- 
a/core/src/main/java/org/apache/struts2/interceptor/ServletResponseAware.java
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/ServletResponseAware.java
@@@ -16,15 -16,17 +16,17 @@@
   * specific language governing permissions and limitations
   * under the License.
   */
- package org.apache.struts2.dispatcher.multipart;
+ package org.apache.struts2.interceptor;
  
- import java.io.File;
 -import javax.servlet.http.HttpServletResponse;
++import jakarta.servlet.http.HttpServletResponse;
  
- public class JakartaMultiPartRequestTest extends AbstractMultiPartRequestTest 
{
+ @Deprecated
+ public interface ServletResponseAware extends 
org.apache.struts2.action.ServletResponseAware {
+ 
+     void setServletResponse(HttpServletResponse httpServletResponse);
  
      @Override
-     protected AbstractMultiPartRequest<File> createMultipartRequest() {
-         return new JakartaMultiPartRequest();
+     default void withServletResponse(HttpServletResponse response) {
+         setServletResponse(response);
      }
- 
- }
+ }
diff --cc core/src/main/java/org/apache/struts2/util/ServletContextAware.java
index e24dc133d,c17bd9a7e..593736643
--- a/core/src/main/java/org/apache/struts2/util/ServletContextAware.java
+++ b/core/src/main/java/org/apache/struts2/util/ServletContextAware.java
@@@ -16,15 -16,17 +16,17 @@@
   * specific language governing permissions and limitations
   * under the License.
   */
- package org.apache.struts2.dispatcher.multipart;
+ package org.apache.struts2.util;
  
- import java.io.File;
 -import javax.servlet.ServletContext;
++import jakarta.servlet.ServletContext;
  
- public class JakartaMultiPartRequestTest extends AbstractMultiPartRequestTest 
{
+ @Deprecated
+ public interface ServletContextAware extends 
org.apache.struts2.action.ServletContextAware {
+ 
+     void setServletContext(ServletContext context);
  
      @Override
-     protected AbstractMultiPartRequest<File> createMultipartRequest() {
-         return new JakartaMultiPartRequest();
+     default void withServletContext(ServletContext context) {
+         setServletContext(context);
      }
- 
- }
+ }
diff --cc 
core/src/test/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequestTest.java
index eaf7049ae,000000000..fb30bb289
mode 100644,000000..100644
--- 
a/core/src/test/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequestTest.java
+++ 
b/core/src/test/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequestTest.java
@@@ -1,496 -1,0 +1,496 @@@
 +/*
 + * 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.struts2.dispatcher.multipart;
 +
 +import 
org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletDiskFileUpload;
 +import org.apache.struts2.dispatcher.LocalizedMessage;
 +import org.assertj.core.api.InstanceOfAssertFactories;
 +import org.junit.After;
 +import org.junit.Before;
 +import org.junit.BeforeClass;
 +import org.junit.Test;
 +import org.springframework.mock.web.MockHttpServletRequest;
 +
 +import java.io.File;
 +import java.io.IOException;
 +import java.nio.charset.StandardCharsets;
 +import java.util.ArrayList;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Objects;
 +
 +import static org.assertj.core.api.Assertions.assertThat;
 +
 +abstract class AbstractMultiPartRequestTest {
 +
 +    protected static String tempDir;
 +
 +    protected MockHttpServletRequest mockRequest;
 +
 +    protected final String boundary = "_boundary_";
 +    protected final String endline = "\r\n";
 +
-     protected AbstractMultiPartRequest<File> multiPart;
++    protected AbstractMultiPartRequest multiPart;
 +
-     abstract protected AbstractMultiPartRequest<File> 
createMultipartRequest();
++    abstract protected AbstractMultiPartRequest createMultipartRequest();
 +
 +    @BeforeClass
 +    public static void beforeClass() throws IOException {
 +        File tempFile = File.createTempFile("struts", "fileupload");
 +        assertThat(tempFile.delete()).isTrue();
 +        assertThat(tempFile.mkdirs()).isTrue();
 +        tempDir = tempFile.getAbsolutePath();
 +    }
 +
 +    @Before
 +    public void before() {
 +        mockRequest = new MockHttpServletRequest();
 +        mockRequest.setCharacterEncoding(StandardCharsets.UTF_8.name());
 +        mockRequest.setMethod("post");
 +        mockRequest.setContentType("multipart/form-data; boundary=" + 
boundary);
 +
 +        multiPart = createMultipartRequest();
 +    }
 +
 +    @After
 +    public void after() {
 +        multiPart.cleanUp();
 +    }
 +
 +    @Test
 +    public void uploadedFilesToDisk() throws IOException {
 +        // given
 +        String content = formFile("file1", "test1.csv", "1,2,3,4") +
 +                formFile("file2", "test2.csv", "5,6,7,8") +
 +                endline + "--" + boundary + "--";
 +
 +        mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
 +
 +        
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
 +
 +        // when
 +        multiPart.setBufferSize("1"); // always write files into disk
 +        multiPart.parse(mockRequest, tempDir);
 +
 +        // then
 +        assertThat(multiPart.getErrors())
 +                .isEmpty();
 +
 +        
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
 +                .asList()
 +                .containsOnly("file1", "file2");
 +        assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
 +            assertThat(file.isFile())
 +                    .isTrue();
 +            assertThat(file.getOriginalName())
 +                    .isEqualTo("test1.csv");
 +            assertThat(file.getContentType())
 +                    .isEqualTo("text/csv");
 +            
assertThat(file.getContent()).asInstanceOf(InstanceOfAssertFactories.FILE)
 +                    .exists()
 +                    .content()
 +                    .isEqualTo("1,2,3,4");
 +        });
 +        assertThat(multiPart.getFile("file2")).allSatisfy(file -> {
 +            assertThat(file.isFile())
 +                    .isTrue();
 +            assertThat(file.getOriginalName())
 +                    .isEqualTo("test2.csv");
 +            assertThat(file.getContentType())
 +                    .isEqualTo("text/csv");
 +            assertThat(file.getContent())
 +                    .asInstanceOf(InstanceOfAssertFactories.FILE)
 +                    .exists()
 +                    .content()
 +                    .isEqualTo("5,6,7,8");
 +        });
 +    }
 +
 +    @Test
 +    public void uploadedMultipleFilesToDisk() throws IOException {
 +        // given
 +        String content = formFile("file1", "test1.csv", "1,2,3,4") +
 +                formFile("file1", "test2.csv", "5,6,7,8") +
 +                endline + "--" + boundary + "--";
 +
 +        mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
 +
 +        
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
 +
 +        // when
 +        multiPart.setBufferSize("1"); // always write files into disk
 +        multiPart.parse(mockRequest, tempDir);
 +
 +        // then
 +        assertThat(multiPart.getErrors())
 +                .isEmpty();
 +
 +        
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
 +                .asList()
 +                .containsOnly("file1");
 +        assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
 +            if (Objects.equals(file.getName(), "test1.csv")) {
 +                assertThat(file.isFile())
 +                        .isTrue();
 +                assertThat(file.getOriginalName())
 +                        .isEqualTo("test1.csv");
 +                assertThat(file.getContentType())
 +                        .isEqualTo("text/csv");
 +                
assertThat(file.getContent()).asInstanceOf(InstanceOfAssertFactories.FILE)
 +                        .exists()
 +                        .content()
 +                        .isEqualTo("1,2,3,4");
 +            }
 +            if (Objects.equals(file.getName(), "test2.csv")) {
 +                assertThat(file.isFile())
 +                        .isTrue();
 +                assertThat(file.getOriginalName())
 +                        .isEqualTo("test2.csv");
 +                assertThat(file.getContentType())
 +                        .isEqualTo("text/csv");
 +                assertThat(file.getContent())
 +                        .asInstanceOf(InstanceOfAssertFactories.FILE)
 +                        .exists()
 +                        .content()
 +                        .isEqualTo("5,6,7,8");
 +            }
 +        });
 +    }
 +
 +    @Test
 +    public void uploadedFilesWithLargeBuffer() throws IOException {
 +        // given
 +        String content = formFile("file1", "test1.csv", "1,2,3,4") +
 +                formFile("file2", "test2.csv", "5,6,7,8") +
 +                endline + "--" + boundary + "--";
 +
 +        mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
 +
 +        
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
 +
 +        // when
 +        multiPart.setBufferSize("8192"); // streams files into disk using 
larger buffer
 +        multiPart.parse(mockRequest, tempDir);
 +
 +        // then
 +        assertThat(multiPart.getErrors())
 +                .isEmpty();
 +
 +        
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
 +                .asList()
 +                .containsOnly("file1", "file2");
 +        assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
 +            assertThat(file.isFile())
 +                    .isTrue();
 +            assertThat(file.getOriginalName())
 +                    .isEqualTo("test1.csv");
 +            assertThat(file.getContentType())
 +                    .isEqualTo("text/csv");
 +            assertThat(file.getContent())
 +                    .asInstanceOf(InstanceOfAssertFactories.FILE)
 +                    .exists()
 +                    .content()
 +                    .isEqualTo("1,2,3,4");
 +        });
 +        assertThat(multiPart.getFile("file2")).allSatisfy(file -> {
 +            assertThat(file.isFile())
 +                    .isTrue();
 +            assertThat(file.getOriginalName())
 +                    .isEqualTo("test2.csv");
 +            assertThat(file.getContent())
 +                    .asInstanceOf(InstanceOfAssertFactories.FILE)
 +                    .exists()
 +                    .content()
 +                    .isEqualTo("5,6,7,8");
 +        });
 +    }
 +
 +    @Test
 +    public void cleanUp() throws IOException {
 +        // given
 +        String content = formFile("file1", "test1.csv", "1,2,3,4") +
 +                formFile("file2", "test2.csv", "5,6,7,8") +
 +                endline + "--" + boundary + "--";
 +
 +        mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
 +
 +        
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
 +
 +        // when
 +        multiPart.parse(mockRequest, tempDir);
 +
 +        // then
 +        assertThat(multiPart.getErrors())
 +                .isEmpty();
 +
 +        
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
 +                .asList()
 +                .containsOnly("file1", "file2");
 +        assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
 +            assertThat(file.isFile())
 +                    .isTrue();
 +            assertThat(file.getOriginalName())
 +                    .isEqualTo("test1.csv");
 +            assertThat(file.getContentType())
 +                    .isEqualTo("text/csv");
 +            
assertThat(file.getContent()).asInstanceOf(InstanceOfAssertFactories.FILE)
 +                    .exists()
 +                    .content()
 +                    .isEqualTo("1,2,3,4");
 +        });
 +        assertThat(multiPart.getFile("file2")).allSatisfy(file -> {
 +            assertThat(file.isFile())
 +                    .isTrue();
 +            assertThat(file.getOriginalName())
 +                    .isEqualTo("test2.csv");
 +            assertThat(file.getContentType())
 +                    .isEqualTo("text/csv");
 +            assertThat(file.getContent())
 +                    .asInstanceOf(InstanceOfAssertFactories.FILE)
 +                    .exists()
 +                    .content()
 +                    .isEqualTo("5,6,7,8");
 +        });
 +
-         List<UploadedFile<File>> uploadedFiles = new ArrayList<>();
-         for (Map.Entry<String, List<UploadedFile<File>>> entry : 
multiPart.uploadedFiles.entrySet()) {
++        List<UploadedFile> uploadedFiles = new ArrayList<>();
++        for (Map.Entry<String, List<UploadedFile>> entry : 
multiPart.uploadedFiles.entrySet()) {
 +            uploadedFiles.addAll(entry.getValue());
 +        }
 +
 +        // when
 +        multiPart.cleanUp();
 +
 +        // then
 +        assertThat(multiPart.uploadedFiles)
 +                .isEmpty();
 +        assertThat(multiPart.parameters)
 +                .isEmpty();
 +        assertThat(uploadedFiles).allSatisfy(file ->
 +                
assertThat(file.getContent()).asInstanceOf(InstanceOfAssertFactories.FILE)
 +                        .doesNotExist()
 +        );
 +    }
 +
 +    @Test
 +    public void nonMultiPartUpload() throws IOException {
 +        String content = formFile("file1", "test1.csv", "1,2,3,4") +
 +                formFile("file2", "test2.csv", "5,6,7,8") +
 +                endline + "--" + boundary + "--";
 +        mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
 +
 +        // given
 +        mockRequest.setContentType("");
 +
 +        // when
 +        multiPart.parse(mockRequest, tempDir);
 +
 +        // then
 +        assertThat(multiPart.getErrors())
 +                .map(LocalizedMessage::getTextKey)
 +                
.containsExactly("struts.messages.upload.error.FileUploadContentTypeException");
 +
 +        
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
 +                .asList()
 +                .isEmpty();
 +    }
 +
 +    @Test
 +    public void maxSize() throws IOException {
 +        // given
 +        String content = formFile("file1", "test1.csv", "1,2,3,4") +
 +                formFile("file2", "test2.csv", "5,6,7,8") +
 +                endline + "--" + boundary + "--";
 +
 +        mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
 +
 +        
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
 +
 +        // when
 +        multiPart.setMaxSize("1");
 +        multiPart.parse(mockRequest, tempDir);
 +
 +        // then
 +        assertThat(multiPart.uploadedFiles)
 +                .isEmpty();
 +
 +        assertThat(multiPart.getErrors())
 +                .map(LocalizedMessage::getTextKey)
 +                
.containsExactly("struts.messages.upload.error.FileUploadSizeException");
 +    }
 +
 +    @Test
 +    public void maxFilesSize() throws IOException {
 +        String content = formFile("file1", "test1.csv", "1,2,3,4") +
 +                formFile("file2", "test2.csv", "5,6,7,8") +
 +                endline + "--" + boundary + "--";
 +
 +        mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
 +
 +        
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
 +
 +        multiPart.setMaxFileSize("1");
 +        multiPart.parse(mockRequest, tempDir);
 +
 +        assertThat(multiPart.getErrors())
 +                .map(LocalizedMessage::getTextKey)
 +                
.containsExactly("struts.messages.upload.error.FileUploadByteCountLimitException");
 +    }
 +
 +    @Test
 +    public void maxFiles() throws IOException {
 +        String content = formFile("file1", "test1.csv", "1,2,3,4") +
 +                formFile("file2", "test2.csv", "5,6,7,8") +
 +                endline + "--" + boundary + "--";
 +
 +        mockRequest.setContent(content.getBytes(StandardCharsets.US_ASCII));
 +
 +        
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
 +
 +        multiPart.setMaxFiles("1");
 +        multiPart.parse(mockRequest, tempDir);
 +
 +        assertThat(multiPart.errors)
 +                .map(LocalizedMessage::getTextKey)
 +                
.containsExactly("struts.messages.upload.error.FileUploadFileCountLimitException");
 +    }
 +
 +    @Test
 +    public void maxStringLength() throws IOException {
 +        String content = formFile("file1", "test1.csv", "1,2,3,4") +
 +                formFile("file2", "test2.csv", "5,6,7,8") +
 +                formField("longText", "very long text") +
 +                formField("shortText", "short text") +
 +                endline + "--" + boundary + "--";
 +
 +        mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
 +
 +        
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
 +
 +        multiPart.setMaxStringLength("10");
 +        multiPart.parse(mockRequest, tempDir);
 +
 +        assertThat(multiPart.getErrors())
 +                .map(LocalizedMessage::getTextKey)
 +                
.containsExactly("struts.messages.upload.error.parameter.too.long");
 +    }
 +
 +    @Test
 +    public void mismatchCharset() throws IOException {
 +        // give
 +        String content = formFile("file1", "test1.csv", "Ł,Ś,Ż,Ó") +
 +                endline + "--" + boundary + "--";
 +
 +        mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
 +
 +        
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
 +
 +        // when
 +        mockRequest.setCharacterEncoding(null);
 +        multiPart.setDefaultEncoding(StandardCharsets.ISO_8859_1.name());
 +        multiPart.parse(mockRequest, tempDir);
 +
 +        // then
 +        assertThat(multiPart.getErrors())
 +                .isEmpty();
 +
 +        
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
 +                .asList()
 +                .containsOnly("file1");
 +        assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
 +            assertThat(file.isFile())
 +                    .isTrue();
 +            assertThat(file.getOriginalName())
 +                    .isEqualTo("test1.csv");
 +            assertThat(file.getContentType())
 +                    .isEqualTo("text/csv");
 +            assertThat(file.getContent())
 +                    .asInstanceOf(InstanceOfAssertFactories.FILE)
 +                    .exists()
 +                    .content()
 +                    .isEqualTo("Ł,Ś,Ż,Ó");
 +        });
 +    }
 +
 +    @Test
 +    public void normalFields() throws IOException {
 +        String content = formFile("file1", "test1.csv", "1,2,3,4") +
 +                formFile("file2", "test2.csv", "5,6,7,8") +
 +                formField("longText", "very long text") +
 +                formField("shortText", "short text") +
 +                formField("multi", "multi1") +
 +                formField("multi", "multi2") +
 +                endline + "--" + boundary + "--";
 +
 +        mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
 +
 +        
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
 +
 +        multiPart.parse(mockRequest, tempDir);
 +
 +        assertThat(multiPart.getErrors())
 +                .isEmpty();
 +
 +        assertThat(multiPart.getParameterNames().asIterator()).toIterable()
 +                .containsOnly("longText", "shortText", "multi");
 +        assertThat(multiPart.getParameterValues("longText"))
 +                .contains("very long text");
 +        assertThat(multiPart.getParameterValues("shortText"))
 +                .contains("short text");
 +        assertThat(multiPart.getParameter("longText"))
 +                .isEqualTo("very long text");
 +        assertThat(multiPart.getParameter("shortText"))
 +                .isEqualTo("short text");
 +        assertThat(multiPart.getParameterValues("multi"))
 +                .containsOnly("multi1", "multi2");
 +    }
 +
 +    @Test
 +    public void unableParseRequest() throws IOException {
 +        String content = formFile("file1", "test1.csv", "1,2,3,4");
 +        mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
 +
 +        
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
 +
 +        multiPart.parse(mockRequest, tempDir);
 +
 +        assertThat(multiPart.getErrors())
 +                .map(LocalizedMessage::getTextKey)
 +                
.containsExactly("struts.messages.upload.error.FileUploadException");
 +    }
 +
 +    protected String formFile(String fieldName, String filename, String 
content) {
 +        return endline +
 +                "--" + boundary + endline +
 +                "Content-Disposition: form-data; name=\"" + fieldName + "\"; 
filename=\"" + filename + "\"" +
 +                endline +
 +                "Content-Type: text/csv" +
 +                endline +
 +                endline +
 +                content;
 +    }
 +
 +    protected String formField(String fieldName, String content) {
 +        return endline +
 +                "--" + boundary + endline +
 +                "Content-Disposition: form-data; name=\"" + fieldName + "\"" +
 +                endline +
 +                endline +
 +                content;
 +    }
 +}
diff --cc 
core/src/test/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequestTest.java
index e24dc133d,5e8f31e4e..c5fe42b1c
--- 
a/core/src/test/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequestTest.java
+++ 
b/core/src/test/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequestTest.java
@@@ -16,15 -16,14 +16,13 @@@
   * specific language governing permissions and limitations
   * under the License.
   */
 -package org.apache.struts2.components;
 +package org.apache.struts2.dispatcher.multipart;
  
- import java.io.File;
- 
 -import com.opensymphony.xwork2.Action;
 +public class JakartaMultiPartRequestTest extends AbstractMultiPartRequestTest 
{
  
 -public class PortletAction {
 -
 -    public String execute() {
 -        return Action.SUCCESS;
 +    @Override
-     protected AbstractMultiPartRequest<File> createMultipartRequest() {
++    protected AbstractMultiPartRequest createMultipartRequest() {
 +        return new JakartaMultiPartRequest();
      }
  
 -}
 +}
diff --cc 
core/src/test/java/org/apache/struts2/interceptor/ActionFileUploadInterceptorTest.java
index 0c383e024,51747b147..5b63c7147
--- 
a/core/src/test/java/org/apache/struts2/interceptor/ActionFileUploadInterceptorTest.java
+++ 
b/core/src/test/java/org/apache/struts2/interceptor/ActionFileUploadInterceptorTest.java
@@@ -51,7 -51,7 +51,7 @@@ import static org.assertj.core.api.Asse
   */
  public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
  
-     private static final UploadedFile<File> EMPTY_FILE = new UploadedFile<>() 
{
 -    public static final UploadedFile EMPTY_FILE = new UploadedFile() {
++    private static final UploadedFile EMPTY_FILE = new UploadedFile() {
          @Override
          public Long length() {
              return 0L;
diff --cc 
core/src/test/java/org/apache/struts2/interceptor/FileUploadInterceptorTest.java
index 57f4650cc,14bb23c36..8dbd173e5
--- 
a/core/src/test/java/org/apache/struts2/interceptor/FileUploadInterceptorTest.java
+++ 
b/core/src/test/java/org/apache/struts2/interceptor/FileUploadInterceptorTest.java
@@@ -53,7 -55,7 +53,7 @@@ import static org.assertj.core.api.Asse
   */
  public class FileUploadInterceptorTest extends StrutsInternalTestCase {
  
-     private static final UploadedFile<File> EMPTY_FILE = new UploadedFile<>() 
{
 -    public static final UploadedFile EMPTY_FILE = new UploadedFile() {
++    private static final UploadedFile EMPTY_FILE = new UploadedFile() {
          @Override
          public Long length() {
              return 0L;
@@@ -204,11 -208,7 +204,11 @@@
          URL url = ClassLoaderUtil.getResource("log4j2.xml", 
FileUploadInterceptorTest.class);
          File file = new File(new URI(url.toString()));
          assertTrue("log4j2.xml should be in src/test folder", file.exists());
-         UploadedFile<File> uploadedFile = 
StrutsUploadedFile.Builder.create(file)
 -        UploadedFile uploadedFile = 
StrutsUploadedFile.Builder.create(file).withContentType("text/html").withOriginalName("filename").build();
++        UploadedFile uploadedFile = StrutsUploadedFile.Builder.create(file)
 +                .withContentType("text/html")
 +                .withOriginalName("filename")
 +                .build();
 +
          boolean notOk = interceptor.acceptFile(validation, uploadedFile, 
"filename", "text/html", "inputName");
  
          assertFalse(notOk);
@@@ -639,19 -612,11 +639,19 @@@
      }
  
      public static class MyFileupAction extends ActionSupport {
 +    }
  
 -        private static final long serialVersionUID = 6255238895447968889L;
 +    public static class MyFileUploadAction extends ActionSupport implements 
UploadedFilesAware {
-         private List<UploadedFile<File>> uploadedFiles;
++        private List<UploadedFile> uploadedFiles;
  
 -        // no methods
 -    }
 +        @Override
-         public void withUploadedFiles(List<UploadedFile<File>> uploadedFiles) 
{
++        public void withUploadedFiles(List<UploadedFile> uploadedFiles) {
 +            this.uploadedFiles = uploadedFiles;
 +        }
  
-         public List<UploadedFile<File>> getUploadFiles() {
++        public List<UploadedFile> getUploadFiles() {
 +            return this.uploadedFiles;
 +        }
 +    }
  
  }
diff --cc 
plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesContainerFactory.java
index 8ef7cc561,ed03f8268..def7aab96
--- 
a/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesContainerFactory.java
+++ 
b/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesContainerFactory.java
@@@ -57,18 -52,26 +52,26 @@@ import org.apache.tiles.ognl.PropertyAc
  import org.apache.tiles.ognl.ScopePropertyAccessor;
  import org.apache.tiles.ognl.TilesApplicationContextNestedObjectExtractor;
  import org.apache.tiles.ognl.TilesContextPropertyAccessorDelegateFactory;
+ import org.apache.tiles.request.ApplicationContext;
+ import org.apache.tiles.request.ApplicationResource;
+ import org.apache.tiles.request.Request;
+ import org.apache.tiles.request.render.BasicRendererFactory;
+ import org.apache.tiles.request.render.ChainedDelegateRenderer;
  import org.apache.tiles.request.render.Renderer;
  
 -import javax.el.ArrayELResolver;
 -import javax.el.BeanELResolver;
 -import javax.el.CompositeELResolver;
 -import javax.el.ELResolver;
 -import javax.el.ListELResolver;
 -import javax.el.MapELResolver;
 -import javax.el.ResourceBundleELResolver;
 -import javax.servlet.jsp.JspFactory;
 +import jakarta.el.ArrayELResolver;
 +import jakarta.el.BeanELResolver;
 +import jakarta.el.CompositeELResolver;
 +import jakarta.el.ELResolver;
 +import jakarta.el.ListELResolver;
 +import jakarta.el.MapELResolver;
 +import jakarta.el.ResourceBundleELResolver;
 +import jakarta.servlet.jsp.JspFactory;
  import java.util.ArrayList;
+ import java.util.Arrays;
  import java.util.Collection;
+ import java.util.Collections;
+ import java.util.HashSet;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
diff --cc 
plugins/tiles/src/test/java/org/apache/struts2/tiles/StrutsTilesContainerFactoryTest.java
index 000000000,122bfe51d..dbadffa7a
mode 000000,100644..100644
--- 
a/plugins/tiles/src/test/java/org/apache/struts2/tiles/StrutsTilesContainerFactoryTest.java
+++ 
b/plugins/tiles/src/test/java/org/apache/struts2/tiles/StrutsTilesContainerFactoryTest.java
@@@ -1,0 -1,128 +1,128 @@@
+ /*
+  * 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.struts2.tiles;
+ 
+ import org.apache.tiles.api.TilesContainer;
+ import org.apache.tiles.core.evaluator.AttributeEvaluatorFactory;
+ import org.apache.tiles.core.evaluator.impl.DirectAttributeEvaluator;
+ import org.apache.tiles.core.locale.LocaleResolver;
+ import org.apache.tiles.core.prepare.factory.BasicPreparerFactory;
+ import org.apache.tiles.core.prepare.factory.PreparerFactory;
+ import org.apache.tiles.ognl.OGNLAttributeEvaluator;
+ import org.apache.tiles.request.ApplicationContext;
+ import org.apache.tiles.request.ApplicationResource;
+ import org.apache.tiles.request.locale.URLApplicationResource;
+ import org.apache.tiles.request.render.BasicRendererFactory;
+ import org.apache.tiles.request.render.ChainedDelegateRenderer;
+ import org.apache.tiles.request.render.Renderer;
+ import org.junit.Before;
+ import org.junit.Test;
+ 
 -import javax.servlet.ServletContext;
 -import javax.servlet.jsp.JspFactory;
++import jakarta.servlet.ServletContext;
++import jakarta.servlet.jsp.JspFactory;
+ import java.util.Collections;
+ import java.util.List;
+ import java.util.Objects;
+ 
+ import static org.junit.Assert.assertEquals;
+ import static org.junit.Assert.assertTrue;
+ import static org.mockito.Mockito.mock;
+ import static org.mockito.Mockito.verify;
+ import static org.mockito.Mockito.when;
+ 
+ public class StrutsTilesContainerFactoryTest {
+ 
+     private StrutsTilesContainerFactory factory;
+     private ApplicationContext applicationContext;
+ 
+     @Before
+     public void setUp() throws Exception {
+         applicationContext = mock(ApplicationContext.class);
+         factory = new StrutsTilesContainerFactory();
+     }
+ 
+     @Test
+     public void getSources() {
+         ApplicationResource pathResource = new URLApplicationResource(
+                 "/org/apache/tiles/core/config/tiles-defs.xml",
+                 
Objects.requireNonNull(getClass().getResource("/org/apache/tiles/core/config/tiles-defs.xml"))
+         );
+         ApplicationResource classpathResource = new URLApplicationResource(
+                 "/org/apache/tiles/core/config/defs1.xml",
+                 
Objects.requireNonNull(getClass().getResource("/org/apache/tiles/core/config/defs1.xml"))
+         );
+         
when(applicationContext.getInitParams()).thenReturn(Collections.emptyMap());
+         
when(applicationContext.getResources("/WEB-INF/**/tiles*.xml")).thenReturn(Collections.singleton(pathResource));
+         
when(applicationContext.getResources("classpath*:META-INF/**/tiles*.xml")).thenReturn(Collections.singleton(classpathResource));
+ 
+         List<ApplicationResource> resources = 
factory.getSources(applicationContext);
+         assertEquals("The urls list is not two-sized", 2, resources.size());
+         assertEquals("The URL is not correct", pathResource, 
resources.get(0));
+         assertEquals("The URL is not correct", classpathResource, 
resources.get(1));
+     }
+ 
+     @Test
+     public void createAttributeEvaluatorFactory() {
+         LocaleResolver resolver = 
factory.createLocaleResolver(applicationContext);
+         // explicitly disables support for EL
+         JspFactory.setDefaultFactory(null);
+ 
+         AttributeEvaluatorFactory attributeEvaluatorFactory = 
factory.createAttributeEvaluatorFactory(applicationContext, resolver);
+         assertTrue("The class of the evaluator is not correct",
+                 attributeEvaluatorFactory.getAttributeEvaluator((String) 
null) instanceof DirectAttributeEvaluator);
+         assertTrue("The class of the evaluator is not correct",
+                 attributeEvaluatorFactory.getAttributeEvaluator("S2") 
instanceof StrutsAttributeEvaluator);
+         assertTrue("The class of the evaluator is not correct",
+                 attributeEvaluatorFactory.getAttributeEvaluator("OGNL") 
instanceof OGNLAttributeEvaluator);
+         assertTrue("The class of the evaluator is not correct",
+                 attributeEvaluatorFactory.getAttributeEvaluator("I18N") 
instanceof I18NAttributeEvaluator);
+         assertTrue("The class of the evaluator is not correct",
+                 attributeEvaluatorFactory.getAttributeEvaluator("EL") 
instanceof DirectAttributeEvaluator);
+     }
+ 
+     @Test
+     public void createPreparerFactory() {
+         PreparerFactory preparerFactory = 
factory.createPreparerFactory(applicationContext);
+         assertTrue("The class of the preparer factory is not correct", 
preparerFactory instanceof BasicPreparerFactory);
+     }
+ 
+     @Test
+     public void createDefaultAttributeRenderer() {
+         TilesContainer container = mock(TilesContainer.class);
+         AttributeEvaluatorFactory attributeEvaluatorFactory = 
mock(AttributeEvaluatorFactory.class);
+         BasicRendererFactory rendererFactory = 
mock(BasicRendererFactory.class);
+         Renderer stringRenderer = mock(Renderer.class);
+         Renderer templateRenderer = mock(Renderer.class);
+         Renderer definitionRenderer = mock(Renderer.class);
+ 
+         
when(rendererFactory.getRenderer("string")).thenReturn(stringRenderer);
+         
when(rendererFactory.getRenderer("template")).thenReturn(templateRenderer);
+         
when(rendererFactory.getRenderer("definition")).thenReturn(definitionRenderer);
+         
when(rendererFactory.getRenderer("freemarker")).thenReturn(definitionRenderer);
+ 
+         Renderer renderer = 
factory.createDefaultAttributeRenderer(rendererFactory, applicationContext, 
container, attributeEvaluatorFactory);
+ 
+         assertTrue("The default renderer class is not correct", renderer 
instanceof ChainedDelegateRenderer);
+         verify(rendererFactory).getRenderer("string");
+         verify(rendererFactory).getRenderer("template");
+         verify(rendererFactory).getRenderer("definition");
+         verify(rendererFactory).getRenderer("freemarker");
+     }
+ 
+ }
diff --cc pom.xml
index 4de073e0d,79824373b..963b80b1a
--- a/pom.xml
+++ b/pom.xml
@@@ -82,9 -82,9 +82,9 @@@
  
      <modules>
          <module>bom</module>
-         <module>jakarta</module>
++        <!--<module>jakarta</module>-->
          <module>core</module>
          <module>plugins</module>
 -        <module>bundles</module>
          <module>apps</module>
      </modules>
  
@@@ -111,20 -110,16 +111,20 @@@
  
          <!-- dependency versions in alphanumeric order -->
          <asm.version>9.6</asm.version>
 -        <jackson.version>2.16.1</jackson.version>
 +        <byte-buddy.version>1.14.11</byte-buddy.version>
 +        <freemarker.version>2.3.32</freemarker.version>
 +        <hibernate-validator.version>8.0.1.Final</hibernate-validator.version>
 +        <jackson.version>2.16.0</jackson.version>
-         <log4j2.version>2.22.1</log4j2.version>
+         <log4j2.version>2.23.1</log4j2.version>
 +        <maven-surefire-plugin.version>3.2.5</maven-surefire-plugin.version>
 +        <mockito.version>5.8.0</mockito.version>
          <ognl.version>3.3.4</ognl.version>
 +        <sitemesh.version>2.5.0</sitemesh.version>
-         <slf4j.version>2.0.11</slf4j.version>
+         <slf4j.version>2.0.12</slf4j.version>
 -        <spring.platformVersion>5.3.31</spring.platformVersion>
 +        <spring.platformVersion>6.0.13</spring.platformVersion>
          <tiles.version>3.0.8</tiles.version>
          <tiles-request.version>1.0.7</tiles-request.version>
 -        <maven-surefire-plugin.version>3.2.5</maven-surefire-plugin.version>
 -        <hibernate-validator.version>6.2.4.Final</hibernate-validator.version>
 -        <freemarker.version>2.3.32</freemarker.version>
 +        <velocity-tools.version>3.1</velocity-tools.version>
  
          <!-- Site generation -->
          <fluido-skin.version>1.9</fluido-skin.version>

Reply via email to