blob: e9e5998fd48a34640992181a3969998edb92d4c6 [file] [log] [blame]
/*
* 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.springboot.openapi;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import io.apicurio.datamodels.openapi.models.OasDocument;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.parser.OpenAPIV3Parser;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import org.apache.camel.CamelContext;
import org.apache.camel.model.rest.RestDefinition;
import org.apache.camel.openapi.BeanConfig;
import org.apache.camel.openapi.DefaultRestDefinitionsResolver;
import org.apache.camel.openapi.RestDefinitionsResolver;
import org.apache.camel.openapi.RestOpenApiReader;
import org.apache.camel.spi.RestConfiguration;
import org.apache.camel.spring.boot.CamelContextConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;
import java.util.List;
import java.util.Map;
import static org.apache.camel.openapi.OpenApiHelper.clearVendorExtensions;
/**
* Open API auto-configuration.
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({OpenApiConfiguration.class})
@ConditionalOnBean(type = "org.apache.camel.spring.boot.CamelAutoConfiguration")
@ConditionalOnProperty(name = "camel.openapi.enabled", matchIfMissing = true)
@AutoConfigureAfter(name = "org.apache.camel.spring.boot.CamelAutoConfiguration")
@AutoConfigureBefore(name = "org.springdoc.core.SpringDocConfiguration")
public class OpenApiAutoConfiguration {
private static final Logger LOG = LoggerFactory.getLogger(OpenApiAutoConfiguration.class);
private final RestOpenApiReader reader = new RestOpenApiReader();
private final RestDefinitionsResolver resolver = new DefaultRestDefinitionsResolver();
private final OpenAPI openAPI = new OpenAPI();
@Bean
CamelContextConfiguration onBeforeStart(GenericApplicationContext ac, CamelContext camelContext, OpenApiConfiguration config) {
return new CamelContextConfiguration() {
@Override
public void beforeApplicationStart(CamelContext camelContext) {
// routes have now been loaded, so we need to detect rest-dsl APIs in Camel
// this will trigger spring boot to create the bean which springdoc can detect
try {
OpenAPI created = createOpenAPI(camelContext);
if (created != null) {
LOG.info("OpenAPI ({}) created from Camel Rest-DSL v{} - {}", created.getOpenapi(), created.getInfo().getVersion(), created.getInfo().getTitle());
// transfer data to the existing
openAPI.setInfo(created.getInfo());
openAPI.setOpenapi(created.getOpenapi());
if (created.getComponents() != null) {
openAPI.setComponents(created.getComponents());
}
if (created.getExtensions() != null) {
openAPI.setExtensions(created.getExtensions());
}
if (created.getSecurity() != null) {
openAPI.setSecurity(created.getSecurity());
}
if (created.getExternalDocs() != null) {
openAPI.setExternalDocs(created.getExternalDocs());
}
if (created.getPaths() != null) {
openAPI.setPaths(created.getPaths());
}
if (created.getTags() != null) {
openAPI.setTags(created.getTags());
}
// do not copy servers as we use the spring-boot configured setting
}
} catch (Exception e) {
LOG.warn("Error generating OpenAPI from Camel Rest DSL due to: " + e.getMessage() + ". This exception is ignored.", e);
}
}
@Override
public void afterApplicationStart(CamelContext camelContext) {
// noop
}
};
}
@Bean
OpenAPI camelRestDSLOpenApi() {
// due to ordering how beans are resolved and setup in spring boot, then we need to create
// this provisional bean which is later updated with the actual Rest DSL APIs after the routes has been loaded
// into Camel
return openAPI;
}
private OpenAPI createOpenAPI(CamelContext camelContext) throws Exception {
List<RestDefinition> rests = resolver.getRestDefinitions(camelContext, null);
if (rests == null || rests.isEmpty()) {
return null;
}
BeanConfig bc = new BeanConfig();
Info info = new Info();
RestConfiguration rc = camelContext.getRestConfiguration();
initOpenApi(bc, info, rc.getApiProperties());
OasDocument openApi = reader.read(camelContext, rests, "", bc, null, camelContext.getClassResolver());
if (!rc.isApiVendorExtension()) {
clearVendorExtensions(openApi);
}
// dump to json
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
Object dump = io.apicurio.datamodels.Library.writeNode(openApi);
byte[] jsonData = mapper.writeValueAsBytes(dump);
// json to yaml
JsonNode node = mapper.readTree(jsonData);
String yaml = new YAMLMapper().writeValueAsString(node);
// parse bytes into swagger
OpenAPIV3Parser parser = new OpenAPIV3Parser();
SwaggerParseResult spr = parser.readContents(yaml);
OpenAPI answer = spr.getOpenAPI();
if (answer != null) {
answer.setInfo(info);
}
return answer;
}
private static void initOpenApi(BeanConfig bc, Info info, Map<String, Object> config) {
// configure openApi options
String s = (String) config.get("openapi.version");
if (s != null) {
bc.setVersion(s);
}
s = (String) config.get("base.path");
if (s != null) {
bc.setBasePath(s);
}
s = (String) config.get("host");
if (s != null) {
bc.setHost(s);
}
s = (String) config.get("schemes");
if (s == null) {
// deprecated due typo
s = (String) config.get("schemas");
}
if (s != null) {
String[] schemes = s.split(",");
bc.setSchemes(schemes);
} else {
// assume http by default
bc.setSchemes(new String[] { "http" });
}
String version = (String) config.get("api.version");
String title = (String) config.get("api.title");
String description = (String) config.get("api.description");
String termsOfService = (String) config.get("api.termsOfService");
String licenseName = (String) config.get("api.license.name");
String licenseUrl = (String) config.get("api.license.url");
String contactName = (String) config.get("api.contact.name");
String contactUrl = (String) config.get("api.contact.url");
String contactEmail = (String) config.get("api.contact.email");
bc.setTitle(title);
bc.setLicense(licenseName);
bc.setLicenseUrl(licenseUrl);
info.setTitle(title);
info.setVersion(version);
info.setDescription(description);
info.setTermsOfService(termsOfService);
if (contactName != null) {
Contact contact = new Contact();
contact.setName(contactName);
contact.setEmail(contactEmail);
contact.setUrl(contactUrl);
info.setContact(contact);
}
}
}