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

reta pushed a commit to branch 4.1.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/4.1.x-fixes by this push:
     new 7ee4f5fac5 CXF-9178: Run Jakarta RESTful Web Services 4.0 TCK. Fix 
multipart test cases (#2844)
7ee4f5fac5 is described below

commit 7ee4f5fac5a9c61f9053f603eefaa50f6a66fb23
Author: Andriy Redko <[email protected]>
AuthorDate: Fri Jan 30 09:12:08 2026 -0500

    CXF-9178: Run Jakarta RESTful Web Services 4.0 TCK. Fix multipart test 
cases (#2844)
    
    (cherry picked from commit e920da53d7538ed702746dec94ddae2aa82e3a01)
---
 .../org/apache/cxf/jaxrs/impl/EntityPartImpl.java  |   6 +-
 .../apache/cxf/jaxrs/utils/EntityPartUtils.java    |  34 +-
 .../org/apache/cxf/jaxrs/utils/InjectionUtils.java |  74 +++-
 .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java     |  18 +-
 .../apache/cxf/jaxrs/utils/InjectionUtilsTest.java |   2 +-
 .../cxf/systest/jaxrs/JAXRSEntityPartTest.java     |  25 +-
 .../jaxrs/multipart/MultipartSupportTest.java      | 385 +++++++++++++++++++++
 7 files changed, 490 insertions(+), 54 deletions(-)

diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/EntityPartImpl.java 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/EntityPartImpl.java
index bce0850ce0..88f054f5a5 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/EntityPartImpl.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/EntityPartImpl.java
@@ -20,6 +20,7 @@
 package org.apache.cxf.jaxrs.impl;
 
 import java.io.ByteArrayInputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PipedInputStream;
@@ -27,7 +28,6 @@ import java.io.PipedOutputStream;
 import java.io.UncheckedIOException;
 import java.lang.reflect.Type;
 import java.nio.file.Files;
-import java.nio.file.Path;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -91,8 +91,8 @@ public class EntityPartImpl implements EntityPart {
                 return (InputStream) content;
             }  else if (content instanceof byte[]) { 
                 return new ByteArrayInputStream((byte[]) content);
-            } else if (fileName != null && !fileName.isBlank()) { 
-                return Files.newInputStream(Path.of(fileName));
+            } else if (fileName != null && content instanceof File) { 
+                return Files.newInputStream(((File) content).toPath());
             } else {
                 return contentAsStream();
             } 
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/EntityPartUtils.java
 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/EntityPartUtils.java
index 08f7c2837b..6eeb8a13f1 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/EntityPartUtils.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/EntityPartUtils.java
@@ -19,16 +19,15 @@
 
 package org.apache.cxf.jaxrs.utils;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
 
 import jakarta.ws.rs.core.EntityPart;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.MultivaluedHashMap;
 import jakarta.ws.rs.ext.Providers;
-import org.apache.cxf.attachment.AttachmentBoundaryDeserializer;
 import org.apache.cxf.common.util.StringUtils;
 import org.apache.cxf.jaxrs.ext.MessageContext;
 import org.apache.cxf.jaxrs.ext.multipart.Attachment;
@@ -50,14 +49,29 @@ final class EntityPartUtils {
         return from(mc.getProviders(), AttachmentUtils.getMultipartBody(mc));
     }
     
-    public static EntityPart getEntityPart(final String value, final Message 
message) {
+    public static EntityPart getEntityPart(final String key, final String 
value, final Message message) {
         final Providers providers = new ProvidersImpl(message);
-        try (InputStream is = new 
ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8))) {
-            final AttachmentBoundaryDeserializer deserializer = new 
AttachmentBoundaryDeserializer(message);
-            final Attachment attachment = new 
Attachment(deserializer.read(is), providers);
-            return createFromAttachment(providers, attachment);
-        } catch (IOException ex) {
-            throw ExceptionUtils.toBadRequestException(null, null);
+        final MultipartBody body = (MultipartBody) 
message.get(MultipartBody.INBOUND_MESSAGE_ATTACHMENTS);
+        if (body != null) {
+            final Attachment attachment = body.getAttachment(key);
+            if (attachment != null) {
+                final ContentDisposition cd = 
attachment.getContentDisposition();
+                final String fileName = (cd != null) ? cd.getFilename() : null;
+
+                if (!StringUtils.isEmpty(fileName)) {
+                    return new EntityPartImpl(providers, key, fileName, value, 
String.class, null,
+                        attachment.getHeaders(), attachment.getContentType());
+                } else {
+                    return new EntityPartImpl(providers, key, null, value, 
String.class, null, attachment.getHeaders(),
+                        attachment.getContentType());
+                }
+            } else {
+                return new EntityPartImpl(providers, key, null, value, 
String.class, 
+                        null, new MultivaluedHashMap<>(), 
MediaType.TEXT_PLAIN_TYPE);
+            }
+        } else {
+            return new EntityPartImpl(providers, key, null, value, 
String.class, 
+                null, new MultivaluedHashMap<>(), MediaType.TEXT_PLAIN_TYPE);
         }
     }
 
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java
 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java
index 17e15d63ff..901d9de4e6 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java
@@ -19,6 +19,8 @@
 
 package org.apache.cxf.jaxrs.utils;
 
+import java.io.InputStream;
+import java.io.StringReader;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
@@ -77,6 +79,7 @@ import org.apache.cxf.common.util.ProxyClassLoaderCache;
 import org.apache.cxf.common.util.ReflectionUtil;
 import org.apache.cxf.common.util.StringUtils;
 import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.io.ReaderInputStream;
 import org.apache.cxf.jaxrs.ext.ContextProvider;
 import org.apache.cxf.jaxrs.ext.MessageContext;
 import org.apache.cxf.jaxrs.ext.ProtocolHeaders;
@@ -100,6 +103,7 @@ import org.apache.cxf.jaxrs.provider.ServerProviderFactory;
 import org.apache.cxf.message.Exchange;
 import org.apache.cxf.message.Message;
 import org.apache.cxf.message.MessageUtils;
+import org.jspecify.annotations.Nullable;
 
 public final class InjectionUtils {
     public static final Set<String> STANDARD_CONTEXT_CLASSES = new HashSet<>();
@@ -414,8 +418,12 @@ public final class InjectionUtils {
         }
         return null;
     }
-
+    
+    /**
+     * @deprecated please use {@link handleParameter} with key argument
+     */
     @SuppressWarnings("unchecked")
+    @Deprecated
     public static <T> T handleParameter(String value,
                                         boolean decoded,
                                         Class<T> pClass,
@@ -423,6 +431,20 @@ public final class InjectionUtils {
                                         Annotation[] paramAnns,
                                         ParameterType pType,
                                         Message message) {
+        return handleParameter(null, value, decoded, pClass, 
+            genericType, paramAnns, pType, message);
+    }
+
+    //CHECKSTYLE:OFF
+    @SuppressWarnings("unchecked")
+    public static <T> T handleParameter(@Nullable String key,
+                                        String value,
+                                        boolean decoded,
+                                        Class<T> pClass,
+                                        Type genericType,
+                                        Annotation[] paramAnns,
+                                        ParameterType pType,
+                                        Message message) {
         if (value == null) {
             return null;
         }
@@ -525,7 +547,11 @@ public final class InjectionUtils {
         }
         
         if (EntityPart.class.isAssignableFrom(pClass) && message != null) {
-            result = EntityPartUtils.getEntityPart(value, message);
+            result = EntityPartUtils.getEntityPart(key, value, message);
+        }
+
+        if (InputStream.class.isAssignableFrom(pClass) && message != null) {
+            result = new ReaderInputStream(new StringReader(value));
         }
 
         if (result == null) {
@@ -539,6 +565,7 @@ public final class InjectionUtils {
             return null;
         }
     }
+    //CHECKSTYLE:ON
 
     private static RuntimeException 
createParamConversionException(ParameterType pType, Exception ex) {
         //
@@ -741,14 +768,14 @@ public final class InjectionUtils {
 
                     for (MultivaluedMap<String, String> processedValues : 
processedValuesList) {
                         if (InjectionUtils.isSupportedCollectionOrArray(type)) 
{
-                            Object appendValue = 
InjectionUtils.injectIntoCollectionOrArray(type,
+                            Object appendValue = 
InjectionUtils.injectIntoCollectionOrArray(memberKey, type,
                                                             genericType, 
paramAnns, processedValues,
                                                             isbean, true,
                                                             pType, message);
                             paramValue = 
InjectionUtils.mergeCollectionsOrArrays(paramValue, appendValue,
                                                             genericType);
                         } else if (isSupportedMap(genericType)) {
-                            Object appendValue = injectIntoMap(
+                            Object appendValue = injectIntoMap(memberKey,
                                 genericType, paramAnns, processedValues, true, 
pType, message);
                             paramValue = mergeMap(paramValue, appendValue);
 
@@ -756,7 +783,7 @@ public final class InjectionUtils {
                             paramValue = InjectionUtils.handleBean(type, 
paramAnns, processedValues,
                                                             pType, message, 
decoded);
                         } else {
-                            paramValue = InjectionUtils.handleParameter(
+                            paramValue = 
InjectionUtils.handleParameter(memberKey,
                                 
processedValues.values().iterator().next().get(0),
                                 decoded, type, type, paramAnns, pType, 
message);
                         }
@@ -787,7 +814,8 @@ public final class InjectionUtils {
         return null;
     }
 
-    private static Object injectIntoMap(Type genericType,
+    private static Object injectIntoMap(String key,
+                                        Type genericType,
                                         Annotation[] paramAnns,
                                         MultivaluedMap<String, String> 
processedValues,
                                         boolean decoded,
@@ -805,7 +833,7 @@ public final class InjectionUtils {
             for (Map.Entry<String, List<String>> processedValuesEntry : 
processedValues.entrySet()) {
                 List<String> valuesList = processedValuesEntry.getValue();
                 for (String value : valuesList) {
-                    Object o = InjectionUtils.handleParameter(value,
+                    Object o = InjectionUtils.handleParameter(key, value,
                                        decoded, valueType, valueType, 
paramAnns, pathParam, message);
                     
theValues.add(convertStringToPrimitive(processedValuesEntry.getKey(), keyType), 
o);
                 }
@@ -818,7 +846,7 @@ public final class InjectionUtils {
         for (Map.Entry<String, List<String>> processedValuesEntry : 
processedValues.entrySet()) {
             List<String> valuesList = processedValuesEntry.getValue();
             for (String value : valuesList) {
-                Object o = InjectionUtils.handleParameter(value,
+                Object o = InjectionUtils.handleParameter(key, value,
                                    decoded, valueType, valueType, paramAnns, 
pathParam, message);
                 theValues.put(
                     convertStringToPrimitive(processedValuesEntry.getKey(), 
keyType),
@@ -955,7 +983,7 @@ public final class InjectionUtils {
 
     }
     //CHECKSTYLE:OFF
-    private static Object injectIntoCollectionOrArray(Class<?> rawType,
+    private static Object injectIntoCollectionOrArray(String key, Class<?> 
rawType,
                                                       Type genericType,
                                                       Annotation[] paramAnns,
                                         MultivaluedMap<String, String> values,
@@ -998,7 +1026,7 @@ public final class InjectionUtils {
             List<String> valuesList = values.values().iterator().next();
             valuesList = checkPathSegment(valuesList, realType, pathParam);
             for (int ind = 0; ind < valuesList.size(); ind++) {
-                Object o = InjectionUtils.handleParameter(valuesList.get(ind), 
decoded,
+                Object o = InjectionUtils.handleParameter(key, 
valuesList.get(ind), decoded,
                                realType, realGenericType, paramAnns, 
pathParam, message);
                 addToCollectionValues(theValues, o, ind);
             }
@@ -1038,9 +1066,29 @@ public final class InjectionUtils {
         }
         return newValues;
     }
-    //
+    
+    /**
+     * @deprecated please use {@link createParameterObject} with key argument
+     */
+    @Deprecated
     //CHECKSTYLE:OFF
     public static Object createParameterObject(List<String> paramValues,
+            Class<?> paramType,
+            Type genericType,
+            Annotation[] paramAnns,
+            String defaultValue,
+            boolean decoded,
+            ParameterType pathParam,
+            Message message) {
+        return createParameterObject(null, paramValues, paramType, 
genericType, 
+            paramAnns, defaultValue, decoded, pathParam, message);
+    }
+    //CHECKSTYLE:ON
+
+    //
+    //CHECKSTYLE:OFF
+    public static Object createParameterObject(String key,
+                                               List<String> paramValues,
                                                Class<?> paramType,
                                                Type genericType,
                                                Annotation[] paramAnns,
@@ -1070,7 +1118,7 @@ public final class InjectionUtils {
         if (InjectionUtils.isSupportedCollectionOrArray(paramType)) {
             MultivaluedMap<String, String> paramValuesMap = new 
MetadataMap<>();
             paramValuesMap.put("", paramValues);
-            value = InjectionUtils.injectIntoCollectionOrArray(paramType, 
genericType, paramAnns,
+            value = InjectionUtils.injectIntoCollectionOrArray(key, paramType, 
genericType, paramAnns,
                                                 paramValuesMap, false, 
decoded, pathParam, message);
         } else {
             String result = null;
@@ -1080,7 +1128,7 @@ public final class InjectionUtils {
                                 : paramValues.get(0);
             }
             if (result != null) {
-                value = InjectionUtils.handleParameter(result, decoded, 
paramType, genericType,
+                value = InjectionUtils.handleParameter(key, result, decoded, 
paramType, genericType,
                                                        paramAnns, pathParam, 
message);
             }
         }
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
index a2ca3d98f2..c89a2952d5 100644
--- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
+++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
@@ -1162,7 +1162,8 @@ public final class JAXRSUtils {
                 return InjectionUtils.handleBean(pClass, paramAnns, params, 
ParameterType.MATRIX, m, false);
             }
             List<String> values = params.get(key);
-            return InjectionUtils.createParameterObject(values,
+            return InjectionUtils.createParameterObject(key,
+                                                        values,
                                                         pClass,
                                                         genericType,
                                                         paramAnns,
@@ -1232,7 +1233,8 @@ public final class JAXRSUtils {
         }
         List<String> results = params.get(key);
 
-        return InjectionUtils.createParameterObject(results,
+        return InjectionUtils.createParameterObject(key,
+                                                    results,
                                                     pClass,
                                                     genericType,
                                                     paramAnns,
@@ -1260,7 +1262,8 @@ public final class JAXRSUtils {
         if (values != null && values.isEmpty()) {
             values = null;
         }
-        return InjectionUtils.createParameterObject(values,
+        return InjectionUtils.createParameterObject(header,
+                                                    values,
                                                     pClass,
                                                     genericType,
                                                     paramAnns,
@@ -1290,7 +1293,8 @@ public final class JAXRSUtils {
         String value = InjectionUtils.isSupportedCollectionOrArray(pClass)
             && InjectionUtils.getActualType(genericType) == Cookie.class
             ? c.toString() : c.getValue();
-        return 
InjectionUtils.createParameterObject(Collections.singletonList(value),
+        return InjectionUtils.createParameterObject(cookieName,
+                                                    
Collections.singletonList(value),
                                                     pClass,
                                                     genericType,
                                                     paramAnns,
@@ -1427,7 +1431,8 @@ public final class JAXRSUtils {
             return InjectionUtils.handleBean(paramType, paramAnns, values, 
ParameterType.PATH, m, decoded);
         }
         List<String> results = values.get(parameterName);
-        return InjectionUtils.createParameterObject(results,
+        return InjectionUtils.createParameterObject(parameterName,
+                                                results,
                                                 paramType,
                                                 genericType,
                                                 paramAnns,
@@ -1453,7 +1458,8 @@ public final class JAXRSUtils {
         if ("".equals(queryName)) {
             return InjectionUtils.handleBean(paramType, paramAnns, queryMap, 
ParameterType.QUERY, m, false);
         }
-        return InjectionUtils.createParameterObject(queryMap.get(queryName),
+        return InjectionUtils.createParameterObject(queryName,
+                                                    queryMap.get(queryName),
                                                     paramType,
                                                     genericType,
                                                     paramAnns,
diff --git 
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/InjectionUtilsTest.java
 
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/InjectionUtilsTest.java
index 1b5f07be2f..7d4a6df867 100644
--- 
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/InjectionUtilsTest.java
+++ 
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/InjectionUtilsTest.java
@@ -199,7 +199,7 @@ public class InjectionUtilsTest {
     public void testJsr310DateExceptionHandling() {
         Field field = CustomerDetailsWithAdapter.class.getDeclaredFields()[0];
         Annotation[] paramAnns = field.getDeclaredAnnotations();
-        
InjectionUtils.createParameterObject(Collections.singletonList("wrongDate"), 
LocalDate.class,
+        InjectionUtils.createParameterObject(field.getName(), 
Collections.singletonList("wrongDate"), LocalDate.class,
                 LocalDate.class, paramAnns, null, false, ParameterType.QUERY, 
createMessage());
     }
 
diff --git 
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSEntityPartTest.java
 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSEntityPartTest.java
index b536c69383..6506372b72 100644
--- 
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSEntityPartTest.java
+++ 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSEntityPartTest.java
@@ -21,6 +21,7 @@ package org.apache.cxf.systest.jaxrs;
 
 import java.io.File;
 import java.io.InputStream;
+import java.io.SequenceInputStream;
 import java.nio.file.Files;
 import java.util.List;
 
@@ -107,19 +108,7 @@ public class JAXRSEntityPartTest extends 
AbstractBusClientServerTestBase {
             InputStream is2 = 
                 
getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/attachmentFormJson"))
 {
 
-            final EntityPart part1 = EntityPart
-                    .withName("bookXML")
-                    .content(is1)
-                    .mediaType(MediaType.APPLICATION_XML)
-                    .build();
-
-            final EntityPart part2 = EntityPart
-                    .withName("gazetteer")
-                    .content(is2)
-                    .mediaType(MediaType.APPLICATION_JSON)
-                    .build();
-
-            try (Response response = client.postCollection(List.of(part1, 
part2), EntityPart.class)) {
+            try (Response response = client.post(new SequenceInputStream(is1, 
is2))) {
                 assertThat(response.getStatus(), equalTo(200));
 
                 try (InputStream expected = 
getClass().getResourceAsStream("resources/expected_add_book.txt")) {
@@ -144,14 +133,8 @@ public class JAXRSEntityPartTest extends 
AbstractBusClientServerTestBase {
 
         try (InputStream is = 
                 
getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/" + 
resourceName)) {
-
-            final EntityPart part = EntityPart
-                    .withName(name)
-                    .content(is)
-                    .mediaType(mt)
-                    .build();
-
-            try (Response response = client.postCollection(List.of(part), 
EntityPart.class)) {
+            
+            try (Response response = client.post(is)) {
                 assertThat(response.getStatus(), equalTo(200));
 
                 try (InputStream expected = 
getClass().getResourceAsStream("resources/expected_add_book.txt")) {
diff --git 
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/multipart/MultipartSupportTest.java
 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/multipart/MultipartSupportTest.java
new file mode 100644
index 0000000000..0a6ab0ebea
--- /dev/null
+++ 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/multipart/MultipartSupportTest.java
@@ -0,0 +1,385 @@
+/**
+ * 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.cxf.systest.jaxrs.multipart;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.List;
+
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.FormParam;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.EntityPart;
+import jakarta.ws.rs.core.GenericEntity;
+import jakarta.ws.rs.core.GenericType;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.apache.cxf.Bus;
+import org.apache.cxf.endpoint.Server;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
+import org.apache.cxf.testutil.common.AbstractServerTestServerBase;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Adapted from ee.jakarta.tck.ws.rs.jaxrs31.ee.multipart.MultipartSupportIT
+ */
+public class MultipartSupportTest extends AbstractBusClientServerTestBase {
+    public static final String PORT = MultipartSupportServer.PORT;
+
+    @BeforeClass
+    public static void startServers() throws Exception {
+        assertTrue("server did not launch correctly", 
launchServer(MultipartSupportServer.class, true));
+    }
+    
+    public static final class MultipartSupportServer extends 
AbstractServerTestServerBase {
+        public static final String PORT = 
allocatePort(MultipartSupportServer.class);
+
+        @Override
+        protected Server createServer(Bus bus) throws Exception {
+            JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
+            sf.setResourceClasses(TestResource.class);
+            sf.setAddress("http://localhost:"; + PORT + "/");
+            return sf.create();
+        }
+
+        public static void main(String[] args) throws Exception {
+            new MultipartSupportServer().start();
+        }
+    }
+
+    private static InputStream xmlFile() {
+        final String xml = "<root>" + System.lineSeparator() 
+            + "  <mid attr1=\"value1\" attr2=\"value2\">" + 
System.lineSeparator()
+            + "    <inner attr3=\"value3\"/>" + System.lineSeparator()
+            + "  </mid>" + System.lineSeparator()
+            + "  <mid attr1=\"value4\" attr2=\"value5\">" + 
System.lineSeparator()
+            + "    <inner attr3=\"value6\"/>" + System.lineSeparator()
+            + "  </mid>" + System.lineSeparator()
+            + "</root>";
+        return new ByteArrayInputStream(xml.getBytes());
+    }
+
+    @Test
+    public void basicTest() throws Exception {
+        try (Client client = ClientBuilder.newClient()) {
+            final List<EntityPart> multipart = List.of(
+                    EntityPart.withName("octet-stream")
+                        .content("test 
string".getBytes(StandardCharsets.UTF_8))
+                        .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
+                        .build(),
+                    EntityPart.withName("file")
+                        .content("test file", xmlFile())
+                        .mediaType(MediaType.APPLICATION_XML)
+                        .build()
+                );
+
+            try (Response response = client.target("http://localhost:"; + PORT 
+ "/test/basicTest")
+                    .request(MediaType.MULTIPART_FORM_DATA_TYPE)
+                    .post(Entity.entity(new GenericEntity<>(multipart) { }, 
MediaType.MULTIPART_FORM_DATA))) {
+
+                Assert.assertEquals(200, response.getStatus());
+                final List<EntityPart> entityParts = response.readEntity(new 
GenericType<>() {
+                });
+
+                if (entityParts.size() != 3) {
+                    Assert.fail("Expected 3 entries, received " + 
entityParts.size() + '.');
+                }
+
+                EntityPart part = find(entityParts, "received-string");
+                Assert.assertNotNull(part);
+                Assert.assertEquals("test string", 
part.getContent(String.class));
+
+                // The javadoc for EntityPart.getContent(Class<T> type) states:
+                // "Subsequent invocations will result in an {@code 
IllegalStateException}. 
+                // Likewise this method will throw an {@code 
IllegalStateException} if it is 
+                // called after calling {@link #getContent} or {@link 
#getContent(GenericType)}.
+                try {
+                    part.getContent(String.class);
+                    Assert.fail("IllegalStateException is expected when 
getContent() is invoked more than once.");
+                } catch (IllegalStateException e) {
+                    // expected exception
+                } catch (Throwable t) {
+                    Assert.fail("Incorrect Throwable received: " + t);
+                }
+                
+                part = find(entityParts, "received-file");
+                Assert.assertNotNull(part);
+                Assert.assertEquals("test file", part.getFileName().get());
+                
Assert.assertTrue(part.getContent(String.class).contains("value6"));
+
+                part = find(entityParts, "added-input-stream");
+                Assert.assertNotNull(part);
+                Assert.assertEquals("Add part on return.", 
part.getContent(String.class));
+
+                // Check headers.  Should be 5:  Content-Disposition, 
Transfer-Encoding, 
+                // Content-Type, and the 2 headers that were added.
+                if ((part.getHeaders() == null) || (part.getHeaders().size() 
!= 5)) {
+                    Assert.fail("Expected 4 headers, received " + 
part.getHeaders().size());
+                }
+
+                Assert.assertEquals("Test1", 
part.getHeaders().get("Header1").get(0));
+                Assert.assertEquals("Test2", 
part.getHeaders().get("Header2").get(0));
+            }
+        }
+    }
+
+    @Test
+    public void multiFormParamTest() throws Exception {
+        try (Client client = ClientBuilder.newClient()) {
+            final List<EntityPart> multipart = List.of(
+                    EntityPart.withName("entity-part")
+                            .content("test entity part")
+                            .mediaType(MediaType.TEXT_PLAIN_TYPE)
+                            .build(),
+                    EntityPart.withName("string-part")
+                            .content("test string")
+                            .mediaType(MediaType.TEXT_PLAIN_TYPE)
+                            .build(),
+                    EntityPart.withName("input-stream-part")
+                            .content("test input 
stream".getBytes(StandardCharsets.UTF_8))
+                            .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
+                            .build()
+                );
+
+            try (Response response = client.target("http://localhost:"; + PORT 
+ "/test/multi-form-param")
+                    .request(MediaType.MULTIPART_FORM_DATA_TYPE)
+                    .post(Entity.entity(new GenericEntity<>(multipart) { }, 
MediaType.MULTIPART_FORM_DATA))) {
+                Assert.assertEquals(200, response.getStatus());
+                final List<EntityPart> entityParts = response.readEntity(new 
GenericType<>() { });
+                if (entityParts.size() != 3) {
+                    Assert.fail("Expected 3 entries, received " + 
entityParts.size() + '.');
+                }
+                verifyEntityPart(entityParts, "received-entity-part", "test 
entity part");
+                verifyEntityPart(entityParts, "received-string", "test 
string");
+                verifyEntityPart(entityParts, "received-input-stream", "test 
input stream");
+            }
+        }
+    }
+    
+    @Test
+    public void singleFormParamTest() throws Exception {
+        try (Client client = ClientBuilder.newClient()) {
+            final List<EntityPart> multipart = List.of(
+                    EntityPart.withName("entity-part")
+                        .content("test entity part")
+                        .mediaType(MediaType.TEXT_PLAIN_TYPE)
+                        .build()
+                );
+
+            try (Response response = client.target("http://localhost:"; + PORT 
+ "/test/single-form-param")
+                    .request(MediaType.MULTIPART_FORM_DATA_TYPE)
+                    .post(Entity.entity(new GenericEntity<>(multipart) { }, 
MediaType.MULTIPART_FORM_DATA))) {
+                Assert.assertEquals(200, response.getStatus());
+                final List<EntityPart> entityParts = response.readEntity(new 
GenericType<>() { });
+                if (entityParts.size() != 1) {
+                    Assert.fail("Expected 1 entries, received " + 
entityParts.size() + '.');
+                }
+                verifyEntityPart(entityParts, "received-entity-part", "test 
entity part");
+            }
+        }
+    }
+    
+    @Test
+    public void singleTest() throws Exception {
+        try (Client client = ClientBuilder.newClient()) {
+            final List<EntityPart> multipart = List.of(
+                    EntityPart.withName("entity-part")
+                        .content(new ByteArrayInputStream("test entity 
part".getBytes(StandardCharsets.UTF_8)))
+                        .mediaType(MediaType.TEXT_PLAIN_TYPE)
+                        .build()
+                );
+
+            try (Response response = client.target("http://localhost:"; + PORT 
+ "/test/single-param")
+                    .request(MediaType.MULTIPART_FORM_DATA_TYPE)
+                    .post(Entity.entity(new GenericEntity<>(multipart) { }, 
MediaType.MULTIPART_FORM_DATA))) {
+                Assert.assertEquals(200, response.getStatus());
+                final List<EntityPart> entityParts = response.readEntity(new 
GenericType<>() { });
+                if (entityParts.size() != 1) {
+                    Assert.fail("Expected 1 entries, received " + 
entityParts.size() + '.');
+                }
+                verifyEntityPart(entityParts, "received-entity-part", "test 
entity part");
+            }
+        }
+    }
+    
+    @Test
+    public void fileTest() throws Exception {
+        final String resource = 
"/org/apache/cxf/systest/jaxrs/resources/add_book.txt";
+        try (InputStream is = getClass().getResourceAsStream(resource)) {
+            final byte[] content = is.readAllBytes();
+            try (Client client = ClientBuilder.newClient()) {
+                final List<EntityPart> multipart = List.of(
+                        EntityPart.withFileName("file-part")
+                            .content(new ByteArrayInputStream(content))
+                            .mediaType(MediaType.APPLICATION_XML_TYPE)
+                            .build()
+                    );
+    
+                try (Response response = client.target("http://localhost:"; + 
PORT + "/test/file-param")
+                        .request(MediaType.MULTIPART_FORM_DATA_TYPE)
+                        .post(Entity.entity(new GenericEntity<>(multipart) { 
}, MediaType.MULTIPART_FORM_DATA))) {
+                    Assert.assertEquals(200, response.getStatus());
+                    final List<EntityPart> entityParts = 
response.readEntity(new GenericType<>() { });
+                    if (entityParts.size() != 1) {
+                        Assert.fail("Expected 1 entries, received " + 
entityParts.size() + '.');
+                    }
+                    verifyEntityPart(entityParts, "received-file-part", new 
String(content, StandardCharsets.UTF_8));
+                }
+            }
+        }
+    }
+
+    private static void verifyEntityPart(final List<EntityPart> parts, final 
String name, final String text)
+            throws IOException {
+        final EntityPart part = find(parts, name);
+        Assert.assertNotNull(part);
+        Assert.assertEquals(text, part.getContent(String.class));
+    }
+    
+    private static String toString(final InputStream in) throws IOException {
+        // try-with-resources fails here due to a bug in the
+        //noinspection TryFinallyCanBeTryWithResources
+        try {
+            final ByteArrayOutputStream out = new ByteArrayOutputStream();
+            byte[] buffer = new byte[32];
+            int len;
+            while ((len = in.read(buffer)) > 0) {
+                out.write(buffer, 0, len);
+            }
+            return out.toString(StandardCharsets.UTF_8);
+        } finally {
+            in.close();
+        }
+    }
+
+    private static EntityPart find(final Collection<EntityPart> parts, final 
String name) {
+        for (EntityPart part : parts) {
+            if (name.equals(part.getName())) {
+                return part;
+            }
+        }
+        return null;
+    }
+
+    @Path("/test")
+    public static class TestResource {
+        @POST
+        @Consumes(MediaType.MULTIPART_FORM_DATA)
+        @Produces(MediaType.MULTIPART_FORM_DATA)
+        @Path("/basicTest")
+        public Response basicTest(final List<EntityPart> parts) throws 
IOException {
+            final List<EntityPart> multipart = List.of(
+                    EntityPart.withName("received-string")
+                        .content(find(parts, 
"octet-stream").getContent(byte[].class))
+                        .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
+                        .build(),
+                    EntityPart.withName("received-file")
+                        .content(find(parts, "file").getFileName().get(), 
find(parts, "file").getContent())
+                        .mediaType(MediaType.APPLICATION_XML)
+                        .build(),
+                    EntityPart.withName("added-input-stream")
+                        .content(new ByteArrayInputStream("Add part on 
return.".getBytes()))
+                        .mediaType("text/asciidoc")
+                        .header("Header1", "Test1")
+                        .header("Header2", "Test2")
+                        .build());
+            return Response.ok(new GenericEntity<>(multipart) { }, 
MediaType.MULTIPART_FORM_DATA).build();
+        }
+
+        @POST
+        @Consumes(MediaType.MULTIPART_FORM_DATA)
+        @Produces(MediaType.MULTIPART_FORM_DATA)
+        @Path("/multi-form-param")
+        public Response multipleFormParamTest(@FormParam("string-part") final 
String string,
+                @FormParam("entity-part") final EntityPart entityPart,
+                @FormParam("input-stream-part") final InputStream in) throws 
IOException {
+            final List<EntityPart> multipart = List.of(
+                    EntityPart.withName("received-entity-part")
+                        .content(entityPart.getContent(String.class))
+                        .mediaType(entityPart.getMediaType())
+                        .fileName(entityPart.getFileName().orElse(null))
+                        .build(),
+                    EntityPart.withName("received-input-stream")
+                        
.content(MultipartSupportTest.toString(in).getBytes(StandardCharsets.UTF_8))
+                        .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
+                        .build(),
+                    EntityPart.withName("received-string")
+                        .content(string)
+                        .mediaType(MediaType.TEXT_PLAIN_TYPE)
+                        .build());
+            return Response.ok(new GenericEntity<>(multipart) { }, 
MediaType.MULTIPART_FORM_DATA).build();
+        }
+
+        @POST
+        @Consumes(MediaType.MULTIPART_FORM_DATA)
+        @Produces(MediaType.MULTIPART_FORM_DATA)
+        @Path("/single-form-param")
+        public Response singleFormParamTest(@FormParam("entity-part") final 
EntityPart entityPart) throws IOException {
+            final List<EntityPart> multipart = List.of(
+                    EntityPart.withName("received-entity-part")
+                        .content(entityPart.getContent(String.class))
+                        .mediaType(entityPart.getMediaType())
+                        .fileName(entityPart.getFileName().orElse(null))
+                        .build());
+            return Response.ok(new GenericEntity<>(multipart) { }, 
MediaType.MULTIPART_FORM_DATA).build();
+        }
+
+        @POST
+        @Consumes(MediaType.MULTIPART_FORM_DATA)
+        @Produces(MediaType.MULTIPART_FORM_DATA)
+        @Path("/single-param")
+        public Response singleParamTest(final List<EntityPart> parts) throws 
IOException {
+            final List<EntityPart> multipart = List.of(
+                    EntityPart.withName("received-entity-part")
+                        .content(find(parts, 
"entity-part").getContent(byte[].class))
+                        .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
+                        .build());
+            return Response.ok(new GenericEntity<>(multipart) { }, 
MediaType.MULTIPART_FORM_DATA).build();
+        }
+        
+        @POST
+        @Consumes(MediaType.MULTIPART_FORM_DATA)
+        @Produces(MediaType.MULTIPART_FORM_DATA)
+        @Path("/file-param")
+        public Response fileParamTest(@FormParam("file") final EntityPart 
filePart) throws IOException {
+            final List<EntityPart> multipart = List.of(
+                    EntityPart.withFileName("received-file-part")
+                        .content(filePart.getContent(byte[].class))
+                        .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
+                        .build());
+            return Response.ok(new GenericEntity<>(multipart) { }, 
MediaType.MULTIPART_FORM_DATA).build();
+        }
+    }
+}
\ No newline at end of file


Reply via email to