/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012-2017 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.jersey.tests.e2e.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Type;
import java.util.List;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.NameBinding;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.Entity;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.Providers;
import org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
/**
* Tests throwing exceptions in {@link MessageBodyReader} and {@link MessageBodyWriter}.
*
* @author Miroslav Fuksa
*/
public class ExceptionMapperTest extends JerseyTest {
@Override
protected Application configure() {
return new ResourceConfig(
Resource.class,
MyMessageBodyWritter.class,
MyMessageBodyReader.class,
ClientErrorExceptionMapper.class,
MyExceptionMapper.class,
ThrowableMapper.class,
MyExceptionMapperCauseAnotherException.class,
// JERSEY-1515
TestResource.class,
VisibilityExceptionMapper.class,
// JERSEY-1525
ExceptionTestResource.class,
ExceptionThrowingFilter.class,
IOExceptionMapper.class,
IOExceptionMessageReader.class,
IOExceptionResource.class,
MessageBodyProviderNotFoundResource.class,
ProviderNotFoundExceptionMapper.class,
// JERSEY-1887
Jersey1887Resource.class,
Jersey1887ExceptionMapperImpl.class,
// JERSEY-2382
Jersey2382Resource.class,
Jersey2382ExceptionMapper.class,
Jersey2382Provider.class
);
}
@Test
public void testReaderThrowsException() {
Response res = target().path("test").request("test/test").header("reader-exception", "throw")
.post(Entity.entity("post", "test/test"));
assertEquals(200, res.getStatus());
final String entity = res.readEntity(String.class);
assertEquals("reader-exception-mapper", entity);
}
@Test
public void testWriterThrowsExceptionBeforeFirstBytesAreWritten() {
Response res = target().path("test/before").request("test/test").get();
assertEquals(200, res.getStatus());
assertEquals("exception-before-first-bytes-exception-mapper", res.readEntity(String.class));
}
@Test
public void testWriterThrowsExceptionAfterFirstBytesAreWritten() throws IOException {
Response res = target().path("test/after").request("test/test").get();
assertEquals(200, res.getStatus());
final InputStream inputStream = res.readEntity(InputStream.class);
byte b;
inputStream.read();
MyMessageBodyWritter.firstBytesReceived = true;
while ((b = (byte) inputStream.read()) >= 0) {
assertEquals('a', b);
}
}
@Test
public void testPreventMultipleExceptionMapping() {
Response res = target().path("test/exception").request("test/test").get();
// firstly exception is thrown in the resource method and is correctly mapped. Then it is again thrown in MBWriter but
// exception can be mapped only once, so second exception in MBWriter cause 500 response code.
assertEquals(500, res.getStatus());
}
@Path("test")
public static class Resource {
@GET
@Path("before")
@Produces("test/test")
public Response exceptionBeforeFirstBytesAreWritten() {
return Response.status(200).header("writer-exception", "before-first-byte").entity("ok").build();
}
@GET
@Path("after")
@Produces("test/test")
public Response exceptionAfterFirstBytesAreWritten() {
return Response.status(200).header("writer-exception", "after-first-byte").entity("aaaaa").build();
}
@POST
@Produces("test/test")
public String post(String str) {
return "post";
}
@GET
@Path("exception")
@Produces("test/test")
public Response throwsException() {
throw new MyAnotherException("resource");
}
@Path("throwable")
@GET
public String throwsThrowable() throws Throwable {
throw new Throwable("throwable",
new RuntimeException("runtime-exception",
new ClientErrorException("client-error", 499)));
}
}
public static class ClientErrorExceptionMapper implements ExceptionMapper<ClientErrorException> {
@Override
public Response toResponse(ClientErrorException exception) {
return Response.status(Response.Status.OK).entity("mapped-client-error-"
+ exception.getResponse().getStatus() + "-" + exception.getMessage()).build();
}
}
public static class MyExceptionMapper implements ExceptionMapper<MyException> {
@Override
public Response toResponse(MyException exception) {
return Response.ok().entity(exception.getMessage() + "-exception-mapper").build();
}
}
public static class ThrowableMapper implements ExceptionMapper<Throwable> {
@Override
public Response toResponse(Throwable throwable) {
throwable.printStackTrace();
return Response.status(Response.Status.OK).entity("mapped-throwable-" + throwable.getMessage()).build();
}
}
public static class MyExceptionMapperCauseAnotherException implements ExceptionMapper<MyAnotherException> {
@Override
public Response toResponse(MyAnotherException exception) {
// the header causes exception to be thrown again in MyMessageBodyWriter
return Response.ok().header("writer-exception", "before-first-byte").entity(exception.getMessage()
+ "-another-exception-mapper").build();
}
}
@Produces("test/test")
public static class MyMessageBodyWritter implements MessageBodyWriter<String> {
public static volatile boolean firstBytesReceived;
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type == String.class;
}
@Override
public long getSize(String s, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return s.length();
}
@Override
public void writeTo(String s, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException,
WebApplicationException {
firstBytesReceived = false;
final List<Object> header = httpHeaders.get("writer-exception");
if (header != null && header.size() > 0) {
if (header.get(0).equals("before-first-byte")) {
throw new MyException("exception-before-first-bytes");
} else if (header.get(0).equals("after-first-byte")) {
int i = 0;
while (!firstBytesReceived && i++ < 500000) {
entityStream.write('a');
entityStream.flush();
}
throw new MyException("exception-after-first-bytes");
}
} else {
OutputStreamWriter osw = new OutputStreamWriter(entityStream);
osw.write(s);
osw.flush();
}
}
}
@Consumes("test/test")
public static class MyMessageBodyReader implements MessageBodyReader<String> {
@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type == String.class;
}
@Override
public String readFrom(Class<String> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException,
WebApplicationException {
final List<String> header = httpHeaders.get("reader-exception");
if (header != null && header.size() > 0 && header.get(0).equals("throw")) {
throw new MyException("reader");
}
return "aaa";
}
}
public static class MyException extends RuntimeException {
public MyException() {
}
public MyException(Throwable cause) {
super(cause);
}
public MyException(String message) {
super(message);
}
public MyException(String message, Throwable cause) {
super(message, cause);
}
}
public static class MyAnotherException extends RuntimeException {
public MyAnotherException() {
}
public MyAnotherException(Throwable cause) {
super(cause);
}
public MyAnotherException(String message) {
super(message);
}
public MyAnotherException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* BEGIN: JERSEY-1515 reproducer code
*/
public static class VisibilityException extends WebApplicationException {
private static final long serialVersionUID = -1159407312691372429L;
}
public static class VisibilityExceptionMapper implements ExceptionMapper<VisibilityException> {
private HttpHeaders headers;
private UriInfo info;
private Application application;
private Request request;
private Providers provider;
protected VisibilityExceptionMapper(@Context HttpHeaders headers,
@Context UriInfo info, @Context Application application,
@Context Request request, @Context Providers provider) {
super();
this.headers = headers;
this.info = info;
this.application = application;
this.request = request;
this.provider = provider;
}
public VisibilityExceptionMapper(@Context HttpHeaders headers,
@Context UriInfo info, @Context Application application,
@Context Request request) {
super();
this.headers = headers;
this.info = info;
this.application = application;
this.request = request;
}
public VisibilityExceptionMapper(@Context HttpHeaders headers,
@Context UriInfo info, @Context Application application) {
super();
this.headers = headers;
this.info = info;
this.application = application;
}
public VisibilityExceptionMapper(@Context HttpHeaders headers,
@Context UriInfo info) {
super();
this.headers = headers;
this.info = info;
}
public VisibilityExceptionMapper(@Context HttpHeaders headers) {
super();
this.headers = headers;
}
@Override
public Response toResponse(VisibilityException exception) {
return Response.ok("visible").build();
}
}
@Path("test/visible")
public static class TestResource {
@GET
public String throwVisibleException() {
throw new VisibilityException();
}
}
@Test
public void testJersey1515() {
Response res = target().path("test/visible").request().get();
assertEquals(200, res.getStatus());
assertEquals("visible", res.readEntity(String.class));
}
/**
* END: JERSEY-1515 reproducer code
*/
/**
* BEGIN: JERSEY-1525 reproducer code.
*/
@Path("test/responseFilter")
public static class ExceptionTestResource {
@GET
@ThrowsNPE
public String getData() {
return "method";
}
}
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
private static @interface ThrowsNPE {
}
@ThrowsNPE
public static class ExceptionThrowingFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
// The if clause prevents throwing exception on a mapped response.
// Not doing so would result in a second exception being thrown
// which would not be mapped again; instead, it would be propagated
// to the hosting container directly.
if (!"mapped-throwable-response-filter-exception".equals(responseContext.getEntity())) {
throw new NullPointerException("response-filter-exception");
}
}
}
@Test
public void testJersey1525() {
Response res = target().path("test/responseFilter").request().get();
assertEquals(200, res.getStatus());
assertEquals("mapped-throwable-response-filter-exception", res.readEntity(String.class));
}
/**
* END: JERSEY-1525 reproducer code
*/
@Provider
public static class IOExceptionMessageReader implements MessageBodyReader<IOBean>, MessageBodyWriter<IOBean> {
@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type == IOBean.class;
}
@Override
public IOBean readFrom(Class<IOBean> type,
Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
throw new IOException("io-exception");
}
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type == IOBean.class;
}
@Override
public long getSize(IOBean ioBean, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return 0;
}
@Override
public void writeTo(IOBean ioBean, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException,
WebApplicationException {
entityStream.write(ioBean.value.getBytes());
}
}
public static class IOBean {
private final String value;
public IOBean(String value) {
this.value = value;
}
}
public static class IOExceptionMapper implements ExceptionMapper<IOException> {
@Override
public Response toResponse(IOException exception) {
return Response.ok("passed").build();
}
}
@Path("io")
public static class IOExceptionResource {
@POST
public String post(IOBean iobean) {
return iobean.value;
}
}
@Test
public void testIOException() {
final Response response = target().register(IOExceptionMessageReader.class)
.path("io").request().post(Entity.entity(new IOBean("io-bean"), MediaType.TEXT_PLAIN));
assertEquals(200, response.getStatus());
assertEquals("passed", response.readEntity(String.class));
}
@Test
public void testThrowableFromResourceMethod() {
Response res = target().path("test/throwable").request().get();
assertEquals(200, res.getStatus());
assertEquals("mapped-throwable-throwable", res.readEntity(String.class));
}
@Path("not-found")
public static class MessageBodyProviderNotFoundResource {
@GET
@Produces("aa/bbb")
public UnknownType get() {
return new UnknownType();
}
}
public static class UnknownType {
}
public static class ProviderNotFoundExceptionMapper implements ExceptionMapper<InternalServerErrorException> {
@Override
public Response toResponse(InternalServerErrorException exception) {
if (exception.getCause() instanceof MessageBodyProviderNotFoundException) {
return Response.ok("mapped-by-ProviderNotFoundExceptionMapper").build();
}
return Response.serverError().entity("Unexpected root cause of InternalServerError").build();
}
}
/**
* Tests that {@link MessageBodyProviderNotFoundException} wrapped into {@link javax.ws.rs.InternalServerErrorException}
* is correctly mapped using an {@link ExceptionMapper}.
*/
@Test
public void testNotFoundResource() {
final Response response = target().path("not-found").request().get();
assertEquals(200, response.getStatus());
assertEquals("mapped-by-ProviderNotFoundExceptionMapper", response.readEntity(String.class));
}
public static class Jersey1887Exception extends RuntimeException {
}
@Provider
public static interface Jersey1887ExceptionMapper extends ExceptionMapper<Jersey1887Exception> {
}
public static class Jersey1887ExceptionMapperImpl implements Jersey1887ExceptionMapper {
@Override
public Response toResponse(final Jersey1887Exception exception) {
return Response.ok("found").build();
}
}
@Path("jersey1887")
public static class Jersey1887Resource {
@GET
public Response get() {
throw new Jersey1887Exception();
}
}
/**
* Test that we're able to use correct exception mapper even when the mapper hierarchy has complex inheritance.
*/
@Test
public void testJersey1887() throws Exception {
final Response response = target().path("jersey1887").request().get();
assertThat(response.getStatus(), equalTo(200));
assertThat(response.readEntity(String.class), equalTo("found"));
}
public static class Jersey2382Exception extends RuntimeException {
}
public static class Jersey2382Entity {
}
@Provider
public static class Jersey2382Provider implements MessageBodyWriter<Jersey2382Entity> {
@Override
public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
final MediaType mediaType) {
return true;
}
@Override
public long getSize(final Jersey2382Entity jersey2382Entity, final Class<?> type, final Type genericType,
final Annotation[] annotations, final MediaType mediaType) {
return -1;
}
@Override
public void writeTo(final Jersey2382Entity jersey2382Entity,
final Class<?> type,
final Type genericType,
final Annotation[] annotations,
final MediaType mediaType,
final MultivaluedMap<String, Object> httpHeaders,
final OutputStream entityStream) throws IOException, WebApplicationException {
if (Jersey2382Entity.class != type) {
entityStream.write("wrong-type".getBytes());
} else if (Jersey2382Entity.class != genericType) {
entityStream.write("wrong-generic-type".getBytes());
} else {
entityStream.write("ok".getBytes());
}
}
}
@Provider
public static class Jersey2382ExceptionMapper implements ExceptionMapper<Jersey2382Exception> {
@Override
public Response toResponse(final Jersey2382Exception exception) {
return Response.ok(new Jersey2382Entity()).build();
}
}
@Path("jersey2382")
public static class Jersey2382Resource {
@GET
public List<List<Integer>> get() {
throw new Jersey2382Exception();
}
}
/**
* Test that we're able to use correct exception mapper even when the mapper hierarchy has complex inheritance.
*/
@Test
public void testJersey2382() throws Exception {
final Response response = target().path("jersey2382").request().get();
assertThat(response.getStatus(), equalTo(200));
assertThat(response.readEntity(String.class), equalTo("ok"));
}
}