CAMEL-10164: swagger component for making rest calls with swagger schema validation and facade to actual HTTP client in use
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/b05fd502 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/b05fd502 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/b05fd502 Branch: refs/heads/master Commit: b05fd50222b99d80920938bf99d02d670c6885d6 Parents: 9801df7 Author: Claus Ibsen <[email protected]> Authored: Fri Aug 26 15:53:00 2016 +0200 Committer: Claus Ibsen <[email protected]> Committed: Fri Aug 26 16:53:31 2016 +0200 ---------------------------------------------------------------------- .../camel/component/rest/RestEndpoint.java | 17 ++++- .../producer/JettyRestProducerApiDocTest.java | 51 +++++++++++++ .../JettyRestProducerInvalidApiDocTest.java | 73 ++++++++++++++++++ .../swagger/SwaggerRestProducerFactory.java | 78 ++++++++------------ .../producer/DummyRestProducerFactory.java | 6 +- 5 files changed, 169 insertions(+), 56 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/b05fd502/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java b/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java index 064924f..671e27f 100644 --- a/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java +++ b/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java @@ -252,6 +252,7 @@ public class RestEndpoint extends DefaultEndpoint { @Override public Producer createProducer() throws Exception { + RestProducerFactory apiDocFactory = null; RestProducerFactory factory = null; if (apiDoc != null) { @@ -262,7 +263,7 @@ public class RestEndpoint extends DefaultEndpoint { Object instance = finder.newInstance(DEFAULT_API_COMPONENT_NAME); if (instance instanceof RestProducerFactory) { // this factory from camel-swagger-java will facade the http component in use - factory = (RestProducerFactory) instance; + apiDocFactory = (RestProducerFactory) instance; } parameters.put("apiDoc", apiDoc); } catch (NoFactoryAvailableException e) { @@ -270,8 +271,8 @@ public class RestEndpoint extends DefaultEndpoint { } } - String cname = null; - if (factory == null && getComponentName() != null) { + String cname = getComponentName(); + if (cname != null) { Object comp = getCamelContext().getRegistry().lookupByName(getComponentName()); if (comp != null && comp instanceof RestProducerFactory) { factory = (RestProducerFactory) comp; @@ -316,7 +317,15 @@ public class RestEndpoint extends DefaultEndpoint { if (factory != null) { LOG.debug("Using RestProducerFactory: {}", factory); - Producer producer = factory.createProducer(getCamelContext(), host, method, path, uriTemplate, queryParameters, consumes, produces, parameters); + + Producer producer; + if (apiDocFactory != null) { + // wrap the factory using the api doc factory which will use the factory + parameters.put("restProducerFactory", factory); + producer = apiDocFactory.createProducer(getCamelContext(), host, method, path, uriTemplate, queryParameters, consumes, produces, parameters); + } else { + producer = factory.createProducer(getCamelContext(), host, method, path, uriTemplate, queryParameters, consumes, produces, parameters); + } return new RestProducer(this, producer); } else { throw new IllegalStateException("Cannot find RestProducerFactory in Registry or as a Component to use"); http://git-wip-us.apache.org/repos/asf/camel/blob/b05fd502/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/rest/producer/JettyRestProducerApiDocTest.java ---------------------------------------------------------------------- diff --git a/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/rest/producer/JettyRestProducerApiDocTest.java b/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/rest/producer/JettyRestProducerApiDocTest.java new file mode 100644 index 0000000..ede12e5 --- /dev/null +++ b/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/rest/producer/JettyRestProducerApiDocTest.java @@ -0,0 +1,51 @@ +/** + * 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.camel.component.jetty.rest.producer; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.jetty.BaseJettyTest; +import org.junit.Test; + +public class JettyRestProducerApiDocTest extends BaseJettyTest { + + @Test + public void testJettyProducerGet() throws Exception { + String out = fluentTemplate.withHeader("name", "Donald Duck").to("direct:start").request(String.class); + assertEquals("Hello Donald Duck", out); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + // configure to use localhost with the given port + restConfiguration().component("jetty").host("localhost").port(getPort()); + + from("direct:start") + .to("rest:get:api/hello/hi/{name}?apiDoc=hello-api.json"); + + // use the rest DSL to define the rest services + rest("/api/") + .get("hello/hi/{name}") + .route() + .transform().simple("Hello ${header.name}"); + } + }; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/b05fd502/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/rest/producer/JettyRestProducerInvalidApiDocTest.java ---------------------------------------------------------------------- diff --git a/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/rest/producer/JettyRestProducerInvalidApiDocTest.java b/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/rest/producer/JettyRestProducerInvalidApiDocTest.java new file mode 100644 index 0000000..9711dab --- /dev/null +++ b/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/rest/producer/JettyRestProducerInvalidApiDocTest.java @@ -0,0 +1,73 @@ +/** + * 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.camel.component.jetty.rest.producer; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +public class JettyRestProducerInvalidApiDocTest extends CamelTestSupport { + + @Override + public boolean isUseRouteBuilder() { + return false; + } + + @Test + public void testInvalidPath() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + // configure to use localhost with the given port + restConfiguration().component("jetty").host("localhost"); + + from("direct:start") + .to("rest:get:api/hello/unknown/{name}?apiDoc=hello-api.json"); + + } + }); + try { + context.start(); + fail("Should fail"); + } catch (Exception e) { + IllegalArgumentException iae = assertIsInstanceOf(IllegalArgumentException.class, e.getCause().getCause()); + assertEquals("Swagger api-doc does not contain operation for get:/api/hello/unknown/{name}", iae.getMessage()); + } + } + + @Test + public void testInvalidQuery() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + // configure to use localhost with the given port + restConfiguration().component("jetty").host("localhost"); + + from("direct:start") + .to("rest:get:api/bye/?unknown={name}&apiDoc=hello-api.json"); + + } + }); + try { + context.start(); + fail("Should fail"); + } catch (Exception e) { + IllegalArgumentException iae = assertIsInstanceOf(IllegalArgumentException.class, e.getCause().getCause()); + assertEquals("Swagger api-doc does not contain query parameter name for get:/api/bye", iae.getMessage()); + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/b05fd502/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/SwaggerRestProducerFactory.java ---------------------------------------------------------------------- diff --git a/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/SwaggerRestProducerFactory.java b/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/SwaggerRestProducerFactory.java index 3a74778..7a7a6a4 100644 --- a/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/SwaggerRestProducerFactory.java +++ b/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/SwaggerRestProducerFactory.java @@ -19,15 +19,13 @@ package org.apache.camel.swagger; import java.io.InputStream; import java.util.List; import java.util.Map; -import java.util.Set; import io.swagger.models.Operation; import io.swagger.models.Path; import io.swagger.models.Swagger; +import io.swagger.models.parameters.Parameter; import io.swagger.parser.SwaggerParser; import org.apache.camel.CamelContext; -import org.apache.camel.Component; -import org.apache.camel.NoSuchBeanException; import org.apache.camel.Producer; import org.apache.camel.spi.RestProducerFactory; import org.apache.camel.util.CollectionStringBuffer; @@ -43,7 +41,7 @@ public class SwaggerRestProducerFactory implements RestProducerFactory { @Override public Producer createProducer(CamelContext camelContext, String host, - String verb, String basePath, String uriTemplate, + String verb, String basePath, String uriTemplate, String queryParameters, String consumes, String produces, Map<String, Object> parameters) throws Exception { String apiDoc = (String) parameters.get("apiDoc"); @@ -64,9 +62,25 @@ public class SwaggerRestProducerFactory implements RestProducerFactory { throw new IllegalArgumentException("Swagger api-doc does not contain operation for " + verb + ":" + path); } + // validate if we have the query parameters also + if (queryParameters != null) { + for (Parameter param : operation.getParameters()) { + if ("query".equals(param.getIn()) && param.getRequired()) { + // check if we have the required query parameter defined + String key = param.getName(); + String token = key + "="; + boolean hasQuery = queryParameters.contains(token); + if (!hasQuery) { + throw new IllegalArgumentException("Swagger api-doc does not contain query parameter " + key + " for " + verb + ":" + path); + } + } + } + } + String componentName = (String) parameters.get("componentName"); - Producer producer = createHttpProducer(camelContext, swagger, operation, host, verb, path, produces, consumes, componentName, parameters); + Producer producer = createHttpProducer(camelContext, swagger, operation, host, verb, path, queryParameters, + produces, consumes, componentName, parameters); return producer; } @@ -83,6 +97,12 @@ public class SwaggerRestProducerFactory implements RestProducerFactory { } private Operation getSwaggerOperation(Swagger swagger, String verb, String path) { + // path may include base path so skip that + String basePath = swagger.getBasePath(); + if (basePath != null && path.startsWith(basePath)) { + path = path.substring(basePath.length()); + } + Path modelPath = swagger.getPath(path); if (modelPath == null) { return null; @@ -109,53 +129,13 @@ public class SwaggerRestProducerFactory implements RestProducerFactory { } private Producer createHttpProducer(CamelContext camelContext, Swagger swagger, Operation operation, - String host, String verb, String path, String consumes, String produces, + String host, String verb, String path, String queryParameters, + String consumes, String produces, String componentName, Map<String, Object> parameters) throws Exception { LOG.debug("Using Swagger operation: {} with {} {}", operation, verb, path); - RestProducerFactory factory = null; - String cname = null; - if (componentName != null) { - Object comp = camelContext.getRegistry().lookupByName(componentName); - if (comp != null && comp instanceof RestProducerFactory) { - factory = (RestProducerFactory) comp; - } else { - comp = camelContext.getComponent(componentName); - if (comp != null && comp instanceof RestProducerFactory) { - factory = (RestProducerFactory) comp; - } - } - - if (factory == null) { - if (comp != null) { - throw new IllegalArgumentException("Component " + componentName + " is not a RestProducerFactory"); - } else { - throw new NoSuchBeanException(componentName, RestProducerFactory.class.getName()); - } - } - cname = componentName; - } - - // try all components - if (factory == null) { - for (String name : camelContext.getComponentNames()) { - Component comp = camelContext.getComponent(name); - if (comp != null && comp instanceof RestProducerFactory) { - factory = (RestProducerFactory) comp; - cname = name; - break; - } - } - } - - // lookup in registry - if (factory == null) { - Set<RestProducerFactory> factories = camelContext.getRegistry().findByType(RestProducerFactory.class); - if (factories != null && factories.size() == 1) { - factory = factories.iterator().next(); - } - } + RestProducerFactory factory = (RestProducerFactory) parameters.remove("restProducerFactory"); if (factory != null) { LOG.debug("Using RestProducerFactory: {}", factory); @@ -200,7 +180,7 @@ public class SwaggerRestProducerFactory implements RestProducerFactory { uriTemplate = null; } - return factory.createProducer(camelContext, host, verb, basePath, uriTemplate, consumes, produces, parameters); + return factory.createProducer(camelContext, host, verb, basePath, uriTemplate, queryParameters, consumes, produces, parameters); } else { throw new IllegalStateException("Cannot find RestProducerFactory in Registry or as a Component to use"); http://git-wip-us.apache.org/repos/asf/camel/blob/b05fd502/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/producer/DummyRestProducerFactory.java ---------------------------------------------------------------------- diff --git a/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/producer/DummyRestProducerFactory.java b/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/producer/DummyRestProducerFactory.java index cd3903f..eddcbd5 100644 --- a/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/producer/DummyRestProducerFactory.java +++ b/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/producer/DummyRestProducerFactory.java @@ -30,7 +30,7 @@ public class DummyRestProducerFactory implements RestProducerFactory { @Override public Producer createProducer(CamelContext camelContext, String host, - String verb, String basePath, final String uriTemplate, + String verb, String basePath, final String uriTemplate, String queryParameters, String consumes, String produces, Map<String, Object> parameters) throws Exception { // use a dummy endpoint @@ -39,12 +39,12 @@ public class DummyRestProducerFactory implements RestProducerFactory { return new DefaultProducer(endpoint) { @Override public void process(Exchange exchange) throws Exception { - String query = exchange.getIn().getHeader(Exchange.HTTP_QUERY, String.class); + String query = exchange.getIn().getHeader(Exchange.REST_HTTP_QUERY, String.class); if (query != null) { String name = ObjectHelper.after(query, "name="); exchange.getIn().setBody("Bye " + name); } - String uri = exchange.getIn().getHeader(Exchange.HTTP_URI, String.class); + String uri = exchange.getIn().getHeader(Exchange.REST_HTTP_URI, String.class); if (uri != null) { int pos = uri.lastIndexOf('/'); String name = uri.substring(pos + 1);
