CAMEL-10932 REST Swagger component This includes the initial implementation of the `rest-swagger` component that allows for a higher level abstraction over the REST API usage from other Camel components that implement the `RestProducerFactory` SPI combined with Swagger (Open API) specifications.
The most simple usage would be: to("rest-swagger:getPetById") Which would pick up the Swagger specification from `swagger.json` and try to find a single component that implements the `RestProducerFactory` SPI and invoke the `getPetById` operation. Other way of using this component could be: to("rest-swagger:http://petstore.swagger.io/v2/swagger.json#getPetById") That loads the Swagger specification from the `http://petstore.swagger.io/v2/swagger.json` URL and invokes the `getPetById` operation. More concise way of configuring would be to configure most properties on the component add it to CamelContext, and use only `operationId` path parameter when triggering the exchange: // add `petstore` component to the CamelContext RestSwaggerComponent petstore = new RestSwaggerComponent(camelContext); petstore.setSpecificationUri("http://petstore.swagger.io/v2/swagger.json"); petstore.setComponentName("undertow"); camelContext.addComponent("petstore", petstore); And then use `operationId` in endpoint definition: ProducerTemplate template = camelContext.getProducerTemplate(); template.requestBodyAndHeaders("petstore:getPetById", null, "petId", petId); Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/5b209152 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/5b209152 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/5b209152 Branch: refs/heads/master Commit: 5b209152b2ffb1ddd507c8b265bdab50aea620ef Parents: ae2ae6f Author: Zoran Regvart <zregv...@apache.org> Authored: Thu Mar 16 12:54:47 2017 +0100 Committer: Zoran Regvart <zregv...@apache.org> Committed: Thu Mar 16 22:28:20 2017 +0100 ---------------------------------------------------------------------- components/camel-rest-swagger/pom.xml | 174 +++++++ .../src/main/docs/rest-swagger-component.adoc | 190 +++++++ .../rest/swagger/RestSwaggerComponent.java | 191 ++++++++ .../rest/swagger/RestSwaggerEndpoint.java | 490 +++++++++++++++++++ .../rest/swagger/RestSwaggerHelper.java | 50 ++ .../src/main/resources/META-INF/LICENSE.txt | 203 ++++++++ .../src/main/resources/META-INF/NOTICE.txt | 11 + .../org/apache/camel/component/rest-swagger | 17 + .../camel/component/rest/swagger/Pet.java | 28 ++ .../rest/swagger/RestSwaggerComponentTest.java | 152 ++++++ .../rest/swagger/RestSwaggerEndpointTest.java | 336 +++++++++++++ .../RestSwaggerEndpointUriParsingTest.java | 66 +++ .../rest/swagger/RestSwaggerHelperTest.java | 50 ++ .../src/test/resources/log4j2.properties | 24 + .../src/test/resources/swagger.json | 1 + components/pom.xml | 1 + docs/user-manual/en/SUMMARY.md | 1 + examples/camel-example-rest-swagger/README.md | 30 ++ examples/camel-example-rest-swagger/pom.xml | 102 ++++ .../camel/example/RestSwaggerApplication.java | 68 +++ parent/pom.xml | 6 + .../camel-rest-swagger-starter/pom.xml | 51 ++ .../RestSwaggerComponentAutoConfiguration.java | 111 +++++ .../RestSwaggerComponentConfiguration.java | 142 ++++++ .../src/main/resources/META-INF/LICENSE.txt | 203 ++++++++ .../src/main/resources/META-INF/NOTICE.txt | 11 + ...dditional-spring-configuration-metadata.json | 10 + .../main/resources/META-INF/spring.factories | 19 + .../src/main/resources/META-INF/spring.provides | 18 + .../spring-boot/components-starter/pom.xml | 1 + 30 files changed, 2757 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/5b209152/components/camel-rest-swagger/pom.xml ---------------------------------------------------------------------- diff --git a/components/camel-rest-swagger/pom.xml b/components/camel-rest-swagger/pom.xml new file mode 100644 index 0000000..39d6da5 --- /dev/null +++ b/components/camel-rest-swagger/pom.xml @@ -0,0 +1,174 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.camel</groupId> + <artifactId>components</artifactId> + <version>2.19.0-SNAPSHOT</version> + </parent> + + <artifactId>camel-rest-swagger</artifactId> + + <name>Camel :: REST Swagger</name> + <description>Camel REST support using Swagger</description> + + <properties> + <firstVersion>2.19.0</firstVersion> + <label>rest,api,http</label> + <camel.osgi.export.pkg>org.apache.camel.component.rest.swagger.*</camel.osgi.export.pkg> + <camel.osgi.export.service>org.apache.camel.spi.ComponentResolver;component=rest-swagger</camel.osgi.export.service> + </properties> + + <dependencies> + + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-core</artifactId> + </dependency> + + <dependency> + <groupId>io.swagger</groupId> + <artifactId>swagger-parser</artifactId> + <version>${swagger-java-parser-version}</version> + <exclusions> + <!-- + Trying to keep transitive dependencies only to required ones. + --> + <exclusion> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </exclusion> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-ext</artifactId> + </exclusion> + <exclusion> + <groupId>io.swagger</groupId> + <artifactId>swagger-annotations</artifactId> + </exclusion> + <exclusion> + <groupId>javax.validation</groupId> + <artifactId>validation-api</artifactId> + </exclusion> + </exclusions> + </dependency> + + <!-- test --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>com.github.tomakehurst</groupId> + <artifactId>wiremock</artifactId> + <version>${wiremock-version}</version> + <scope>test</scope> + <exclusions> + <exclusion> + <!-- + Adding `camel-jetty9` forces WireMock to use mixed Jetty + versions, here we exclude WireMocks Jetty dependencies so + that we only use the ones declared by the `jetty9` component + --> + <groupId>org.eclipse.jetty</groupId> + <artifactId>*</artifactId> + </exclusion> + </exclusions> + </dependency> + + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-jaxb</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-http</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-http4</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-netty4-http</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-restlet</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-jetty9</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-undertow</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j-impl</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-core</artifactId> + <scope>test</scope> + </dependency> + + </dependencies> + +</project> http://git-wip-us.apache.org/repos/asf/camel/blob/5b209152/components/camel-rest-swagger/src/main/docs/rest-swagger-component.adoc ---------------------------------------------------------------------- diff --git a/components/camel-rest-swagger/src/main/docs/rest-swagger-component.adoc b/components/camel-rest-swagger/src/main/docs/rest-swagger-component.adoc new file mode 100644 index 0000000..acdde17 --- /dev/null +++ b/components/camel-rest-swagger/src/main/docs/rest-swagger-component.adoc @@ -0,0 +1,190 @@ +## REST Swagger Component + +*Available as of Camel version 2.19* + +The *rest-swagger* configures rest producers from +http://swagger.io/[Swagger] (Open API) specification document and +delegates to a component implementing the _RestProducerFactory_ +interface. Currently known working components are: + +* link:http-component.html[http] +* link:http4-component.html[http4] +* link:netty4-http-component.html[netty4-http] +* link:restlet-component.html[restlet] +* link:jetty-component.html[jetty] +* link:undertow-component.html[undertow] + +Maven users will need to add the following dependency to their +`pom.xml` for this component: + +[source,xml] +------------------------------------------------------------ +<dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-rest-swagger</artifactId> + <version>x.x.x</version> + <!-- use the same version as your Camel core version --> +</dependency> +------------------------------------------------------------ + +### URI format + +[source,java] +------------------------------------------------------- +rest-swagger:[specificationPath#]operationId +------------------------------------------------------- + +Where `operationId` is the ID of the operation in the Swagger +specification, and `specificationPath` is the path to the +specification. +If the `specificationPath` is not specified it defaults to +`swagger.json`. The lookup mechanism uses Camels `ResourceHelper` to +load the resource, which means that you can use CLASSPATH resources +(`classpath:my-specification.json`), files +(`file:/some/path.json`), the web +(`http://api.example.com/swagger.json`) or reference a bean +(`ref:nameOfBean`) or use a method of a bean +(`bean:nameOfBean.methodName`) to get the specification resource, +failing that Swagger's own resource loading support. + +This component does not act as a HTTP client, it delegates that to +another component mentioned above. The lookup mechanism searches for a +single component that implements the _RestProducerFactory_ interface and +uses that. If the CLASSPATH contains more than one, then the property +`componentName` should be set to indicate which component to delegate +to. + +Most of the configuration is taken from the Swagger specification but +the option exists to override those by specifying them on the component +or on the endpoint. Typically you would just need to override the +`host` or `basePath` if those differ from the specification. + +NOTE: The `host` parameter should contain the absolute URI containing +scheme, hostname and port number, for instance: +`https://api.example.com` + +With `componentName` you specify what component is used to perform the +requests, this named component needs to be present in the Camel context +and implement the required _RestProducerFactory_ interface -- as do the +components listed at the top. + +If you do not specify the _componentName_ at either component or +endpoint level, CLASSPATH is searched for a suitable delegate. There +should be only one component present on the CLASSPATH that implements +the _RestProducerFactory_ interface for this to work. + +### Options + +// component options: START +The REST Swagger component supports 7 options which are listed below. + + + +[width="100%",cols="2,1,1m,1m,5",options="header"] +|======================================================================= +| Name | Group | Default | Java Type | Description +| basePath | producer | | String | API basePath for example /v2. Default is unset if set overrides the value present in Swagger specification. +| componentName | producer | | String | Name of the Camel component that will perform the requests. The compnent must be present in Camel registry and it must implement RestProducerFactory service provider interface. If not set CLASSPATH is searched for single component that implements RestProducerFactory SPI. Can be overriden in endpoint configuration. +| consumes | producer | | String | What payload type this component capable of consuming. Could be one type like application/json or multiple types as application/json application/xml; q=0.5 according to the RFC7231. This equates to the value of Accept HTTP header. If set overrides any value found in the Swagger specification. Can be overriden in endpoint configuration +| host | producer | | String | Scheme hostname and port to direct the HTTP requests to in the form of https://hostname:port. Can be configured at the endpoint component or in the correspoding REST configuration in the Camel Context. If you give this component a name (e.g. petstore) that REST configuration is consulted first rest-swagger next and global configuration last. If set overrides any value found in the Swagger specification RestConfiguration. Can be overriden in endpoint configuration. +| produces | producer | | String | What payload type this component is producing. For example application/json according to the RFC7231. This equates to the value of Content-Type HTTP header. If set overrides any value present in the Swagger specification. Can be overriden in endpoint configuration. +| specificationUri | producer | swagger.json | URI | Path to the Swagger specification file. The scheme host base path are taken from this specification but these can be overriden with properties on the component or endpoint level. If not given the component tries to load swagger.json resource. Note that the host defined on the component and endpoint of this Component should contain the scheme hostname and optionally the port in the URI syntax (i.e. https://api.example.com:8080). Can be overriden in endpoint configuration. +| resolvePropertyPlaceholders | advanced | true | boolean | Whether the component should resolve property placeholders on itself when starting. Only properties which are of String type can use property placeholders. +|======================================================================= +// component options: END + +// endpoint options: START +The REST Swagger endpoint is configured using URI syntax: + + rest-swagger:specificationUri#operationId + +with the following path and query parameters: + +#### Path Parameters (2 parameters): + +[width="100%",cols="2,1,1m,6",options="header"] +|======================================================================= +| Name | Default | Java Type | Description +| specificationUri | swagger.json | URI | Path to the Swagger specification file. The scheme host base path are taken from this specification but these can be overriden with properties on the component or endpoint level. If not given the component tries to load swagger.json resource. Note that the host defined on the component and endpoint of this Component should contain the scheme hostname and optionally the port in the URI syntax (i.e. https://api.example.com:8080). Overrides component configuration. +| operationId | | String | *Required* ID of the operation from the Swagger specification. +|======================================================================= + +#### Query Parameters (6 parameters): + +[width="100%",cols="2,1,1m,1m,5",options="header"] +|======================================================================= +| Name | Group | Default | Java Type | Description +| basePath | producer | | String | API basePath for example /v2. Default is unset if set overrides the value present in Swagger specification and in the component configuration. +| componentName | producer | | String | Name of the Camel component that will perform the requests. The compnent must be present in Camel registry and it must implement RestProducerFactory service provider interface. If not set CLASSPATH is searched for single component that implements RestProducerFactory SPI. Overrides component configuration. +| consumes | producer | | String | What payload type this component capable of consuming. Could be one type like application/json or multiple types as application/json application/xml; q=0.5 according to the RFC7231. This equates to the value of Accept HTTP header. If set overrides any value found in the Swagger specification and. in the component configuration +| host | producer | | String | Scheme hostname and port to direct the HTTP requests to in the form of https://hostname:port. Can be configured at the endpoint component or in the correspoding REST configuration in the Camel Context. If you give this component a name (e.g. petstore) that REST configuration is consulted first rest-swagger next and global configuration last. If set overrides any value found in the Swagger specification RestConfiguration. Overrides all other configuration. +| produces | producer | | String | What payload type this component is producing. For example application/json according to the RFC7231. This equates to the value of Content-Type HTTP header. If set overrides any value present in the Swagger specification. Overrides all other configuration. +| synchronous | advanced | false | boolean | Sets whether synchronous processing should be strictly used or Camel is allowed to use asynchronous processing (if supported). +|======================================================================= +// endpoint options: END + +### Example: PetStore + +Checkout the example in the `camel-example-rest-swagger` project in +the `examples` directory. + +For example if you wanted to use the +http://petstore.swagger.io/[_PetStore_] provided REST API simply +reference the specification URI and desired operation id from the +Swagger specification or download the specification and store it as +`swagger.json` (in the root) of CLASSPATH that way it will be +automaticaly used. Let's use the link:undertow-component.html[Undertow] +component to perform all the requests and Camels excelent support for +link:spring-boot.html[Spring Boot]. + +Here are our dependencies defined in Maven POM file: + +[source,xml] +---- +<dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-undertow-starter</artifactId> +</dependency> + +<dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-rest-swagger-starter</artifactId> +</dependency> +---- + +Start by defining the _Undertow_ component and the +_RestSwaggerComponent_: + +[source,java] +---- +@Bean +public Component petstore(CamelContext camelContext, UndertowComponent undertow) { + RestSwaggerComponent petstore = new RestSwaggerComponent(camelContext); + petstore.setSpecificationUri("http://petstore.swagger.io/v2/swagger.json"); + petstore.setDelegate(undertow); + + return petstore; +} +---- + +NOTE: Support in Camel for Spring Boot will auto create the +`UndertowComponent` Spring bean, and you can configure it using +`application.properties` (or `application.yml`) using prefix +`camel.component.undertow.`. We are defining the `petstore` +component here in order to have a named component in the Camel context +that we can use to interact with the PetStore REST API, if this is the +only `rest-swagger` component used we might configure it in the same +manner (using `application.properties`). + +Now in our application we can simply use the `ProducerTemplate` to +invoke PetStore REST methods: + +[source,java] +---- +@Autowired +ProducerTemplate template; + +String getPetJsonById(int petId) { + return template.requestBodyAndHeaders("petstore:getPetById", null, "petId", petId); +} +---- http://git-wip-us.apache.org/repos/asf/camel/blob/5b209152/components/camel-rest-swagger/src/main/java/org/apache/camel/component/rest/swagger/RestSwaggerComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-rest-swagger/src/main/java/org/apache/camel/component/rest/swagger/RestSwaggerComponent.java b/components/camel-rest-swagger/src/main/java/org/apache/camel/component/rest/swagger/RestSwaggerComponent.java new file mode 100644 index 0000000..943f834 --- /dev/null +++ b/components/camel-rest-swagger/src/main/java/org/apache/camel/component/rest/swagger/RestSwaggerComponent.java @@ -0,0 +1,191 @@ +/** + * 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.rest.swagger; + +import java.net.URI; +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.Endpoint; +import org.apache.camel.impl.DefaultComponent; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.RestProducerFactory; + +import static org.apache.camel.component.rest.swagger.RestSwaggerHelper.isHostParam; +import static org.apache.camel.component.rest.swagger.RestSwaggerHelper.isMediaRange; +import static org.apache.camel.util.ObjectHelper.notNull; +import static org.apache.camel.util.StringHelper.notEmpty; + +/** + * An awesome REST component backed by Swagger specifications. Creates endpoints + * that connect to REST APIs defined by Swagger specification. This component + * delegates to other {@link RestProducerFactory} components to act as REST + * clients, but it configures them from Swagger specification. Client needs to + * point to operation that it wants to invoke via REST, provide any additional + * HTTP headers as headers in the Camel message, and any payload as the body of + * the incoming message. + * <p> + * Example usage using Java DSL: + * <p> + * + * <pre> + * from(...).to("rest-swagger:http://petstore.swagger.io/v2/swagger.json#getPetById") + * </pre> + * + * This relies on only one {@link RestProducerFactory} component being available + * to Camel, you can use specific, for instance preconfigured component by using + * the {@code componentName} endpoint property. For example using Undertow + * component in Java DSL: + * <p> + * + * <pre> + * Component undertow = new UndertowComponent(); + * undertow.setSslContextParameters(...); + * //... + * camelContext.addComponent("myUndertow", undertow); + * + * from(...).to("rest-swagger:http://petstore.swagger.io/v2/swagger.json#getPetById?componentName=myUndertow") + * </pre> + * + * The most concise way of using this component would be to define it in the + * Camel context under a meaningful name, for example: + * + * <pre> + * Component petstore = new RestSwaggerComponent(); + * petstore.setSpecificationUri("http://petstore.swagger.io/v2/swagger.json"); + * petstore.setComponentName("undertow"); + * //... + * camelContext.addComponent("petstore", petstore); + * + * from(...).to("petstore:getPetById") + * </pre> + */ +public final class RestSwaggerComponent extends DefaultComponent { + public static final String DEFAULT_BASE_PATH = "/"; + + static final URI DEFAULT_SPECIFICATION_URI = URI.create(RestSwaggerComponent.DEFAULT_SPECIFICATION_URI_STR); + + static final String DEFAULT_SPECIFICATION_URI_STR = "swagger.json"; + + @Metadata( + description = "API basePath, for example \"`/v2`\". Default is unset, if set overrides the value present in Swagger specification.", + defaultValue = "", label = "producer", required = "false") + private String basePath = ""; + + @Metadata(description = "Name of the Camel component that will perform the requests. The compnent must be present" + + " in Camel registry and it must implement RestProducerFactory service provider interface. If not set" + + " CLASSPATH is searched for single component that implements RestProducerFactory SPI. Can be overriden in" + + " endpoint configuration.", label = "producer", required = "false") + private String componentName; + + @Metadata( + description = "What payload type this component capable of consuming. Could be one type, like `application/json`" + + " or multiple types as `application/json, application/xml; q=0.5` according to the RFC7231. This equates" + + " to the value of `Accept` HTTP header. If set overrides any value found in the Swagger specification." + + " Can be overriden in endpoint configuration", + label = "producer", required = "false") + private String consumes; + + @Metadata(description = "Scheme hostname and port to direct the HTTP requests to in the form of" + + " `http[s]://hostname[:port]`. Can be configured at the endpoint, component or in the correspoding" + + " REST configuration in the Camel Context. If you give this component a name (e.g. `petstore`) that" + + " REST configuration is consulted first, `rest-swagger` next, and global configuration last. If set" + + " overrides any value found in the Swagger specification, RestConfiguration. Can be overriden in endpoint" + + " configuration.", label = "producer", required = "false") + private String host; + + @Metadata( + description = "What payload type this component is producing. For example `application/json`" + + " according to the RFC7231. This equates to the value of `Content-Type` HTTP header. If set overrides" + + " any value present in the Swagger specification. Can be overriden in endpoint configuration.", + label = "producer", required = "false") + private String produces; + + @Metadata(description = "Path to the Swagger specification file. The scheme, host base path are taken from this" + + " specification, but these can be overriden with properties on the component or endpoint level. If not" + + " given the component tries to load `swagger.json` resource. Note that the `host` defined on the" + + " component and endpoint of this Component should contain the scheme, hostname and optionally the" + + " port in the URI syntax (i.e. `https://api.example.com:8080`). Can be overriden in endpoint" + + " configuration.", defaultValue = DEFAULT_SPECIFICATION_URI_STR, label = "producer", required = "false") + private URI specificationUri; + + public RestSwaggerComponent() { + } + + public RestSwaggerComponent(final CamelContext context) { + super(context); + } + + public String getBasePath() { + return basePath; + } + + public String getComponentName() { + return componentName; + } + + public String getConsumes() { + return consumes; + } + + public String getHost() { + return host; + } + + public String getProduces() { + return produces; + } + + public URI getSpecificationUri() { + return specificationUri; + } + + public void setBasePath(final String basePath) { + this.basePath = notEmpty(basePath, "basePath"); + } + + public void setComponentName(final String componentName) { + this.componentName = notEmpty(componentName, "componentName"); + } + + public void setConsumes(final String consumes) { + this.consumes = isMediaRange(consumes, "consumes"); + } + + public void setHost(final String host) { + this.host = isHostParam(host); + } + + public void setProduces(final String produces) { + this.produces = isMediaRange(produces, "produces"); + } + + public void setSpecificationUri(final URI specificationUri) { + this.specificationUri = notNull(specificationUri, "specificationUri"); + } + + @Override + protected Endpoint createEndpoint(final String uri, final String remaining, final Map<String, Object> parameters) + throws Exception { + final Endpoint endpoint = new RestSwaggerEndpoint(uri, remaining, this); + + setProperties(endpoint, parameters); + + return endpoint; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/5b209152/components/camel-rest-swagger/src/main/java/org/apache/camel/component/rest/swagger/RestSwaggerEndpoint.java ---------------------------------------------------------------------- diff --git a/components/camel-rest-swagger/src/main/java/org/apache/camel/component/rest/swagger/RestSwaggerEndpoint.java b/components/camel-rest-swagger/src/main/java/org/apache/camel/component/rest/swagger/RestSwaggerEndpoint.java new file mode 100644 index 0000000..463d534 --- /dev/null +++ b/components/camel-rest-swagger/src/main/java/org/apache/camel/component/rest/swagger/RestSwaggerEndpoint.java @@ -0,0 +1,490 @@ +/** + * 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.rest.swagger; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.stream.Collectors; + +import static java.util.Optional.ofNullable; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.swagger.models.HttpMethod; +import io.swagger.models.Operation; +import io.swagger.models.Path; +import io.swagger.models.Scheme; +import io.swagger.models.Swagger; +import io.swagger.parser.SwaggerParser; +import io.swagger.util.Json; + +import org.apache.camel.CamelContext; +import org.apache.camel.Consumer; +import org.apache.camel.Endpoint; +import org.apache.camel.ExchangePattern; +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.RestConfiguration; +import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriPath; +import org.apache.camel.util.ResourceHelper; + +import static org.apache.camel.component.rest.swagger.RestSwaggerHelper.isHostParam; +import static org.apache.camel.component.rest.swagger.RestSwaggerHelper.isMediaRange; +import static org.apache.camel.util.ObjectHelper.isNotEmpty; +import static org.apache.camel.util.ObjectHelper.notNull; +import static org.apache.camel.util.StringHelper.after; +import static org.apache.camel.util.StringHelper.before; +import static org.apache.camel.util.StringHelper.notEmpty; + +/** + * An awesome REST endpoint backed by Swagger specifications. + */ +@UriEndpoint(firstVersion = "2.19.0", scheme = "rest-swagger", title = "REST Swagger", + syntax = "rest-swagger:specificationUri#operationId", label = "rest,swagger,http", producerOnly = true) +public final class RestSwaggerEndpoint extends DefaultEndpoint { + + /** The name of the Camel component, be it `rest-swagger` or `petstore` */ + private String assignedComponentName; + + @UriParam( + description = "API basePath, for example \"`/v2`\". Default is unset, if set overrides the value present in" + + " Swagger specification and in the component configuration.", + defaultValue = "", label = "producer") + @Metadata(required = "false") + private String basePath; + + @UriParam(description = "Name of the Camel component that will perform the requests. The compnent must be present" + + " in Camel registry and it must implement RestProducerFactory service provider interface. If not set" + + " CLASSPATH is searched for single component that implements RestProducerFactory SPI. Overrides" + + " component configuration.", label = "producer") + @Metadata(required = "false") + private String componentName; + + @UriParam( + description = "What payload type this component capable of consuming. Could be one type, like `application/json`" + + " or multiple types as `application/json, application/xml; q=0.5` according to the RFC7231. This equates" + + " to the value of `Accept` HTTP header. If set overrides any value found in the Swagger specification and." + + " in the component configuration", + label = "producer") + private String consumes; + + @UriParam(description = "Scheme hostname and port to direct the HTTP requests to in the form of" + + " `http[s]://hostname[:port]`. Can be configured at the endpoint, component or in the correspoding" + + " REST configuration in the Camel Context. If you give this component a name (e.g. `petstore`) that" + + " REST configuration is consulted first, `rest-swagger` next, and global configuration last. If set" + + " overrides any value found in the Swagger specification, RestConfiguration. Overrides all other " + + " configuration.", label = "producer") + private String host; + + @UriPath(description = "ID of the operation from the Swagger specification.", label = "producer") + @Metadata(required = "true") + private String operationId; + + @UriParam(description = "What payload type this component is producing. For example `application/json`" + + " according to the RFC7231. This equates to the value of `Content-Type` HTTP header. If set overrides" + + " any value present in the Swagger specification. Overrides all other configuration.", label = "producer") + private String produces; + + @UriPath( + description = "Path to the Swagger specification file. The scheme, host base path are taken from this" + + " specification, but these can be overriden with properties on the component or endpoint level. If not" + + " given the component tries to load `swagger.json` resource. Note that the `host` defined on the" + + " component and endpoint of this Component should contain the scheme, hostname and optionally the" + + " port in the URI syntax (i.e. `https://api.example.com:8080`). Overrides component configuration.", + defaultValue = RestSwaggerComponent.DEFAULT_SPECIFICATION_URI_STR, + defaultValueNote = "By default loads `swagger.json` file", label = "producer") + private URI specificationUri = RestSwaggerComponent.DEFAULT_SPECIFICATION_URI; + + public RestSwaggerEndpoint() { + // help tooling instantiate endpoint + } + + public RestSwaggerEndpoint(final String uri, final String remaining, final RestSwaggerComponent component) { + super(notEmpty(uri, "uri"), notNull(component, "component")); + + assignedComponentName = before(uri, ":"); + + final URI componentSpecificationUri = component.getSpecificationUri(); + + specificationUri = ofNullable(before(remaining, "#")).map(URI::create) + .orElse(ofNullable(componentSpecificationUri).orElse(RestSwaggerComponent.DEFAULT_SPECIFICATION_URI)); + + operationId = ofNullable(after(remaining, "#")).orElse(remaining); + + setExchangePattern(ExchangePattern.InOut); + } + + @Override + public Consumer createConsumer(final Processor processor) throws Exception { + throw new UnsupportedOperationException("Consumer not supported"); + } + + @Override + public Producer createProducer() throws Exception { + final CamelContext camelContext = getCamelContext(); + final Swagger swagger = loadSpecificationFrom(camelContext, specificationUri); + + final Map<String, Path> paths = swagger.getPaths(); + + for (final Entry<String, Path> pathEntry : paths.entrySet()) { + final Path path = pathEntry.getValue(); + + final Optional<Entry<HttpMethod, Operation>> maybeOperationEntry = path.getOperationMap().entrySet() + .stream().filter(operationEntry -> operationId.equals(operationEntry.getValue().getOperationId())) + .findAny(); + + if (maybeOperationEntry.isPresent()) { + final Entry<HttpMethod, Operation> operationEntry = maybeOperationEntry.get(); + + final String uriTemplate = pathEntry.getKey(); + + final HttpMethod httpMethod = operationEntry.getKey(); + final String method = httpMethod.name(); + + final Operation operation = operationEntry.getValue(); + + return createProducerFor(swagger, operation, method, uriTemplate); + } + } + + final String supportedOperations = paths.values().stream().flatMap(p -> p.getOperations().stream()) + .map(Operation::getOperationId).collect(Collectors.joining(", ")); + + throw new IllegalArgumentException("The specified operation with ID: `" + operationId + + "` cannot be found in the Swagger specification loaded from `" + specificationUri + + "`. Operations defined in the specification are: " + supportedOperations); + } + + public String getBasePath() { + return basePath; + } + + public String getComponentName() { + return componentName; + } + + public String getConsumes() { + return consumes; + } + + public String getHost() { + return host; + } + + public String getOperationId() { + return operationId; + } + + public String getProduces() { + return produces; + } + + public URI getSpecificationUri() { + return specificationUri; + } + + @Override + public boolean isSingleton() { + return true; + } + + public void setBasePath(final String basePath) { + this.basePath = notEmpty(basePath, "basePath"); + } + + public void setComponentName(final String componentName) { + this.componentName = notEmpty(componentName, "componentName"); + } + + public void setConsumes(final String consumes) { + this.consumes = isMediaRange(consumes, "consumes"); + } + + public void setHost(final String host) { + this.host = isHostParam(host); + } + + public void setOperationId(final String operationId) { + this.operationId = notEmpty(operationId, "operationId"); + } + + public void setProduces(final String produces) { + this.produces = isMediaRange(produces, "produces"); + } + + public void setSpecificationUri(final URI specificationUri) { + this.specificationUri = notNull(specificationUri, "specificationUri"); + } + + RestSwaggerComponent component() { + return (RestSwaggerComponent) getComponent(); + } + + Producer createProducerFor(final Swagger swagger, final Operation operation, final String method, + final String uriTemplate) throws Exception { + final String basePath = determineBasePath(swagger); + + final StringBuilder componentEndpointUri = new StringBuilder(200).append("rest:").append(method).append(":") + .append(basePath).append(":").append(uriTemplate); + + final CamelContext camelContext = getCamelContext(); + + final Endpoint endpoint = camelContext.getEndpoint(componentEndpointUri.toString()); + + setProperties(endpoint, determineEndpointParameters(swagger, operation)); + + return endpoint.createProducer(); + } + + String determineBasePath(final Swagger swagger) { + if (isNotEmpty(basePath)) { + return basePath; + } + + final String componentBasePath = component().getBasePath(); + if (isNotEmpty(componentBasePath)) { + return componentBasePath; + } + + final String specificationBasePath = swagger.getBasePath(); + if (isNotEmpty(specificationBasePath)) { + return specificationBasePath; + } + + final CamelContext camelContext = getCamelContext(); + final RestConfiguration specificConfiguration = camelContext.getRestConfiguration(assignedComponentName, false); + if (specificConfiguration != null && isNotEmpty(specificConfiguration.getContextPath())) { + return specificConfiguration.getContextPath(); + } + + final RestConfiguration restConfiguration = camelContext.getRestConfiguration("rest-swagger", true); + final String restConfigurationBasePath = restConfiguration.getContextPath(); + if (isNotEmpty(restConfigurationBasePath)) { + return restConfigurationBasePath; + } + + return RestSwaggerComponent.DEFAULT_BASE_PATH; + } + + String determineComponentName() { + return Optional.ofNullable(componentName).orElse(component().getComponentName()); + } + + Map<String, Object> determineEndpointParameters(final Swagger swagger, final Operation operation) { + final Map<String, Object> parameters = new HashMap<>(); + + final String componentName = determineComponentName(); + if (componentName != null) { + parameters.put("componentName", componentName); + } + + final String host = determineHost(swagger); + if (host != null) { + parameters.put("host", host); + } + + // what we consume is what the API defined by Swagger specification + // produces + final String determinedConsumes = determineOption(swagger.getProduces(), operation.getProduces(), + component().getConsumes(), consumes); + + if (isNotEmpty(determinedConsumes)) { + parameters.put("consumes", determinedConsumes); + } + + // what we produce is what the API defined by Swagger specification + // consumes + final String determinedProducers = determineOption(swagger.getConsumes(), operation.getConsumes(), + component().getProduces(), produces); + + if (isNotEmpty(determinedProducers)) { + parameters.put("produces", determinedProducers); + } + + return parameters; + } + + String determineHost(final Swagger swagger) { + if (isNotEmpty(host)) { + return host; + } + + final String componentHost = component().getHost(); + if (isNotEmpty(componentHost)) { + return componentHost; + } + + final String swaggerScheme = pickBestScheme(specificationUri.getScheme(), swagger.getSchemes()); + final String swaggerHost = swagger.getHost(); + + if (isNotEmpty(swaggerScheme) && isNotEmpty(swaggerHost)) { + return swaggerScheme + "://" + swaggerHost; + } + + final CamelContext camelContext = getCamelContext(); + + final RestConfiguration specificRestConfiguration = camelContext.getRestConfiguration(assignedComponentName, + false); + final String specificConfigurationHost = hostFrom(specificRestConfiguration); + if (specificConfigurationHost != null) { + return specificConfigurationHost; + } + + final RestConfiguration componentRestConfiguration = camelContext.getRestConfiguration("rest-swagger", false); + final String componentConfigurationHost = hostFrom(componentRestConfiguration); + if (componentConfigurationHost != null) { + return componentConfigurationHost; + } + + final RestConfiguration globalRestConfiguration = camelContext.getRestConfiguration(); + final String globalConfigurationHost = hostFrom(globalRestConfiguration); + if (globalConfigurationHost != null) { + return globalConfigurationHost; + } + + final String specificationScheme = specificationUri.getScheme(); + if (specificationUri.isAbsolute() && specificationScheme.toLowerCase().startsWith("http")) { + try { + return new URI(specificationUri.getScheme(), specificationUri.getUserInfo(), specificationUri.getHost(), + specificationUri.getPort(), null, null, null).toString(); + } catch (final URISyntaxException e) { + throw new IllegalStateException("Unable to create a new URI from: " + specificationUri, e); + } + } + + final boolean areTheSame = "rest-swagger".equals(assignedComponentName); + + throw new IllegalStateException("Unable to determine destionation host for requests. The Swagger specification" + + " does not specify `scheme` and `host` parameters, the specification URI is not absolute with `http` or" + + " `https` scheme, and no RestConfigurations configured with `scheme`, `host` and `port` were found for `" + + (areTheSame ? "rest-swagger` component" : assignedComponentName + "` or `rest-swagger` components") + + " and there is no global RestConfiguration with those properties"); + } + + static String determineOption(final List<String> specificationLevel, final List<String> operationLevel, + final String componentLevel, final String endpointLevel) { + if (isNotEmpty(endpointLevel)) { + return endpointLevel; + } + + if (isNotEmpty(componentLevel)) { + return componentLevel; + } + + if (operationLevel != null && !operationLevel.isEmpty()) { + return String.join(", ", operationLevel); + } + + if (specificationLevel != null && !specificationLevel.isEmpty()) { + return String.join(", ", specificationLevel); + } + + return null; + } + + static String hostFrom(final RestConfiguration restConfiguration) { + if (restConfiguration == null) { + return null; + } + + final String scheme = restConfiguration.getScheme(); + final String host = restConfiguration.getHost(); + final int port = restConfiguration.getPort(); + + if (scheme == null || host == null) { + return null; + } + + final StringBuilder answer = new StringBuilder(scheme).append("://").append(host); + if (port > 0 && !("http".equalsIgnoreCase(scheme) && port == 80) + && !("https".equalsIgnoreCase(scheme) && port == 443)) { + answer.append(':').append(port); + } + + return answer.toString(); + } + + /** + * Loads the Swagger definition model from the given path. Tries to resolve + * the resource using Camel's resource loading support, if it fails uses + * Swagger's resource loading support instead. + * + * @param uri URI of the specification + * @param camelContext context to use + * @return the specification + * @throws IOException + */ + static Swagger loadSpecificationFrom(final CamelContext camelContext, final URI uri) throws IOException { + final ObjectMapper mapper = Json.mapper(); + + final SwaggerParser swaggerParser = new SwaggerParser(); + + final String uriAsString = uri.toString(); + + try (InputStream stream = ResourceHelper.resolveMandatoryResourceAsInputStream(camelContext, uriAsString)) { + final JsonNode node = mapper.readTree(stream); + + return swaggerParser.read(node); + } catch (final IOException e) { + // try Swaggers loader + final Swagger swagger = swaggerParser.read(uriAsString); + + if (swagger != null) { + return swagger; + } + + throw new IllegalArgumentException("The given Swagger specification could not be loaded from `" + uri + + "`. Tried loading using Camel's resource resolution and using Swagger's own resource resolution." + + " Swagger tends to swallow exceptions while parsing, try specifying Java system property `debugParser`" + + " (e.g. `-DdebugParser=true`), the exception that occured when loading using Camel's resource" + + " loader follows", e); + } + } + + static String pickBestScheme(final String specificationScheme, final List<Scheme> schemes) { + if (schemes != null && !schemes.isEmpty()) { + if (schemes.contains(Scheme.HTTPS)) { + return "https"; + } + + if (schemes.contains(Scheme.HTTP)) { + return "http"; + } + } + + if (specificationScheme != null) { + return specificationScheme; + } + + // there is no support for WebSocket (Scheme.WS, Scheme.WSS) + + return null; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/5b209152/components/camel-rest-swagger/src/main/java/org/apache/camel/component/rest/swagger/RestSwaggerHelper.java ---------------------------------------------------------------------- diff --git a/components/camel-rest-swagger/src/main/java/org/apache/camel/component/rest/swagger/RestSwaggerHelper.java b/components/camel-rest-swagger/src/main/java/org/apache/camel/component/rest/swagger/RestSwaggerHelper.java new file mode 100644 index 0000000..ca7e972 --- /dev/null +++ b/components/camel-rest-swagger/src/main/java/org/apache/camel/component/rest/swagger/RestSwaggerHelper.java @@ -0,0 +1,50 @@ +/** + * 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.rest.swagger; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.camel.util.StringHelper; + +import static org.apache.camel.util.StringHelper.notEmpty; + +final class RestSwaggerHelper { + + private static final Pattern HOST_PATTERN = Pattern.compile("https?://[^:]+(:\\d+)?", Pattern.CASE_INSENSITIVE); + + private RestSwaggerHelper() { + // utility class + } + + public static String isMediaRange(final String given, final String name) { + return notEmpty(given, name); + } + + static String isHostParam(final String given) { + final String hostUri = StringHelper.notEmpty(given, "host"); + + final Matcher matcher = HOST_PATTERN.matcher(given); + + if (!matcher.matches()) { + throw new IllegalArgumentException( + "host must be an apsolute URI (e.g. http://api.example.com), given: `" + hostUri + "`"); + } + + return hostUri; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/5b209152/components/camel-rest-swagger/src/main/resources/META-INF/LICENSE.txt ---------------------------------------------------------------------- diff --git a/components/camel-rest-swagger/src/main/resources/META-INF/LICENSE.txt b/components/camel-rest-swagger/src/main/resources/META-INF/LICENSE.txt new file mode 100755 index 0000000..6b0b127 --- /dev/null +++ b/components/camel-rest-swagger/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. + http://git-wip-us.apache.org/repos/asf/camel/blob/5b209152/components/camel-rest-swagger/src/main/resources/META-INF/NOTICE.txt ---------------------------------------------------------------------- diff --git a/components/camel-rest-swagger/src/main/resources/META-INF/NOTICE.txt b/components/camel-rest-swagger/src/main/resources/META-INF/NOTICE.txt new file mode 100644 index 0000000..2e215bf --- /dev/null +++ b/components/camel-rest-swagger/src/main/resources/META-INF/NOTICE.txt @@ -0,0 +1,11 @@ + ========================================================================= + == NOTICE file corresponding to the section 4 d of == + == the Apache License, Version 2.0, == + == in this case for the Apache Camel distribution. == + ========================================================================= + + This product includes software developed by + The Apache Software Foundation (http://www.apache.org/). + + Please read the different LICENSE files present in the licenses directory of + this distribution. http://git-wip-us.apache.org/repos/asf/camel/blob/5b209152/components/camel-rest-swagger/src/main/resources/META-INF/services/org/apache/camel/component/rest-swagger ---------------------------------------------------------------------- diff --git a/components/camel-rest-swagger/src/main/resources/META-INF/services/org/apache/camel/component/rest-swagger b/components/camel-rest-swagger/src/main/resources/META-INF/services/org/apache/camel/component/rest-swagger new file mode 100644 index 0000000..f4213a9 --- /dev/null +++ b/components/camel-rest-swagger/src/main/resources/META-INF/services/org/apache/camel/component/rest-swagger @@ -0,0 +1,17 @@ +# +# 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.component.rest.swagger.RestSwaggerComponent http://git-wip-us.apache.org/repos/asf/camel/blob/5b209152/components/camel-rest-swagger/src/test/java/org/apache/camel/component/rest/swagger/Pet.java ---------------------------------------------------------------------- diff --git a/components/camel-rest-swagger/src/test/java/org/apache/camel/component/rest/swagger/Pet.java b/components/camel-rest-swagger/src/test/java/org/apache/camel/component/rest/swagger/Pet.java new file mode 100644 index 0000000..50308ba --- /dev/null +++ b/components/camel-rest-swagger/src/test/java/org/apache/camel/component/rest/swagger/Pet.java @@ -0,0 +1,28 @@ +/** + * 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.rest.swagger; + +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "Pet") +public class Pet { + + public Integer id; + + public String name; + +} http://git-wip-us.apache.org/repos/asf/camel/blob/5b209152/components/camel-rest-swagger/src/test/java/org/apache/camel/component/rest/swagger/RestSwaggerComponentTest.java ---------------------------------------------------------------------- diff --git a/components/camel-rest-swagger/src/test/java/org/apache/camel/component/rest/swagger/RestSwaggerComponentTest.java b/components/camel-rest-swagger/src/test/java/org/apache/camel/component/rest/swagger/RestSwaggerComponentTest.java new file mode 100644 index 0000000..c69649c --- /dev/null +++ b/components/camel-rest-swagger/src/test/java/org/apache/camel/component/rest/swagger/RestSwaggerComponentTest.java @@ -0,0 +1,152 @@ +/** + * 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.rest.swagger; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; + +import org.apache.camel.CamelContext; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.rest.RestEndpoint; +import org.apache.camel.converter.jaxb.JaxbDataFormat; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; + +@RunWith(Parameterized.class) +public class RestSwaggerComponentTest extends CamelTestSupport { + + @ClassRule + public static WireMockRule petstore = new WireMockRule(wireMockConfig().dynamicPort()); + + static final Object NO_BODY = null; + + @Parameter + public String componentName; + + @Before + public void resetWireMock() { + petstore.resetRequests(); + } + + @Test + public void shouldBeAddingPets() { + final Pet pet = new Pet(); + pet.name = "Jean-Luc Picard"; + + final Pet created = template.requestBody("direct:addPet", pet, Pet.class); + + assertNotNull(created); + + assertEquals(Integer.valueOf(14), created.id); + + petstore.verify( + postRequestedFor(urlEqualTo("/v2/pet")).withHeader("Accept", equalTo("application/xml, application/json")) + .withHeader("Content-Type", equalTo("application/xml"))); + } + + @Test + public void shouldBeGettingPetsById() { + final Pet pet = template.requestBodyAndHeader("direct:getPetById", NO_BODY, "petId", 14, Pet.class); + + assertNotNull(pet); + + assertEquals(Integer.valueOf(14), pet.id); + assertEquals("Olafur Eliason Arnalds", pet.name); + + petstore.verify(getRequestedFor(urlEqualTo("/v2/pet/14")).withHeader("Accept", + equalTo("application/xml, application/json"))); + } + + @Override + protected CamelContext createCamelContext() throws Exception { + final CamelContext camelContext = super.createCamelContext(); + + final RestSwaggerComponent component = new RestSwaggerComponent(); + component.setComponentName(componentName); + component.setHost("http://localhost:" + petstore.port()); + + camelContext.addComponent("petStore", component); + + return camelContext; + } + + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + final JAXBContext jaxbContext = JAXBContext.newInstance(Pet.class); + + final JaxbDataFormat jaxb = new JaxbDataFormat(jaxbContext); + jaxb.setJaxbProviderProperties(Collections.singletonMap(Marshaller.JAXB_FORMATTED_OUTPUT, false)); + + from("direct:getPetById").to("petStore:getPetById").unmarshal(jaxb); + + from("direct:addPet").marshal(jaxb).to("petStore:addPet").unmarshal(jaxb); + } + }; + } + + @Parameters(name = "component = {0}") + public static Iterable<String> knownProducers() { + return Arrays.asList(RestEndpoint.DEFAULT_REST_PRODUCER_COMPONENTS); + } + + @BeforeClass + public static void setupStubs() throws IOException, URISyntaxException { + petstore.stubFor(get(urlEqualTo("/swagger.json")).willReturn(aResponse().withBody( + Files.readAllBytes(Paths.get(RestSwaggerComponentTest.class.getResource("/swagger.json").toURI()))))); + + petstore.stubFor(post(urlEqualTo("/v2/pet")) + .withRequestBody(equalTo( + "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><Pet><name>Jean-Luc Picard</name></Pet>")) + .willReturn(aResponse().withStatus(HttpURLConnection.HTTP_CREATED) + .withBody("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><Pet><id>14</id></Pet>"))); + + petstore.stubFor( + get(urlEqualTo("/v2/pet/14")).willReturn(aResponse().withStatus(HttpURLConnection.HTTP_OK).withBody( + "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><Pet><id>14</id><name>Olafur Eliason Arnalds</name></Pet>"))); + } + +}