/* * 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.server.internal.inject; import java.lang.annotation.Annotation; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.ext.ParamConverter; import javax.ws.rs.ext.ParamConverterProvider; import org.glassfish.jersey.internal.inject.ExtractorException; import org.glassfish.jersey.internal.util.ReflectionHelper; import org.glassfish.jersey.internal.util.collection.ClassTypePair; import org.glassfish.jersey.server.ApplicationHandler; import org.glassfish.jersey.server.ContainerResponse; import org.glassfish.jersey.server.RequestContextBuilder; import org.glassfish.jersey.server.ResourceConfig; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; /** * Tests {@link ParamConverter param converters}. * * @author Miroslav Fuksa */ public class ParamConverterInternalTest extends AbstractTest { @Path("/") public static class BadDateResource { @GET public String doGet(@QueryParam("d") final Date d) { return "DATE"; } } @Test public void testBadDateResource() throws ExecutionException, InterruptedException { initiateWebApplication(BadDateResource.class); final ContainerResponse responseContext = getResponseContext(UriBuilder.fromPath("/") .queryParam("d", "123").build().toString()); assertEquals(404, responseContext.getStatus()); } @Path("/") public static class BadEnumResource { public enum ABC { A, B, C } @GET public String doGet(@QueryParam("d") final ABC d) { return "ENUM"; } } @Test public void testBadEnumResource() throws ExecutionException, InterruptedException { initiateWebApplication(BadEnumResource.class); final ContainerResponse responseContext = getResponseContext(UriBuilder.fromPath("/") .queryParam("d", "123").build().toString()); assertEquals(404, responseContext.getStatus()); } public static class URIStringReaderProvider implements ParamConverterProvider { @Override public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType, final Annotation[] annotations) { if (rawType != URI.class) { return null; } //noinspection unchecked return (ParamConverter<T>) new ParamConverter<URI>() { public URI fromString(final String value) { try { return URI.create(value); } catch (final IllegalArgumentException iae) { throw new ExtractorException(iae); } } @Override public String toString(final URI value) throws IllegalArgumentException { return value.toString(); } }; } } @Path("/") public static class BadURIResource { @GET public String doGet(@QueryParam("d") final URI d) { return "URI"; } } @Test public void testBadURIResource() throws ExecutionException, InterruptedException { initiateWebApplication(BadURIResource.class, URIStringReaderProvider.class); final ContainerResponse responseContext = getResponseContext(UriBuilder.fromPath("/") .queryParam("d", "::::123").build().toString()); assertEquals(404, responseContext.getStatus()); } public static class ListOfStringReaderProvider implements ParamConverterProvider { @Override public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType, final Annotation[] annotations) { if (rawType != List.class) { return null; } if (genericType instanceof ParameterizedType) { final ParameterizedType parameterizedType = (ParameterizedType) genericType; if (parameterizedType.getActualTypeArguments().length != 1) { return null; } if (parameterizedType.getActualTypeArguments()[0] != String.class) { return null; } } else { return null; } //noinspection unchecked return (ParamConverter<T>) new ParamConverter<List<String>>() { @Override public List<String> fromString(final String value) { return Arrays.asList(value.split(",")); } @Override public String toString(final List<String> value) throws IllegalArgumentException { return value.toString(); } }; } } @Path("/") public static class ListOfStringResource { @GET public String doGet(@QueryParam("l") final List<List<String>> l) { return l.toString(); } } @Test public void testListOfStringReaderProvider() throws ExecutionException, InterruptedException { initiateWebApplication(ListOfStringResource.class, ListOfStringReaderProvider.class); final ContainerResponse responseContext = getResponseContext(UriBuilder.fromPath("/") .queryParam("l", "1,2,3").build().toString()); final String s = (String) responseContext.getEntity(); assertEquals(Collections.singletonList(Arrays.asList("1", "2", "3")).toString(), s); } public static class IntegerListConverterProvider implements ParamConverterProvider { @Override public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType, final Annotation[] annotations) { if (rawType == List.class) { final List<ClassTypePair> typePairs = ReflectionHelper.getTypeArgumentAndClass(genericType); final ClassTypePair typePair = (typePairs.size() == 1) ? typePairs.get(0) : null; if (typePair != null && typePair.rawClass() == Integer.class) { return new ParamConverter<T>() { @Override public T fromString(final String value) { final List<String> values = Arrays.asList(value.split(",")); return rawType.cast(values.stream().map(Integer::valueOf).collect(Collectors.toList())); } @Override public String toString(final T value) { return value.toString(); } }; } } return null; } } @Path("/") public static class IntegerListResource { @GET @Path("{path}") public String get(@PathParam("path") final List<Integer> paths, @QueryParam("query") final List<Integer> queries) { final List<Integer> intersection = new ArrayList<>(paths); intersection.retainAll(queries); return intersection.toString(); } } @Test public void testCustomListParamConverter() throws Exception { initiateWebApplication(IntegerListResource.class, IntegerListConverterProvider.class); final ContainerResponse responseContext = getResponseContext(UriBuilder.fromPath("/1,2,3,4,5") .queryParam("query", "3,4,5,6,7").build().toString()); //noinspection unchecked assertThat((String) responseContext.getEntity(), is("[3, 4, 5]")); } @Test public void testEagerConverter() throws Exception { try { new ApplicationHandler(new ResourceConfig(MyEagerParamProvider.class, Resource.class)); fail("ExtractorException expected."); } catch (final ExtractorException expected) { // ok } } @Test public void testLazyConverter() throws Exception { final ApplicationHandler application = new ApplicationHandler( new ResourceConfig(MyLazyParamProvider.class, Resource.class)); final ContainerResponse response = application.apply(RequestContextBuilder.from("/resource", "GET").build()).get(); assertEquals(400, response.getStatus()); } /** * This test verifies that the DateProvider is used for date string conversion instead of * string constructor that would be invoking deprecated Date(String) constructor. */ @Test public void testDateParamConverterIsChosenForDateString() { initiateWebApplication(); final ParamConverter<Date> converter = new ParamConverters.AggregatedProvider().getConverter(Date.class, Date.class, null); assertEquals("Unexpected date converter provider class", ParamConverters.DateProvider.class, converter.getClass().getEnclosingClass()); } @Path("resource") public static class Resource { @GET public String wrongDefaultValue(@HeaderParam("header") @DefaultValue("fail") final MyBean header) { return "a"; } } public static class MyEagerParamProvider implements ParamConverterProvider { @Override public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType, final Annotation[] annotations) { if (rawType != MyBean.class) { return null; } //noinspection unchecked return (ParamConverter<T>) new MyEagerParamConverter(); } } public static class MyLazyParamProvider implements ParamConverterProvider { @Override public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType, final Annotation[] annotations) { if (rawType != MyBean.class) { return null; } //noinspection unchecked return (ParamConverter<T>) new MyLazyParamConverter(); } } public static class MyAbstractParamConverter implements ParamConverter<MyBean> { @Override public MyBean fromString(final String value) throws IllegalArgumentException { if (value == null) { throw new IllegalArgumentException("Supplied value is null"); } if ("fail".equals(value)) { throw new RuntimeException("fail"); } final MyBean myBean = new MyBean(); myBean.setValue("*" + value + "*"); return myBean; } @Override public String toString(final MyBean bean) throws IllegalArgumentException { return "*:" + bean.getValue() + ":*"; } } public static class MyEagerParamConverter extends MyAbstractParamConverter { } @ParamConverter.Lazy public static class MyLazyParamConverter extends MyAbstractParamConverter { } public static class MyBean { private String value; public MyBean() { } public void setValue(final String value) { this.value = value; } public String getValue() { return value; } @Override public String toString() { return "MyBean{" + "value='" + value + '\'' + '}'; } } }