This is an automated email from the ASF dual-hosted git repository. johndament pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 6073a8eb236483bc6c0f8f3bd2c1026c7660c29a Author: Andy McCright <[email protected]> AuthorDate: Tue Dec 19 15:26:45 2017 -0600 MP Rest Client interface validation tests Checks: - that the passed-in type is an interface - that each method has, at most, one HTTP method annotation - that URI template variables in @Path annotations are resolved by @PathParam annotations --- .../client/CxfTypeSafeClientBuilder.java | 1 + .../cxf/microprofile/client/Messages.properties | 24 ++++ .../client/MicroProfileClientConfigurableImpl.java | 8 +- .../apache/cxf/microprofile/client/Validator.java | 124 +++++++++++++++++++ .../cxf/microprofile/client/ValidatorTest.java | 134 +++++++++++++++++++++ 5 files changed, 290 insertions(+), 1 deletion(-) diff --git a/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/CxfTypeSafeClientBuilder.java b/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/CxfTypeSafeClientBuilder.java index d895586..2daf944 100644 --- a/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/CxfTypeSafeClientBuilder.java +++ b/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/CxfTypeSafeClientBuilder.java @@ -42,6 +42,7 @@ public class CxfTypeSafeClientBuilder implements RestClientBuilder, Configurable if (baseUri == null) { throw new IllegalStateException("baseUrl not set"); } + Validator.checkValid(aClass); RegisterProvider[] providers = aClass.getAnnotationsByType(RegisterProvider.class); Configuration config = configImpl.getConfiguration(); if (providers != null) { diff --git a/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/Messages.properties b/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/Messages.properties new file mode 100644 index 0000000..d2d5363 --- /dev/null +++ b/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/Messages.properties @@ -0,0 +1,24 @@ +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# + +VALIDATION_NOT_AN_INTERFACE=The type, {0}, passed in to the build method is not an interface. +VALIDATION_METHOD_WITH_MULTIPLE_VERBS=The client interface contains a method, {0} annotated with more than one HTTP method: {1} +VALIDATION_UNRESOLVED_PATH_PARAMS=The client interface, {0} has one or more methods with unresolved path template variables: {1} diff --git a/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/MicroProfileClientConfigurableImpl.java b/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/MicroProfileClientConfigurableImpl.java index 3df4246..b27c47e 100644 --- a/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/MicroProfileClientConfigurableImpl.java +++ b/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/MicroProfileClientConfigurableImpl.java @@ -56,7 +56,13 @@ public class MicroProfileClientConfigurableImpl<C extends Configurable<C>> if (prop instanceof Boolean) { return (Boolean)prop; } else { - Config config = ConfigProvider.getConfig(); + Config config; + try { + config = ConfigProvider.getConfig(); + } catch (ExceptionInInitializerError | NoClassDefFoundError | IllegalStateException ex) { + // no config provider implementation + return false; + } return config.getOptionalValue(CONFIG_KEY_DISABLE_MAPPER, Boolean.class).orElse(false); } diff --git a/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/Validator.java b/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/Validator.java new file mode 100644 index 0000000..8d77e02 --- /dev/null +++ b/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/Validator.java @@ -0,0 +1,124 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.microprofile.client; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.Set; + +import javax.ws.rs.HttpMethod; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +import org.apache.cxf.common.i18n.BundleUtils; +import org.apache.cxf.common.i18n.Message; +import org.apache.cxf.jaxrs.model.URITemplate; +import org.eclipse.microprofile.rest.client.RestClientDefinitionException; + +final class Validator { + private static final ResourceBundle BUNDLE = BundleUtils.getBundle(Validator.class); + + private Validator() { + } + + + public static void checkValid(Class<?> userType) throws RestClientDefinitionException { + if (!userType.isInterface()) { + throwException("VALIDATION_NOT_AN_INTERFACE", userType); + } + Method[] methods = userType.getMethods(); + checkMethodsForMultipleHTTPMethodAnnotations(methods); + checkMethodsForInvalidURITemplates(userType, methods); + } + + private static void checkMethodsForMultipleHTTPMethodAnnotations(Method[] clientMethods) + throws RestClientDefinitionException { + + Map<String, Class<? extends Annotation>> httpMethods = new HashMap<>(); + for (Method method : clientMethods) { + + for (Annotation anno : method.getAnnotations()) { + Class<? extends Annotation> annoClass = anno.annotationType(); + HttpMethod verb = annoClass.getAnnotation(HttpMethod.class); + if (verb != null) { + httpMethods.put(verb.value(), annoClass); + } + } + if (httpMethods.size() > 1) { + throwException("VALIDATION_METHOD_WITH_MULTIPLE_VERBS", method, httpMethods.values()); + } + httpMethods.clear(); + } + + } + + private static void checkMethodsForInvalidURITemplates(Class<?> userType, Method[] methods) + throws RestClientDefinitionException { + + Path classPathAnno = userType.getAnnotation(Path.class); + + final Set<String> classLevelVariables = new HashSet<>(); + URITemplate classTemplate = null; + if (classPathAnno != null) { + classTemplate = new URITemplate(classPathAnno.value()); + classLevelVariables.addAll(classTemplate.getVariables()); + } + URITemplate template; + for (Method method : methods) { + + Path methodPathAnno = method.getAnnotation(Path.class); + if (methodPathAnno != null) { + template = classPathAnno == null ? new URITemplate(methodPathAnno.value()) + : new URITemplate(classPathAnno.value() + "/" + methodPathAnno.value()); + } else { + template = classTemplate; + } + if (template == null) { + continue; + } + Set<String> allVariables = new HashSet<>(template.getVariables()); + if (!allVariables.isEmpty()) { + Map<String, String> paramMap = new HashMap<>(); + for (Parameter p : method.getParameters()) { + PathParam pathParam = p.getAnnotation(PathParam.class); + if (pathParam != null) { + paramMap.put(pathParam.value(), "x"); + } + } + try { + template.substitute(paramMap, Collections.<String>emptySet(), false); + } catch (IllegalArgumentException ex) { + throwException("VALIDATION_UNRESOLVED_PATH_PARAMS", userType, method); + } + } + } + } + + private static void throwException(String msgKey, Object...msgParams) throws RestClientDefinitionException { + Message msg = new Message(msgKey, BUNDLE, msgParams); + throw new RestClientDefinitionException(msg.toString()); + } + +} diff --git a/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/ValidatorTest.java b/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/ValidatorTest.java new file mode 100644 index 0000000..7877574 --- /dev/null +++ b/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/ValidatorTest.java @@ -0,0 +1,134 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.microprofile.client; + +import java.net.MalformedURLException; +import java.net.URL; + +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.RestClientDefinitionException; + +import org.junit.Assert; +import org.junit.Test; + +public class ValidatorTest extends Assert { + + public abstract static class NotAnInterface { + @GET + public abstract Response get(); + } + + public interface MultiVerbMethod { + @GET + Response get(); + @PUT + Response put(String x); + @POST + @DELETE + Response postAndDelete(); + } + + @Path("/rest/{class}") + public interface UnresolvedClassUriTemplate { + @GET + Response myUnresolvedMethod(); + } + + @Path("/rest") + public interface UnresolvedMethodUriTemplate { + @Path("/{method}") + @GET + Response myOtherUnresolvedMethod(); + } + + @Path("/rest/{class}") + public interface PartiallyResolvedUriTemplate { + @GET + Response get(@PathParam("class")String className); + + @PUT + @Path("/{method}") + Response put(@PathParam("method")String methodName); + } + + @Path("/rest/{class}") + public interface PartiallyResolvedUriTemplate2 { + @DELETE + Response delete(@PathParam("class")String className); + + @POST + @Path("/{method}") + Response post(@PathParam("class")String className); + } + + private static RestClientBuilder newBuilder() { + RestClientBuilder builder = RestClientBuilder.newBuilder(); + try { + builder = builder.baseUrl(new URL("http://localhost:8080/test")); + } catch (MalformedURLException e) { + fail("MalformedURL - bad testcase"); + } + return builder; + } + + @Test + public void testNotAnInterface() { + test(NotAnInterface.class, "is not an interface", "NotAnInterface"); + } + + @Test + public void testMethodWithMultipleVerbs() { + test(MultiVerbMethod.class, "more than one HTTP method", "postAndDelete", "javax.ws.rs.POST", + "javax.ws.rs.DELETE"); + } + + @Test + public void testUnresolvedUriTemplates() { + test(UnresolvedClassUriTemplate.class, "unresolved path template variables", "UnresolvedClassUriTemplate", + "myUnresolvedMethod"); + test(UnresolvedMethodUriTemplate.class, "unresolved path template variables", "UnresolvedMethodUriTemplate", + "myOtherUnresolvedMethod"); + test(PartiallyResolvedUriTemplate.class, "unresolved path template variables", "PartiallyResolvedUriTemplate", + "put"); + test(PartiallyResolvedUriTemplate2.class, "unresolved path template variables", "PartiallyResolvedUriTemplate2", + "post"); + } + + private void test(Class<?> clientInterface, String...expectedMessageTexts) { + try { + newBuilder().build(clientInterface); + fail("Expected RestClientDefinitionException"); + } catch (RestClientDefinitionException ex) { + String msgText = ex.getMessage(); + assertNotNull("No message text in RestClientDefinitionException", msgText); + for (String expectedMessageText : expectedMessageTexts) { + assertTrue("Exception text does not contain expected message: " + expectedMessageText, + msgText.contains(expectedMessageText)); + } + } + } +} -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
