/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.swagger;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.management.AttributeNotFoundException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
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 io.swagger.jaxrs.config.BeanConfig;
import io.swagger.models.Contact;
import io.swagger.models.Info;
import io.swagger.models.License;
import io.swagger.models.Swagger;
import io.swagger.util.Yaml;
import org.apache.camel.Exchange;
import org.apache.camel.model.ModelHelper;
import org.apache.camel.model.rest.RestDefinition;
import org.apache.camel.model.rest.RestsDefinition;
import org.apache.camel.spi.ClassResolver;
import org.apache.camel.spi.RestConfiguration;
import org.apache.camel.util.CamelVersionHelper;
import org.apache.camel.util.EndpointHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A support class for that allows SPI to plugin
* and offer Swagger API service listings as part of the Camel component. This allows rest-dsl components
* such as servlet/jetty/netty4-http to offer Swagger API listings with minimal effort.
*/
public class RestSwaggerSupport {
private static final Logger LOG = LoggerFactory.getLogger(RestSwaggerSupport.class);
private RestSwaggerReader reader = new RestSwaggerReader();
private boolean cors;
public void initSwagger(BeanConfig swaggerConfig, Map<String, Object> config) {
// configure swagger options
String s = (String) config.get("swagger.version");
if (s != null) {
swaggerConfig.setVersion(s);
}
s = (String) config.get("base.path");
if (s != null) {
swaggerConfig.setBasePath(s);
}
s = (String) config.get("host");
if (s != null) {
swaggerConfig.setHost(s);
}
s = (String) config.get("cors");
if (s != null) {
cors = "true".equalsIgnoreCase(s);
}
s = (String) config.get("schemes");
if (s == null) {
// deprecated due typo
s = (String) config.get("schemas");
}
if (s != null) {
String[] schemes = s.split(",");
swaggerConfig.setSchemes(schemes);
} else {
// assume http by default
swaggerConfig.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");
Info info = new Info();
info.setVersion(version);
info.setTitle(title);
info.setDescription(description);
info.setTermsOfService(termsOfService);
if (licenseName != null || licenseUrl != null) {
License license = new License();
license.setName(licenseName);
license.setUrl(licenseUrl);
info.setLicense(license);
}
if (contactName != null || contactUrl != null || contactEmail != null) {
Contact contact = new Contact();
contact.setName(contactName);
contact.setUrl(contactUrl);
contact.setEmail(contactEmail);
info.setContact(contact);
}
swaggerConfig.setInfo(info);
}
public List<RestDefinition> getRestDefinitions(String camelId) throws Exception {
ObjectName found = null;
boolean supportResolvePlaceholder = false;
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
Set<ObjectName> names = server.queryNames(new ObjectName("org.apache.camel:type=context,*"), null);
for (ObjectName on : names) {
String id = on.getKeyProperty("name");
if (id.startsWith("\"") && id.endsWith("\"")) {
id = id.substring(1, id.length() - 1);
}
if (camelId == null || camelId.equals(id)) {
// filter out older Camel versions as this requires Camel 2.15 or better (rest-dsl)
String version = (String) server.getAttribute(on, "CamelVersion");
if (CamelVersionHelper.isGE("2.15.0", version)) {
found = on;
}
if (CamelVersionHelper.isGE("2.15.3", version)) {
supportResolvePlaceholder = true;
}
}
}
if (found != null) {
String xml;
if (supportResolvePlaceholder) {
xml = (String) server.invoke(found, "dumpRestsAsXml", new Object[]{true}, new String[]{"boolean"});
} else {
xml = (String) server.invoke(found, "dumpRestsAsXml", null, null);
}
if (xml != null) {
LOG.debug("DumpRestAsXml:\n{}", xml);
RestsDefinition rests = ModelHelper.createModelFromXml(null, xml, RestsDefinition.class);
if (rests != null) {
return rests.getRests();
}
}
}
return null;
}
public List<String> findCamelContexts() throws Exception {
List<String> answer = new ArrayList<>();
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
Set<ObjectName> names = server.queryNames(new ObjectName("*:type=context,*"), null);
for (ObjectName on : names) {
String id = on.getKeyProperty("name");
if (id.startsWith("\"") && id.endsWith("\"")) {
id = id.substring(1, id.length() - 1);
}
// filter out older Camel versions as this requires Camel 2.15 or better (rest-dsl)
try {
String version = (String) server.getAttribute(on, "CamelVersion");
if (CamelVersionHelper.isGE("2.15.0", version)) {
answer.add(id);
}
} catch (AttributeNotFoundException ex) {
// ignore
}
}
return answer;
}
public void renderResourceListing(RestApiResponseAdapter response, BeanConfig swaggerConfig, String contextId, String route, boolean json, boolean yaml,
ClassResolver classResolver, RestConfiguration configuration) throws Exception {
LOG.trace("renderResourceListing");
if (cors) {
setupCorsHeaders(response, configuration.getCorsHeaders());
}
List<RestDefinition> rests = getRestDefinitions(contextId);
if (rests != null) {
if (json) {
response.setHeader(Exchange.CONTENT_TYPE, "application/json");
// read the rest-dsl into swagger model
Swagger swagger = reader.read(rests, route, swaggerConfig, contextId, classResolver);
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
byte[] bytes = mapper.writeValueAsBytes(swagger);
int len = bytes.length;
response.setHeader(Exchange.CONTENT_LENGTH, "" + len);
response.writeBytes(bytes);
} else {
response.setHeader(Exchange.CONTENT_TYPE, "text/yaml");
// read the rest-dsl into swagger model
Swagger swagger = reader.read(rests, route, swaggerConfig, contextId, classResolver);
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
byte[] jsonData = mapper.writeValueAsBytes(swagger);
// json to yaml
JsonNode node = mapper.readTree(jsonData);
byte[] bytes = Yaml.mapper().writerWithDefaultPrettyPrinter().writeValueAsBytes(node);
int len = bytes.length;
response.setHeader(Exchange.CONTENT_LENGTH, "" + len);
response.writeBytes(bytes);
}
} else {
response.noContent();
}
}
/**
* Renders a list of available CamelContexts in the JVM
*/
public void renderCamelContexts(RestApiResponseAdapter response, String contextId, String contextIdPattern, boolean json, boolean yaml,
RestConfiguration configuration) throws Exception {
LOG.trace("renderCamelContexts");
if (cors) {
setupCorsHeaders(response, configuration.getCorsHeaders());
}
List<String> contexts = findCamelContexts();
// filter non matched CamelContext's
if (contextIdPattern != null) {
Iterator<String> it = contexts.iterator();
while (it.hasNext()) {
String name = it.next();
boolean match;
if ("#name#".equals(contextIdPattern)) {
match = name.equals(contextId);
} else {
match = EndpointHelper.matchPattern(name, contextIdPattern);
}
if (!match) {
it.remove();
}
}
}
StringBuffer sb = new StringBuffer();
if (json) {
response.setHeader(Exchange.CONTENT_TYPE, "application/json");
sb.append("[\n");
for (int i = 0; i < contexts.size(); i++) {
String name = contexts.get(i);
sb.append("{\"name\": \"").append(name).append("\"}");
if (i < contexts.size() - 1) {
sb.append(",\n");
}
}
sb.append("\n]");
} else {
response.setHeader(Exchange.CONTENT_TYPE, "text/yaml");
for (int i = 0; i < contexts.size(); i++) {
String name = contexts.get(i);
sb.append("- \"").append(name).append("\"\n");
}
}
int len = sb.length();
response.setHeader(Exchange.CONTENT_LENGTH, "" + len);
response.writeBytes(sb.toString().getBytes());
}
private static void setupCorsHeaders(RestApiResponseAdapter response, Map<String, String> corsHeaders) {
// use default value if none has been configured
String allowOrigin = corsHeaders != null ? corsHeaders.get("Access-Control-Allow-Origin") : null;
if (allowOrigin == null) {
allowOrigin = RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_ORIGIN;
}
String allowMethods = corsHeaders != null ? corsHeaders.get("Access-Control-Allow-Methods") : null;
if (allowMethods == null) {
allowMethods = RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_METHODS;
}
String allowHeaders = corsHeaders != null ? corsHeaders.get("Access-Control-Allow-Headers") : null;
if (allowHeaders == null) {
allowHeaders = RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_HEADERS;
}
String maxAge = corsHeaders != null ? corsHeaders.get("Access-Control-Max-Age") : null;
if (maxAge == null) {
maxAge = RestConfiguration.CORS_ACCESS_CONTROL_MAX_AGE;
}
if (LOG.isTraceEnabled()) {
LOG.trace("Using CORS headers[");
LOG.trace(" Access-Control-Allow-Origin={}", allowOrigin);
LOG.trace(" Access-Control-Allow-Methods={}", allowMethods);
LOG.trace(" Access-Control-Allow-Headers={}", allowHeaders);
LOG.trace(" Access-Control-Max-Age={}", maxAge);
LOG.trace("]");
}
response.setHeader("Access-Control-Allow-Origin", allowOrigin);
response.setHeader("Access-Control-Allow-Methods", allowMethods);
response.setHeader("Access-Control-Allow-Headers", allowHeaders);
response.setHeader("Access-Control-Max-Age", maxAge);
}
}