new InvocationBuilderListener SPI (#4313)
* new InvocationBuilderListener SPI
Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/InvocationBuilderListenerStage.java b/core-client/src/main/java/org/glassfish/jersey/client/InvocationBuilderListenerStage.java
new file mode 100644
index 0000000..dc947c6
--- /dev/null
+++ b/core-client/src/main/java/org/glassfish/jersey/client/InvocationBuilderListenerStage.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2019 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.client;
+
+import org.glassfish.jersey.client.spi.InvocationBuilderListener;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.model.internal.RankedComparator;
+
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Client request processing stage. During a request creation, when the {@link Invocation.Builder}
+ * would be created, this class is utilized.
+ */
+/* package */ class InvocationBuilderListenerStage {
+ final Iterator<InvocationBuilderListener> invocationBuilderListenerIterator;
+
+ /* package */ InvocationBuilderListenerStage(InjectionManager injectionManager) {
+ final RankedComparator<InvocationBuilderListener> comparator =
+ new RankedComparator<>(RankedComparator.Order.ASCENDING);
+ invocationBuilderListenerIterator = Providers
+ .getAllProviders(injectionManager, InvocationBuilderListener.class, comparator).iterator();
+ }
+
+ /* package */ void invokeListener(JerseyInvocation.Builder builder) {
+ while (invocationBuilderListenerIterator.hasNext()) {
+ invocationBuilderListenerIterator.next().onNewBuilder(new InvocationBuilderContextImpl(builder));
+ }
+ }
+
+ private static class InvocationBuilderContextImpl implements InvocationBuilderListener.InvocationBuilderContext {
+ private final JerseyInvocation.Builder builder;
+
+ private InvocationBuilderContextImpl(JerseyInvocation.Builder builder) {
+ this.builder = builder;
+ }
+
+ @Override
+ public InvocationBuilderListener.InvocationBuilderContext accept(String... mediaTypes) {
+ builder.accept(mediaTypes);
+ return this;
+ }
+
+ @Override
+ public InvocationBuilderListener.InvocationBuilderContext accept(MediaType... mediaTypes) {
+ builder.accept(mediaTypes);
+ return this;
+ }
+
+ @Override
+ public InvocationBuilderListener.InvocationBuilderContext acceptLanguage(Locale... locales) {
+ builder.acceptLanguage(locales);
+ return this;
+ }
+
+ @Override
+ public InvocationBuilderListener.InvocationBuilderContext acceptLanguage(String... locales) {
+ builder.acceptLanguage(locales);
+ return this;
+ }
+
+ @Override
+ public InvocationBuilderListener.InvocationBuilderContext acceptEncoding(String... encodings) {
+ builder.acceptEncoding(encodings);
+ return this;
+ }
+
+ @Override
+ public InvocationBuilderListener.InvocationBuilderContext cookie(Cookie cookie) {
+ builder.cookie(cookie);
+ return this;
+ }
+
+ @Override
+ public InvocationBuilderListener.InvocationBuilderContext cookie(String name, String value) {
+ builder.cookie(name, value);
+ return this;
+ }
+
+ @Override
+ public InvocationBuilderListener.InvocationBuilderContext cacheControl(CacheControl cacheControl) {
+ builder.cacheControl(cacheControl);
+ return this;
+ }
+
+ @Override
+ public List<String> getAccepted() {
+ return getHeader(HttpHeaders.ACCEPT);
+ }
+
+ @Override
+ public List<String> getAcceptedLanguages() {
+ return getHeader(HttpHeaders.ACCEPT_LANGUAGE);
+ }
+
+ @Override
+ public List<CacheControl> getCacheControls() {
+ return (List<CacheControl>) (List<?>) builder.request().getHeaders().get(HttpHeaders.CACHE_CONTROL);
+ }
+
+ @Override
+ public Configuration getConfiguration() {
+ return builder.request().getConfiguration();
+ }
+
+ @Override
+ public Map<String, Cookie> getCookies() {
+ return builder.request().getCookies();
+ }
+
+ @Override
+ public List<String> getEncodings() {
+ return getHeader(HttpHeaders.ACCEPT_ENCODING);
+ }
+
+ @Override
+ public List<String> getHeader(String name) {
+ return builder.request().getRequestHeader(name);
+ }
+
+ @Override
+ public MultivaluedMap<String, Object> getHeaders() {
+ return builder.request().getHeaders();
+ }
+
+ @Override
+ public Object getProperty(String name) {
+ return builder.request().getProperty(name);
+ }
+
+ @Override
+ public Collection<String> getPropertyNames() {
+ return builder.request().getPropertyNames();
+ }
+
+ @Override
+ public URI getUri() {
+ return builder.request().getUri();
+ }
+
+
+ @Override
+ public InvocationBuilderListener.InvocationBuilderContext header(String name, Object value) {
+ builder.header(name, value);
+ return this;
+ }
+
+ @Override
+ public InvocationBuilderListener.InvocationBuilderContext headers(MultivaluedMap<String, Object> headers) {
+ builder.headers(headers);
+ return this;
+ }
+
+ @Override
+ public InvocationBuilderListener.InvocationBuilderContext property(String name, Object value) {
+ builder.property(name, value);
+ return this;
+ }
+
+ @Override
+ public void removeProperty(String name) {
+ builder.request().removeProperty(name);
+ }
+ }
+}
+
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java b/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java
index 6c4f029..ed986d7 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java
@@ -188,14 +188,15 @@
@Override
public JerseyInvocation.Builder request() {
checkNotClosed();
- return new JerseyInvocation.Builder(getUri(), config.snapshot());
+ JerseyInvocation.Builder b = new JerseyInvocation.Builder(getUri(), config.snapshot());
+ return onBuilder(b);
}
@Override
public JerseyInvocation.Builder request(String... acceptedResponseTypes) {
checkNotClosed();
JerseyInvocation.Builder b = new JerseyInvocation.Builder(getUri(), config.snapshot());
- b.request().accept(acceptedResponseTypes);
+ onBuilder(b).request().accept(acceptedResponseTypes);
return b;
}
@@ -203,7 +204,7 @@
public JerseyInvocation.Builder request(MediaType... acceptedResponseTypes) {
checkNotClosed();
JerseyInvocation.Builder b = new JerseyInvocation.Builder(getUri(), config.snapshot());
- b.request().accept(acceptedResponseTypes);
+ onBuilder(b).request().accept(acceptedResponseTypes);
return b;
}
@@ -358,4 +359,9 @@
public String toString() {
return "JerseyWebTarget { " + targetUri.toTemplate() + " }";
}
+
+ private static JerseyInvocation.Builder onBuilder(JerseyInvocation.Builder builder) {
+ new InvocationBuilderListenerStage(builder.request().getInjectionManager()).invokeListener(builder);
+ return builder;
+ }
}
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/spi/InvocationBuilderListener.java b/core-client/src/main/java/org/glassfish/jersey/client/spi/InvocationBuilderListener.java
new file mode 100644
index 0000000..d0cd24c
--- /dev/null
+++ b/core-client/src/main/java/org/glassfish/jersey/client/spi/InvocationBuilderListener.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2019 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.client.spi;
+
+import org.glassfish.jersey.Beta;
+import org.glassfish.jersey.spi.Contract;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import java.net.URI;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Implementations of this interface will be notified when a new Invocation.Builder
+ * is created. This will allow implementations to access the invocation builders,
+ * and is intended for global providers. For example, the Invocation.Builder properties can be
+ * accessed to set properties that are available on the {@link javax.ws.rs.client.ClientRequestContext}.
+ * <p>
+ * In order for the InvocationBuilderListener to be called, the implementation of the interface needs
+ * to be registered on the {@code Client} the same way the {@code ClientRequestFilter} is registered, for instance.
+ *
+ * If multiple {@code InvocationBuilderListeners} are to be utilized, the order of execution is driven by the {@code Priority},
+ * the lower the priority value, the higher the priority, the sooner the execution.
+ *
+ * @since 2.30
+ */
+@Beta
+@Contract
+@ConstrainedTo(RuntimeType.CLIENT)
+public interface InvocationBuilderListener {
+
+ /**
+ * An {@link javax.ws.rs.client.Invocation.Builder} subset of setter methods.
+ */
+ public interface InvocationBuilderContext {
+ /**
+ * Add the accepted response media types.
+ *
+ * @param mediaTypes accepted response media types.
+ * @return the updated context.
+ */
+ InvocationBuilderContext accept(String... mediaTypes);
+
+ /**
+ * Add the accepted response media types.
+ *
+ * @param mediaTypes accepted response media types.
+ * @return the updated context.
+ */
+ InvocationBuilderContext accept(MediaType... mediaTypes);
+
+ /**
+ * Add acceptable languages.
+ *
+ * @param locales an array of the acceptable languages.
+ * @return the updated context.
+ */
+ InvocationBuilderContext acceptLanguage(Locale... locales);
+
+ /**
+ * Add acceptable languages.
+ *
+ * @param locales an array of the acceptable languages.
+ * @return the updated context.
+ */
+ InvocationBuilderContext acceptLanguage(String... locales);
+
+ /**
+ * Add acceptable encodings.
+ *
+ * @param encodings an array of the acceptable encodings.
+ * @return the updated context.
+ */
+ InvocationBuilderContext acceptEncoding(String... encodings);
+
+ /**
+ * Add a cookie to be set.
+ *
+ * @param cookie to be set.
+ * @return the updated context.
+ */
+ InvocationBuilderContext cookie(Cookie cookie);
+
+ /**
+ * Add a cookie to be set.
+ *
+ * @param name the name of the cookie.
+ * @param value the value of the cookie.
+ * @return the updated context.
+ */
+ InvocationBuilderContext cookie(String name, String value);
+
+ /**
+ * Set the cache control data of the message.
+ *
+ * @param cacheControl the cache control directives, if {@code null}
+ * any existing cache control directives will be removed.
+ * @return the updated context.
+ */
+ InvocationBuilderContext cacheControl(CacheControl cacheControl);
+
+ /**
+ * Get the accepted response media types.
+ *
+ * @return accepted response media types.
+ */
+ List<String> getAccepted();
+
+ /**
+ * Get acceptable languages.
+ *
+ * @return acceptable languages.
+ */
+ List<String> getAcceptedLanguages();
+
+ /**
+ * Get the cache control data of the message.
+ *
+ * @return the cache control data of the message.
+ */
+ List<CacheControl> getCacheControls();
+
+ /**
+ * Get runtime configuration.
+ *
+ * @return runtime configuration.
+ */
+ Configuration getConfiguration();
+
+ /**
+ * Get any cookies that accompanied the request.
+ *
+ * @return a read-only map of cookie name (String) to {@link javax.ws.rs.core.Cookie}.
+ */
+ Map<String, Cookie> getCookies();
+
+ /**
+ * Get acceptable encodings.
+ *
+ * @return acceptable encodings.
+ */
+ List<String> getEncodings();
+
+ /**
+ * Get the values of a HTTP request header. The returned List is read-only.
+ *
+ * @param name the header name, case insensitive.
+ * @return a read-only list of header values.
+ */
+ List<String> getHeader(String name);
+
+ /**
+ * Get the mutable message headers multivalued map.
+ *
+ * @return mutable multivalued map of message headers.
+ */
+ MultivaluedMap<String, Object> getHeaders();
+
+ /**
+ * Returns the property with the given name registered in the current request/response
+ * exchange context, or {@code null} if there is no property by that name.
+ * <p>
+ * A property allows filters and interceptors to exchange
+ * additional custom information not already provided by this interface.
+ * </p>
+ * <p>
+ * A list of supported properties can be retrieved using {@link #getPropertyNames()}.
+ * Custom property names should follow the same convention as package names.
+ * </p>
+ *
+ * @param name a {@code String} specifying the name of the property.
+ * @return an {@code Object} containing the value of the property, or
+ * {@code null} if no property exists matching the given name.
+ * @see #getPropertyNames()
+ */
+ Object getProperty(String name);
+
+ /**
+ * Returns an immutable {@link Collection collection} containing the property names
+ * available within the context of the current request/response exchange context.
+ * <p>
+ * Use the {@link #getProperty} method with a property name to get the value of
+ * a property.
+ * </p>
+ *
+ * @return an immutable {@link Collection collection} of property names.
+ * @see #getProperty
+ */
+ Collection<String> getPropertyNames();
+
+ /**
+ * Get the request URI.
+ *
+ * @return request URI.
+ */
+ URI getUri();
+
+ /**
+ * Add an arbitrary header.
+ *
+ * @param name the name of the header
+ * @param value the value of the header, the header will be serialized
+ * using a {@link javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate} if
+ * one is available via {@link javax.ws.rs.ext.RuntimeDelegate#createHeaderDelegate(java.lang.Class)}
+ * for the class of {@code value} or using its {@code toString} method
+ * if a header delegate is not available. If {@code value} is {@code null}
+ * then all current headers of the same name will be removed.
+ * @return the updated context.
+ */
+ InvocationBuilderContext header(String name, Object value);
+
+ /**
+ * Replaces all existing headers with the newly supplied headers.
+ *
+ * @param headers new headers to be set, if {@code null} all existing
+ * headers will be removed.
+ * @return the updated context.
+ */
+ InvocationBuilderContext headers(MultivaluedMap<String, Object> headers);
+
+ /**
+ * Set a new property in the context of a request represented by this invocation builder.
+ * <p>
+ * The property is available for a later retrieval via {@link ClientRequestContext#getProperty(String)}
+ * or {@link javax.ws.rs.ext.InterceptorContext#getProperty(String)}.
+ * If a property with a given name is already set in the request context,
+ * the existing value of the property will be updated.
+ * Setting a {@code null} value into a property effectively removes the property
+ * from the request property bag.
+ * </p>
+ *
+ * @param name property name.
+ * @param value (new) property value. {@code null} value removes the property
+ * with the given name.
+ * @return the updated context.
+ * @see Invocation#property(String, Object)
+ */
+ InvocationBuilderContext property(String name, Object value);
+
+ /**
+ * Removes a property with the given name from the current request/response
+ * exchange context. After removal, subsequent calls to {@link #getProperty}
+ * to retrieve the property value will return {@code null}.
+ *
+ * @param name a {@code String} specifying the name of the property to be removed.
+ */
+ void removeProperty(String name);
+ }
+
+ /**
+ * Whenever an {@link Invocation.Builder} is created, (i.e. when
+ * {@link WebTarget#request()}, {@link WebTarget#request(String...)},
+ * {@link WebTarget#request(MediaType...)} is called), this method would be invoked.
+ *
+ * @param context the updated {@link InvocationBuilderContext}.
+ */
+ void onNewBuilder(InvocationBuilderContext context);
+
+}
diff --git a/core-client/src/test/java/org/glassfish/jersey/client/spi/InvocationBuilderListenerTest.java b/core-client/src/test/java/org/glassfish/jersey/client/spi/InvocationBuilderListenerTest.java
new file mode 100644
index 0000000..ddb6e76
--- /dev/null
+++ b/core-client/src/test/java/org/glassfish/jersey/client/spi/InvocationBuilderListenerTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2019 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.client.spi;
+
+import org.glassfish.jersey.internal.PropertiesDelegate;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.annotation.Priority;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.RuntimeDelegate;
+import java.io.IOException;
+import java.util.Date;
+import java.util.Locale;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+
+public class InvocationBuilderListenerTest {
+
+ private static final String PROPERTY_NAME = "test_property";
+ private static final String ONE = "one";
+
+ private WebTarget target;
+
+ @Before
+ public void setUp() {
+ target = ClientBuilder.newClient().target("http://localhost:8080").register(AbortRequestFilter.class)
+ .register(new PropertySetterInvocationBuilderListener(a -> a.property(key(ONE), ONE)));
+ }
+
+ @Test
+ public void testRequest() throws ExecutionException, InterruptedException {
+ try (Response r = target.request().async().get().get()) {
+ assertDefault(r);
+ }
+ }
+
+ @Test
+ public void testRequestString() {
+ try (Response r = target.request(MediaType.TEXT_HTML).build("GET").invoke()) {
+ assertDefault(r);
+ }
+ }
+
+ @Test
+ public void testRequestMediaType() throws ExecutionException, InterruptedException {
+ try (Response r = target.request(MediaType.TEXT_PLAIN_TYPE).rx().get().toCompletableFuture().get()) {
+ assertDefault(r);
+ }
+ }
+
+ @Test
+ public void testConfigurationProperties() {
+ String value = "OTHER_VALUE";
+ try (Response r = target.property(key(ConfigurationInvocationBuilderListener.OTHER_PROPERTY), value)
+ .register(ConfigurationInvocationBuilderListener.class).request().get()) {
+ Assert.assertTrue(
+ r.readEntity(String.class).contains(key(ConfigurationInvocationBuilderListener.OTHER_PROPERTY) + "=" + value)
+ );
+ }
+ }
+
+ @Test
+ public void testGetters() {
+ try (Response r = target.register(SetterInvocationBuilderListener.class, 100)
+ .register(GetterInvocationBuilderListener.class, 200).request().get()) {
+ assertDefault(r);
+ }
+ }
+
+ private void assertDefault(Response response) {
+ Assert.assertEquals(key(ONE) + "=" + ONE, response.readEntity(String.class));
+ }
+
+ private static String key(String keySuffix) {
+ return new StringBuilder().append(PROPERTY_NAME).append('_').append(keySuffix).toString();
+ }
+
+ public static class PropertySetterInvocationBuilderListener implements InvocationBuilderListener {
+
+ private final Consumer<InvocationBuilderContext> builderConsumer;
+
+ public PropertySetterInvocationBuilderListener(Consumer<InvocationBuilderContext> builderConsumer) {
+ this.builderConsumer = builderConsumer;
+ }
+
+ @Override
+ public void onNewBuilder(InvocationBuilderContext context) {
+ builderConsumer.accept(context);
+ }
+ }
+
+ public static class AbortRequestFilter implements ClientRequestFilter {
+
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ for (String propertyName : requestContext.getPropertyNames()) {
+ if (propertyName.startsWith(PROPERTY_NAME)) {
+ sb.append(propertyName).append("=").append(requestContext.getProperty(propertyName));
+ }
+ }
+ requestContext.abortWith(Response.ok().entity(sb.toString()).build());
+ }
+ }
+
+ public static class ConfigurationInvocationBuilderListener implements InvocationBuilderListener {
+ static final String OTHER_PROPERTY = "OTHER_PROPERTY";
+
+ @Override
+ public void onNewBuilder(InvocationBuilderContext context) {
+ context.property(key(OTHER_PROPERTY), context.getConfiguration().getProperty(key(OTHER_PROPERTY)));
+ }
+ }
+
+ public static class SetterInvocationBuilderListener implements InvocationBuilderListener {
+
+ @Override
+ public void onNewBuilder(InvocationBuilderContext context) {
+ context.accept(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON_PATCH_JSON_TYPE)
+ .acceptEncoding("GZIP")
+ .acceptLanguage(Locale.GERMAN)
+ .acceptLanguage(new Locale.Builder().setLanguage("sr").setScript("Latn").setRegion("RS").build())
+ .property(PROPERTY_NAME, PROPERTY_NAME)
+ .cacheControl(CacheControl.valueOf(PROPERTY_NAME))
+ .cookie("Cookie", "CookieValue")
+ .header(HttpHeaders.CONTENT_ID, PROPERTY_NAME);
+ }
+ }
+
+ public static class GetterInvocationBuilderListener implements InvocationBuilderListener {
+
+ @Override
+ public void onNewBuilder(InvocationBuilderContext context) {
+ Date date = new Date();
+ RuntimeDelegate.HeaderDelegate localeDelegate = RuntimeDelegate.getInstance().createHeaderDelegate(Locale.class);
+ Assert.assertThat(context.getAccepted(),
+ Matchers.containsInAnyOrder(MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_PATCH_JSON));
+ Assert.assertThat(context.getEncodings(), Matchers.contains("GZIP"));
+ Assert.assertThat(context.getAcceptedLanguages(),
+ Matchers.containsInAnyOrder(localeDelegate.toString(Locale.GERMAN),
+ localeDelegate.toString(
+ new Locale.Builder().setLanguage("sr").setScript("Latn").setRegion("RS").build()
+ )
+ )
+ );
+
+ Assert.assertThat(context.getHeader(HttpHeaders.CONTENT_ID), Matchers.contains(PROPERTY_NAME));
+ context.getHeaders().add(HttpHeaders.DATE, date);
+ Assert.assertThat(context.getHeader(HttpHeaders.DATE), Matchers.notNullValue());
+ Assert.assertThat(context.getHeaders().getFirst(HttpHeaders.DATE), Matchers.is(date));
+
+ Assert.assertNotNull(context.getUri());
+ Assert.assertTrue(context.getUri().toASCIIString().startsWith("http://"));
+
+ Assert.assertThat(context.getPropertyNames(), Matchers.contains(PROPERTY_NAME));
+ Assert.assertThat(context.getProperty(PROPERTY_NAME), Matchers.is(PROPERTY_NAME));
+ context.removeProperty(PROPERTY_NAME);
+ Assert.assertTrue(context.getPropertyNames().isEmpty());
+
+ Assert.assertThat(context.getCacheControls().get(0).toString(),
+ Matchers.is(CacheControl.valueOf(PROPERTY_NAME).toString())
+ );
+ Assert.assertThat(context.getCookies().size(), Matchers.is(1));
+ Assert.assertThat(context.getCookies().get("Cookie"), Matchers.notNullValue());
+ }
+ }
+}