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/14352b1c Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/14352b1c Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/14352b1c Branch: refs/heads/master Commit: 14352b1c069f4b170fd8ee97faa59c7e674c115a Parents: 8a1549f Author: Claus Ibsen <[email protected]> Authored: Wed Aug 24 11:37:36 2016 +0200 Committer: Claus Ibsen <[email protected]> Committed: Fri Aug 26 16:53:31 2016 +0200 ---------------------------------------------------------------------- components/camel-swagger-java/pom.xml | 5 + .../swagger/component/SwaggerComponent.java | 93 ++++++++++++ .../swagger/component/SwaggerEndpoint.java | 131 +++++++++++++++++ .../swagger/component/SwaggerProducer.java | 147 +++++++++++++++++++ .../services/org/apache/camel/component/swagger | 18 +++ .../swagger/RestSwaggerReaderApiDocsTest.java | 1 + .../component/SwaggerComponentGetTest.java | 51 +++++++ .../camel/swagger/component/SwaggerGetTest.java | 46 ++++++ .../src/test/resources/hello-api.json | 26 ++++ parent/pom.xml | 1 + 10 files changed, 519 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/14352b1c/components/camel-swagger-java/pom.xml ---------------------------------------------------------------------- diff --git a/components/camel-swagger-java/pom.xml b/components/camel-swagger-java/pom.xml index e981833..e8d5040 100644 --- a/components/camel-swagger-java/pom.xml +++ b/components/camel-swagger-java/pom.xml @@ -96,6 +96,11 @@ <artifactId>swagger-jaxrs</artifactId> <version>${swagger-java-version}</version> </dependency> + <dependency> + <groupId>io.swagger</groupId> + <artifactId>swagger-parser</artifactId> + <version>${swagger-java-parser-version}</version> + </dependency> <!-- servlet api --> <dependency> http://git-wip-us.apache.org/repos/asf/camel/blob/14352b1c/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/component/SwaggerComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/component/SwaggerComponent.java b/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/component/SwaggerComponent.java new file mode 100644 index 0000000..a439379 --- /dev/null +++ b/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/component/SwaggerComponent.java @@ -0,0 +1,93 @@ +/** + * 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.swagger.component; + +import java.util.Map; + +import org.apache.camel.Endpoint; +import org.apache.camel.impl.UriEndpointComponent; + +public class SwaggerComponent extends UriEndpointComponent { + + private String componentName = "http"; + private String schema; + + public SwaggerComponent() { + super(SwaggerEndpoint.class); + } + + @Override + protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { + SwaggerEndpoint endpoint = new SwaggerEndpoint(uri, this); + endpoint.setComponentName(componentName); + + String schema; + String verb; + String path; + String[] parts = remaining.split(":"); + if (parts.length == 2) { + schema = this.schema; + verb = parts[0]; + path = parts[1]; + } else if (parts.length == 3) { + schema = parts[0]; + verb = parts[1]; + path = parts[2]; + } else { + throw new IllegalArgumentException("Invalid syntax. Expected swagger:schema:verb:path?options"); + } + + endpoint.setSchema(schema); + endpoint.setVerb(verb); + // path must start with leading slash + if (!path.startsWith("/")) { + path = "/" + path; + } + endpoint.setPath(path); + + setProperties(endpoint, parameters); + // any leftover parameters should be kept as additional uri parameters + + + return endpoint; + } + + public String getSchema() { + return schema; + } + + /** + * The swagger schema to use in json format. + * <p/> + * The schema is loaded as a resource from the classpath or file system. + */ + public void setSchema(String schema) { + this.schema = schema; + } + + public String getComponentName() { + return componentName; + } + + /** + * The camel component to use as HTTP client for calling the REST service. + * The default value is: http + */ + public void setComponentName(String componentName) { + this.componentName = componentName; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/14352b1c/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/component/SwaggerEndpoint.java ---------------------------------------------------------------------- diff --git a/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/component/SwaggerEndpoint.java b/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/component/SwaggerEndpoint.java new file mode 100644 index 0000000..c5f7cb5 --- /dev/null +++ b/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/component/SwaggerEndpoint.java @@ -0,0 +1,131 @@ +/** + * 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.swagger.component; + +import java.io.InputStream; + +import io.swagger.models.Swagger; +import io.swagger.parser.SwaggerParser; +import org.apache.camel.Component; +import org.apache.camel.Consumer; +import org.apache.camel.Processor; +import org.apache.camel.Producer; +import org.apache.camel.impl.DefaultEndpoint; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriPath; +import org.apache.camel.util.IOHelper; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.camel.util.ResourceHelper.resolveMandatoryResourceAsInputStream; + +@UriEndpoint(scheme = "swagger", title = "Swagger", syntax = "swagger:schema:verb:path", + producerOnly = true, label = "rest", lenientProperties = true) +public class SwaggerEndpoint extends DefaultEndpoint { + + private static final Logger LOG = LoggerFactory.getLogger(SwaggerEndpoint.class); + + private transient Swagger swagger; + + @UriPath + private String schema; + @UriPath(enums = "get,put,post,head,delete,patch,options") @Metadata(required = "true") + private String verb; + @UriPath @Metadata(required = "true") + private String path; + @UriParam + private String componentName; + + public SwaggerEndpoint(String endpointUri, Component component) { + super(endpointUri, component); + } + + @Override + public Producer createProducer() throws Exception { + SwaggerProducer answer = new SwaggerProducer(this); + answer.setSwagger(swagger); + return answer; + } + + @Override + public Consumer createConsumer(Processor processor) throws Exception { + throw new UnsupportedOperationException("Consumer not supported"); + } + + @Override + public boolean isSingleton() { + return true; + } + + public String getSchema() { + return schema; + } + + public void setSchema(String schema) { + this.schema = schema; + } + + public String getVerb() { + return verb; + } + + public void setVerb(String verb) { + this.verb = verb; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getComponentName() { + return componentName; + } + + /** + * The camel component to use as HTTP client for calling the REST service. + * The default value is: http + */ + public void setComponentName(String componentName) { + this.componentName = componentName; + } + + @Override + protected void doStart() throws Exception { + super.doStart(); + + // load json model + ObjectHelper.notEmpty(schema, "schema"); + + InputStream is = resolveMandatoryResourceAsInputStream(getCamelContext(), schema); + try { + SwaggerParser parser = new SwaggerParser(); + String json = getCamelContext().getTypeConverter().mandatoryConvertTo(String.class, is); + LOG.debug("Loaded swagger schema:\n{}", json); + swagger = parser.parse(json); + } finally { + IOHelper.close(is); + } + + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/14352b1c/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/component/SwaggerProducer.java ---------------------------------------------------------------------- diff --git a/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/component/SwaggerProducer.java b/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/component/SwaggerProducer.java new file mode 100644 index 0000000..9af31be --- /dev/null +++ b/components/camel-swagger-java/src/main/java/org/apache/camel/swagger/component/SwaggerProducer.java @@ -0,0 +1,147 @@ +/** + * 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.swagger.component; + +import java.util.LinkedHashMap; +import java.util.Map; + +import io.swagger.models.Operation; +import io.swagger.models.Path; +import io.swagger.models.Swagger; +import io.swagger.models.parameters.Parameter; +import org.apache.camel.AsyncCallback; +import org.apache.camel.Endpoint; +import org.apache.camel.Exchange; +import org.apache.camel.impl.DefaultAsyncProducer; +import org.apache.camel.util.StringHelper; +import org.apache.camel.util.URISupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SwaggerProducer extends DefaultAsyncProducer { + + private static final Logger LOG = LoggerFactory.getLogger(SwaggerProducer.class); + + // TODO: delegate to actual producer + + private Swagger swagger; + + public SwaggerProducer(Endpoint endpoint) { + super(endpoint); + } + + @Override + public SwaggerEndpoint getEndpoint() { + return (SwaggerEndpoint) super.getEndpoint(); + } + + @Override + public boolean process(Exchange exchange, AsyncCallback callback) { + String verb = getEndpoint().getVerb(); + String path = getEndpoint().getPath(); + + Operation op = getSwaggerOperation(verb, path); + if (op == null) { + exchange.setException(new IllegalArgumentException("Swagger schema does not contain operation for " + verb + ":" + path)); + callback.done(true); + return true; + } + + try { + // build context path to use for actual HTTP call + // replace path parameters with value from header + String contextPath = path; + Map<String, Object> query = new LinkedHashMap<>(); + for (Parameter param : op.getParameters()) { + if ("path".equals(param.getIn())) { + String name = param.getName(); + if (name != null) { + String value = exchange.getIn().getHeader(name, String.class); + if (value != null) { + String key = "{" + name + "}"; + contextPath = StringHelper.replaceAll(contextPath, key, value); + } + } + } else if ("query".equals(param.getIn())) { + String name = param.getName(); + if (name != null) { + String value = exchange.getIn().getHeader(name, String.class); + if (value != null) { + query.put(name, value); + } + } + } + } + if (!query.isEmpty()) { + String options = URISupport.createQueryString(query); + contextPath = contextPath + "?" + options; + } + + LOG.debug("Using context-path: {}", contextPath); + + } catch (Throwable e) { + exchange.setException(e); + callback.done(true); + return true; + } + + // TODO: bind to consumes context-type + // TODO: if binding is turned on/off/auto etc + // TODO: use the component and build uri with verb/path + // TODO: build dynamic uri for component (toD, headers) + + exchange.getIn().setBody("Hello Donald Duck"); + + // do some binding first + callback.done(true); + return true; + } + + private Operation getSwaggerOperation(String verb, String path) { + Path modelPath = swagger.getPath(path); + if (modelPath == null) { + return null; + } + + // get,put,post,head,delete,patch,options + Operation op = null; + if ("get".equals(verb)) { + op = modelPath.getGet(); + } else if ("put".equals(verb)) { + op = modelPath.getPut(); + } else if ("post".equals(verb)) { + op = modelPath.getPost(); + } else if ("head".equals(verb)) { + op = modelPath.getHead(); + } else if ("delete".equals(verb)) { + op = modelPath.getDelete(); + } else if ("patch".equals(verb)) { + op = modelPath.getPatch(); + } else if ("options".equals(verb)) { + op = modelPath.getOptions(); + } + return op; + } + + public Swagger getSwagger() { + return swagger; + } + + public void setSwagger(Swagger swagger) { + this.swagger = swagger; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/14352b1c/components/camel-swagger-java/src/main/resources/META-INF/services/org/apache/camel/component/swagger ---------------------------------------------------------------------- diff --git a/components/camel-swagger-java/src/main/resources/META-INF/services/org/apache/camel/component/swagger b/components/camel-swagger-java/src/main/resources/META-INF/services/org/apache/camel/component/swagger new file mode 100644 index 0000000..909e061 --- /dev/null +++ b/components/camel-swagger-java/src/main/resources/META-INF/services/org/apache/camel/component/swagger @@ -0,0 +1,18 @@ +# +# 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. +# + +class=org.apache.camel.swagger.component.SwaggerComponent http://git-wip-us.apache.org/repos/asf/camel/blob/14352b1c/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/RestSwaggerReaderApiDocsTest.java ---------------------------------------------------------------------- diff --git a/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/RestSwaggerReaderApiDocsTest.java b/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/RestSwaggerReaderApiDocsTest.java index 14a7bf2..e1a31ed 100644 --- a/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/RestSwaggerReaderApiDocsTest.java +++ b/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/RestSwaggerReaderApiDocsTest.java @@ -74,6 +74,7 @@ public class RestSwaggerReaderApiDocsTest extends CamelTestSupport { String json = mapper.writeValueAsString(swagger); log.info(json); + System.out.println(json); assertTrue(json.contains("\"host\" : \"localhost:8080\"")); assertTrue(json.contains("\"basePath\" : \"/api\"")); http://git-wip-us.apache.org/repos/asf/camel/blob/14352b1c/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/component/SwaggerComponentGetTest.java ---------------------------------------------------------------------- diff --git a/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/component/SwaggerComponentGetTest.java b/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/component/SwaggerComponentGetTest.java new file mode 100644 index 0000000..451b3cb --- /dev/null +++ b/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/component/SwaggerComponentGetTest.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.swagger.component; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +public class SwaggerComponentGetTest extends CamelTestSupport { + + @Test + public void testSwaggerGet() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Hello Donald Duck"); + + template.sendBodyAndHeader("direct:start", null, "name", "Donald Duck"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + SwaggerComponent sc = new SwaggerComponent(); + sc.setSchema("hello-api.json"); + + context.addComponent("swagger", sc); + + from("direct:start") + .to("swagger:get:hello/hi/{name}") + .to("mock:result"); + } + }; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/14352b1c/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/component/SwaggerGetTest.java ---------------------------------------------------------------------- diff --git a/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/component/SwaggerGetTest.java b/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/component/SwaggerGetTest.java new file mode 100644 index 0000000..eb29281 --- /dev/null +++ b/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/component/SwaggerGetTest.java @@ -0,0 +1,46 @@ +/** + * 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.swagger.component; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +public class SwaggerGetTest extends CamelTestSupport { + + @Test + public void testSwaggerGet() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Hello Donald Duck"); + + template.sendBodyAndHeader("direct:start", null, "name", "Donald Duck"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .to("swagger:hello-api.json:get:hello/hi/{name}") + .to("mock:result"); + } + }; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/14352b1c/components/camel-swagger-java/src/test/resources/hello-api.json ---------------------------------------------------------------------- diff --git a/components/camel-swagger-java/src/test/resources/hello-api.json b/components/camel-swagger-java/src/test/resources/hello-api.json new file mode 100644 index 0000000..82d61f8 --- /dev/null +++ b/components/camel-swagger-java/src/test/resources/hello-api.json @@ -0,0 +1,26 @@ +{ + "swagger" : "2.0", + "host" : "localhost:8080", + "basePath" : "/api", + "tags" : [ { + "name" : "hello" + } ], + "schemes" : [ "http" ], + "paths" : { + "/hello/hi/{name}" : { + "get" : { + "tags" : [ "hello" ], + "summary" : "Saying hi", + "consumes" : [ "application/json" ], + "produces" : [ "application/json" ], + "parameters" : [ { + "name" : "name", + "in" : "path", + "description" : "Who is it", + "required" : true, + "type" : "string" + } ] + } + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/14352b1c/parent/pom.xml ---------------------------------------------------------------------- diff --git a/parent/pom.xml b/parent/pom.xml index 412b855..ee1bd1f 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -577,6 +577,7 @@ <swagger-scala-guava-version>15.0</swagger-scala-guava-version> <swagger-scala-bundle-version>1.3.12_1</swagger-scala-bundle-version> <swagger-java-version>1.5.10</swagger-java-version> + <swagger-java-parser-version>1.0.22</swagger-java-parser-version> <swagger-java-guava-version>18.0</swagger-java-guava-version> <stax-api-version>1.0.1</stax-api-version> <stax2-api-bundle-version>3.1.4</stax2-api-bundle-version>
