/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can * select the license that you prefer but you may not use this file except in * compliance with one of these Licenses. * * You can obtain a copy of the Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * See the Licenses for the specific language governing permissions and * limitations under the Licenses. * * Alternatively, you can obtain a royalty free commercial license with less * limitations, transferable or non-transferable, directly at * http://restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet S.A.S. */ package org.restlet.ext.apispark.internal.conversion.swagger.v2_0; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.restlet.data.ChallengeScheme; import org.restlet.engine.util.StringUtils; import org.restlet.ext.apispark.internal.introspection.util.Types; import org.restlet.ext.apispark.internal.model.Definition; import org.restlet.ext.apispark.internal.model.Endpoint; import org.restlet.ext.apispark.internal.model.Header; import org.restlet.ext.apispark.internal.model.Operation; import org.restlet.ext.apispark.internal.model.PathVariable; import org.restlet.ext.apispark.internal.model.PayLoad; import org.restlet.ext.apispark.internal.model.Property; import org.restlet.ext.apispark.internal.model.QueryParameter; import org.restlet.ext.apispark.internal.model.Representation; import org.restlet.ext.apispark.internal.model.Resource; import org.restlet.ext.apispark.internal.model.Response; import org.restlet.ext.apispark.internal.utils.SampleUtils; import com.wordnik.swagger.models.ArrayModel; import com.wordnik.swagger.models.Contact; import com.wordnik.swagger.models.Info; import com.wordnik.swagger.models.License; import com.wordnik.swagger.models.ModelImpl; import com.wordnik.swagger.models.Path; import com.wordnik.swagger.models.RefModel; import com.wordnik.swagger.models.Scheme; import com.wordnik.swagger.models.Swagger; import com.wordnik.swagger.models.auth.BasicAuthDefinition; import com.wordnik.swagger.models.auth.SecuritySchemeDefinition; import com.wordnik.swagger.models.parameters.BodyParameter; import com.wordnik.swagger.models.parameters.HeaderParameter; import com.wordnik.swagger.models.parameters.PathParameter; import com.wordnik.swagger.models.properties.AbstractNumericProperty; import com.wordnik.swagger.models.properties.AbstractProperty; import com.wordnik.swagger.models.properties.ArrayProperty; import com.wordnik.swagger.models.properties.BooleanProperty; import com.wordnik.swagger.models.properties.DateProperty; import com.wordnik.swagger.models.properties.DoubleProperty; import com.wordnik.swagger.models.properties.FileProperty; import com.wordnik.swagger.models.properties.FloatProperty; import com.wordnik.swagger.models.properties.IntegerProperty; import com.wordnik.swagger.models.properties.LongProperty; import com.wordnik.swagger.models.properties.RefProperty; import com.wordnik.swagger.models.properties.StringProperty; /** * Translator : RWADEF <-> Swagger 2.0. */ public class Swagger2Translator { // TODO wait for Swagger class private static class ByteProperty extends AbstractProperty { private ByteProperty() { setType("string"); setFormat("byte"); } } // TODO wait for Swagger class private static class ShortProperty extends AbstractProperty { private ShortProperty() { setType("integer"); setFormat("int32"); // int16 not supported } } /** Internal logger. */ protected static Logger LOGGER = Logger.getLogger(Swagger2Translator.class .getName()); public static final String SWAGGER_VERSION = "2.0"; /** * Fill Swagger "SecuritySchemeDefinition" objects from RWADef definition * * @param definition * RWADef definition * @param swagger * Swagger definition */ private static void fillAuthentication(Definition definition, Swagger swagger) { Map<String, SecuritySchemeDefinition> securitySchemes = new HashMap<>(); // Supported schemes String httpBasic = ChallengeScheme.HTTP_BASIC.getName(); for (Endpoint endpoint : definition.getEndpoints()) { if (httpBasic.equals(endpoint.getAuthenticationProtocol())) { securitySchemes.put(httpBasic, new BasicAuthDefinition()); } } swagger.setSecurityDefinitions(securitySchemes.isEmpty() ? null : securitySchemes); } /** * Fill Swagger "Definitions" objects from RWADef definition * * @param definition * RWADef definition * @param swagger * Swagger definition */ private static void fillDefinitions(Definition definition, Swagger swagger) { for (Representation representation : definition.getContract() .getRepresentations()) { if (representation.isRaw() || Types.isPrimitiveType(representation.getName())) { continue; } if (StringUtils.isNullOrEmpty(representation.getName())) { LOGGER.warning("A representation should have an identifier:" + representation.getName()); continue; } /* Representation -> Model */ ModelImpl modelSwagger = new ModelImpl(); fillModel(representation.getName(), representation.getDescription(), representation.getProperties(), swagger, modelSwagger); } } /** * Fill Swagger "Info" object from RWADef definition * * @param definition * RWADef definition * @param swagger * Swagger definition */ private static void fillInfo(Definition definition, Swagger swagger) { Info infoSwagger = new Info(); infoSwagger.setTitle(definition.getContract().getName()); // required infoSwagger.setDescription(definition.getContract().getDescription()); infoSwagger.setVersion(definition.getVersion()); // required Contact contactSwagger = new Contact(); if (definition.getContact() != null) { contactSwagger.setName(definition.getContact().getName()); contactSwagger.setEmail(definition.getContact().getEmail()); contactSwagger.setUrl(definition.getContact().getUrl()); } infoSwagger.setContact(contactSwagger); License licenseSwagger = new License(); if (definition.getLicense() != null) { if (!StringUtils.isNullOrEmpty(definition.getLicense().getName())) { org.restlet.ext.apispark.internal.model.License license = definition .getLicense(); licenseSwagger.setName(license.getName()); // required licenseSwagger.setUrl(license.getUrl()); infoSwagger.setLicense(licenseSwagger); } else if (!StringUtils.isNullOrEmpty(definition.getLicense() .getUrl())) { LOGGER.warning("You must specify a license name"); } } swagger.setInfo(infoSwagger); // required } /** * Fills Swagger main attributes from Restlet Web API definition * * @param definition * The Restlet Web API definition * @param swagger * The Swagger 2.0 definition */ private static void fillMainAttributes(Definition definition, Swagger swagger) { // basePath if (definition.getEndpoints() != null && !definition.getEndpoints().isEmpty()) { Endpoint endpoint = definition.getEndpoints().get(0); swagger.setHost(endpoint.getDomain() + (endpoint.getPort() == null ? "" : (":" + endpoint .getPort()))); swagger.setBasePath(endpoint.getBasePath()); // Should be any of "http", "https", "ws", "wss" swagger.setSchemes(Arrays.asList(Scheme.forValue(endpoint .getProtocol()))); } } /** * Fill Swagger "Model" objects from RWADef. * * @param name * The name of the Swagger model. * @param description * the description of the Swagger model. * @param properties * The list of RWADef properties of the Swagger model. * @param swagger * The Swagger definition. * @param modelSwagger * The Swagger model. */ private static void fillModel(String name, String description, List<Property> properties, Swagger swagger, ModelImpl modelSwagger) { modelSwagger.setName(name); modelSwagger.setDescription(description); /* Property -> Property */ for (Property property : properties) { com.wordnik.swagger.models.properties.Property propertySwagger; Object exampleObject = SampleUtils.getPropertyExampleValue(property); String example = exampleObject == null ? null : exampleObject .toString(); // property type if (property.getMaxOccurs() != null && (property.getMaxOccurs() > 1 || property.getMaxOccurs() == -1)) { ArrayProperty arrayProperty = new ArrayProperty(); com.wordnik.swagger.models.properties.Property itemProperty; if (Types.isCompositeType(property.getType())) { String compositePropertyType = name + StringUtils.firstUpper(property.getName()); itemProperty = newPropertyForType(compositePropertyType); // List of properties -> Model */ ModelImpl ms = new ModelImpl(); fillModel( compositePropertyType, null, property.getProperties(), swagger, ms); } else { itemProperty = newPropertyForType(property.getType()); } itemProperty.setExample(example); arrayProperty.setItems(itemProperty); propertySwagger = arrayProperty; } else { if (Types.isCompositeType(property.getType())) { String compositePropertyType = name + StringUtils.firstUpper(property.getName()); propertySwagger = newPropertyForType(compositePropertyType); // List of properties -> Model */ ModelImpl ms = new ModelImpl(); fillModel( compositePropertyType, null, property.getProperties(), swagger, ms); propertySwagger.setExample(example); } else { propertySwagger = newPropertyForType(property.getType()); propertySwagger.setExample(example); } } propertySwagger.setName(property.getName()); propertySwagger.setDescription(property.getDescription()); // min and max if (propertySwagger instanceof AbstractNumericProperty) { AbstractNumericProperty abstractNumericProperty = (AbstractNumericProperty) propertySwagger; try { if (property.getMin() != null) { abstractNumericProperty.setMinimum(Double .valueOf(property.getMin())); } } catch (NumberFormatException e) { LOGGER.warning("Min property is not a number: " + property.getMin()); } try { if (property.getMax() != null) { abstractNumericProperty.setMaximum(Double .valueOf(property.getMax())); } } catch (NumberFormatException e) { LOGGER.warning("Max property is not a number: " + property.getMax()); } } modelSwagger.property(property.getName(), propertySwagger); } swagger.addDefinition(modelSwagger.getName(), modelSwagger); } private static void fillOperationParameters(Definition definition, Resource resource, Operation operation, com.wordnik.swagger.models.Operation operationSwagger) { // Path parameters for (PathVariable pathVariable : resource.getPathVariables()) { PathParameter pathParameterSwagger = new PathParameter(); SwaggerTypeFormat swaggerTypeFormat = SwaggerTypes .toSwaggerType(pathVariable.getType()); pathParameterSwagger.setType(swaggerTypeFormat.getType()); // required pathParameterSwagger.setFormat(swaggerTypeFormat.getFormat()); pathParameterSwagger.setName(pathVariable.getName()); // required pathParameterSwagger.setDescription(pathVariable.getDescription()); // TODO: add when implemented // pathParameterSwagger.setDefaultValue(pathVariable.getDefaultValue()); operationSwagger.addParameter(pathParameterSwagger); } // Body if (operation.getInputPayLoad() != null) { BodyParameter bodyParameterSwagger = new BodyParameter(); bodyParameterSwagger.setName("body"); PayLoad inRepr = operation.getInputPayLoad(); Representation representation = definition.getContract() .getRepresentation(inRepr.getType()); if (representation != null && representation.isRaw()) { ModelImpl modelImpl = new ModelImpl(); modelImpl.setType(representation.getName()); modelImpl.setDescription(representation.getDescription()); bodyParameterSwagger.setSchema(modelImpl); } else { if (inRepr.isArray()) { ArrayModel arrayModel = new ArrayModel(); arrayModel.setType("array"); // primitive or ref type arrayModel.setItems(newPropertyForType(inRepr.getType())); bodyParameterSwagger.setSchema(arrayModel); } else { if (Types.isPrimitiveType(inRepr.getType())) { ModelImpl modelImpl = new ModelImpl(); modelImpl.setType(inRepr.getType()); bodyParameterSwagger.setSchema(modelImpl); } else { RefModel refModel = new RefModel(); refModel.asDefault(inRepr.getType()); bodyParameterSwagger.setSchema(refModel); } } } operationSwagger.addParameter(bodyParameterSwagger); } // query parameters for (QueryParameter queryParameter : operation.getQueryParameters()) { com.wordnik.swagger.models.parameters.QueryParameter queryParameterSwagger = new com.wordnik.swagger.models.parameters.QueryParameter(); queryParameterSwagger.setRequired(queryParameter.isRequired()); queryParameterSwagger.setDefaultValue(queryParameter .getDefaultValue()); if (queryParameter.isAllowMultiple()) { queryParameterSwagger.setType("array"); queryParameterSwagger .setItems(newPropertyForType(queryParameter.getType())); // do not set "csv" as it's the default format // queryParameterSwagger.setCollectionFormat("csv"); } else { queryParameterSwagger.setType(SwaggerTypes.toSwaggerType( queryParameter.getType()).getType()); queryParameterSwagger.setFormat(SwaggerTypes.toSwaggerType( queryParameter.getType()).getFormat()); } queryParameterSwagger.setName(queryParameter.getName()); queryParameterSwagger.setDescription(queryParameter .getDescription()); operationSwagger.addParameter(queryParameterSwagger); } for (Header header : operation.getHeaders()) { HeaderParameter headerParameterSwagger = new HeaderParameter(); headerParameterSwagger.setRequired(header.isRequired()); headerParameterSwagger.setDefaultValue(header.getDefaultValue()); headerParameterSwagger.setType(SwaggerTypes.toSwaggerType( header.getType()).getType()); headerParameterSwagger.setFormat(SwaggerTypes.toSwaggerType( header.getType()).getFormat()); headerParameterSwagger.setName(header.getName()); headerParameterSwagger.setDescription(header.getDescription()); operationSwagger.addParameter(headerParameterSwagger); } } private static void fillOperationResponses(Definition definition, Operation operation, com.wordnik.swagger.models.Operation operationSwagger) { for (Response response : operation.getResponses()) { /* Response -> Response */ com.wordnik.swagger.models.Response responseSwagger = new com.wordnik.swagger.models.Response(); // may be null String description = response.getDescription(); responseSwagger.setDescription((description != null) ? description : response.getCode() + " status response"); // required // Response Schema if (response.getOutputPayLoad() != null && response.getOutputPayLoad().getType() != null) { PayLoad entity = response.getOutputPayLoad(); final Representation representation = definition.getContract() .getRepresentation(entity.getType()); if (representation != null && representation.isRaw()) { FileProperty fileProperty = new FileProperty(); fileProperty .setDescription(representation.getDescription()); responseSwagger.setSchema(fileProperty); } else if (entity.isArray()) { ArrayProperty arrayProperty = new ArrayProperty(); arrayProperty .setItems(newPropertyForType(entity.getType())); responseSwagger.setSchema(arrayProperty); } else { responseSwagger.setSchema(newPropertyForType(entity .getType())); } } operationSwagger.addResponse(String.valueOf(response.getCode()), responseSwagger); } // TODO check that at least one success code is present } /** * Fill Swagger "Paths.Operations" objects from RWADef definition * * @param definition * RWADef definition * @param resource * RWADef.resource definition * @param pathSwagger * Swagger.path definition */ private static void fillPathOperations(Definition definition, Resource resource, Path pathSwagger) { for (Operation operation : resource.getOperations()) { com.wordnik.swagger.models.Operation operationSwagger = new com.wordnik.swagger.models.Operation(); operationSwagger.setTags(new ArrayList<String>()); operationSwagger.getTags().addAll(resource.getSections()); String method = operation.getMethod().toLowerCase(); Path setResult = pathSwagger.set(method, operationSwagger); if (setResult == null) { LOGGER.warning("Method not supported:" + method); return; } String description = operation.getDescription(); if (description != null) { operationSwagger .setSummary(description.length() > 120 ? description .substring(0, 120) : description); } operationSwagger.setDescription(description); operationSwagger.setOperationId(operation.getName()); operationSwagger.setConsumes(operation.getConsumes()); operationSwagger.setProduces(operation.getProduces()); // TODO add security // operationSwagger.setSecurity(); fillOperationParameters(definition, resource, operation, operationSwagger); fillOperationResponses(definition, operation, operationSwagger); } } /** * Fill Swagger "Paths" objects from RWADef definition * * @param definition * RWADef definition * @param swagger * Swagger definition */ private static void fillPaths(Definition definition, Swagger swagger) { Map<String, Path> paths = new LinkedHashMap<>(); for (Resource resource : definition.getContract().getResources()) { Path pathSwagger = new Path(); fillPathOperations(definition, resource, pathSwagger); paths.put(resource.getResourcePath(), pathSwagger); } swagger.setPaths(paths); } /** * Translates a Restlet Web API Definition to a Swagger definition * * @param definition * The Restlet Web API definition * @return Swagger The translated Swagger 2.0 definition */ public static Swagger getSwagger(Definition definition) { // conversion Swagger swagger = new Swagger(); swagger.setSwagger(SWAGGER_VERSION); // required // fill Swagger main attributes fillMainAttributes(definition, swagger); // fill authentication information fillAuthentication(definition, swagger); // fill Swagger.info fillInfo(definition, swagger); // required // fill Swagger.paths fillPaths(definition, swagger); // required // fill Swagger.definitions fillDefinitions(definition, swagger); // TODO add authorization attribute return swagger; } /** * Get new property for Swagger 2.0 for the primitive type of Rwadef. * * @param type * Type Rwadef * @return Type Swagger */ private static com.wordnik.swagger.models.properties.Property newPropertyForType( String type) { switch (type.toLowerCase()) { case "string": return new StringProperty(); case "byte": return new ByteProperty(); case "short": return new ShortProperty(); case "integer": return new IntegerProperty(); case "long": return new LongProperty(); case "float": return new FloatProperty(); case "double": return new DoubleProperty(); case "date": return new DateProperty(); case "boolean": return new BooleanProperty(); } // Reference to a representation return new RefProperty().asDefault(type); } }