Implementation of EntityPart API (#4859)
* Implementation of EntityPart API
Signed-off-by: jansupol <jan.supol@oracle.com>
diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/multipart/JerseyEntityPart.java b/core-common/src/main/java/org/glassfish/jersey/innate/multipart/JerseyEntityPart.java
new file mode 100644
index 0000000..944a230
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/innate/multipart/JerseyEntityPart.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+package org.glassfish.jersey.innate.multipart;
+
+import jakarta.ws.rs.core.EntityPart;
+
+import java.lang.reflect.Type;
+
+/**
+ * Jersey extended {@code EntityPart}. Contains arbitrary useful methods.
+ *
+ * @since 3.1.0
+ */
+public interface JerseyEntityPart extends EntityPart {
+ /**
+ * Converts the content stream for this part to the specified class and returns
+ * it.
+ *
+ * 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 similar {@code getContent} method.
+ *
+ * @param <T> type parameter of the value returned
+ * @param type the {@code Class} that the implementation should convert this
+ * part to
+ * @param <T> the entity type
+ * @return an instance of the specified {@code Class} representing the content
+ * of this part
+ * @throws IllegalStateException if this method or any of the other
+ * {@code getContent} methods has already been
+ * invoked
+ */
+ <T> T getContent(Class<T> type, Type genericType);
+}
diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/multipart/package-info.java b/core-common/src/main/java/org/glassfish/jersey/innate/multipart/package-info.java
new file mode 100644
index 0000000..e6ebf1f
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/innate/multipart/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Multipart Jersey innate classes. This innate package will be opened by JPMS only to Jersey-media-multipart.
+ */
+package org.glassfish.jersey.innate.multipart;
diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/package-info.java b/core-common/src/main/java/org/glassfish/jersey/innate/package-info.java
new file mode 100644
index 0000000..b0648e7
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/innate/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey innate packages. The innate packages will not be opened by JPMS outside of Jersey.
+ */
+package org.glassfish.jersey.innate;
diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/spi/EntityPartBuilderProvider.java b/core-common/src/main/java/org/glassfish/jersey/innate/spi/EntityPartBuilderProvider.java
new file mode 100644
index 0000000..606e212
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/innate/spi/EntityPartBuilderProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.innate.spi;
+
+import jakarta.ws.rs.core.EntityPart;
+
+/**
+ * Jersey extension of provider of EntityPart.Builder.
+ * A service meant to be implemented solely by Jersey.
+ *
+ * @since 3.1.0
+ */
+public interface EntityPartBuilderProvider {
+
+ /**
+ * @param partName name of the part to create within the multipart entity.
+ * @return {@link EntityPart.Builder} for building new {@link EntityPart} instances.
+ */
+ public EntityPart.Builder withName(String partName);
+}
diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/spi/package-info.java b/core-common/src/main/java/org/glassfish/jersey/innate/spi/package-info.java
new file mode 100644
index 0000000..3d70312
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/innate/spi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Common Jersey innate SPI classes. The innate package will not be opened by JPMS.
+ */
+package org.glassfish.jersey.innate.spi;
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/AbstractRuntimeDelegate.java b/core-common/src/main/java/org/glassfish/jersey/internal/AbstractRuntimeDelegate.java
index aa439db..39940fb 100644
--- a/core-common/src/main/java/org/glassfish/jersey/internal/AbstractRuntimeDelegate.java
+++ b/core-common/src/main/java/org/glassfish/jersey/internal/AbstractRuntimeDelegate.java
@@ -25,14 +25,20 @@
import jakarta.ws.rs.core.CacheControl;
import jakarta.ws.rs.core.Configuration;
import jakarta.ws.rs.core.Cookie;
+import jakarta.ws.rs.core.EntityPart;
import jakarta.ws.rs.core.EntityTag;
import jakarta.ws.rs.core.Link;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.NewCookie;
import jakarta.ws.rs.core.Response.ResponseBuilder;
import jakarta.ws.rs.core.UriBuilder;
+import jakarta.ws.rs.ext.ParamConverter;
import jakarta.ws.rs.ext.RuntimeDelegate;
+import org.glassfish.jersey.innate.spi.EntityPartBuilderProvider;
+import org.glassfish.jersey.internal.util.collection.LazyValue;
+import org.glassfish.jersey.internal.util.collection.Value;
+import org.glassfish.jersey.internal.util.collection.Values;
import org.glassfish.jersey.message.internal.JerseyLink;
import org.glassfish.jersey.message.internal.OutboundJaxrsResponse;
import org.glassfish.jersey.message.internal.OutboundMessageContext;
@@ -50,6 +56,8 @@
private final Set<HeaderDelegateProvider> hps;
private final Map<Class<?>, HeaderDelegate<?>> map;
+ private LazyValue<EntityPartBuilderProvider> entityPartBuilderProvider = Values.lazy(
+ (Value<EntityPartBuilderProvider>) () -> findEntityPartBuilderProvider());
/**
* Initialization constructor. The injection manager will be shut down.
@@ -117,4 +125,24 @@
return null;
}
+
+ @Override
+ public EntityPart.Builder createEntityPartBuilder(String partName) throws IllegalArgumentException {
+ return entityPartBuilderProvider.get().withName(partName);
+ }
+
+ /**
+ * Obtain a {@code RuntimeDelegate} instance using the method described in {@link #getInstance}.
+ *
+ * @return an instance of {@code RuntimeDelegate}.
+ */
+ private static EntityPartBuilderProvider findEntityPartBuilderProvider() {
+ for (final EntityPartBuilderProvider entityPartBuilder : ServiceFinder.find(EntityPartBuilderProvider.class)) {
+ if (entityPartBuilder != null) {
+ return entityPartBuilder;
+ }
+ }
+
+ throw new IllegalArgumentException(LocalizationMessages.NO_ENTITYPART_BUILDER_FOUND());
+ }
}
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateImpl.java b/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateImpl.java
index 4895008..58602e2 100644
--- a/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateImpl.java
+++ b/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateImpl.java
@@ -72,15 +72,6 @@
throw new UnsupportedOperationException(LocalizationMessages.NO_CONTAINER_AVAILABLE());
}
- @Override
- public EntityPart.Builder createEntityPartBuilder(String partName) throws IllegalArgumentException {
- final RuntimeDelegate runtimeDelegate = findServerDelegate();
- if (runtimeDelegate != null) {
- return runtimeDelegate.createEntityPartBuilder(partName);
- }
- throw new UnsupportedOperationException(LocalizationMessages.NO_CONTAINER_AVAILABLE());
- }
-
// TODO : Do we need multiple RuntimeDelegates?
private RuntimeDelegate findServerDelegate() {
for (RuntimeDelegate delegate : ServiceFinder.find(RuntimeDelegate.class)) {
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/inject/ParamConverters.java b/core-common/src/main/java/org/glassfish/jersey/internal/inject/ParamConverters.java
index 6a234e5..d1b3e9a 100644
--- a/core-common/src/main/java/org/glassfish/jersey/internal/inject/ParamConverters.java
+++ b/core-common/src/main/java/org/glassfish/jersey/internal/inject/ParamConverters.java
@@ -17,12 +17,16 @@
package org.glassfish.jersey.internal.inject;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
import java.security.AccessController;
import java.text.ParseException;
import java.util.Date;
@@ -256,6 +260,39 @@
}
/**
+ * Provider of {@link ParamConverter param converter} that convert the supplied string into a Java
+ * {@link InputStream} instance.
+ */
+ public static class InputStreamProvider implements ParamConverterProvider {
+
+ @Override
+ public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
+ return rawType != InputStream.class ? null : new ParamConverter<T>() {
+
+ @Override
+ public T fromString(String value) {
+ if (value == null) {
+ throw new IllegalArgumentException(LocalizationMessages.METHOD_PARAMETER_CANNOT_BE_NULL("value"));
+ }
+ return rawType.cast(new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8)));
+ }
+
+ @Override
+ public String toString(T value) {
+ if (value == null) {
+ throw new IllegalArgumentException(LocalizationMessages.METHOD_PARAMETER_CANNOT_BE_NULL("value"));
+ }
+ try {
+ return new String(((InputStream) value).readAllBytes());
+ } catch (IOException ioe) {
+ throw new ExtractorException(ioe);
+ }
+ }
+ };
+ }
+ }
+
+ /**
* Provider of {@link ParamConverter param converter} that produce the Optional instance
* by invoking {@link ParamConverterProvider}.
*/
@@ -414,6 +451,7 @@
new TypeFromStringEnum(),
new TypeValueOf(),
new CharacterProvider(),
+ new InputStreamProvider(),
new TypeFromString(),
new StringConstructor(),
new OptionalCustomProvider(manager),
diff --git a/core-common/src/main/resources/org/glassfish/jersey/internal/localization.properties b/core-common/src/main/resources/org/glassfish/jersey/internal/localization.properties
index 285b9de..b60bf25 100644
--- a/core-common/src/main/resources/org/glassfish/jersey/internal/localization.properties
+++ b/core-common/src/main/resources/org/glassfish/jersey/internal/localization.properties
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2018 Payara Foundation and/or its affiliates.
#
# This program and the accompanying materials are made available under the
@@ -116,6 +116,7 @@
new.cookie.is.null=New cookie is null.
no.container.available=No container available.
no.error.processing.in.scope=There is no error processing in scope.
+no.entitypart.builder.found="No EntityPart.Builder implementation found. Is jersey-media-multipart on a classpath?";
not.supported.on.outbound.message=Method not supported on an outbound message.
osgi.registry.error.opening.resource.stream=Unable to open an input stream for resource {0}.
osgi.registry.error.processing.resource.stream=Unexpected error occurred while processing resource stream {0}.
diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/TestRuntimeDelegate.java b/core-common/src/test/java/org/glassfish/jersey/internal/TestRuntimeDelegate.java
index 8d77ffc..de4815e 100644
--- a/core-common/src/test/java/org/glassfish/jersey/internal/TestRuntimeDelegate.java
+++ b/core-common/src/test/java/org/glassfish/jersey/internal/TestRuntimeDelegate.java
@@ -60,11 +60,6 @@
throw new UnsupportedOperationException("Not supported yet.");
}
- @Override
- public EntityPart.Builder createEntityPartBuilder(String partName) throws IllegalArgumentException {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
public void testMediaType() {
MediaType m = new MediaType("text", "plain");
Assert.assertNotNull(m);
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java
index ac823d7..07d0f46 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java
@@ -111,10 +111,4 @@
};
});
}
-
- @Override
- public EntityPart.Builder createEntityPartBuilder(String partName) throws IllegalArgumentException {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
}
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/FormParamValueParamProvider.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/FormParamValueParamProvider.java
index f1fcbeb..de6fd72 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/FormParamValueParamProvider.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/FormParamValueParamProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,19 +16,25 @@
package org.glassfish.jersey.server.internal.inject;
+import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
import java.net.URLDecoder;
import java.net.URLEncoder;
+import java.security.AccessController;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiFunction;
import java.util.function.Function;
import jakarta.ws.rs.Encoded;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.EntityPart;
import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
@@ -36,8 +42,16 @@
import jakarta.inject.Provider;
import jakarta.inject.Singleton;
+import jakarta.ws.rs.ext.RuntimeDelegate;
+import org.glassfish.jersey.innate.multipart.JerseyEntityPart;
import org.glassfish.jersey.internal.inject.ExtractorException;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.internal.util.collection.LazyValue;
import org.glassfish.jersey.internal.util.collection.NullableMultivaluedHashMap;
+import org.glassfish.jersey.internal.util.collection.Value;
+import org.glassfish.jersey.internal.util.collection.Values;
import org.glassfish.jersey.message.internal.MediaTypes;
import org.glassfish.jersey.message.internal.ReaderWriter;
import org.glassfish.jersey.server.ContainerRequest;
@@ -45,6 +59,7 @@
import org.glassfish.jersey.server.internal.InternalServerProperties;
import org.glassfish.jersey.server.internal.LocalizationMessages;
import org.glassfish.jersey.server.model.Parameter;
+import org.glassfish.jersey.server.spi.internal.ValueParamProvider;
/**
* Value factory provider supporting the {@link FormParam} injection annotation.
@@ -55,13 +70,17 @@
@Singleton
final class FormParamValueParamProvider extends AbstractValueParamProvider {
+ private final MultipartFormParamValueProvider multipartProvider;
/**
* Injection constructor.
*
* @param mpep extractor provider.
+ * @param injectionManager
*/
- public FormParamValueParamProvider(Provider<MultivaluedParameterExtractorProvider> mpep) {
+ public FormParamValueParamProvider(Provider<MultivaluedParameterExtractorProvider> mpep,
+ InjectionManager injectionManager) {
super(mpep, Parameter.Source.FORM);
+ this.multipartProvider = new MultipartFormParamValueProvider(injectionManager);
}
@Override
@@ -77,18 +96,24 @@
if (e == null) {
return null;
}
- return new FormParamValueProvider(e, !parameter.isEncoded());
+ return new FormParamValueProvider(e, multipartProvider, !parameter.isEncoded(), parameter);
}
private static final class FormParamValueProvider implements Function<ContainerRequest, Object> {
private static final Annotation encodedAnnotation = getEncodedAnnotation();
private final MultivaluedParameterExtractor<?> extractor;
+ private final MultipartFormParamValueProvider multipartProvider;
private final boolean decode;
+ private final Parameter parameter;
- FormParamValueProvider(MultivaluedParameterExtractor<?> extractor, boolean decode) {
+ FormParamValueProvider(MultivaluedParameterExtractor<?> extractor,
+ MultipartFormParamValueProvider multipartProvider,
+ boolean decode, Parameter parameter) {
this.extractor = extractor;
+ this.multipartProvider = multipartProvider;
this.decode = decode;
+ this.parameter = parameter;
}
private static Form getCachedForm(final ContainerRequest request, boolean decode) {
@@ -121,24 +146,27 @@
@Override
public Object apply(ContainerRequest request) {
- Form form = getCachedForm(request, decode);
+ if (MediaTypes.typeEqual(MediaType.MULTIPART_FORM_DATA_TYPE, request.getMediaType())) {
+ return multipartProvider.apply(request, parameter);
+ } else {
+ Form form = getCachedForm(request, decode);
- if (form == null) {
- Form otherForm = getCachedForm(request, !decode);
- if (otherForm != null) {
- form = switchUrlEncoding(request, otherForm);
- cacheForm(request, form);
- } else {
- form = getForm(request);
+ if (form == null) {
+ Form otherForm = getCachedForm(request, !decode);
+ if (otherForm != null) {
+ form = switchUrlEncoding(request, otherForm);
+ } else {
+ form = getForm(request);
+ }
cacheForm(request, form);
}
- }
- try {
- return extractor.extract(form.asMap());
- } catch (ExtractorException e) {
- throw new ParamException.FormParamException(e.getCause(),
- extractor.getName(), extractor.getDefaultValueString());
+ try {
+ return extractor.extract(form.asMap());
+ } catch (ExtractorException e) {
+ throw new ParamException.FormParamException(e.getCause(),
+ extractor.getName(), extractor.getDefaultValueString());
+ }
}
}
@@ -199,4 +227,70 @@
}
}
}
+
+ @Singleton
+ private static class MultipartFormParamValueProvider implements BiFunction<ContainerRequest, Parameter, Object> {
+ private static final class FormParamHolder {
+ @FormParam("name")
+ public static final Void dummy = null; // field to get an instance of FormParam annotation
+ }
+ private static Parameter entityPartParameter =
+ Parameter.create(
+ EntityPart.class, EntityPart.class, false, EntityPart.class, EntityPart.class,
+ AccessController.doPrivileged(ReflectionHelper.getDeclaredFieldsPA(FormParamHolder.class))[0]
+ .getAnnotations()
+ );
+
+ private final InjectionManager injectionManager;
+ private final LazyValue<ValueParamProvider> entityPartProvider;
+
+ private MultipartFormParamValueProvider(InjectionManager injectionManager) {
+ this.injectionManager = injectionManager;
+
+ //Get the provider from jersey-media-multipart
+ entityPartProvider = Values.lazy((Value<ValueParamProvider>) () -> {
+ Set<ValueParamProvider> providers = Providers.getProviders(injectionManager, ValueParamProvider.class);
+ for (ValueParamProvider vfp : providers) {
+ Function<ContainerRequest, ?> paramValueSupplier = vfp.getValueProvider(entityPartParameter);
+ if (paramValueSupplier != null && !FormParamValueParamProvider.class.isInstance(vfp)) {
+ return vfp;
+ }
+ }
+ return null;
+ });
+ }
+
+
+ @Override
+ public Object apply(ContainerRequest containerRequest, Parameter parameter) {
+ Object entity = null;
+ if (entityPartProvider.get() != null) { // else jersey-multipart module is missing
+ final Function<ContainerRequest, ?> valueSupplier = entityPartProvider.get().getValueProvider(
+ new WrappingFormParamParameter(entityPartParameter, parameter));
+ final JerseyEntityPart entityPart = (JerseyEntityPart) valueSupplier.apply(containerRequest);
+ try {
+ entity = parameter.getType() != parameter.getRawType()
+ ? entityPart.getContent(parameter.getRawType(), parameter.getType())
+ : entityPart.getContent(parameter.getRawType());
+ } catch (IOException e) {
+ throw new ProcessingException(e);
+ }
+ }
+
+ return entity;
+ }
+
+ private static class WrappingFormParamParameter extends Parameter {
+ protected WrappingFormParamParameter(Parameter entityPartDataParam, Parameter realDataParam) {
+ super(realDataParam.getAnnotations(),
+ realDataParam.getSourceAnnotation(),
+ realDataParam.getSource(),
+ realDataParam.getSourceName(),
+ entityPartDataParam.getRawType(),
+ entityPartDataParam.getType(),
+ realDataParam.isEncoded(),
+ realDataParam.getDefaultValue());
+ }
+ }
+ }
}
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/ValueParamProviderConfigurator.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/ValueParamProviderConfigurator.java
index b9ddc55..07337eb 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/ValueParamProviderConfigurator.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/ValueParamProviderConfigurator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -87,7 +87,7 @@
EntityParamValueParamProvider entityProvider = new EntityParamValueParamProvider(paramExtractor);
suppliers.add(entityProvider);
- FormParamValueParamProvider formProvider = new FormParamValueParamProvider(paramExtractor);
+ FormParamValueParamProvider formProvider = new FormParamValueParamProvider(paramExtractor, injectionManager);
suppliers.add(formProvider);
HeaderParamValueParamProvider headerProvider = new HeaderParamValueParamProvider(paramExtractor);
diff --git a/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/ParamConverterInternalTest.java b/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/ParamConverterInternalTest.java
index 5cd8568..8ec946a 100644
--- a/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/ParamConverterInternalTest.java
+++ b/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/ParamConverterInternalTest.java
@@ -17,6 +17,8 @@
package org.glassfish.jersey.server.internal.inject;
+import java.io.IOException;
+import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
@@ -48,6 +50,7 @@
import org.glassfish.jersey.server.RequestContextBuilder;
import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
@@ -300,6 +303,24 @@
}
}
+ @Path("/")
+ public static class InpuStreamConverterTestResource {
+ @GET
+ public String inputStream(@QueryParam("param") InputStream inputStream) throws IOException {
+ return new String(inputStream.readAllBytes());
+ }
+ }
+
+ @Test
+ public void inputStreamTest() throws ExecutionException, InterruptedException {
+ initiateWebApplication(InpuStreamConverterTestResource.class);
+
+ final ContainerResponse responseContext = getResponseContext(UriBuilder.fromPath("/")
+ .queryParam("param", "Hello").build().toString());
+
+ Assert.assertEquals("Hello", responseContext.getEntity());
+ }
+
public static class MyEagerParamProvider implements ParamConverterProvider {
@Override
diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/BodyPart.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/BodyPart.java
index 0e20e25..7287759 100644
--- a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/BodyPart.java
+++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/BodyPart.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -18,9 +18,11 @@
import java.io.IOException;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
import java.text.ParseException;
import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.MessageBodyReader;
@@ -263,21 +265,42 @@
* entity instance is not the unconverted content of the body part entity.
*/
public <T> T getEntityAs(final Class<T> clazz) {
+ return getEntityAs(clazz, clazz);
+ }
+
+ /**
+ * Returns the entity after appropriate conversion to the requested type. This is useful only when the containing
+ * {@link MultiPart} instance has been received, which causes the {@code providers} property to have been set.
+ *
+ * @param genericEntity desired entity type into which the entity should be converted.
+ * @return entity after appropriate conversion to the requested type.
+ *
+ * @throws ProcessingException if an IO error arises during reading an entity.
+ * @throws IllegalArgumentException if no {@link MessageBodyReader} can be found to perform the requested conversion.
+ * @throws IllegalStateException if this method is called when the {@code providers} property has not been set or when the
+ * entity instance is not the unconverted content of the body part entity.
+ */
+ <T> T getEntityAs(final GenericType<T> genericEntity) {
+ return (T) getEntityAs(genericEntity.getRawType(), genericEntity.getType());
+ }
+
+ <T> T getEntityAs(final Class<T> type, Type genericType) {
if (entity == null || !(entity instanceof BodyPartEntity)) {
throw new IllegalStateException(LocalizationMessages.ENTITY_HAS_WRONG_TYPE());
}
- if (clazz == BodyPartEntity.class) {
- return clazz.cast(entity);
+ if (type == BodyPartEntity.class) {
+ return type.cast(entity);
}
final Annotation[] annotations = new Annotation[0];
- final MessageBodyReader<T> reader = messageBodyWorkers.getMessageBodyReader(clazz, clazz, annotations, mediaType);
+ final MessageBodyReader<T> reader = messageBodyWorkers.getMessageBodyReader(type, genericType, annotations, mediaType);
if (reader == null) {
- throw new IllegalArgumentException(LocalizationMessages.NO_AVAILABLE_MBR(clazz, mediaType));
+ throw new IllegalArgumentException(LocalizationMessages.NO_AVAILABLE_MBR(type, mediaType));
}
try {
- return reader.readFrom(clazz, clazz, annotations, mediaType, headers, ((BodyPartEntity) entity).getInputStream());
+ return reader.readFrom(type, genericType, annotations, mediaType, headers,
+ ((BodyPartEntity) entity).getInputStream());
} catch (final IOException ioe) {
throw new ProcessingException(LocalizationMessages.ERROR_READING_ENTITY(String.class), ioe);
}
diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/FormDataBodyPart.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/FormDataBodyPart.java
index 2380c44..2643b5a 100644
--- a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/FormDataBodyPart.java
+++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/FormDataBodyPart.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,11 +16,18 @@
package org.glassfish.jersey.media.multipart;
+import java.io.InputStream;
+import java.lang.reflect.Type;
import java.text.ParseException;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.EntityPart;
+import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.MediaType;
+import org.glassfish.jersey.innate.multipart.JerseyEntityPart;
import org.glassfish.jersey.media.multipart.internal.LocalizationMessages;
import org.glassfish.jersey.message.internal.MediaTypes;
@@ -59,9 +66,10 @@
* @author Paul Sandoz
* @author Michal Gajdos
*/
-public class FormDataBodyPart extends BodyPart {
+public class FormDataBodyPart extends BodyPart implements JerseyEntityPart, EntityPart {
private final boolean fileNameFix;
+ protected final AtomicBoolean contentRead = new AtomicBoolean(false);
/**
* Instantiates an unnamed new {@link FormDataBodyPart} with a
@@ -231,6 +239,43 @@
return formDataContentDisposition.getName();
}
+ @Override
+ public Optional<String> getFileName() {
+ return Optional.ofNullable(getFormDataContentDisposition().getFileName());
+ }
+
+ @Override
+ public InputStream getContent() {
+ return getContent(InputStream.class);
+ }
+
+ @Override
+ public <T> T getContent(Class<T> type) {
+ if (contentRead.compareAndExchange(false, true)) {
+ throw new IllegalStateException(LocalizationMessages.CONTENT_HAS_BEEN_ALREADY_READ());
+ }
+ final Object entity = getEntity();
+ return type.isInstance(entity) ? type.cast(entity) : getEntityAs(type);
+ }
+
+ @Override
+ public <T> T getContent(GenericType<T> type) {
+ if (contentRead.compareAndExchange(false, true)) {
+ throw new IllegalStateException(LocalizationMessages.CONTENT_HAS_BEEN_ALREADY_READ());
+ }
+ final Object entity = getEntity();
+ return type.getRawType().isInstance(entity) ? (T) entity : getEntityAs(type);
+ }
+
+ @Override
+ public <T> T getContent(Class<T> type, Type genericType) {
+ if (contentRead.compareAndExchange(false, true)) {
+ throw new IllegalStateException(LocalizationMessages.CONTENT_HAS_BEEN_ALREADY_READ());
+ }
+ final Object entity = getEntity();
+ return type.isInstance(entity) ? (T) entity : getEntityAs(type, genericType);
+ }
+
/**
* Sets the control name.
*
diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/JerseyEntityPartBuilderProvider.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/JerseyEntityPartBuilderProvider.java
new file mode 100644
index 0000000..61583d1
--- /dev/null
+++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/JerseyEntityPartBuilderProvider.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.media.multipart;
+
+import jakarta.ws.rs.WebApplicationException;
+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.MultivaluedHashMap;
+import jakarta.ws.rs.core.MultivaluedMap;
+import org.glassfish.jersey.innate.spi.EntityPartBuilderProvider;
+import org.glassfish.jersey.media.multipart.file.FileDataBodyPart;
+import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart;
+import org.glassfish.jersey.media.multipart.internal.LocalizationMessages;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Jersey implementation of {@link EntityPart.Builder}.
+ * @since 3.1.0
+ */
+public class JerseyEntityPartBuilderProvider implements EntityPartBuilderProvider {
+
+ @Override
+ public EntityPart.Builder withName(String partName) {
+ return new EnityPartBuilder().withName(partName);
+ }
+
+ private static class EnityPartBuilder implements EntityPart.Builder {
+
+ private String partName;
+ private String fileName = null;
+ private MultivaluedMap<String, String> headers = new MultivaluedHashMap<>();
+ private MediaType mediaType = null;
+ private MethodData methodData;
+
+ private EntityPart.Builder withName(String partName) {
+ this.partName = partName;
+ return this;
+ }
+
+ @Override
+ public EntityPart.Builder mediaType(MediaType mediaType) throws IllegalArgumentException {
+ this.mediaType = mediaType;
+ return this;
+ }
+
+ @Override
+ public EntityPart.Builder mediaType(String mediaTypeString) throws IllegalArgumentException {
+ this.mediaType = MediaType.valueOf(mediaTypeString);
+ return this;
+ }
+
+ @Override
+ public EntityPart.Builder header(String headerName, String... headerValues) throws IllegalArgumentException {
+ this.headers.addAll(headerName, headerValues);
+ return this;
+ }
+
+ @Override
+ public EntityPart.Builder headers(MultivaluedMap<String, String> newHeaders) throws IllegalArgumentException {
+ for (Map.Entry<String, List<String>> entry : newHeaders.entrySet()) {
+ header(entry.getKey(), entry.getValue().toArray(new String[0]));
+ }
+ return this;
+ }
+
+ @Override
+ public EntityPart.Builder fileName(String fileName) throws IllegalArgumentException {
+ this.fileName = fileName;
+ return this;
+ }
+
+ @Override
+ public EntityPart.Builder content(InputStream content) throws IllegalArgumentException {
+ methodData = new InputStreamMethodData(content);
+ return this;
+ }
+
+ @Override
+ public <T> EntityPart.Builder content(T content, Class<? extends T> type) throws IllegalArgumentException {
+ if (File.class.equals(type)) {
+ methodData = new FileMethodData((File) content);
+ } else if (InputStream.class.equals(type)) {
+ methodData = new InputStreamMethodData((InputStream) content);
+ } else {
+ methodData = new GenericData(content, null);
+ }
+ return this;
+ }
+
+ @Override
+ public <T> EntityPart.Builder content(T content, GenericType<T> type) throws IllegalArgumentException {
+ if (File.class.equals(type.getRawType())) {
+ methodData = new FileMethodData((File) content);
+ } else if (InputStream.class.equals(type.getRawType())) {
+ methodData = new InputStreamMethodData((InputStream) content);
+ } else {
+ methodData = new GenericData(content, type);
+ }
+ return this;
+ }
+
+ @Override
+ public EntityPart build() throws IllegalStateException, IOException, WebApplicationException {
+ if (methodData == null) {
+ throw new IllegalStateException(LocalizationMessages.ENTITY_CONTENT_NOT_SET());
+ }
+ final FormDataBodyPart bodyPart = methodData.build();
+ return bodyPart;
+ }
+
+
+ private abstract class MethodData<T> {
+ protected MethodData(T content) {
+ this.content = content;
+ }
+ protected final T content;
+ protected abstract FormDataBodyPart build();
+ protected void fillFormData(FormDataBodyPart bodyPart) {
+ FormDataContentDisposition contentDisposition =
+ FormDataContentDisposition.name(partName).fileName(fileName).build();
+ bodyPart.setContentDisposition(contentDisposition);
+ if (mediaType != null) {
+ bodyPart.setMediaType(mediaType);
+ }
+ for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
+ bodyPart.getHeaders().addAll(entry.getKey(), entry.getValue().toArray(new String[0]));
+ }
+ }
+ }
+
+ private class InputStreamMethodData extends MethodData<InputStream> {
+ protected InputStreamMethodData(InputStream content) {
+ super(content);
+ }
+
+ @Override
+ protected FormDataBodyPart build() {
+ final StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart();
+ streamDataBodyPart.setFilename(fileName);
+ fillFormData(streamDataBodyPart);
+ streamDataBodyPart.setStreamEntity(content);
+ return streamDataBodyPart;
+ }
+ }
+
+ private class FileMethodData extends MethodData<File> {
+ protected FileMethodData(File content) {
+ super(content);
+ }
+
+ @Override
+ protected FormDataBodyPart build() {
+ final FileDataBodyPart fileDataBodyPart = new FileDataBodyPart();
+ fillFormData(fileDataBodyPart);
+ fileDataBodyPart.setFileEntity(content);
+ return fileDataBodyPart;
+ }
+ }
+
+ private class GenericData extends MethodData<Object> {
+ private final GenericType<?> genericEntity;
+
+ protected GenericData(Object content, GenericType<?> genericEntity) {
+ super(content);
+ this.genericEntity = genericEntity;
+ }
+
+ @Override
+ protected FormDataBodyPart build() {
+ final FormDataBodyPart formDataBodyPart = new FormDataBodyPart();
+ fillFormData(formDataBodyPart);
+ if (genericEntity != null && !GenericEntity.class.isInstance(content)) {
+ GenericEntity entity = new GenericEntity(content, genericEntity.getType());
+ formDataBodyPart.setEntity(entity);
+ } else {
+ formDataBodyPart.setEntity(content);
+ }
+
+ return formDataBodyPart;
+ }
+ }
+ }
+}
diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/MultiPartFeature.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/MultiPartFeature.java
index 3224ec5..03bf661 100644
--- a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/MultiPartFeature.java
+++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/MultiPartFeature.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -20,6 +20,8 @@
import jakarta.ws.rs.core.Feature;
import jakarta.ws.rs.core.FeatureContext;
+import org.glassfish.jersey.media.multipart.internal.EntityPartReader;
+import org.glassfish.jersey.media.multipart.internal.EntityPartWriter;
import org.glassfish.jersey.media.multipart.internal.FormDataParamInjectionFeature;
import org.glassfish.jersey.media.multipart.internal.MultiPartReaderClientSide;
import org.glassfish.jersey.media.multipart.internal.MultiPartReaderServerSide;
@@ -45,6 +47,9 @@
context.register(MultiPartWriter.class);
+ context.register(EntityPartReader.class);
+ context.register(EntityPartWriter.class);
+
return true;
}
}
diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/MultiPartFeatureAutodiscoverable.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/MultiPartFeatureAutodiscoverable.java
new file mode 100644
index 0000000..f9cfb9d
--- /dev/null
+++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/MultiPartFeatureAutodiscoverable.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.media.multipart;
+
+import jakarta.ws.rs.core.FeatureContext;
+import org.glassfish.jersey.internal.spi.AutoDiscoverable;
+
+/**
+ * Automatic registration of {@link MultiPartFeature}.
+ * @since 3.1.0
+ */
+public class MultiPartFeatureAutodiscoverable implements AutoDiscoverable {
+ @Override
+ public void configure(FeatureContext context) {
+ context.register(MultiPartFeature.class);
+ }
+}
diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/file/StreamDataBodyPart.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/file/StreamDataBodyPart.java
index 3ddedf2..707bedf 100644
--- a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/file/StreamDataBodyPart.java
+++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/file/StreamDataBodyPart.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -18,6 +18,7 @@
import java.io.InputStream;
import java.text.MessageFormat;
+import java.util.Optional;
import jakarta.ws.rs.core.MediaType;
@@ -275,4 +276,8 @@
return filename;
}
+ @Override
+ public Optional<String> getFileName() {
+ return Optional.ofNullable(getFilename());
+ }
}
diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/EntityPartReader.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/EntityPartReader.java
new file mode 100644
index 0000000..864cec4
--- /dev/null
+++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/EntityPartReader.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.media.multipart.internal;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.EntityPart;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.ext.MessageBodyReader;
+import jakarta.ws.rs.ext.Providers;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.media.multipart.BodyPart;
+import org.glassfish.jersey.media.multipart.FormDataBodyPart;
+import org.glassfish.jersey.media.multipart.JerseyEntityPartBuilderProvider;
+import org.glassfish.jersey.media.multipart.MultiPart;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Reader supporting List<EntityPart> Make sure {@code GenericEntity} class is used when reading the
+ * {@link EntityPart}s.
+ * @since 3.1.0
+ */
+@Consumes(MediaType.MULTIPART_FORM_DATA)
+@Singleton
+public class EntityPartReader implements MessageBodyReader<List<EntityPart>> {
+
+ private MultiPartReaderClientSide multiPartReaderClientSide;
+
+ @Inject
+ Providers providers;
+
+ @Override
+ public boolean isReadable(Class<?> type, Type generic, Annotation[] annotations, MediaType mediaType) {
+ return List.class.isAssignableFrom(type)
+ && ParameterizedType.class.isInstance(generic)
+ && EntityPart.class.isAssignableFrom(ReflectionHelper.getGenericTypeArgumentClasses(generic).get(0));
+ }
+
+ @Override
+ public List<EntityPart> readFrom(Class<List<EntityPart>> type, Type genericType, Annotation[] annotations,
+ MediaType mediaType, MultivaluedMap<String, String> httpHeaders,
+ InputStream entityStream) throws IOException, WebApplicationException {
+
+ if (multiPartReaderClientSide == null) {
+ multiPartReaderClientSide = (MultiPartReaderClientSide) providers.getMessageBodyReader(
+ MultiPart.class, MultiPart.class, new Annotation[0], MediaType.MULTIPART_FORM_DATA_TYPE);
+ }
+
+ final MultiPart multiPart = multiPartReaderClientSide.readFrom(
+ MultiPart.class, MultiPart.class, annotations, mediaType, httpHeaders, entityStream);
+ final List<BodyPart> bodyParts = multiPart.getBodyParts();
+ final List<EntityPart> entityParts = new LinkedList<>();
+
+ for (BodyPart bp : bodyParts) {
+ if (FormDataBodyPart.class.isInstance(bp)) {
+ entityParts.add((EntityPart) bp);
+ } else {
+ final EntityPart ep = new JerseyEntityPartBuilderProvider().withName("")
+ .mediaType(bp.getMediaType()).content(bp.getEntity()).headers(bp.getHeaders()).build();
+ entityParts.add(ep);
+ }
+ }
+
+ return entityParts;
+ }
+}
diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/EntityPartWriter.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/EntityPartWriter.java
new file mode 100644
index 0000000..f5c3472
--- /dev/null
+++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/EntityPartWriter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.media.multipart.internal;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.EntityPart;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.ext.MessageBodyWriter;
+import jakarta.ws.rs.ext.Providers;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.media.multipart.BodyPart;
+import org.glassfish.jersey.media.multipart.MultiPart;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * Writer supporting List<EntityPart> Make sure {@code GenericEntity} class is used when sending the
+ * {@link EntityPart}s.
+ * @since 3.1.0
+ */
+@Produces(MediaType.MULTIPART_FORM_DATA)
+@Singleton
+public class EntityPartWriter implements MessageBodyWriter<List<EntityPart>> {
+
+ private MultiPartWriter multiPartWriter;
+
+ @Inject
+ Providers providers;
+
+ @Override
+ public boolean isWriteable(Class<?> type, Type generic, Annotation[] annotations, MediaType mediaType) {
+ return List.class.isAssignableFrom(type)
+ && ParameterizedType.class.isInstance(generic)
+ && EntityPart.class.isAssignableFrom(ReflectionHelper.getGenericTypeArgumentClasses(generic).get(0));
+ }
+
+ @Override
+ public void writeTo(List<EntityPart> entityParts, Class<?> type, Type genericType, Annotation[] annotations,
+ MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
+ throws IOException, WebApplicationException {
+ final MultiPart multiPart = new MultiPart();
+ multiPart.setMediaType(mediaType);
+ for (EntityPart ep : entityParts) {
+ multiPart.bodyPart((BodyPart) ep);
+ }
+
+ if (multiPartWriter == null) {
+ multiPartWriter = (MultiPartWriter) providers.getMessageBodyWriter(
+ MultiPart.class, MultiPart.class, new Annotation[0], MediaType.MULTIPART_FORM_DATA_TYPE);
+ }
+
+ multiPartWriter.writeTo(multiPart, MultiPart.class, MultiPart.class,
+ annotations, multiPart.getMediaType(), httpHeaders, entityStream);
+ }
+}
diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/FormDataParamValueParamProvider.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/FormDataParamValueParamProvider.java
index d95b320..6c834d9 100644
--- a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/FormDataParamValueParamProvider.java
+++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/FormDataParamValueParamProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -31,6 +31,8 @@
import java.util.stream.Collectors;
import jakarta.ws.rs.BadRequestException;
+import jakarta.ws.rs.FormParam;
+import jakarta.ws.rs.core.EntityPart;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.MessageBodyReader;
@@ -59,7 +61,7 @@
/**
* Value supplier provider supporting the {@link FormDataParam} injection annotation and entity ({@link FormDataMultiPart})
- * injection.
+ * injection. Also supports {@link FormParam} {@code EntityPart} annotation injection.
*
* @author Craig McClanahan
* @author Paul Sandoz
@@ -311,6 +313,43 @@
}
}
+ /**
+ * Provider supplier for list of {@link EntityPart} types injected via
+ * {@link jakarta.ws.rs.FormParam} annotation.
+ */
+ private final class ListEntityPartValueProvider extends ValueProvider<List<EntityPart>> {
+
+ private final String name;
+
+ public ListEntityPartValueProvider(final String name) {
+ this.name = name;
+ }
+
+ @Override
+ public List<EntityPart> apply(ContainerRequest request) {
+ return (List<EntityPart>) (List<?>) getEntity(request).getFields(name);
+ }
+ }
+
+ /**
+ * Provider supplier for list of {@link EntityPart} types injected via
+ * {@link jakarta.ws.rs.FormParam} annotation.
+ */
+ private final class EntityPartValueProvider extends ValueProvider<EntityPart> {
+
+ private final String name;
+
+ public EntityPartValueProvider(final String name) {
+ this.name = name;
+ }
+
+ @Override
+ public EntityPart apply(ContainerRequest request) {
+ List<FormDataBodyPart> bodyParts = getEntity(request).getFields(name);
+ return bodyParts.size() != 0 ? bodyParts.get(0) : null;
+ }
+ }
+
private static final Set<Class<?>> TYPES = initializeTypes();
private static Set<Class<?>> initializeTypes() {
@@ -344,7 +383,7 @@
* @param extractorProvider multi-valued map parameter extractor provider.
*/
public FormDataParamValueParamProvider(Provider<MultivaluedParameterExtractorProvider> extractorProvider) {
- super(extractorProvider, Parameter.Source.ENTITY, Parameter.Source.UNKNOWN);
+ super(extractorProvider, Parameter.Source.ENTITY, Parameter.Source.FORM, Parameter.Source.UNKNOWN);
}
@Override
@@ -386,6 +425,16 @@
} else {
return new FormDataParamValueProvider(parameter, get(parameter));
}
+ } else if (FormParam.class.equals(parameter.getSourceAnnotation().annotationType())) {
+ final String paramName = parameter.getSourceName();
+ if (Collection.class == rawType || List.class == rawType) {
+ final Class clazz = ReflectionHelper.getGenericTypeArgumentClasses(parameter.getType()).get(0);
+ if (EntityPart.class.equals(clazz)) {
+ return new ListEntityPartValueProvider(paramName);
+ }
+ } else if (EntityPart.class.equals(rawType)) {
+ return new EntityPartValueProvider(paramName);
+ }
}
return null;
diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/MultiPartWriter.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/MultiPartWriter.java
index 4e155f8..011096a 100644
--- a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/MultiPartWriter.java
+++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/MultiPartWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -30,6 +30,7 @@
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.GenericEntity;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
@@ -195,14 +196,21 @@
bodyEntity = ((BodyPartEntity) bodyEntity).getInputStream();
}
+ Type bodyType = bodyClass;
+ if (GenericEntity.class.isInstance(bodyEntity)) {
+ bodyClass = ((GenericEntity<?>) bodyEntity).getRawType();
+ bodyType = ((GenericEntity) bodyEntity).getType();
+ bodyEntity = ((GenericEntity<?>) bodyEntity).getEntity();
+ }
+
final MessageBodyWriter bodyWriter = providers.getMessageBodyWriter(
bodyClass,
- bodyClass,
+ bodyType,
EMPTY_ANNOTATIONS,
bodyMediaType);
if (bodyWriter == null) {
- throw new IllegalArgumentException(LocalizationMessages.NO_AVAILABLE_MBW(bodyClass, mediaType));
+ throw new IllegalArgumentException(LocalizationMessages.NO_AVAILABLE_MBW(bodyClass, bodyMediaType));
}
bodyWriter.writeTo(
diff --git a/media/multipart/src/main/resources/META-INF/services/org.glassfish.jersey.innate.spi.EntityPartBuilderProvider b/media/multipart/src/main/resources/META-INF/services/org.glassfish.jersey.innate.spi.EntityPartBuilderProvider
new file mode 100644
index 0000000..32738ac
--- /dev/null
+++ b/media/multipart/src/main/resources/META-INF/services/org.glassfish.jersey.innate.spi.EntityPartBuilderProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.media.multipart.JerseyEntityPartBuilderProvider
\ No newline at end of file
diff --git a/media/multipart/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable b/media/multipart/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
new file mode 100644
index 0000000..04a6776
--- /dev/null
+++ b/media/multipart/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
@@ -0,0 +1 @@
+org.glassfish.jersey.media.multipart.MultiPartFeatureAutodiscoverable
\ No newline at end of file
diff --git a/media/multipart/src/main/resources/org/glassfish/jersey/media/multipart/internal/localization.properties b/media/multipart/src/main/resources/org/glassfish/jersey/media/multipart/internal/localization.properties
index 851c026..904214d 100644
--- a/media/multipart/src/main/resources/org/glassfish/jersey/media/multipart/internal/localization.properties
+++ b/media/multipart/src/main/resources/org/glassfish/jersey/media/multipart/internal/localization.properties
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License v. 2.0, which is available at
@@ -15,6 +15,8 @@
#
cannot.inject.file=Cannot provide file for an entity body part.
+content.has.been.already.read=Content method has already been invoked.
+entity.content.not.set=EntityPart content is not set.
entity.has.wrong.type=Entity instance does not contain the unconverted content.
error.parsing.content.disposition=Error parsing content disposition: {0}
error.reading.entity=Error reading entity as {0}.
diff --git a/media/multipart/src/test/java/org/glassfish/jersey/media/multipart/internal/EntityPartTest.java b/media/multipart/src/test/java/org/glassfish/jersey/media/multipart/internal/EntityPartTest.java
new file mode 100644
index 0000000..91e3368
--- /dev/null
+++ b/media/multipart/src/test/java/org/glassfish/jersey/media/multipart/internal/EntityPartTest.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.media.multipart.internal;
+
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.FormParam;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Application;
+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.MultivaluedMap;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.MessageBodyReader;
+import jakarta.ws.rs.ext.MessageBodyWriter;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class EntityPartTest extends JerseyTest {
+
+ private static final GenericType<List<EntityPart>> LIST_ENTITY_PART_TYPE = new GenericType<List<EntityPart>>(){};
+ private static final GenericType<AtomicReference<String>> ATOMIC_REFERENCE_GENERIC_TYPE = new GenericType<>(){};
+
+ @Path("/")
+ public static class EntityPartTestResource {
+ @GET
+ public Response getResponse() throws IOException {
+ List<EntityPart> list = new LinkedList<>();
+ list.add(EntityPart.withName("part-01").content("TEST1").build());
+ list.add(EntityPart.withName("part-02").content("TEST2").build());
+ GenericEntity<List<EntityPart>> genericEntity = new GenericEntity<>(list) {};
+ return Response.ok(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE).build();
+ }
+
+ @POST
+ @Path("/postList")
+ public String postEntityPartList(List<EntityPart> list) throws IOException {
+ String entity = list.get(0).getContent(String.class) + list.get(1).getContent(String.class);
+ return entity;
+ }
+
+ @POST
+ @Path("/postForm")
+ public String postEntityPartForm(@FormParam("part-01") EntityPart part1, @FormParam("part-02") EntityPart part2)
+ throws IOException {
+ String entity = part1.getContent(String.class) + part2.getContent(String.class);
+ return entity;
+ }
+
+ @POST
+ @Path("/postListForm")
+ public String postEntityPartForm(@FormParam("part-0x") List<EntityPart> part)
+ throws IOException {
+ String entity = part.get(0).getContent(String.class) + part.get(1).getContent(String.class);
+ return entity;
+ }
+
+ @POST
+ @Path("/postStreams")
+ public Response postEntityStreams(@FormParam("name1") EntityPart part1,
+ @FormParam("name2") EntityPart part2,
+ @FormParam("name3") EntityPart part3) throws IOException {
+ List<EntityPart> list = new LinkedList<>();
+ list.add(EntityPart.withName(part1.getName()).fileName(part1.getFileName().get()).content(
+ new ByteArrayInputStream(part1.getContent(String.class).getBytes(StandardCharsets.UTF_8))).build());
+ list.add(EntityPart.withName(part2.getName()).fileName(part2.getFileName().get()).content(
+ new ByteArrayInputStream(part2.getContent(String.class).getBytes(StandardCharsets.UTF_8))).build());
+ list.add(EntityPart.withName(part3.getName()).fileName(part3.getFileName().get())
+ .content(part3.getContent(StringHolder.class), StringHolder.class)
+ .mediaType(part3.getMediaType()).build());
+ GenericEntity<List<EntityPart>> genericEntity = new GenericEntity<>(list) {};
+ return Response.ok(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE).build();
+ }
+
+ @POST
+ @Path("/postHeaders")
+ public Response postEntityStreams(@FormParam("name1") EntityPart part1) throws IOException {
+ List<EntityPart> list = new LinkedList<>();
+ list.add(EntityPart.withName(part1.getName()).content(part1.getContent(String.class))
+ .headers(part1.getHeaders()).build());
+ GenericEntity<List<EntityPart>> genericEntity = new GenericEntity<>(list) {};
+ return Response.ok(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE).build();
+ }
+
+ @POST
+ @Path("/postGeneric")
+ public Response postGeneric(@FormParam("name1") EntityPart part1) throws IOException {
+ List<EntityPart> list = new LinkedList<>();
+ list.add(EntityPart.withName(part1.getName())
+ .content(part1.getContent(ATOMIC_REFERENCE_GENERIC_TYPE), ATOMIC_REFERENCE_GENERIC_TYPE)
+ .mediaType(MediaType.TEXT_PLAIN_TYPE)
+ .build());
+ GenericEntity<List<EntityPart>> genericEntity = new GenericEntity<>(list) {};
+ return Response.ok(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE).build();
+ }
+
+ @POST
+ @Path("/postFormVarious")
+ public Response postFormVarious(@FormParam("name1") EntityPart part1,
+ @FormParam("name2") InputStream part2,
+ @FormParam("name3") String part3) throws IOException {
+ List<EntityPart> list = new LinkedList<>();
+ list.add(EntityPart.withName(part1.getName())
+ .content(part1.getContent(String.class) + new String(part2.readAllBytes()) + part3)
+ .mediaType(MediaType.TEXT_PLAIN_TYPE)
+ .build());
+ GenericEntity<List<EntityPart>> genericEntity = new GenericEntity<>(list) {};
+ return Response.ok(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE).build();
+ }
+
+ @GET
+ @Produces(MediaType.MULTIPART_FORM_DATA)
+ @Path("/getList")
+ public List<EntityPart> getList() throws IOException {
+ List<EntityPart> list = new LinkedList<>();
+ list.add(EntityPart.withName("name1").content("data1").build());
+ return list;
+ }
+ }
+
+ public static class StringHolder extends AtomicReference<String> {
+ StringHolder(String name) {
+ set(name);
+ }
+ }
+
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Produces(MediaType.TEXT_PLAIN)
+ public static class StringHolderWorker implements MessageBodyReader<StringHolder>, MessageBodyWriter<StringHolder> {
+
+ @Override
+ public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+ return type == StringHolder.class;
+ }
+
+ @Override
+ public StringHolder readFrom(Class<StringHolder> type, Type genericType, Annotation[] annotations,
+ MediaType mediaType, MultivaluedMap<String, String> httpHeaders,
+ InputStream entityStream) throws IOException, WebApplicationException {
+ final StringHolder holder = new StringHolder(new String(entityStream.readAllBytes()));
+ return holder;
+ }
+
+ @Override
+ public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+ return type == StringHolder.class;
+ }
+
+ @Override
+ public void writeTo(StringHolder s, Class<?> type, Type genericType, Annotation[] annotations,
+ MediaType mediaType, MultivaluedMap<String, Object> httpHeaders,
+ OutputStream entityStream) throws IOException, WebApplicationException {
+ entityStream.write(s.get().getBytes(StandardCharsets.UTF_8));
+ }
+ }
+
+ @Override
+ protected Application configure() {
+ return new ResourceConfig(EntityPartTestResource.class,
+ StringHolderWorker.class, AtomicReferenceProvider.class)
+ .property(ServerProperties.WADL_FEATURE_DISABLE, true);
+ }
+
+ @Override
+ protected void configureClient(ClientConfig config) {
+ config.register(StringHolderWorker.class).register(AtomicReferenceProvider.class);
+ }
+
+ @Test
+ public void getEntityPartListTest() throws IOException {
+ try (Response response = target().request().get()) {
+ List<EntityPart> list = response.readEntity(LIST_ENTITY_PART_TYPE);
+ Assert.assertEquals("TEST1", list.get(0).getContent(String.class));
+ Assert.assertEquals("TEST2", list.get(1).getContent(String.class));
+ }
+ }
+
+ @Test
+ public void postEntityPartListTest() throws IOException {
+ List<EntityPart> list = new LinkedList<>();
+ list.add(EntityPart.withName("part-01").content("TEST").build());
+ list.add(EntityPart.withName("part-02").content("1").build());
+ GenericEntity<List<EntityPart>> genericEntity = new GenericEntity<>(list) {};
+ Entity entity = Entity.entity(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE);
+ try (Response response = target("/postList").request().post(entity)) {
+ Assert.assertEquals("TEST1", response.readEntity(String.class));
+ }
+ }
+
+ @Test
+ public void postEntityPartFormParamTest() throws IOException {
+ List<EntityPart> list = new LinkedList<>();
+ list.add(EntityPart.withName("part-01").content("TEST").build());
+ list.add(EntityPart.withName("part-02").content("1").build());
+ GenericEntity<List<EntityPart>> genericEntity = new GenericEntity<>(list) {};
+ Entity entity = Entity.entity(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE);
+ try (Response response = target("/postForm").request().post(entity)) {
+ Assert.assertEquals("TEST1", response.readEntity(String.class));
+ }
+ }
+
+ @Test
+ public void postListEntityPartFormParamTest() throws IOException {
+ List<EntityPart> list = new LinkedList<>();
+ list.add(EntityPart.withName("part-0x").content("TEST").build());
+ list.add(EntityPart.withName("part-0x").content("1").build());
+ GenericEntity<List<EntityPart>> genericEntity = new GenericEntity<>(list) {};
+ Entity entity = Entity.entity(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE);
+ try (Response response = target("/postListForm").request().post(entity)) {
+ Assert.assertEquals("TEST1", response.readEntity(String.class));
+ }
+ }
+
+ @Test
+ public void postEntityPartStreamsTest() throws IOException {
+ List<EntityPart> list = new LinkedList<>();
+ list.add(EntityPart.withName("name1").fileName("file1.doc").content(
+ new ByteArrayInputStream("data1".getBytes(StandardCharsets.UTF_8))).build());
+ list.add(EntityPart.withName("name2").fileName("file2.doc").content(
+ new ByteArrayInputStream("data2".getBytes(StandardCharsets.UTF_8))).build());
+ list.add(EntityPart.withName("name3").fileName("file3.xml")
+ .content(new StringHolder("data3"), StringHolder.class)
+ .mediaType(MediaType.TEXT_PLAIN_TYPE).build());
+ GenericEntity<List<EntityPart>> genericEntity = new GenericEntity<>(list) {
+ };
+ Entity entity = Entity.entity(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE);
+
+ try (Response response = target("/postStreams").request().post(entity)) {
+ List<EntityPart> result = response.readEntity(LIST_ENTITY_PART_TYPE);
+
+ EntityPart part1 = result.get(0);
+ Assert.assertEquals("name1", part1.getName());
+ Assert.assertEquals("file1.doc", part1.getFileName().get());
+ Assert.assertEquals("data1", part1.getContent(String.class));
+
+ EntityPart part2 = result.get(1);
+ Assert.assertEquals("name2", part2.getName());
+ Assert.assertEquals("file2.doc", part2.getFileName().get());
+ Assert.assertEquals("data2", part2.getContent(String.class));
+
+ EntityPart part3 = result.get(2);
+ Assert.assertEquals("name3", part3.getName());
+ Assert.assertEquals("file3.xml", part3.getFileName().get());
+ Assert.assertEquals("data3", part3.getContent(String.class));
+ Assert.assertEquals(MediaType.TEXT_PLAIN_TYPE, part3.getMediaType());
+ }
+ }
+
+ @Test
+ public void postHeaderTest() throws IOException {
+ List<EntityPart> list = new LinkedList<>();
+ list.add(EntityPart.withName("name1").content("data1")
+ .header("header-01", "value-01").build());
+ GenericEntity<List<EntityPart>> genericEntity = new GenericEntity<>(list) {};
+ Entity entity = Entity.entity(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE);
+ try (Response response = target("/postHeaders").request().post(entity)) {
+ List<EntityPart> result = response.readEntity(LIST_ENTITY_PART_TYPE);
+ Assert.assertEquals("value-01", result.get(0).getHeaders().getFirst("header-01"));
+ Assert.assertEquals("data1", result.get(0).getContent(String.class));
+ }
+ }
+
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Produces(MediaType.TEXT_PLAIN)
+ public static class AtomicReferenceProvider implements
+ MessageBodyReader<AtomicReference<String>>,
+ MessageBodyWriter<AtomicReference<String>> {
+
+ @Override
+ public boolean isReadable(Class<?> type, Type generic, Annotation[] annotations, MediaType mediaType) {
+ return type == AtomicReference.class
+ && ParameterizedType.class.isInstance(generic)
+ && String.class.isAssignableFrom(ReflectionHelper.getGenericTypeArgumentClasses(generic).get(0));
+ }
+
+ @Override
+ public AtomicReference<String> readFrom(Class<AtomicReference<String>> type, Type genericType,
+ Annotation[] annotations, MediaType mediaType,
+ MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
+ throws IOException, WebApplicationException {
+ return new AtomicReference<String>(new String(entityStream.readAllBytes(), StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+ return isReadable(type, genericType, annotations, mediaType);
+ }
+
+ @Override
+ public void writeTo(AtomicReference<String> stringAtomicReference, Class<?> type, Type genericType,
+ Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders,
+ OutputStream entityStream) throws IOException, WebApplicationException {
+ entityStream.write(stringAtomicReference.get().getBytes(StandardCharsets.UTF_8));
+ }
+ }
+
+ @Test
+ public void genericEntityTest() throws IOException {
+ List<EntityPart> list = new LinkedList<>();
+ list.add(EntityPart.withName("name1")
+ .content(new AtomicReference<String>("data1"), ATOMIC_REFERENCE_GENERIC_TYPE)
+ .mediaType(MediaType.TEXT_PLAIN_TYPE)
+ .build());
+ GenericEntity<List<EntityPart>> genericEntity = new GenericEntity<>(list) {};
+ Entity entity = Entity.entity(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE);
+ try (Response response = target("/postGeneric").request().post(entity)) {
+ List<EntityPart> result = response.readEntity(LIST_ENTITY_PART_TYPE);
+ Assert.assertEquals("data1", result.get(0).getContent(String.class));
+ }
+ }
+
+ @Test
+ public void postVariousTest() throws IOException {
+ List<EntityPart> list = new LinkedList<>();
+ list.add(EntityPart.withName("name1").content("Hello ").build());
+ list.add(EntityPart.withName("name2").content("world").build());
+ list.add(EntityPart.withName("name3").content("!").build());
+ GenericEntity<List<EntityPart>> genericEntity = new GenericEntity<>(list) {
+ };
+ Entity entity = Entity.entity(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE);
+
+ try (Response response = target("/postFormVarious").request().post(entity)) {
+ List<EntityPart> result = response.readEntity(LIST_ENTITY_PART_TYPE);
+ Assert.assertEquals("Hello world!", result.get(0).getContent(String.class));
+ }
+ }
+
+ @Test
+ public void getListTest() throws IOException {
+ try (Response response = target("/getList").request().get()) {
+ List<EntityPart> result = response.readEntity(LIST_ENTITY_PART_TYPE);
+ Assert.assertEquals("data1", result.get(0).getContent(String.class));
+ }
+ }
+}
diff --git a/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/TestRuntimeDelegate.java b/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/TestRuntimeDelegate.java
index 81c75d4..dd7c562 100644
--- a/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/TestRuntimeDelegate.java
+++ b/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/TestRuntimeDelegate.java
@@ -61,11 +61,6 @@
throw new UnsupportedOperationException("Not supported yet.");
}
- @Override
- public EntityPart.Builder createEntityPartBuilder(String partName) throws IllegalArgumentException {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
public void testMediaType() {
MediaType m = new MediaType("text", "plain");
Assert.assertNotNull(m);