blob: dc021a399ff306c349c2ed6abb2bd44f4c3eebf9 [file] [log] [blame]
/*
* Copyright (c) 2012, 2018 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 java.io.IOException;
import java.net.ConnectException;
import java.net.ProtocolException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.AsyncInvoker;
import javax.ws.rs.client.Client;
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.Invocation;
import javax.ws.rs.client.InvocationCallback;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.hamcrest.CoreMatchers;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author Martin Matula
*/
public class JerseyInvocationTest {
/**
* Regression test for JERSEY-1257
*/
@Test
public void testOverrideHeadersWithMap() {
final MultivaluedMap<String, Object> map = new MultivaluedHashMap<String, Object>();
map.add("a-header", "b-header");
final JerseyInvocation invocation = buildInvocationWithHeaders(map);
assertEquals(1, invocation.request().getHeaders().size());
assertEquals("b-header", invocation.request().getHeaders().getFirst("a-header"));
}
/**
* Regression test for JERSEY-1257
*/
@Test
public void testClearHeaders() {
final JerseyInvocation invocation = buildInvocationWithHeaders(null);
assertTrue(invocation.request().getHeaders().isEmpty());
}
/**
* Regression test for JERSEY-2562.
*/
@Test
public void testClearHeader() {
final Client client = ClientBuilder.newClient();
final Invocation.Builder builder = client.target("http://localhost:8080/mypath").request();
final JerseyInvocation invocation = (JerseyInvocation) builder
.header("foo", "bar").header("foo", null).header("bar", "foo")
.buildGet();
final MultivaluedMap<String, Object> headers = invocation.request().getHeaders();
assertThat(headers.size(), is(1));
assertThat(headers.keySet(), hasItem("bar"));
}
private JerseyInvocation buildInvocationWithHeaders(final MultivaluedMap<String, Object> headers) {
final Client c = ClientBuilder.newClient();
final Invocation.Builder builder = c.target("http://localhost:8080/mypath").request();
return (JerseyInvocation) builder.header("unexpected-header", "unexpected-header").headers(headers).buildGet();
}
/**
* Checks that presence of request entity fo HTTP DELETE method does not fail in Jersey.
* Instead, the request is propagated up to HttpURLConnection, where it fails with
* {@code ProtocolException}.
* <p/>
* See also JERSEY-1711.
*
* @see #overrideHttpMethodBasedComplianceCheckNegativeTest()
*/
@Test
public void overrideHttpMethodBasedComplianceCheckTest() {
final Client c1 = ClientBuilder.newClient().property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
try {
c1.target("http://localhost:8080/myPath").request().method("DELETE", Entity.text("body"));
fail("ProcessingException expected.");
} catch (final ProcessingException ex) {
assertThat(ex.getCause().getClass(), anyOf(CoreMatchers.<Class<?>>equalTo(ProtocolException.class),
CoreMatchers.<Class<?>>equalTo(ConnectException.class)));
}
final Client c2 = ClientBuilder.newClient();
try {
c2.target("http://localhost:8080/myPath").request().property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION,
true).method("DELETE", Entity.text("body"));
fail("ProcessingException expected.");
} catch (final ProcessingException ex) {
assertThat(ex.getCause().getClass(), anyOf(CoreMatchers.<Class<?>>equalTo(ProtocolException.class),
CoreMatchers.<Class<?>>equalTo(ConnectException.class)));
}
final Client c3 = ClientBuilder.newClient().property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, false);
try {
c3.target("http://localhost:8080/myPath").request().property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION,
true).method("DELETE", Entity.text("body"));
fail("ProcessingException expected.");
} catch (final ProcessingException ex) {
assertThat(ex.getCause().getClass(), anyOf(CoreMatchers.<Class<?>>equalTo(ProtocolException.class),
CoreMatchers.<Class<?>>equalTo(ConnectException.class)));
}
}
/**
* Checks that presence of request entity fo HTTP DELETE method fails in Jersey with {@code IllegalStateException}
* if HTTP spec compliance is not suppressed by {@link ClientProperties#SUPPRESS_HTTP_COMPLIANCE_VALIDATION} property.
* <p/>
* See also JERSEY-1711.
*
* @see #overrideHttpMethodBasedComplianceCheckTest()
*/
@Test
public void overrideHttpMethodBasedComplianceCheckNegativeTest() {
final Client c1 = ClientBuilder.newClient();
try {
c1.target("http://localhost:8080/myPath").request().method("DELETE", Entity.text("body"));
fail("IllegalStateException expected.");
} catch (final IllegalStateException expected) {
// pass
}
final Client c2 = ClientBuilder.newClient();
try {
c2.target("http://localhost:8080/myPath").request().property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION,
false).method("DELETE", Entity.text("body"));
fail("IllegalStateException expected.");
} catch (final IllegalStateException expected) {
// pass
}
final Client c3 = ClientBuilder.newClient().property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
try {
c3.target("http://localhost:8080/myPath").request().property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION,
false).method("DELETE", Entity.text("body"));
fail("IllegalStateException expected.");
} catch (final IllegalStateException expected) {
// pass
}
}
@Test
public void testNullResponseType() throws Exception {
final Client client = ClientBuilder.newClient();
client.register(new ClientRequestFilter() {
@Override
public void filter(final ClientRequestContext requestContext) throws IOException {
requestContext.abortWith(Response.ok().build());
}
});
final WebTarget target = client.target("http://localhost:8080/mypath");
final Class<Response> responseType = null;
final String[] methods = new String[] {"GET", "PUT", "POST", "DELETE", "OPTIONS"};
for (final String method : methods) {
final Invocation.Builder request = target.request();
try {
request.method(method, responseType);
fail("IllegalArgumentException expected.");
} catch (final IllegalArgumentException iae) {
// OK.
}
final Invocation build = "PUT".equals(method) ? request.build(method, Entity.entity("",
MediaType.TEXT_PLAIN_TYPE)) : request.build(method);
try {
build.submit(responseType);
fail("IllegalArgumentException expected.");
} catch (final IllegalArgumentException iae) {
// OK.
}
try {
build.invoke(responseType);
fail("IllegalArgumentException expected.");
} catch (final IllegalArgumentException iae) {
// OK.
}
try {
request.async().method(method, responseType);
fail("IllegalArgumentException expected.");
} catch (final IllegalArgumentException iae) {
// OK.
}
}
}
@Test
public void failedCallbackTest() throws InterruptedException {
final Invocation.Builder builder = ClientBuilder.newClient().target("http://localhost:888/").request();
for (int i = 0; i < 1; i++) {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicInteger ai = new AtomicInteger(0);
final InvocationCallback<String> callback = new InvocationCallback<String>() {
@Override
public void completed(final String arg0) {
try {
ai.set(ai.get() + 1);
} finally {
latch.countDown();
}
}
@Override
public void failed(final Throwable throwable) {
try {
int result = 10;
if (throwable instanceof ProcessingException) {
result += 100;
}
final Throwable ioe = throwable.getCause();
if (ioe instanceof IOException) {
result += 1000;
}
ai.set(ai.get() + result);
} finally {
latch.countDown();
}
}
};
final Invocation invocation = builder.buildGet();
final Future<String> future = invocation.submit(callback);
try {
future.get();
fail("future.get() should have failed.");
} catch (final ExecutionException e) {
final Throwable pe = e.getCause();
assertTrue("Execution exception cause is not a ProcessingException: " + pe.toString(),
pe instanceof ProcessingException);
final Throwable ioe = pe.getCause();
assertTrue("Execution exception cause is not an IOException: " + ioe.toString(), ioe instanceof IOException);
} catch (final InterruptedException e) {
throw new RuntimeException(e);
}
latch.await(1, TimeUnit.SECONDS);
assertEquals(1110, ai.get());
}
}
public static class MyUnboundCallback<V> implements InvocationCallback<V> {
private final CountDownLatch latch;
private volatile Throwable throwable;
public MyUnboundCallback(final CountDownLatch latch) {
this.latch = latch;
}
@Override
public void completed(final V v) {
latch.countDown();
}
@Override
public void failed(final Throwable throwable) {
this.throwable = throwable;
latch.countDown();
}
public Throwable getThrowable() {
return throwable;
}
}
@Test
public void failedUnboundGenericCallback() throws InterruptedException {
final Invocation invocation = ClientBuilder.newClient().target("http://localhost:888/").request().buildGet();
final CountDownLatch latch = new CountDownLatch(1);
final MyUnboundCallback<String> callback = new MyUnboundCallback<String>(latch);
invocation.submit(callback);
latch.await(1, TimeUnit.SECONDS);
assertThat(callback.getThrowable(), CoreMatchers.instanceOf(ProcessingException.class));
assertThat(callback.getThrowable().getCause(), CoreMatchers.instanceOf(IllegalArgumentException.class));
assertThat(callback.getThrowable().getCause().getMessage(), CoreMatchers
.allOf(CoreMatchers.containsString(MyUnboundCallback.class.getName()),
CoreMatchers.containsString(InvocationCallback.class.getName())));
}
@Test
public void testSubmitWithGenericType() throws Exception {
_submitWithGenericType(new GenericType<String>() {});
}
@Test
public void testSubmitWithGenericTypeParam() throws Exception {
_submitWithGenericType(new GenericType(String.class) {});
}
private void _submitWithGenericType(final GenericType type) throws Exception {
final Invocation.Builder builder = ClientBuilder.newClient()
.register(TerminatingFilter.class)
.target("http://localhost/")
.request();
final AtomicReference<String> reference = new AtomicReference<String>();
final CountDownLatch latch = new CountDownLatch(1);
final InvocationCallback callback = new InvocationCallback<Object>() {
@Override
public void completed(final Object obj) {
reference.set(obj.toString());
latch.countDown();
}
@Override
public void failed(final Throwable throwable) {
latch.countDown();
}
};
//noinspection unchecked
((JerseyInvocation) builder.buildGet())
.submit(type, (InvocationCallback<String>) callback);
latch.await();
assertThat(reference.get(), is("ENTITY"));
}
public static class TerminatingFilter implements ClientRequestFilter {
@Override
public void filter(final ClientRequestContext requestContext) throws IOException {
requestContext.abortWith(Response.ok("ENTITY").build());
}
}
@Test
public void runtimeExceptionInAsyncInvocation() throws ExecutionException, InterruptedException {
final AsyncInvoker ai = ClientBuilder.newClient().register(new ExceptionInvokerFilter())
.target("http://localhost:888/").request().async();
try {
ai.get().get();
fail("ExecutionException should be thrown");
} catch (ExecutionException ee) {
assertEquals(ProcessingException.class, ee.getCause().getClass());
assertEquals(RuntimeException.class, ee.getCause().getCause().getClass());
}
}
public static class ExceptionInvokerFilter implements ClientRequestFilter {
@Override
public void filter(final ClientRequestContext requestContext) throws IOException {
throw new RuntimeException("ExceptionInvokerFilter RuntimeException");
}
}
}