ValidatorTest.java
/*
* Copyright (c) 2010, 2022 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.server.model;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import javax.ws.rs.BeanParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.CookieParam;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.MatrixParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.Context;
import javax.ws.rs.sse.SseEventSink;
import javax.inject.Singleton;
import org.glassfish.jersey.Severity;
import org.glassfish.jersey.internal.BootstrapConfigurator;
import org.glassfish.jersey.internal.Errors;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.Injections;
import org.glassfish.jersey.internal.inject.PerLookup;
import org.glassfish.jersey.internal.util.Producer;
import org.glassfish.jersey.message.internal.MessageBodyFactory;
import org.glassfish.jersey.server.ApplicationHandler;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ContainerResponse;
import org.glassfish.jersey.server.RequestContextBuilder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerBootstrapBag;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.server.TestConfigConfigurator;
import org.glassfish.jersey.server.internal.inject.ParamExtractorConfigurator;
import org.glassfish.jersey.server.internal.inject.ValueParamProviderConfigurator;
import org.glassfish.jersey.server.model.internal.ModelErrors;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Taken from Jersey 1: jersey-server:com.sun.jersey.server.impl.modelapi.validation.ResourceModelValidatorTest.java
*
* @author Jakub Podlesak
*/
public class ValidatorTest {
private static final Logger LOGGER = Logger.getLogger(ValidatorTest.class.getName());
@Path("rootNonAmbigCtors")
public static class TestRootResourceNonAmbigCtors {
// TODO: hmmm, even if this is not ambiguous, it is strange; shall we warn, the 1st and the 2nd ctor won't be used?
public TestRootResourceNonAmbigCtors(@QueryParam("s") String s) {
}
public TestRootResourceNonAmbigCtors(@QueryParam("n") int n) {
}
public TestRootResourceNonAmbigCtors(@QueryParam("s") String s, @QueryParam("n") int n) {
}
@GET
public String getIt() {
return "it";
}
}
@Test
public void testRootResourceNonAmbigConstructors() throws Exception {
LOGGER.info("No issue should be reported if more public ctors exists with the same number of params, "
+ "but another just one is presented with more params at a root resource:");
Resource resource = Resource.builder(TestRootResourceNonAmbigCtors.class).build();
ServerBootstrapBag serverBag = initializeApplication();
ComponentModelValidator validator = new ComponentModelValidator(
serverBag.getValueParamProviders(), serverBag.getMessageBodyWorkers());
validator.validate(resource);
assertTrue(validator.getIssueList().isEmpty());
}
public static class MyBeanParam {
@HeaderParam("h")
String hParam;
}
@Singleton
@Path("rootSingleton/{p}")
public static class TestCantInjectFieldsForSingleton {
@MatrixParam("m")
String matrixParam;
@QueryParam("q")
String queryParam;
@PathParam("p")
String pParam;
@CookieParam("c")
String cParam;
@HeaderParam("h")
String hParam;
@BeanParam
MyBeanParam beanParam;
@GET
public String getIt() {
return "it";
}
}
public static interface ChildOfContainerRequestFilter extends ContainerRequestFilter {
}
@Path("rootSingleton/{p}")
public static class TestCantInjectFieldsForProvider implements ChildOfContainerRequestFilter {
@MatrixParam("m")
String matrixParam;
@QueryParam("q")
String queryParam;
@PathParam("p")
String pParam;
@CookieParam("c")
String cParam;
@HeaderParam("h")
String hParam;
@BeanParam
MyBeanParam beanParam;
@GET
public String getIt() {
return "it";
}
@Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
}
}
@Singleton
@Path("rootSingletonConstructorB/{p}")
public static class TestCantInjectConstructorParamsForSingleton {
public TestCantInjectConstructorParamsForSingleton() {
}
public TestCantInjectConstructorParamsForSingleton(@QueryParam("q") String queryParam) {
}
@GET
public String getIt() {
return "it";
}
}
@Singleton
@Path("rootSingletonConstructorB/{p}")
public static class TestCantInjectMethodParamsForSingleton {
@GET
public String getIt(@QueryParam("q") String queryParam) {
return "it";
}
}
@Singleton
@Path("sseEventSinkWithReturnType")
public static class TestSseEventSinkValidations {
@Path("notVoid")
@GET
public SseEventSink nonVoidReturnType(@Context SseEventSink eventSink) {
return eventSink;
}
@Path("multiple")
@GET
public void multipleInjection(@Context SseEventSink firstSink, @Context SseEventSink secondSink) {
// do nothing
}
}
@Path("rootRelaxedParser")
@Produces(" a/b, c/d ")
@Consumes({"e/f,g/h", " i/j"})
public static class TestRelaxedProducesConsumesParserRules {
@GET
@Produces({"e/f,g/h", " i/j"})
@Consumes(" a/b, c/d ")
public String getIt(@QueryParam("q") String queryParam) {
return "it";
}
}
@Test
public void testRelaxedProducesConsumesParserRules() throws Exception {
LOGGER.info("An issue should not be reported with the relaxed Produces/Consumes values parser.");
List<ResourceModelIssue> issues = testResourceValidation(TestCantInjectMethodParamsForSingleton.class);
assertTrue(issues.isEmpty());
}
@Test
public void testSingletonFieldsInjection() throws Exception {
LOGGER.info("An issue should be reported if injection is required for a singleton life-cycle:");
List<ResourceModelIssue> issues = testResourceValidation(TestCantInjectFieldsForSingleton.class);
assertTrue(!issues.isEmpty());
assertEquals(6, issues.size());
}
@Test
public void testCantReturnFromEventSinkInjectedMethod() {
LOGGER.info("An issue should be reported if method with injected SseEventSink parameter does not return void.");
final List<ResourceModelIssue> issues = testResourceValidation(TestSseEventSinkValidations.class);
assertTrue(!issues.isEmpty());
assertEquals(2, issues.size());
}
@Test
public void testProviderFieldsInjection() throws Exception {
LOGGER.info("An issue should be reported if injection is required for a class which is provider and "
+ "therefore singleton:");
List<ResourceModelIssue> issues = testResourceValidation(TestCantInjectFieldsForProvider.class);
assertTrue(!issues.isEmpty());
assertEquals(7, issues.size());
}
@Test
public void testSingletonConstructorParamsInjection() throws Exception {
LOGGER.info("An issue should be reported if injection is required for a singleton life-cycle:");
List<ResourceModelIssue> issues = testResourceValidation(TestCantInjectConstructorParamsForSingleton.class);
assertTrue(!issues.isEmpty());
assertEquals(1, issues.size());
}
@Test
public void testSingletonMethodParamsInjection() throws Exception {
LOGGER.info("An issue should not be reported as injections into the methods are allowed.");
List<ResourceModelIssue> issues = testResourceValidation(TestCantInjectMethodParamsForSingleton.class);
assertTrue(issues.isEmpty());
}
@Path("resourceAsProvider")
public static class ResourceAsProvider implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
}
@GET
public String get() {
return "get";
}
}
@Singleton
@PerLookup
@Path("resourceMultipleScopes")
public static class ResourceWithMultipleScopes implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
}
@GET
public String get() {
return "get";
}
}
@Test
public void testResourceAsProvider() throws Exception {
LOGGER.info("An issue should be reported as resource implements provider but does not define scope.");
List<ResourceModelIssue> issues = testResourceValidation(ResourceAsProvider.class);
assertEquals(1, issues.size());
}
@Test
public void testResourceWithMultipleScopes() throws Exception {
LOGGER.info("An issue should not be reported as resource defines multiple scopes.");
List<ResourceModelIssue> issues = testResourceValidation(ResourceWithMultipleScopes.class);
assertEquals(1, issues.size());
}
private List<ResourceModelIssue> testResourceValidation(final Class<?>... resourceClasses) {
return Errors.process(new Producer<List<ResourceModelIssue>>() {
@Override
public List<ResourceModelIssue> call() {
List<Resource> resources = new ArrayList<>();
for (Class<?> clazz : resourceClasses) {
final Resource res = Resource.builder(clazz).build();
if (res.getPath() != null) {
resources.add(res);
}
}
ResourceModel model = new ResourceModel.Builder(resources, false).build();
ServerBootstrapBag serverBag = initializeApplication();
ComponentModelValidator validator = new ComponentModelValidator(
serverBag.getValueParamProviders(), serverBag.getMessageBodyWorkers());
validator.validate(model);
return ModelErrors.getErrorsAsResourceModelIssues();
}
});
}
public static class TestNonPublicRM {
@POST
public String publicPost() {
return "public";
}
@GET
private String privateGet() {
return "private";
}
}
@Test
public void testNonPublicRM() throws Exception {
LOGGER.info("An issue should be reported if a resource method is not public:");
List<ResourceModelIssue> issues = testResourceValidation(TestNonPublicRM.class);
assertTrue(!issues.isEmpty());
}
public static class TestMoreThanOneEntity {
@PUT
public void put(String one, String two) {
}
}
@Test
@Disabled("Multiple entity validation not implemented yet.")
// TODO implement validation
public void suspendedTestMoreThanOneEntity() throws Exception {
LOGGER.info("An issue should be reported if a resource method takes more than one entity params:");
List<ResourceModelIssue> issues = testResourceValidation(TestMoreThanOneEntity.class);
assertTrue(!issues.isEmpty());
}
@Path("test")
public static class TestGetRMReturningVoid {
@GET
public void getMethod() {
}
}
@Test
public void testGetRMReturningVoid() throws Exception {
LOGGER.info("An issue should be reported if a non-async get method returns void:");
List<ResourceModelIssue> issues = testResourceValidation(TestGetRMReturningVoid.class);
assertFalse(issues.isEmpty());
assertNotEquals(Severity.FATAL, issues.get(0).getSeverity());
}
public static class TestAsyncGetRMReturningVoid {
@GET
public void getMethod(@Suspended AsyncResponse ar) {
}
}
@Test
public void testAsyncGetRMReturningVoid() throws Exception {
LOGGER.info("An issue should NOT be reported if a async get method returns void:");
List<ResourceModelIssue> issues = testResourceValidation(TestAsyncGetRMReturningVoid.class);
assertTrue(issues.isEmpty());
}
@Path("test")
public static class TestGetRMConsumingEntity {
@GET
public String getMethod(Object o) {
return "it";
}
}
@Test
public void testGetRMConsumingEntity() throws Exception {
LOGGER.info("An issue should be reported if a get method consumes an entity:");
List<ResourceModelIssue> issues = testResourceValidation(TestGetRMConsumingEntity.class);
assertFalse(issues.isEmpty());
assertNotEquals(Severity.FATAL, issues.get(0).getSeverity());
}
@Path("test")
public static class TestGetRMConsumingFormParam {
@GET
public String getMethod(@FormParam("f") String s1, @FormParam("g") String s2) {
return "it";
}
}
@Test
public void testGetRMConsumingFormParam() throws Exception {
LOGGER.info("An issue should be reported if a get method consumes a form param:");
List<ResourceModelIssue> issues = testResourceValidation(TestGetRMConsumingFormParam.class);
assertTrue(issues.size() == 1);
assertEquals(Severity.FATAL, issues.get(0).getSeverity());
}
@Path("test")
public static class TestSRLReturningVoid {
@Path("srl")
public void srLocator() {
}
}
@Test
public void testSRLReturningVoid() throws Exception {
LOGGER.info("An issue should be reported if a sub-resource locator returns void:");
Resource resource = Resource.builder(TestSRLReturningVoid.class).build();
ServerBootstrapBag serverBag = initializeApplication();
ComponentModelValidator validator = new ComponentModelValidator(
serverBag.getValueParamProviders(), serverBag.getMessageBodyWorkers());
validator.validate(resource);
assertTrue(validator.fatalIssuesFound());
}
@Path("test")
public static class TestGetSRMReturningVoid {
@GET
@Path("srm")
public void getSRMethod() {
}
}
@Test
public void testGetSRMReturningVoid() throws Exception {
LOGGER.info("An issue should be reported if a get sub-resource method returns void:");
List<ResourceModelIssue> issues = testResourceValidation(TestGetSRMReturningVoid.class);
assertFalse(issues.isEmpty());
assertNotEquals(Severity.FATAL, issues.get(0).getSeverity());
}
@Path("test")
public static class TestGetSRMConsumingEntity {
@Path("p")
@GET
public String getMethod(Object o) {
return "it";
}
}
@Test
public void testGetSRMConsumingEntity() throws Exception {
LOGGER.info("An issue should be reported if a get method consumes an entity:");
List<ResourceModelIssue> issues = testResourceValidation(TestGetSRMConsumingEntity.class);
assertFalse(issues.isEmpty());
assertNotEquals(Severity.FATAL, issues.get(0).getSeverity());
}
@Path("root")
public static class TestGetSRMConsumingFormParam {
@GET
@Path("p")
public String getMethod(@FormParam("f") String formParam) {
return "it";
}
}
@Test
public void testGetSRMConsumingFormParam() throws Exception {
LOGGER.info("An issue should be reported if a get method consumes a form param:");
List<ResourceModelIssue> issues = testResourceValidation(TestGetSRMConsumingFormParam.class);
assertFalse(issues.isEmpty());
assertEquals(Severity.FATAL, issues.get(0).getSeverity());
}
@Path("rootMultipleHttpMethodDesignatorsRM")
public static class TestMultipleHttpMethodDesignatorsRM {
@GET
@PUT
public String getPutIt() {
return "it";
}
}
@Test
public void testMultipleHttpMethodDesignatorsRM() throws Exception {
LOGGER.info("An issue should be reported if more than one HTTP method designator exist on a resource "
+ "method:");
Resource resource = Resource.builder(TestMultipleHttpMethodDesignatorsRM.class).build();
ServerBootstrapBag serverBag = initializeApplication();
ComponentModelValidator validator = new ComponentModelValidator(
serverBag.getValueParamProviders(), serverBag.getMessageBodyWorkers());
validator.validate(resource);
assertTrue(validator.fatalIssuesFound());
}
@Path("rootMultipleHttpMethodDesignatorsSRM")
public static class TestMultipleHttpMethodDesignatorsSRM {
@Path("srm")
@POST
@PUT
public String postPutIt() {
return "it";
}
}
@Test
public void testMultipleHttpMethodDesignatorsSRM() throws Exception {
LOGGER.info("An issue should be reported if more than one HTTP method designator exist on a sub-resource "
+ "method:");
Resource resource = Resource.builder(TestMultipleHttpMethodDesignatorsSRM.class).build();
ServerBootstrapBag serverBag = initializeApplication();
ComponentModelValidator validator = new ComponentModelValidator(
serverBag.getValueParamProviders(), serverBag.getMessageBodyWorkers());
validator.validate(resource);
assertTrue(validator.fatalIssuesFound());
}
@Path("rootEntityParamOnSRL")
public static class TestEntityParamOnSRL {
@Path("srl")
public String locator(String s) {
return "it";
}
}
@Test
public void testEntityParamOnSRL() throws Exception {
LOGGER.info("An issue should be reported if an entity parameter exists on a sub-resource locator:");
Resource resource = Resource.builder(TestEntityParamOnSRL.class).build();
ServerBootstrapBag serverBag = initializeApplication();
ComponentModelValidator validator = new ComponentModelValidator(
serverBag.getValueParamProviders(), serverBag.getMessageBodyWorkers());
validator.validate(resource);
assertTrue(validator.fatalIssuesFound());
}
@Path(value = "/DeleteTest")
public static class TestNonConflictingHttpMethodDelete {
static String html_content =
"<html>" + "<head><title>Delete text/html</title></head>"
+ "<body>Delete text/html</body></html>";
@DELETE
@Produces(value = "text/plain")
public String getPlain() {
return "Delete text/plain";
}
@DELETE
@Produces(value = "text/html")
public String getHtml() {
return html_content;
}
@DELETE
@Path(value = "/sub")
@Produces(value = "text/html")
public String getSub() {
return html_content;
}
}
@Test
public void testNonConflictingHttpMethodDelete() throws Exception {
LOGGER.info("No issue should be reported if produced mime types differ");
List<ResourceModelIssue> issues = testResourceValidation(TestNonConflictingHttpMethodDelete.class);
assertTrue(issues.isEmpty());
}
@Path(value = "/AmbigParamTest")
public static class TestAmbiguousParams {
@QueryParam("q")
@HeaderParam("q")
private int a;
@QueryParam("b")
@HeaderParam("b")
@MatrixParam("q")
public void setB(String b) {
}
@GET
@Path("a")
public String get(@PathParam("a") @QueryParam("a") String a) {
return "hi";
}
@GET
@Path("b")
public String getSub(@PathParam("a") @QueryParam("b") @MatrixParam("c") String a,
@MatrixParam("m") @QueryParam("m") int i) {
return "hi";
}
@Path("c")
public Object getSubLoc(@MatrixParam("m") @CookieParam("c") String a) {
return null;
}
}
@Test
public void testAmbiguousParams() throws Exception {
LOGGER.info("A warning should be reported if ambiguous source of a parameter is seen");
Errors.process(new Runnable() {
@Override
public void run() {
Resource resource = Resource.builder(TestAmbiguousParams.class).build();
ServerBootstrapBag serverBag = initializeApplication();
ComponentModelValidator validator = new ComponentModelValidator(
serverBag.getValueParamProviders(), serverBag.getMessageBodyWorkers());
validator.validate(resource);
assertTrue(!validator.fatalIssuesFound());
assertEquals(4, validator.getIssueList().size());
assertEquals(6, Errors.getErrorMessages().size());
}
});
}
@Path(value = "/EmptyPathSegmentTest")
public static class TestEmptyPathSegment {
@GET
@Path("/")
public String get() {
return "hi";
}
}
@Test
public void testEmptyPathSegment() throws Exception {
LOGGER.info("A warning should be reported if @Path with \"/\" or empty string value is seen");
Resource resource = Resource.builder(TestEmptyPathSegment.class).build();
ServerBootstrapBag serverBag = initializeApplication();
ComponentModelValidator validator = new ComponentModelValidator(
serverBag.getValueParamProviders(), serverBag.getMessageBodyWorkers());
validator.validate(resource);
assertTrue(!validator.fatalIssuesFound());
assertEquals(1, validator.getIssueList().size());
}
public static class TypeVariableResource<T, V> {
@QueryParam("v")
V fieldV;
V methodV;
@QueryParam("v")
public void set(V methodV) {
this.methodV = methodV;
}
@GET
public String get(@BeanParam() V getV) {
return getV.toString() + fieldV.toString() + methodV.toString();
}
@POST
public T post(T t) {
return t;
}
@Path("sub")
@POST
public T postSub(T t) {
return t;
}
}
@Test
public void testTypeVariableResource() throws Exception {
LOGGER.info("");
Errors.process(new Runnable() {
@Override
public void run() {
Resource resource = Resource.builder(TypeVariableResource.class).build();
ServerBootstrapBag serverBag = initializeApplication();
ComponentModelValidator validator = new ComponentModelValidator(
serverBag.getValueParamProviders(), serverBag.getMessageBodyWorkers());
validator.validate(resource);
assertTrue(!validator.fatalIssuesFound());
assertEquals(5, validator.getIssueList().size());
assertEquals(7, Errors.getErrorMessages().size());
}
});
}
public static class ParameterizedTypeResource<T, V> {
@QueryParam("v")
Collection<V> fieldV;
List<List<V>> methodV;
@QueryParam("v")
public void set(List<List<V>> methodV) {
this.methodV = methodV;
}
@GET
public String get(@QueryParam("v") List<V> getV) {
return "";
}
@POST
public Collection<T> post(Collection<T> t) {
return t;
}
@Path("sub")
@POST
public Collection<T> postSub(Collection<T> t) {
return t;
}
}
public static class ConcreteParameterizedTypeResource extends ParameterizedTypeResource<String, String> {
}
@Test
public void testParameterizedTypeResource() throws Exception {
LOGGER.info("");
Resource resource = Resource.builder(ConcreteParameterizedTypeResource.class).build();
ServerBootstrapBag serverBag = initializeApplication();
ComponentModelValidator validator = new ComponentModelValidator(
serverBag.getValueParamProviders(), serverBag.getMessageBodyWorkers());
validator.validate(resource);
assertTrue(!validator.fatalIssuesFound());
assertEquals(0, validator.getIssueList().size());
}
public static class GenericArrayResource<T, V> {
@QueryParam("v")
V[] fieldV;
V[] methodV;
@QueryParam("v")
public void set(V[] methodV) {
this.methodV = methodV;
}
@POST
public T[] post(T[] t) {
return t;
}
@Path("sub")
@POST
public T[] postSub(T[] t) {
return t;
}
}
public static class ConcreteGenericArrayResource extends GenericArrayResource<String, String> {
}
@Test
public void testGenericArrayResource() throws Exception {
LOGGER.info("");
Resource resource = Resource.builder(ConcreteGenericArrayResource.class).build();
ServerBootstrapBag serverBag = initializeApplication();
ComponentModelValidator validator = new ComponentModelValidator(
serverBag.getValueParamProviders(), serverBag.getMessageBodyWorkers());
validator.validate(resource);
assertTrue(!validator.fatalIssuesFound());
assertEquals(0, validator.getIssueList().size());
}
// TODO: test multiple root resources with the same uriTemplate (in WebApplicationImpl.processRootResources ?)
@Path("test1")
public static class PercentEncodedTest {
@GET
@Path("%5B%5D")
public String percent() {
return "percent";
}
@GET
@Path("[]")
public String notEncoded() {
return "not-encoded";
}
}
@Test
public void testPercentEncoded() throws Exception {
List<ResourceModelIssue> issues = testResourceValidation(PercentEncodedTest.class);
assertEquals(1, issues.size());
assertEquals(Severity.FATAL, issues.get(0).getSeverity());
}
@Path("test2")
public static class PercentEncodedCaseSensitiveTest {
@GET
@Path("%5B%5D")
public String percent() {
return "percent";
}
@GET
@Path("%5b%5d")
public String notEncoded() {
return "not-encoded";
}
}
@Test
public void testPercentEncodedCaseSensitive() throws Exception {
List<ResourceModelIssue> issues = testResourceValidation(PercentEncodedCaseSensitiveTest.class);
assertEquals(1, issues.size());
assertEquals(Severity.FATAL, issues.get(0).getSeverity());
}
@Path("ambiguous-parameter")
public static class AmbiguousParameterResource {
@POST
public String moreNonAnnotatedParameters(@HeaderParam("something") String header, String entity1, String entity2) {
return "x";
}
}
@Test
public void testNotAnnotatedParameters() throws Exception {
Resource resource = Resource.builder(AmbiguousParameterResource.class).build();
ServerBootstrapBag serverBag = initializeApplication();
ComponentModelValidator validator = new ComponentModelValidator(
serverBag.getValueParamProviders(), serverBag.getMessageBodyWorkers());
validator.validate(resource);
final List<ResourceModelIssue> errorMessages = validator.getIssueList();
assertEquals(1, errorMessages.size());
assertEquals(Severity.FATAL, errorMessages.get(0).getSeverity());
}
public static class SubResource {
public static final String MESSAGE = "Got it!";
@GET
public String getIt() {
return MESSAGE;
}
}
/**
* Should report warning during validation as Resource cannot have resource method and sub resource locators on the same path.
*/
@Path("failRoot")
public static class MethodAndLocatorResource {
@Path("/")
public SubResource getSubResourceLocator() {
return new SubResource();
}
@GET
public String get() {
return "should never be called - fails during validation";
}
}
@Test
public void testLocatorAndMethodValidation() throws Exception {
LOGGER.info("Should report warning during validation as Resource cannot have resource method and sub "
+ "resource locators on the same path.");
List<ResourceModelIssue> issues = testResourceValidation(MethodAndLocatorResource.class);
assertEquals(1, issues.size());
assertNotEquals(Severity.FATAL, issues.get(0).getSeverity());
}
/**
* Should report warning during validation as Resource cannot have resource method and sub resource locators on the same path.
*/
@Path("failRoot")
public static class MethodAndLocatorResource2 {
@Path("a")
public SubResource getSubResourceLocator() {
return new SubResource();
}
@GET
@Path("a")
public String get() {
return "should never be called - fails during validation";
}
}
@Test
public void testLocatorAndMethod2Validation() throws Exception {
LOGGER.info("Should report warning during validation as Resource cannot have resource method and sub "
+ "resource locators on the same path.");
List<ResourceModelIssue> issues = testResourceValidation(MethodAndLocatorResource2.class);
assertEquals(1, issues.size());
assertNotEquals(Severity.FATAL, issues.get(0).getSeverity());
}
/**
* Warning should be reported informing wich locator will be used in runtime
*/
@Path("locator")
public static class TwoLocatorsResource {
@Path("a")
public SubResource locator() {
return new SubResource();
}
@Path("a")
public SubResource locator2() {
return new SubResource();
}
}
@Test
public void testLocatorPathValidationFail() throws Exception {
LOGGER.info("Should report error during validation as Resource cannot have ambiguous sub resource locators.");
List<ResourceModelIssue> issues = testResourceValidation(TwoLocatorsResource.class);
assertEquals(1, issues.size());
assertEquals(Severity.FATAL, issues.get(0).getSeverity());
}
@Path("root")
public static class ResourceRoot {
@GET
@Path("sub-root") // in path collision with ResourceSubRoot.get()
public String get() {
return "should never be called - fails during validation";
}
}
@Path("root/sub-root")
public static class ResourceSubPathRoot {
@GET
public String get() {
return "should never be called - fails during validation";
}
}
@Path("root")
public static class ResourceRootNotUnique {
@GET
@Path("sub-root") // in path collision with ResourceSubRoot.get()
public String get() {
return "should never be called - fails during validation";
}
}
@Test
@Disabled
// TODO: need to add validation to detect ambiguous problems of ResourceSubPathRoot and two other resources.
public void testTwoOverlappingSubResourceValidation() throws Exception {
List<ResourceModelIssue> issues = testResourceValidation(ResourceRoot.class, ResourceSubPathRoot.class);
assertEquals(1, issues.size());
assertEquals(Severity.FATAL, issues.get(0).getSeverity());
}
@Test
@Disabled
public void testTwoOverlappingResourceValidation() throws Exception {
List<ResourceModelIssue> issues = testResourceValidation(ResourceRoot.class, ResourceRootNotUnique.class);
assertEquals(1, issues.size());
assertEquals(Severity.FATAL, issues.get(0).getSeverity());
}
@Path("root")
public static class EmptyResource {
public String get() {
return "not a get method.";
}
}
@Test
public void testEmptyResourcel() throws Exception {
LOGGER.info("Should report warning during validation as Resource cannot have resource method and sub "
+ "resource locators on the same path.");
List<ResourceModelIssue> issues = testResourceValidation(EmptyResource.class);
assertEquals(1, issues.size());
assertFalse(issues.get(0).getSeverity() == Severity.FATAL);
}
@Path("{abc}")
public static class AmbiguousResource1 {
@Path("x")
@GET
public String get() {
return "get";
}
}
@Path("{def}")
public static class AmbiguousResource2 {
@Path("x")
@GET
public String get() {
return "get";
}
}
@Path("unique")
public static class UniqueResource {
@Path("x")
public String get() {
return "get";
}
}
@Test
public void testAmbiguousResources() throws Exception {
LOGGER.info("Should report warning during validation error as resource path patterns are ambiguous ({abc} and {def} "
+ "results into same path pattern).");
List<ResourceModelIssue> issues = testResourceValidation(AmbiguousResource1.class, AmbiguousResource2.class,
UniqueResource.class);
assertEquals(1, issues.size());
assertEquals(Severity.FATAL, issues.get(0).getSeverity());
}
@Path("{abc}")
public static class AmbiguousLocatorResource1 {
@Path("x")
public SubResource locator() {
return new SubResource();
}
}
@Path("{def}")
public static class AmbiguousLocatorResource2 {
@Path("x")
public SubResource locator2() {
return new SubResource();
}
}
@Test
public void testAmbiguousResourceLocators() throws Exception {
LOGGER.info("Should report warning during validation error as resource path patterns are ambiguous ({abc} and {def} "
+ "results into same path pattern).");
List<ResourceModelIssue> issues = testResourceValidation(AmbiguousLocatorResource1.class,
AmbiguousLocatorResource2.class);
assertEquals(1, issues.size());
assertEquals(Severity.FATAL, issues.get(0).getSeverity());
}
@Path("resource")
public static class ResourceMethodWithVoidReturnType {
@GET
@Path("error")
public void error() {
}
}
@Test
public void testVoidReturnType() throws Exception {
LOGGER.info("Should report hint during validation as @GET resource method returns void.");
List<ResourceModelIssue> issues = testResourceValidation(ResourceMethodWithVoidReturnType.class);
assertEquals(1, issues.size());
assertEquals(Severity.HINT, issues.get(0).getSeverity());
}
@Path("paramonsetter")
public static class ResourceWithParamOnSetter {
@QueryParam("id")
public void setId(String id) {
}
@GET
public String get() {
return "get";
}
}
@Test
public void testParamOnSetterIsOk() {
LOGGER.info("Validation should report no issues.");
List<ResourceModelIssue> issues = testResourceValidation(ResourceWithParamOnSetter.class);
assertEquals(0, issues.size());
}
@Path("paramonresourcepath")
public static class ResourceWithParamOnResourcePathAnnotatedMethod {
@QueryParam("id")
@Path("fail")
public String query() {
return "post";
}
}
@Test
public void testParamOnResourcePathAnnotatedMethodFails() {
LOGGER.info("Should report fatal during validation as @Path method should not be annotated with parameter annotation");
List<ResourceModelIssue> issues = testResourceValidation(ResourceWithParamOnResourcePathAnnotatedMethod.class);
assertEquals(1, issues.size());
assertEquals(Severity.FATAL, issues.get(0).getSeverity());
}
@Path("paramonresourceget")
public static class ResourceGETMethodFails {
@QueryParam("id")
@GET
public String get(@PathParam("abc") String id) {
return "get";
}
}
@Test
public void testParamOnResourceGETMethodFails() {
LOGGER.info("Should report fatal during validation as @GET method should not be annotated with parameter annotation");
List<ResourceModelIssue> issues = testResourceValidation(ResourceGETMethodFails.class);
assertEquals(1, issues.size());
assertEquals(Severity.FATAL, issues.get(0).getSeverity());
}
/**
* Test of disabled validation failing on errors.
*/
@Path("test-disable-validation-fail-on-error")
public static class TestDisableValidationFailOnErrorResource {
@GET
public String get() {
return "PASSED";
}
}
@Test
public void testDisableFailOnErrors() throws ExecutionException, InterruptedException {
final ResourceConfig rc = new ResourceConfig(
AmbiguousLocatorResource1.class,
AmbiguousLocatorResource2.class,
AmbiguousParameterResource.class,
AmbiguousResource1.class,
AmbiguousResource2.class,
ConcreteGenericArrayResource.class,
ConcreteParameterizedTypeResource.class,
EmptyResource.class,
GenericArrayResource.class,
MethodAndLocatorResource.class,
MethodAndLocatorResource2.class,
MyBeanParam.class,
ParameterizedTypeResource.class,
PercentEncodedCaseSensitiveTest.class,
PercentEncodedTest.class,
ResourceAsProvider.class,
ResourceGETMethodFails.class,
ResourceMethodWithVoidReturnType.class,
ResourceRoot.class,
ResourceRootNotUnique.class,
ResourceSubPathRoot.class,
ResourceWithMultipleScopes.class,
ResourceWithParamOnResourcePathAnnotatedMethod.class,
TestAmbiguousParams.class,
TestAsyncGetRMReturningVoid.class,
TestEmptyPathSegment.class,
TestEntityParamOnSRL.class,
TestGetRMConsumingEntity.class,
TestGetRMConsumingFormParam.class,
TestGetRMReturningVoid.class,
TestGetSRMConsumingEntity.class,
TestGetSRMConsumingFormParam.class,
TestGetSRMReturningVoid.class,
TestMoreThanOneEntity.class,
TestMultipleHttpMethodDesignatorsRM.class,
TestMultipleHttpMethodDesignatorsSRM.class,
TestNonConflictingHttpMethodDelete.class,
TestNonPublicRM.class,
TestRelaxedProducesConsumesParserRules.class,
TestRootResourceNonAmbigCtors.class,
TestSRLReturningVoid.class,
TwoLocatorsResource.class,
TypeVariableResource.class,
UniqueResource.class,
TestDisableValidationFailOnErrorResource.class // we should still be able to invoke a GET on this one.
);
rc.property(ServerProperties.RESOURCE_VALIDATION_IGNORE_ERRORS, true);
ApplicationHandler ah = new ApplicationHandler(rc);
final ContainerRequest request = RequestContextBuilder.from("/test-disable-validation-fail-on-error", "GET").build();
ContainerResponse response = ah.apply(request).get();
assertEquals(200, response.getStatus());
assertEquals("PASSED", response.getEntity());
}
private ServerBootstrapBag initializeApplication() {
List<BootstrapConfigurator> bootstrapConfigurators = Collections.unmodifiableList(Arrays.asList(
new TestConfigConfigurator(),
new ParamExtractorConfigurator(),
new ValueParamProviderConfigurator(),
new MessageBodyFactory.MessageBodyWorkersConfigurator()));
InjectionManager injectionManager = Injections.createInjectionManager();
ServerBootstrapBag bootstrapBag = new ServerBootstrapBag();
bootstrapConfigurators.forEach(configurer -> configurer.init(injectionManager, bootstrapBag));
injectionManager.completeRegistration();
bootstrapConfigurators.forEach(configurer -> configurer.postInit(injectionManager, bootstrapBag));
return bootstrapBag;
}
}