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/69c12a0a Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/69c12a0a Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/69c12a0a Branch: refs/heads/master Commit: 69c12a0ab5d7c1f3152f0f2295ce1fa00f87170d Parents: 2e39b2c Author: Claus Ibsen <[email protected]> Authored: Thu Aug 25 07:46:53 2016 +0200 Committer: Claus Ibsen <[email protected]> Committed: Fri Aug 26 16:53:31 2016 +0200 ---------------------------------------------------------------------- .../apache/camel/spi/RestProducerFactory.java | 8 +- .../component/jetty/JettyHttpComponent.java | 16 +- .../swagger/component/SwaggerProducer.java | 169 +++++++++++-------- .../component/DummyRestProducerFactory.java | 20 ++- 4 files changed, 117 insertions(+), 96 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/69c12a0a/camel-core/src/main/java/org/apache/camel/spi/RestProducerFactory.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/spi/RestProducerFactory.java b/camel-core/src/main/java/org/apache/camel/spi/RestProducerFactory.java index 255ea6a..ae7f6f5 100644 --- a/camel-core/src/main/java/org/apache/camel/spi/RestProducerFactory.java +++ b/camel-core/src/main/java/org/apache/camel/spi/RestProducerFactory.java @@ -32,21 +32,17 @@ public interface RestProducerFactory { * Creates a new REST producer. * * @param camelContext the camel context - * @param exchange the exchange * @param scheme scheme to use such as http or https * @param host host (incl port) of the REST service * @param verb HTTP verb such as GET, POST * @param basePath base path * @param uriTemplate uri template - * @param resolvedUriTemplate uri template where path parameters has been resolved - * @param queryParameters query parameters * @param consumes media-types for what the REST service consume as input (accept-type), is <tt>null</tt> or <tt>*/*</tt> for anything * @param produces media-types for what the REST service produces as output, can be <tt>null</tt> * @param parameters additional parameters * @return a newly created REST producer * @throws Exception can be thrown */ - Producer createProducer(CamelContext camelContext, Exchange exchange, String scheme, String host, - String verb, String basePath, String uriTemplate, String resolvedUriTemplate, String queryParameters, - String consumes, String produces, Map<String, Object> parameters) throws Exception; + Producer createProducer(CamelContext camelContext, String scheme, String host, + String verb, String basePath, String uriTemplate, String consumes, String produces, Map<String, Object> parameters) throws Exception; } http://git-wip-us.apache.org/repos/asf/camel/blob/69c12a0a/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java b/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java index 6b9394f..3b3e91d 100644 --- a/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java +++ b/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java @@ -1151,25 +1151,13 @@ public abstract class JettyHttpComponent extends HttpCommonComponent implements } @Override - public Producer createProducer(CamelContext camelContext, Exchange exchange, String scheme, String host, - String verb, String basePath, String uriTemplate, String resolvedUriTemplate, String queryParameters, + public Producer createProducer(CamelContext camelContext, String scheme, String host, + String verb, String basePath, String uriTemplate, String consumes, String produces, Map<String, Object> parameters) throws Exception { // avoid leading slash basePath = FileUtil.stripLeadingSeparator(basePath); uriTemplate = FileUtil.stripLeadingSeparator(uriTemplate); - resolvedUriTemplate = FileUtil.stripLeadingSeparator(resolvedUriTemplate); - - // does the uri template use path parameters? - if (uriTemplate.contains("{") && resolvedUriTemplate != null) { - // if so us a header for the dynamic uri template so we reuse same endpoint but the header overrides the actual url to use - String overrideUri = String.format("%s://%s/%s/%s", scheme, host, basePath, resolvedUriTemplate); - exchange.getIn().setHeader(Exchange.HTTP_URI, overrideUri); - } - if (queryParameters != null) { - // use a header for the query parameters - exchange.getIn().setHeader(Exchange.HTTP_QUERY, queryParameters); - } // get the endpoint String url = "jetty:%s://%s/%s/%s"; http://git-wip-us.apache.org/repos/asf/camel/blob/69c12a0a/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 index a5d0c18..7e1fda2 100644 --- 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 @@ -37,7 +37,7 @@ import org.apache.camel.impl.DefaultAsyncProducer; import org.apache.camel.spi.RestProducerFactory; import org.apache.camel.util.AsyncProcessorConverterHelper; import org.apache.camel.util.CollectionStringBuffer; -import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.FileUtil; import org.apache.camel.util.ServiceHelper; import org.apache.camel.util.StringHelper; import org.apache.camel.util.URISupport; @@ -49,6 +49,8 @@ public class SwaggerProducer extends DefaultAsyncProducer { private static final Logger LOG = LoggerFactory.getLogger(SwaggerProducer.class); private Swagger swagger; + private Operation operation; + private AsyncProcessor producer; public SwaggerProducer(Endpoint endpoint) { super(endpoint); @@ -61,29 +63,14 @@ public class SwaggerProducer extends DefaultAsyncProducer { @Override public boolean process(Exchange exchange, AsyncCallback callback) { - String verb = getEndpoint().getVerb(); - String path = getEndpoint().getPath(); - - Operation operation = getSwaggerOperation(verb, path); - if (operation == null) { - exchange.setException(new IllegalArgumentException("Swagger schema does not contain operation for " + verb + ":" + path)); - callback.done(true); - return true; - } + // TODO: bind to consumes context-type + // TODO: if binding is turned on/off/auto etc try { - // TODO: bind to consumes context-type - // TODO: if binding is turned on/off/auto etc - // TODO: build dynamic uri for component (toD, headers) - // create http producer to use for calling the remote HTTP service - // TODO: create the producer once and reuse (create HTTP_XXX headers for dynamic values) - Producer producer = createHttpProducer(exchange, operation, verb, path); if (producer != null) { - ServiceHelper.startService(producer); - AsyncProcessor async = AsyncProcessorConverterHelper.convert(producer); - return async.process(exchange, callback); + prepareExchange(exchange); + return producer.process(exchange, callback); } - } catch (Throwable e) { exchange.setException(e); } @@ -93,6 +80,96 @@ public class SwaggerProducer extends DefaultAsyncProducer { return true; } + protected void prepareExchange(Exchange exchange) throws Exception { + boolean hasPath = false; + boolean hasQuery = false; + + // uri template with path parameters resolved + String resolvedUriTemplate = getEndpoint().getPath(); + // for query parameters + Map<String, Object> query = new LinkedHashMap<>(); + for (Parameter param : operation.getParameters()) { + if ("query".equals(param.getIn())) { + String name = param.getName(); + if (name != null) { + String value = exchange.getIn().getHeader(name, String.class); + if (value != null) { + hasQuery = true; + // we need to remove the header as they are sent as query instead + // TODO: we could use a header filter strategy to skip these headers + exchange.getIn().removeHeader(param.getName()); + query.put(name, value); + } else if (param.getRequired()) { + throw new NoSuchHeaderException(exchange, name, String.class); + } + } + } else if ("path".equals(param.getIn())) { + String value = exchange.getIn().getHeader(param.getName(), String.class); + if (value != null) { + hasPath = true; + // we need to remove the header as they are sent as path instead + // TODO: we could use a header filter strategy to skip these headers + exchange.getIn().removeHeader(param.getName()); + String token = "{" + param.getName() + "}"; + resolvedUriTemplate = StringHelper.replaceAll(resolvedUriTemplate, token, value); + } else if (param.getRequired()) { + // the parameter is required but we do not have a header + throw new NoSuchHeaderException(exchange, param.getName(), String.class); + } + } + } + + if (hasQuery) { + String queryParameters = URISupport.createQueryString(query); + exchange.getIn().setHeader(Exchange.HTTP_QUERY, queryParameters); + } + + if (hasPath) { + String scheme = swagger.getSchemes() != null && swagger.getSchemes().size() == 1 ? swagger.getSchemes().get(0).toValue() : "http"; + String host = getEndpoint().getHost() != null ? getEndpoint().getHost() : swagger.getHost(); + String basePath = swagger.getBasePath(); + basePath = FileUtil.stripLeadingSeparator(basePath); + resolvedUriTemplate = FileUtil.stripLeadingSeparator(resolvedUriTemplate); + // if so us a header for the dynamic uri template so we reuse same endpoint but the header overrides the actual url to use + String overrideUri = String.format("%s://%s/%s/%s", scheme, host, basePath, resolvedUriTemplate); + exchange.getIn().setHeader(Exchange.HTTP_URI, overrideUri); + } + } + + public Swagger getSwagger() { + return swagger; + } + + public void setSwagger(Swagger swagger) { + this.swagger = swagger; + } + + @Override + protected void doStart() throws Exception { + super.doStart(); + + String verb = getEndpoint().getVerb(); + String path = getEndpoint().getPath(); + + operation = getSwaggerOperation(verb, path); + if (operation == null) { + throw new IllegalArgumentException("Swagger schema does not contain operation for " + verb + ":" + path); + } + + Producer processor = createHttpProducer(operation, verb, path); + if (processor != null) { + producer = AsyncProcessorConverterHelper.convert(processor); + } + ServiceHelper.startService(producer); + } + + @Override + protected void doStop() throws Exception { + super.doStop(); + + ServiceHelper.stopService(producer); + } + private Operation getSwaggerOperation(String verb, String path) { Path modelPath = swagger.getPath(path); if (modelPath == null) { @@ -119,15 +196,10 @@ public class SwaggerProducer extends DefaultAsyncProducer { return op; } - public Swagger getSwagger() { - return swagger; - } + private Producer createHttpProducer(Operation operation, String verb, String path) throws Exception { - public void setSwagger(Swagger swagger) { - this.swagger = swagger; - } + LOG.debug("Using Swagger operation: {} with {} {}", operation, verb, path); - protected Producer createHttpProducer(Exchange exchange, Operation operation, String verb, String path) throws Exception { RestProducerFactory factory = null; String cname = null; if (getEndpoint().getComponentName() != null) { @@ -172,6 +244,7 @@ public class SwaggerProducer extends DefaultAsyncProducer { } if (factory != null) { + LOG.debug("Using RestProducerFactory: {}", factory); CollectionStringBuffer produces = new CollectionStringBuffer(","); List<String> list = operation.getProduces(); @@ -200,46 +273,8 @@ public class SwaggerProducer extends DefaultAsyncProducer { String basePath = swagger.getBasePath(); String uriTemplate = path; - // uri template with path parameters resolved - String resolvedUriTemplate = uriTemplate; - // for query parameters - Map<String, Object> query = new LinkedHashMap<>(); - for (Parameter param : operation.getParameters()) { - if ("query".equals(param.getIn())) { - String name = param.getName(); - if (name != null) { - String value = exchange.getIn().getHeader(name, String.class); - if (value != null) { - // we need to remove the header as they are sent as query instead - // TODO: we could use a header filter strategy to skip these headers - exchange.getIn().removeHeader(param.getName()); - query.put(name, value); - } else if (param.getRequired()) { - throw new NoSuchHeaderException(exchange, name, String.class); - } - } - } else if ("path".equals(param.getIn())) { - String value = exchange.getIn().getHeader(param.getName(), String.class); - if (value != null) { - // we need to remove the header as they are sent as path instead - // TODO: we could use a header filter strategy to skip these headers - exchange.getIn().removeHeader(param.getName()); - String token = "{" + param.getName() + "}"; - resolvedUriTemplate = StringHelper.replaceAll(resolvedUriTemplate, token, value); - } else if (param.getRequired()) { - // the parameter is required but we do not have a header - throw new NoSuchHeaderException(exchange, param.getName(), String.class); - } - } - } - // build as query string - String queryParameters = null; - if (!query.isEmpty()) { - queryParameters = URISupport.createQueryString(query); - } - - return factory.createProducer(getEndpoint().getCamelContext(), exchange, scheme, host, verb, basePath, uriTemplate, resolvedUriTemplate, - queryParameters, (consumes.isEmpty() ? "" : consumes.toString()), (produces.isEmpty() ? "" : produces.toString()), null); + return factory.createProducer(getEndpoint().getCamelContext(), scheme, host, verb, basePath, uriTemplate, + (consumes.isEmpty() ? "" : consumes.toString()), (produces.isEmpty() ? "" : produces.toString()), null); } 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/69c12a0a/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/component/DummyRestProducerFactory.java ---------------------------------------------------------------------- diff --git a/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/component/DummyRestProducerFactory.java b/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/component/DummyRestProducerFactory.java index 4f1da5c..499dd7e 100644 --- a/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/component/DummyRestProducerFactory.java +++ b/components/camel-swagger-java/src/test/java/org/apache/camel/swagger/component/DummyRestProducerFactory.java @@ -29,8 +29,8 @@ import org.apache.camel.util.ObjectHelper; public class DummyRestProducerFactory implements RestProducerFactory { @Override - public Producer createProducer(CamelContext camelContext, Exchange exchange, String scheme, String host, - String verb, String basePath, final String uriTemplate, final String resolvedUriTemplate, final String queryParameters, + public Producer createProducer(CamelContext camelContext, String scheme, String host, + String verb, String basePath, final String uriTemplate, String consumes, String produces, Map<String, Object> parameters) throws Exception { // use a dummy endpoint @@ -39,15 +39,17 @@ public class DummyRestProducerFactory implements RestProducerFactory { return new DefaultProducer(endpoint) { @Override public void process(Exchange exchange) throws Exception { - // for testing purpose, check if we have {name} in template - if (uriTemplate.contains("{name}")) { - int pos = resolvedUriTemplate.lastIndexOf('/'); - String name = resolvedUriTemplate.substring(pos + 1); - exchange.getIn().setBody("Hello " + name); - } else if (queryParameters.contains("name=")) { - String name = ObjectHelper.after(queryParameters, "name="); + String query = exchange.getIn().getHeader(Exchange.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); + if (uri != null) { + int pos = uri.lastIndexOf('/'); + String name = uri.substring(pos + 1); + exchange.getIn().setBody("Hello " + name); + } } }; }
