Skip to content
This repository was archived by the owner on May 28, 2018. It is now read-only.

Commit 7bd6748

Browse files
shamohGerrit Code Review
authored andcommitted
Merge "Resolved JERSEY-2540."
2 parents c0f8c14 + bd80bde commit 7bd6748

File tree

5 files changed

+559
-4
lines changed

5 files changed

+559
-4
lines changed

connectors/grizzly-connector/src/main/java/org/glassfish/jersey/grizzly/connector/GrizzlyConnector.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,13 @@ class GrizzlyConnector implements Connector {
101101
/**
102102
* Create new connector based on Grizzly asynchronous client library.
103103
*
104-
* @param client Jersey client instance to create the connector for.
105-
* @param config Jersey client runtime configuration to be used to configure the connector parameters.
104+
* @param client Jersey client instance to create the connector for.
105+
* @param config Jersey client runtime configuration to be used to configure the connector parameters.
106+
* @param asyncClientCustomizer Async HTTP Client configuration builder customizer.
106107
*/
107-
GrizzlyConnector(Client client, Configuration config) {
108+
GrizzlyConnector(final Client client,
109+
final Configuration config,
110+
final GrizzlyConnectorProvider.AsyncClientCustomizer asyncClientCustomizer) {
108111
AsyncHttpClientConfig.Builder builder = new AsyncHttpClientConfig.Builder();
109112

110113
ExecutorService executorService;
@@ -161,6 +164,10 @@ class GrizzlyConnector implements Connector {
161164
builder.setHostnameVerifier(client.getHostnameVerifier());
162165
}
163166

167+
if (asyncClientCustomizer != null) {
168+
builder = asyncClientCustomizer.customize(client, config, builder);
169+
}
170+
164171
AsyncHttpClientConfig asyncClientConfig = builder.build();
165172

166173
this.grizzlyClient = new AsyncHttpClient(new GrizzlyAsyncHttpProvider(asyncClientConfig), asyncClientConfig);
@@ -401,6 +408,14 @@ public OutputStream getOutputStream(int contentLength) throws IOException {
401408
builder.setBody(getEntityWriter(requestContext));
402409
}
403410
}
411+
412+
final GrizzlyConnectorProvider.RequestCustomizer requestCustomizer = requestContext.resolveProperty(
413+
GrizzlyConnectorProvider.REQUEST_CUSTOMIZER,
414+
GrizzlyConnectorProvider.RequestCustomizer.class);
415+
if (requestCustomizer != null) {
416+
builder = requestCustomizer.customize(requestContext, builder);
417+
}
418+
404419
return builder.build();
405420
}
406421

connectors/grizzly-connector/src/main/java/org/glassfish/jersey/grizzly/connector/GrizzlyConnectorProvider.java

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,20 @@
4040
package org.glassfish.jersey.grizzly.connector;
4141

4242
import javax.ws.rs.client.Client;
43+
import javax.ws.rs.client.Invocation;
4344
import javax.ws.rs.core.Configurable;
4445
import javax.ws.rs.core.Configuration;
4546

47+
import org.glassfish.jersey.client.ClientConfig;
48+
import org.glassfish.jersey.client.ClientRequest;
4649
import org.glassfish.jersey.client.Initializable;
4750
import org.glassfish.jersey.client.spi.Connector;
4851
import org.glassfish.jersey.client.spi.ConnectorProvider;
52+
import org.glassfish.jersey.internal.util.Property;
4953

5054
import com.ning.http.client.AsyncHttpClient;
55+
import com.ning.http.client.AsyncHttpClientConfig;
56+
import com.ning.http.client.RequestBuilder;
5157

5258
/**
5359
* Connector provider for Jersey {@link Connector connectors} that utilize
@@ -86,9 +92,135 @@
8692
* @since 2.5
8793
*/
8894
public class GrizzlyConnectorProvider implements ConnectorProvider {
95+
/**
96+
* A {@link GrizzlyConnectorProvider.RequestCustomizer request customizer} instance to be used to customize the
97+
* request.
98+
*
99+
* The value MUST be an instance implementing the {@link GrizzlyConnectorProvider.RequestCustomizer} SPI.
100+
* <p>
101+
* A default value is not set (is {@code null}).
102+
* </p>
103+
* <p>
104+
* The name of the configuration property is <tt>{@value}</tt>.
105+
* </p>
106+
*
107+
* @see #register(Invocation.Builder, GrizzlyConnectorProvider.RequestCustomizer)
108+
* @see org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider.RequestCustomizer
109+
*/
110+
@Property
111+
static final String REQUEST_CUSTOMIZER = "jersey.config.grizzly.client.request.customizer";
112+
113+
private final AsyncClientCustomizer asyncClientCustomizer;
114+
115+
/**
116+
* A customization SPI for the async client instance underlying Grizzly connectors.
117+
* <p>
118+
* An implementation of async client customizer can be
119+
* registered in a {@code GrizzlyConnectorProvider}
120+
* {@link GrizzlyConnectorProvider#GrizzlyConnectorProvider(GrizzlyConnectorProvider.AsyncClientCustomizer) constructor}.
121+
* When a connector instance is then created, the customizer is invoked to update the
122+
* {@link com.ning.http.client.AsyncHttpClientConfig.Builder underlying async client configuration builder} before the actual
123+
* configuration instance is built and used to create the async client instance.
124+
* The customizer thus provides a way how to configure parts of the underlying async client SPI that are not directly
125+
* exposed in the {@code GrizzlyConnectorProvider} API.
126+
* </p>
127+
*
128+
* @see org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider.RequestCustomizer
129+
* @since 2.10
130+
*/
131+
public static interface AsyncClientCustomizer {
132+
/**
133+
* Customize the underlying asynchronous client configuration builder.
134+
* <p>
135+
* The configuration builder instance instance returned from the method will be subsequently used to build the
136+
* configuration object that configures both the {@link com.ning.http.client.providers.grizzly.GrizzlyAsyncHttpProvider}
137+
* Grizzly async client provider} as well as the underlying {@link com.ning.http.client.AsyncHttpClient async HTTP
138+
* client} instance itself.
139+
* </p>
140+
* <p>
141+
* Note that any JAX-RS and Jersey specific configuration updates on the configuration builder happen before this method
142+
* is invoked. As such, changes made to the configuration builder may override or cancel the effect of the JAX-RS and
143+
* Jersey specific configuration changes. As such any configuration changes should be made with care and implementers
144+
* should be aware of possible side effect of their changes.
145+
* </p>
146+
*
147+
* @param client JAX-RS client for which the connector is being created.
148+
* @param config JAX-RS configuration that was used to initialize connector's configuration.
149+
* @param configBuilder Async HTTP Client configuration builder that has been initialized based on the JAX-RS
150+
* configuration.
151+
* @return Async HTTP Client builder instance to be used to configure the underlying Grizzly provider and async HTTP
152+
* client instance. Typically, the method returns the same {@code configBuilder} instance that has been passed into
153+
* the method as an input parameter, but it is not required to do so.
154+
*/
155+
public AsyncHttpClientConfig.Builder customize(final Client client,
156+
final Configuration config,
157+
final AsyncHttpClientConfig.Builder configBuilder);
158+
}
159+
160+
/**
161+
* A customization SPI for the async client request instances.
162+
*
163+
* A request customizer can be used to configure Async HTTP Client specific details of the request, which are not directly
164+
* exposed via the JAX-RS, Jersey or Grizzly connector provider API.
165+
* <p>
166+
* Before a request is built and sent for execution, a registered request customizer
167+
* implementation can update the Async HTTP Client {@link com.ning.http.client.RequestBuilder request builder} used
168+
* to build the request instance ultimately sent for processing.
169+
* An instance of the request customizer can be either {@link #register(ClientConfig, RequestCustomizer) registered globally}
170+
* for all requests by registering the customizer in the Jersey client configuration, or it can be individually
171+
* {@link #register(Invocation.Builder, RequestCustomizer) registered per request}, by registering it into a specific
172+
* invocation builder instance. In case of a conflict when one instance is registered globally and another per request, the
173+
* per request registered customizer takes precedence and the global customizer will be ignored.
174+
* </p>
175+
*
176+
* @see org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider.AsyncClientCustomizer
177+
* @see #register(org.glassfish.jersey.client.ClientConfig, GrizzlyConnectorProvider.RequestCustomizer)
178+
* @see #register(Invocation.Builder, GrizzlyConnectorProvider.RequestCustomizer)
179+
* @since 2.10
180+
*/
181+
public static interface RequestCustomizer {
182+
/**
183+
* Customize the underlying Async HTTP Client request builder associated with a specific Jersey client request.
184+
* <p>
185+
* The request builder instance returned from the method will be subsequently used to build the actual Async HTTP Client
186+
* request instance sent for execution.
187+
* </p>
188+
* <p>
189+
* Note that any JAX-RS and Jersey specific request configuration updates on the request builder happen before this
190+
* method is invoked. As such, changes made to the request builder may override or cancel the effect of the JAX-RS and
191+
* Jersey specific request configuration changes. As such any request builder changes should be made with care and
192+
* implementers should be aware of possible side effect of their changes.
193+
* </p>
194+
*
195+
* @param requestContext Jersey client request instance for which the Async HTTP Client request is being built.
196+
* @param requestBuilder Async HTTP Client request builder for the Jersey request.
197+
* @return Async HTTP Client request builder instance that will be used to build the actual Async HTTP Client
198+
* request instance sent for execution. Typically, the method returns the same {@code requestBuilder} instance that
199+
* has been passed into the method as an input parameter, but it is not required to do so.
200+
*/
201+
public RequestBuilder customize(final ClientRequest requestContext, final RequestBuilder requestBuilder);
202+
}
203+
204+
/**
205+
* Create new Grizzly Async HTTP Client connector provider.
206+
*/
207+
public GrizzlyConnectorProvider() {
208+
this.asyncClientCustomizer = null;
209+
}
210+
211+
/**
212+
* Create new Grizzly Async HTTP Client connector provider with a custom client configuration customizer.
213+
*
214+
* @param asyncClientCustomizer Async HTTP Client configuration customizer.
215+
* @since 2.10
216+
*/
217+
public GrizzlyConnectorProvider(final AsyncClientCustomizer asyncClientCustomizer) {
218+
this.asyncClientCustomizer = asyncClientCustomizer;
219+
}
220+
89221
@Override
90222
public Connector getConnector(Client client, Configuration config) {
91-
return new GrizzlyConnector(client, config);
223+
return new GrizzlyConnector(client, config, asyncClientCustomizer);
92224
}
93225

94226
/**
@@ -124,4 +256,41 @@ public static AsyncHttpClient getHttpClient(Configurable<?> component) {
124256

125257
throw new IllegalArgumentException(LocalizationMessages.EXPECTED_CONNECTOR_PROVIDER_NOT_USED());
126258
}
259+
260+
/**
261+
* Register a request customizer for a single request.
262+
*
263+
* A registered customizer will be used to customize the underlying Async HTTP Client request builder.
264+
* <p>
265+
* Invoking this method on an instance that is not configured to use Grizzly Async HTTP Client
266+
* connector does not have any effect.
267+
* </p>
268+
*
269+
* @param builder JAX-RS request invocation builder.
270+
* @param customizer request customizer to be registered.
271+
* @return updated Jersey client config with the Grizzly
272+
* {@link org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider.RequestCustomizer} attached.
273+
*/
274+
public static Invocation.Builder register(Invocation.Builder builder, RequestCustomizer customizer) {
275+
return builder.property(REQUEST_CUSTOMIZER, customizer);
276+
}
277+
278+
/**
279+
* Register a request customizer for a all requests executed by a client instance configured with this client config.
280+
*
281+
* A registered customizer will be used to customize underlying Async HTTP Client request builders for all requests created
282+
* using the Jersey client instance configured with this client config.
283+
* <p>
284+
* Invoking this method on an instance that is not configured to use Grizzly Async HTTP Client
285+
* connector does not have any effect.
286+
* </p>
287+
*
288+
* @param config Jersey client configuration.
289+
* @param customizer Async HTTP Client configuration customizer.
290+
* @return updated JAX-RS client invocation builder with the Grizzly
291+
* {@link org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider.RequestCustomizer RequestCustomizer} attached.
292+
*/
293+
public static ClientConfig register(ClientConfig config, RequestCustomizer customizer) {
294+
return config.property(REQUEST_CUSTOMIZER, customizer);
295+
}
127296
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3+
*
4+
* Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved.
5+
*
6+
* The contents of this file are subject to the terms of either the GNU
7+
* General Public License Version 2 only ("GPL") or the Common Development
8+
* and Distribution License("CDDL") (collectively, the "License"). You
9+
* may not use this file except in compliance with the License. You can
10+
* obtain a copy of the License at
11+
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
12+
* or packager/legal/LICENSE.txt. See the License for the specific
13+
* language governing permissions and limitations under the License.
14+
*
15+
* When distributing the software, include this License Header Notice in each
16+
* file and include the License file at packager/legal/LICENSE.txt.
17+
*
18+
* GPL Classpath Exception:
19+
* Oracle designates this particular file as subject to the "Classpath"
20+
* exception as provided by Oracle in the GPL Version 2 section of the License
21+
* file that accompanied this code.
22+
*
23+
* Modifications:
24+
* If applicable, add the following below the License Header, with the fields
25+
* enclosed by brackets [] replaced by your own identifying information:
26+
* "Portions Copyright [year] [name of copyright owner]"
27+
*
28+
* Contributor(s):
29+
* If you wish your version of this file to be governed by only the CDDL or
30+
* only the GPL Version 2, indicate your decision by adding "[Contributor]
31+
* elects to include this software in this distribution under the [CDDL or GPL
32+
* Version 2] license." If you don't indicate a single choice of license, a
33+
* recipient has the option to distribute your version of this file under
34+
* either the CDDL, the GPL Version 2 or to extend the choice of license to
35+
* its licensees as provided above. However, if you add GPL Version 2 code
36+
* and therefore, elected the GPL Version 2 license, then the option applies
37+
* only if the new code is made subject to such option by the copyright
38+
* holder.
39+
*/
40+
package org.glassfish.jersey.grizzly.connector;
41+
42+
import javax.ws.rs.HeaderParam;
43+
import javax.ws.rs.POST;
44+
import javax.ws.rs.Path;
45+
import javax.ws.rs.client.Client;
46+
import javax.ws.rs.client.Invocation;
47+
import javax.ws.rs.core.Application;
48+
import javax.ws.rs.core.Configuration;
49+
import javax.ws.rs.core.Response;
50+
51+
import static javax.ws.rs.client.Entity.text;
52+
53+
import org.glassfish.jersey.client.ClientConfig;
54+
import org.glassfish.jersey.client.ClientRequest;
55+
import org.glassfish.jersey.server.ResourceConfig;
56+
import org.glassfish.jersey.test.JerseyTest;
57+
58+
import org.junit.Test;
59+
60+
import static org.junit.Assert.assertEquals;
61+
62+
import com.ning.http.client.AsyncHttpClientConfig;
63+
import com.ning.http.client.RequestBuilder;
64+
import com.ning.http.client.filter.FilterContext;
65+
import com.ning.http.client.filter.FilterException;
66+
import com.ning.http.client.filter.RequestFilter;
67+
68+
/**
69+
* Async HTTP Client Config and Request customizers unit tests.
70+
*
71+
* @author Marek Potociar (marek.potociar at oracle.com)
72+
*/
73+
public class CustomizersTest extends JerseyTest {
74+
75+
@Path("/test")
76+
public static class EchoResource {
77+
@POST
78+
public Response post(@HeaderParam("X-Test-Config") String testConfigHeader,
79+
@HeaderParam("X-Test-Request") String testRequestHeader,
80+
String entity) {
81+
return Response.ok("POSTed " + entity)
82+
.header("X-Test-Config", testConfigHeader)
83+
.header("X-Test-Request", testRequestHeader)
84+
.build();
85+
}
86+
}
87+
88+
@Override
89+
protected Application configure() {
90+
return new ResourceConfig(EchoResource.class);
91+
}
92+
93+
@Override
94+
protected void configureClient(ClientConfig config) {
95+
final GrizzlyConnectorProvider connectorProvider = new GrizzlyConnectorProvider(
96+
new GrizzlyConnectorProvider.AsyncClientCustomizer() {
97+
@Override
98+
public AsyncHttpClientConfig.Builder customize(Client client,
99+
Configuration config,
100+
AsyncHttpClientConfig.Builder configBuilder) {
101+
return configBuilder.addRequestFilter(new RequestFilter() {
102+
@Override
103+
public FilterContext filter(FilterContext filterContext) throws FilterException {
104+
filterContext.getRequest().getHeaders().add("X-Test-Config", "tested");
105+
return filterContext;
106+
}
107+
});
108+
}
109+
});
110+
config.connectorProvider(connectorProvider);
111+
GrizzlyConnectorProvider.register(config, new GrizzlyConnectorProvider.RequestCustomizer() {
112+
@Override
113+
public RequestBuilder customize(ClientRequest requestContext, RequestBuilder requestBuilder) {
114+
requestBuilder.addHeader("X-Test-Request", "tested-global");
115+
return requestBuilder;
116+
}
117+
});
118+
}
119+
120+
/**
121+
* Jersey-2540 related test.
122+
*/
123+
@Test
124+
public void testCustomizers() {
125+
Response response;
126+
127+
// now using global request customizer
128+
response = target("test").request().post(text("echo"));
129+
assertEquals("POSTed echo", response.readEntity(String.class));
130+
assertEquals("tested", response.getHeaderString("X-Test-Config"));
131+
assertEquals("tested-global", response.getHeaderString("X-Test-Request"));
132+
133+
134+
// now using request-specific request customizer
135+
final Invocation.Builder builder = target("test").request();
136+
GrizzlyConnectorProvider.register(builder, new GrizzlyConnectorProvider.RequestCustomizer() {
137+
@Override
138+
public RequestBuilder customize(ClientRequest requestContext, RequestBuilder requestBuilder) {
139+
requestBuilder.addHeader("X-Test-Request", "tested-per-request");
140+
return requestBuilder;
141+
}
142+
});
143+
response = builder.post(text("echo"));
144+
assertEquals("POSTed echo", response.readEntity(String.class));
145+
assertEquals("tested", response.getHeaderString("X-Test-Config"));
146+
assertEquals("tested-per-request", response.getHeaderString("X-Test-Request"));
147+
148+
149+
// now using global request customizer again
150+
response = target("test").request().post(text("echo"));
151+
assertEquals("POSTed echo", response.readEntity(String.class));
152+
assertEquals("tested", response.getHeaderString("X-Test-Config"));
153+
assertEquals("tested-global", response.getHeaderString("X-Test-Request"));
154+
}
155+
}

0 commit comments

Comments
 (0)