org.apache.camel.component.rest.swagger.RestSwaggerEndpoint Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of camel-rest-swagger Show documentation
Show all versions of camel-rest-swagger Show documentation
Camel REST support using Swagger
/*
* 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.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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.models.auth.ApiKeyAuthDefinition;
import io.swagger.models.auth.In;
import io.swagger.models.auth.SecuritySchemeDefinition;
import io.swagger.models.parameters.Parameter;
import io.swagger.models.parameters.QueryParameter;
import io.swagger.parser.SwaggerParser;
import io.swagger.util.Json;
import org.apache.camel.CamelContext;
import org.apache.camel.Category;
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.component.http.HttpComponent;
import org.apache.camel.component.http.HttpEndpoint;
import org.apache.camel.component.http.HttpProducer;
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.support.CamelContextHelper;
import org.apache.camel.support.DefaultEndpoint;
import org.apache.camel.support.ResourceHelper;
import org.apache.camel.support.jsse.SSLContextParameters;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.StringHelper;
import org.apache.camel.util.UnsafeUriCharactersEncoder;
import org.apache.http.client.methods.HttpGet;
import static java.util.Optional.ofNullable;
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;
/**
* Configure REST producers based on a Swagger (OpenAPI) specification document delegating to a component implementing
* the RestProducerFactory interface.
*/
@UriEndpoint(firstVersion = "2.19.0", scheme = "rest-swagger", title = "REST Swagger",
syntax = "rest-swagger:specificationUri#operationId",
category = { Category.REST, Category.SWAGGER, Category.HTTP }, producerOnly = true)
public final class RestSwaggerEndpoint extends DefaultEndpoint {
/**
* Remaining parameters specified in the Endpoint URI.
*/
Map parameters = Collections.emptyMap();
@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")
private String basePath;
@UriParam(description = "Name of the Camel component that will perform the requests. The component 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")
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 corresponding"
+ " 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;
@UriParam(label = "security", description = "To configure security using SSLContextParameters.")
private SSLContextParameters sslContextParameters;
@UriPath(description = "Path to the Swagger specification file. The scheme, host base path are taken from this"
+ " specification, but these can be overridden with properties on the component or endpoint level. If not"
+ " given the component tries to load `swagger.json` resource from the classpath. 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. `http://api.example.com:8080`). Overrides component configuration."
+ " The Swagger specification can be loaded from different sources by prefixing with file: classpath: http: https:."
+ " Support for https is limited to using the JDK installed UrlHandler, and as such it can be cumbersome to setup"
+ " TLS/SSL certificates for https (such as setting a number of javax.net.ssl JVM system properties)."
+ " How to do that consult the JDK documentation for UrlHandler.",
defaultValue = RestSwaggerComponent.DEFAULT_SPECIFICATION_URI_STR,
defaultValueNote = "By default loads `swagger.json` file", label = "producer")
private URI specificationUri = RestSwaggerComponent.DEFAULT_SPECIFICATION_URI;
@UriParam(description = "Resolve references in Swagger specification.", label = "producer")
private Boolean resolveReferences;
public RestSwaggerEndpoint() {
// help tooling instantiate endpoint
}
public RestSwaggerEndpoint(final String uri, final String remaining, final RestSwaggerComponent component,
final Map parameters) {
super(notEmpty(uri, "uri"), notNull(component, "component"));
this.parameters = parameters;
final URI componentSpecificationUri = component.getSpecificationUri();
specificationUri = before(remaining, "#", StringHelper::trimToNull).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, resolveSslContextParameters(),
determineResolveReferences());
final Map paths = swagger.getPaths();
for (final Entry pathEntry : paths.entrySet()) {
final Path path = pathEntry.getValue();
final Optional> maybeOperationEntry = path.getOperationMap().entrySet()
.stream().filter(operationEntry -> operationId.equals(operationEntry.getValue().getOperationId()))
.findAny();
if (maybeOperationEntry.isPresent()) {
final Entry operationEntry = maybeOperationEntry.get();
final Operation operation = operationEntry.getValue();
final Map pathParameters = operation.getParameters().stream()
.filter(p -> "path".equals(p.getIn()))
.collect(Collectors.toMap(Parameter::getName, Function.identity()));
final String uriTemplate = resolveUri(pathEntry.getKey(), pathParameters);
final HttpMethod httpMethod = operationEntry.getKey();
final String method = httpMethod.name();
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);
}
private SSLContextParameters resolveSslContextParameters() {
if (sslContextParameters != null) {
return sslContextParameters;
}
if (component().getSslContextParameters() != null) {
return component().getSslContextParameters();
}
return component().retrieveGlobalSslContextParameters();
}
public String getBasePath() {
return basePath;
}
public String getComponentName() {
return componentName;
}
public String getConsumes() {
return consumes;
}
public SSLContextParameters getSslContextParameters() {
return sslContextParameters;
}
public String getHost() {
return host;
}
public String getOperationId() {
return operationId;
}
public String getProduces() {
return produces;
}
public URI getSpecificationUri() {
return specificationUri;
}
@Override
public boolean isLenientProperties() {
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 setSslContextParameters(SSLContextParameters sslContextParameters) {
this.sslContextParameters = sslContextParameters;
}
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");
}
public Boolean getResolveReferences() {
return resolveReferences;
}
public void setResolveReferences(Boolean resolveReferences) {
this.resolveReferences = resolveReferences;
}
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());
Map params = determineEndpointParameters(swagger, operation);
boolean hasHost = params.containsKey("host");
if (endpoint instanceof DefaultEndpoint) {
// let the rest endpoint configure itself
DefaultEndpoint de = (DefaultEndpoint) endpoint;
de.setProperties(endpoint, params);
}
// if there is a host then we should use this hardcoded host instead of any Header that may have an existing
// Host header from some other HTTP input, and if so then lets remove it
return new RestSwaggerProducer(endpoint.createProducer(), hasHost);
}
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 restConfiguration
= CamelContextHelper.getRestConfiguration(camelContext, null, determineComponentName());
final String restConfigurationBasePath = restConfiguration.getContextPath();
if (isNotEmpty(restConfigurationBasePath)) {
return restConfigurationBasePath;
}
return RestSwaggerComponent.DEFAULT_BASE_PATH;
}
String determineComponentName() {
return Optional.ofNullable(componentName).orElse(component().getComponentName());
}
Boolean determineResolveReferences() {
return Stream.of(getResolveReferences(), component().getResolveReferences())
.filter(Objects::nonNull)
.findFirst()
.orElse(false);
}
Map determineEndpointParameters(final Swagger swagger, final Operation operation) {
final Map parameters = new HashMap<>();
final String componentName = determineComponentName();
if (componentName != null) {
parameters.put("producerComponentName", componentName);
}
final String host = determineHost(swagger);
if (host != null) {
parameters.put("host", host);
}
final RestSwaggerComponent component = component();
// 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 determinedProduces = determineOption(swagger.getConsumes(), operation.getConsumes(),
component.getProduces(), produces);
if (isNotEmpty(determinedProduces)) {
parameters.put("produces", determinedProduces);
}
final String queryParameters = determineQueryParameters(swagger, operation).map(this::queryParameter)
.collect(Collectors.joining("&"));
if (isNotEmpty(queryParameters)) {
parameters.put("queryParameters", queryParameters);
}
// pass properties that might be applied if the delegate component is
// created, i.e. if it's not
// present in the Camel Context already
final Map componentParameters = new HashMap<>();
if (component.isUseGlobalSslContextParameters()) {
// by default it's false
componentParameters.put("useGlobalSslContextParameters", component.isUseGlobalSslContextParameters());
}
if (component.getSslContextParameters() != null) {
componentParameters.put("sslContextParameters", component.getSslContextParameters());
}
final Map
© 2015 - 2025 Weber Informatics LLC | Privacy Policy