Skip to content

Commit

Permalink
fix for JAX-RS SPI resource finder (eclipse-ee4j#4143)
Browse files Browse the repository at this point in the history
Signed-off-by: Maxim Nesen <[email protected]>
Signed-off-by: Jan Supol <[email protected]>
  • Loading branch information
senivam authored and jansupol committed Jun 6, 2019
1 parent 44c05a5 commit f539999
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 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
Expand Down Expand Up @@ -34,6 +34,7 @@

import javax.inject.Provider;

import org.glassfish.jersey.client.internal.ClientExceptionMapperUtil;
import org.glassfish.jersey.client.internal.LocalizationMessages;
import org.glassfish.jersey.client.spi.AsyncConnectorCallback;
import org.glassfish.jersey.client.spi.Connector;
Expand All @@ -52,6 +53,7 @@
import org.glassfish.jersey.process.internal.RequestScope;
import org.glassfish.jersey.process.internal.Stage;
import org.glassfish.jersey.process.internal.Stages;
import org.glassfish.jersey.spi.ExceptionMappers;

/**
* Client-side request processing runtime.
Expand All @@ -77,6 +79,7 @@ class ClientRuntime implements JerseyClient.ShutdownHook, ClientExecutor {
private final AtomicBoolean closed = new AtomicBoolean(false);
private final ManagedObjectsFinalizer managedObjectsFinalizer;
private final InjectionManager injectionManager;
private final ExceptionMappers exceptionMappers;

/**
* Create new client request processing runtime.
Expand Down Expand Up @@ -117,6 +120,7 @@ public ClientRuntime(final ClientConfig config, final Connector connector, final

this.injectionManager = injectionManager;
this.lifecycleListeners = Providers.getAllProviders(injectionManager, ClientLifecycleListener.class);
this.exceptionMappers = bootstrapBag.getExceptionMappers();

for (final ClientLifecycleListener listener : lifecycleListeners) {
try {
Expand Down Expand Up @@ -254,6 +258,8 @@ public ClientResponse invoke(final ClientRequest request) {
response = connector.apply(addUserAgent(Stages.process(request, requestProcessingRoot), connector.getName()));
} catch (final AbortException aborted) {
response = aborted.getAbortResponse();
} catch (final Throwable throwable) {
response = processExceptionMappers(request, throwable);
}

return Stages.process(response, responseProcessingRoot);
Expand All @@ -264,6 +270,16 @@ public ClientResponse invoke(final ClientRequest request) {
}
}

private <T extends Throwable> ClientResponse processExceptionMappers(final ClientRequest request, final T throwable)
throws Throwable {
final ClientExceptionMapperUtil.ExceptionCauseMapper mapper = ClientExceptionMapperUtil
.findMappingIncludingCause(exceptionMappers, throwable);
if (mapper != null) {
return new ClientResponse(request, mapper.exceptionMapper.toResponse(mapper.exception));
}
throw throwable;
}

/**
* Get the request scope instance configured for the runtime.
*
Expand All @@ -282,6 +298,14 @@ public ClientConfig getConfig() {
return config;
}

/**
* Get exception mappers instance
* @return exception mappers
*/
ExceptionMappers getExceptionMappers() {
return exceptionMappers;
}

/**
* This will be used as the last resort to clean things up
* in the case that this instance gets garbage collected
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 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
Expand Down Expand Up @@ -57,6 +57,7 @@
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;

import org.glassfish.jersey.client.internal.ClientExceptionMapperUtil;
import org.glassfish.jersey.client.internal.LocalizationMessages;
import org.glassfish.jersey.internal.MapPropertiesDelegate;
import org.glassfish.jersey.internal.inject.Providers;
Expand Down Expand Up @@ -837,6 +838,15 @@ public void failed(final ProcessingException error) {

private <T> T translate(final ClientResponse response, final RequestScope scope, final Class<T> responseType)
throws ProcessingException {
try {
return _translate(response, scope, responseType);
} catch (ProcessingException exception) {
return _translate(processExceptionMappers(exception), scope, responseType);
}
}

private <T> T _translate(final ClientResponse response, final RequestScope scope, final Class<T> responseType)
throws ProcessingException {
if (responseType == Response.class) {
return responseType.cast(new InboundJaxrsResponse(response, scope));
}
Expand Down Expand Up @@ -902,6 +912,15 @@ public void failed(final ProcessingException error) {

private <T> T translate(final ClientResponse response, final RequestScope scope, final GenericType<T> responseType)
throws ProcessingException {
try {
return _translate(response, scope, responseType);
} catch (ProcessingException exception) {
return _translate(processExceptionMappers(exception), scope, responseType);
}
}

private <T> T _translate(final ClientResponse response, final RequestScope scope, final GenericType<T> responseType)
throws ProcessingException {
if (responseType.getRawType() == Response.class) {
//noinspection unchecked
return (T) new InboundJaxrsResponse(response, scope);
Expand All @@ -925,6 +944,15 @@ private <T> T translate(final ClientResponse response, final RequestScope scope,
}
}

private ClientResponse processExceptionMappers(final ProcessingException exception) {
final ClientExceptionMapperUtil.ExceptionCauseMapper mapper = ClientExceptionMapperUtil
.findMappingIncludingCause(request().getClientRuntime().getExceptionMappers(), exception);
if (mapper != null) {
return new ClientResponse(requestContext, mapper.exceptionMapper.toResponse(mapper.exception));
}
throw exception;
}

@Override
public <T> Future<T> submit(final InvocationCallback<T> callback) {
return submit(null, callback);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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.internal;

import org.glassfish.jersey.spi.ExceptionMappers;

import javax.ws.rs.ConstrainedTo;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.ext.ExceptionMapper;

public abstract class ClientExceptionMapperUtil {
private ClientExceptionMapperUtil() {
throw new IllegalStateException("Cannot instantiate");
}

/**
* Searches for a mapping of {@link ExceptionMapper} of a given Exception by calling
* {@link ExceptionMappers#findMapping(Throwable)}. If not found, it searches recursively for
* an exception mapper of a cause of the Exception. For instance, for a
* {@link javax.ws.rs.ProcessingException ProcessingException} an
* {@code ExceptionMapper<ProcessingException>} would work, but an {@code ExceptionMapper}
* for a cause of the {@code ProcessingException} would work, too.
* <p/>
* Note that the {@code ExceptionMapper} needs to be annotated with
* {@link ConstrainedTo ConstrainedTo(RuntimeType.Client)}
*
* @param exceptionMappers the {@link ExceptionMappers} instance
* @param exceptionInstance the actual exception for which the {@link ExceptionMapper} is to be find
* @return A pair of an {@link ExceptionMapper} and the corresponding exception (or cause),
* or {@code null} when none found.
*/
public static <T extends Throwable> ExceptionCauseMapper findMappingIncludingCause(final ExceptionMappers exceptionMappers,
final T exceptionInstance) {
final ExceptionMapper<T> mapper = exceptionMappers.findMapping(exceptionInstance);
if (mapper != null) {
final ConstrainedTo annotation = mapper.getClass().getAnnotation(ConstrainedTo.class);
if (annotation != null && annotation.value().equals(RuntimeType.CLIENT)) {
return new ExceptionCauseMapper(mapper, exceptionInstance);
}
}
return exceptionInstance.getCause() == null
? null : findMappingIncludingCause(exceptionMappers, exceptionInstance.getCause());
}

/**
* A holder class for for a pair of {@link ExceptionMapper} and the corresponding {@code Exception}
*
* @param <EXCEPTION> a {@link Throwable}
*/
public static class ExceptionCauseMapper<EXCEPTION extends Throwable> {
public ExceptionMapper<EXCEPTION> exceptionMapper;
public EXCEPTION exception;

public ExceptionCauseMapper(ExceptionMapper<EXCEPTION> exceptionMapper, EXCEPTION exception) {
this.exceptionMapper = exceptionMapper;
this.exception = exception;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* 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.junit.Assert;
import org.junit.Test;

import javax.ws.rs.ConstrainedTo;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Providers;

import java.net.ConnectException;

import static org.hamcrest.Matchers.is;

public class ClientExceptionMapperTest {
private static final String expectedResponse = "HelloFromExceptionMapper";

@Test
public void exceptionMapperNoConstraintToTest() {
try {
final String response = ClientBuilder.newClient()
.register(new ExceptionMapper<Throwable>() {

@Override
public Response toResponse(Throwable exception) {
return Response.ok(expectedResponse).build();
}
})
.target("http://localhost:9900/doesnotexist").request().get(String.class);
Assert.fail("ProcessingException has not been thrown");
} catch (ProcessingException pe) {
// expected
}
}

@Test
public void exceptionMapperConstraintClientToTest() {
final String response = ClientBuilder.newClient()
.register(ClientExceptionMapper.class)
.target("http://localhost:9900/doesnotexist").request().get(String.class);
Assert.assertThat(response, is(expectedResponse));
}

@Test
public void exceptionMapperConstraintToServerTest() {
try {
final String response = ClientBuilder.newClient()
.register(ServerExceptionMapper.class)
.target("http://localhost:9900/doesnotexist").request().get(String.class);
Assert.assertThat(response, is(expectedResponse));
Assert.fail("ProcessingException has not been thrown");
} catch (ProcessingException pe) {
// expected
}
}

@ConstrainedTo(RuntimeType.CLIENT)
public static class ClientExceptionMapper implements ExceptionMapper<ConnectException> {
@Override
public Response toResponse(ConnectException exception) {
return Response.ok(expectedResponse).build();
}
}

@ConstrainedTo(RuntimeType.SERVER)
public static class ServerExceptionMapper implements ExceptionMapper<ConnectException> {
@Override
public Response toResponse(ConnectException exception) {
return Response.ok(expectedResponse).build();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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.tests.e2e.client;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Assert;
import org.junit.Test;

import javax.ws.rs.ConstrainedTo;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;

import static org.hamcrest.Matchers.is;

public class ClientExceptionMapperTest extends JerseyTest {

@ConstrainedTo(RuntimeType.CLIENT)
public static class NotFoundExceptionMapper implements ExceptionMapper<NotFoundException> {

@Override
public Response toResponse(NotFoundException exception) {
return Response.ok("404").build();
}
}

@Path("/test")
public static class TestResource {
@GET
public String ok() {
return "ok";
}
}

@Override
protected Application configure() {
return new ResourceConfig().register(TestResource.class);
}

@Test
public void test404get() {
String entity = target().path("nowhere")
.register(NotFoundExceptionMapper.class)
.request().get(String.class);
Assert.assertThat(entity, is("404"));
}

@Test
public void test404getGenericType() {
String entity = target().path("nowhere")
.register(NotFoundExceptionMapper.class)
.request().get(new GenericType<String>(String.class){});
Assert.assertThat(entity, is("404"));
}

@Test
public void test404post() {
String entity = target().path("nowhere")
.register(NotFoundExceptionMapper.class)
.request().post(Entity.entity("something", MediaType.TEXT_PLAIN_TYPE), String.class);
Assert.assertThat(entity, is("404"));
}
}

0 comments on commit f539999

Please sign in to comment.