/**
* 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.raml;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.raml.model.Action;
import org.raml.model.ActionType;
import org.raml.model.MimeType;
import org.raml.model.Raml;
import org.raml.model.SecurityScheme;
import org.raml.model.parameter.UriParameter;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Status;
import org.restlet.ext.apispark.internal.conversion.TranslationException;
import org.restlet.ext.apispark.internal.introspection.util.Types;
import org.restlet.ext.apispark.internal.model.Contract;
import org.restlet.ext.apispark.internal.model.Definition;
import org.restlet.ext.apispark.internal.model.Endpoint;
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.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.jsonSchema.types.SimpleTypeSchema;
/**
* Tools library for converting Restlet Web API Definition to and from RAML
* documentation.
*
* @author Cyprien Quilici
*/
public abstract class RamlTranslator {
/** Internal logger. */
protected static Logger LOGGER = Logger.getLogger(RamlTranslator.class
.getName());
/**
* Returns the {@link org.restlet.ext.apispark.internal.model.PathVariable}
* as described by the given {@link org.raml.model.parameter.UriParameter}.
*
* @param paramName
* The name of the path variable.
* @param uriParameter
* The uri parameter.
* @return The {@link org.restlet.ext.apispark.internal.model.PathVariable}
* as described by the given
* {@link org.raml.model.parameter.UriParameter}.
*/
private static PathVariable getPathVariable(String paramName,
UriParameter uriParameter) {
PathVariable pathVariable = new PathVariable();
pathVariable.setName(paramName);
pathVariable.setDescription(uriParameter.getDescription());
// pathVariable.setType(uriParameter.getType().toString().toLowerCase());
return pathVariable;
}
/**
* Returns the list of
* {@link org.restlet.ext.apispark.internal.model.PathVariable} as defined
* by the given {@link org.raml.model.Resource}.
*
* @param resource
* The given resource.
* @return The list of
* {@link org.restlet.ext.apispark.internal.model.PathVariable} as
* defined by the given {@link org.raml.model.Resource}.
*/
private static List<PathVariable> getPathVariables(
org.raml.model.Resource resource) {
List<PathVariable> pathVariables = new ArrayList<PathVariable>();
for (Entry<String, UriParameter> entry : resource.getUriParameters()
.entrySet()) {
pathVariables
.add(getPathVariable(entry.getKey(), entry.getValue()));
}
if (resource.getParentResource() != null) {
pathVariables
.addAll(getPathVariables(resource.getParentResource()));
}
return pathVariables;
}
/**
* Builds a sample map for each Representation of the Contract
*
* @param contract
* The Restlet Web API Contract
* @return A map of representations' names and sample maps
*/
private static Map<String, Map<String, Object>> getSamples(Contract contract) {
Map<String, Map<String, Object>> samples = new HashMap<>();
for (Representation representation : contract.getRepresentations()) {
samples.put(representation.getName(),
SampleUtils.getRepresentationSample(representation));
}
return samples;
}
/**
* Retrieves the RAML API declaration corresponding to a category of the
* given Restlet Web API Definition.
*
* @param definition
* The Restlet Web API Definition.
* @return The RAML API definition of the given category.
*/
public static Raml getRaml(Definition definition) {
Raml raml = new Raml();
ObjectMapper m = new ObjectMapper();
// TODO see how to translate it (1.0.0 to v1 ???)
if (definition.getVersion() != null) {
raml.setVersion(definition.getVersion());
}
Contract contract = definition.getContract();
Map<String, Map<String, Object>> representationSamples = getSamples(contract);
// No way to specify multiple endpoints in RAML so we take the first one
Endpoint endpoint = null;
if (!definition.getEndpoints().isEmpty()) {
endpoint = definition.getEndpoints().get(0);
raml.setBaseUri(endpoint.computeUrl());
} else {
raml.setBaseUri("http://example.com/v1");
}
// Authentication
raml.setSecuritySchemes(new ArrayList<Map<String, SecurityScheme>>());
fillSecuritySchemes(raml.getSecuritySchemes(), endpoint);
// raml.setBaseUriParameters(new HashMap<String, UriParameter>());
// raml.getBaseUriParameters().put("version", new
// UriParameter("version"));
raml.setTitle(contract.getName());
raml.setResources(new HashMap<String, org.raml.model.Resource>());
fillResources(raml.getResources(), m, contract, representationSamples);
// Representations
raml.setSchemas(new ArrayList<Map<String, String>>());
Map<String, String> schemas = new HashMap<String, String>();
raml.getSchemas().add(schemas);
for (Representation representation : contract.getRepresentations()) {
if (RamlUtils.isPrimitiveType(representation.getName())) {
continue;
}
try {
RamlUtils.fillSchemas(representation, schemas, m);
} catch (JsonProcessingException e) {
LOGGER.log(Level.WARNING,
"Error when putting mime type schema for representation: "
+ representation.getName(), e);
}
}
return raml;
}
private static void fillResources(
Map<String, org.raml.model.Resource> resources, ObjectMapper m,
Contract contract,
Map<String, Map<String, Object>> representationSamples) {
org.raml.model.Resource ramlResource;
List<String> paths = new ArrayList<String>();
// Resources
for (Resource resource : contract.getResources()) {
ramlResource = new org.raml.model.Resource();
if (resource.getName() != null) {
ramlResource.setDisplayName(resource.getName());
} else {
ramlResource.setDisplayName(RamlUtils
.processResourceName(resource.getResourcePath()));
}
ramlResource.setDescription(resource.getDescription());
ramlResource.setParentUri("");
ramlResource.setRelativeUri(resource.getResourcePath());
// Path variables
UriParameter uiParam = new UriParameter();
ramlResource.setUriParameters(new HashMap<String, UriParameter>());
for (PathVariable pathVariable : resource.getPathVariables()) {
uiParam.setDisplayName(pathVariable.getName());
uiParam.setDescription(pathVariable.getDescription());
uiParam.setType(RamlUtils.getParamType(pathVariable.getType()));
uiParam.setExample(pathVariable.getExample());
ramlResource.getUriParameters().put(pathVariable.getName(),
uiParam);
}
// Operations
Action action;
ramlResource.setActions(new HashMap<ActionType, Action>());
for (Operation operation : resource.getOperations()) {
action = new Action();
action.setDescription(operation.getDescription());
action.setResource(ramlResource);
// In representation
if (operation.getInputPayLoad() != null) {
MimeType ramlInRepresentation = new MimeType();
fillInputRepresentation(m, representationSamples, action,
operation, ramlInRepresentation);
}
// Query parameters
action.setQueryParameters(new HashMap<String, org.raml.model.parameter.QueryParameter>());
for (QueryParameter queryParameter : operation
.getQueryParameters()) {
org.raml.model.parameter.QueryParameter ramlQueryParameter = new org.raml.model.parameter.QueryParameter();
ramlQueryParameter.setDisplayName(queryParameter.getName());
// ramlQueryParameter.setType(RamlUtils
// .getParamType(queryParameter.getType()));
ramlQueryParameter.setDescription(queryParameter
.getDescription());
ramlQueryParameter.setRequired(queryParameter.isRequired());
ramlQueryParameter.setExample(queryParameter.getExample());
// TODO when enumerations have been added in RWADef
// ramlQueryParameter.setEnumeration(queryParameter.getEnumeration());
ramlQueryParameter.setDefaultValue(queryParameter
.getDefaultValue());
ramlQueryParameter.setRepeat(queryParameter
.isAllowMultiple());
action.getQueryParameters().put(queryParameter.getName(),
ramlQueryParameter);
}
// Responses + out representation
MimeType ramlOutRepresentation;
org.raml.model.Response ramlResponse = new org.raml.model.Response();
action.setResponses(new HashMap<String, org.raml.model.Response>());
for (Response response : operation.getResponses()) {
ramlResponse = new org.raml.model.Response();
ramlResponse.setDescription(response.getDescription());
ramlResponse.setBody(new HashMap<String, MimeType>());
ramlOutRepresentation = new MimeType();
if (Status.isSuccess(response.getCode())
&& response.getOutputPayLoad() != null
&& response.getOutputPayLoad().getType() != null) {
if (RamlUtils.isPrimitiveType(response
.getOutputPayLoad().getType())) {
Property outRepresentationPrimitive = new Property();
outRepresentationPrimitive.setName("");
outRepresentationPrimitive.setType(response
.getOutputPayLoad().getType());
SimpleTypeSchema outRepresentationSchema = RamlUtils
.generatePrimitiveSchema(outRepresentationPrimitive);
try {
ramlOutRepresentation
.setSchema(m
.writeValueAsString(outRepresentationSchema));
} catch (JsonProcessingException e) {
LOGGER.log(Level.WARNING,
"Error when setting mime type schema.",
e);
}
} else {
ramlOutRepresentation.setSchema(response
.getOutputPayLoad().getType());
}
}
if (response.getOutputPayLoad() != null) {
MimeType ramlOutRepresentationWithMediaType;
for (String mediaType : operation.getProduces()) {
ramlOutRepresentationWithMediaType = new MimeType();
ramlOutRepresentationWithMediaType
.setSchema(ramlOutRepresentation
.getSchema());
try {
ramlOutRepresentationWithMediaType
.setExample(getExampleFromPayLoad(
response.getOutputPayLoad(),
representationSamples,
mediaType));
} catch (Exception e) {
LOGGER.log(Level.WARNING,
"Error when writting sample.", e);
}
ramlResponse.getBody().put(mediaType,
ramlOutRepresentationWithMediaType);
}
}
action.getResponses().put(
Integer.toString(response.getCode()), ramlResponse);
}
ramlResource.getActions().put(
RamlUtils.getActionType(operation.getMethod()), action);
}
paths.add(resource.getResourcePath());
resources.put(ramlResource.getRelativeUri(), ramlResource);
}
}
private static void fillInputRepresentation(ObjectMapper m,
Map<String, Map<String, Object>> representationSamples,
Action action, Operation operation, MimeType ramlInRepresentation) {
ramlInRepresentation.setType(operation.getInputPayLoad().getType());
if (RamlUtils.isPrimitiveType(operation.getInputPayLoad().getType())) {
Property inRepresentationPrimitive = new Property();
inRepresentationPrimitive.setName("");
inRepresentationPrimitive.setType(operation.getInputPayLoad()
.getType());
SimpleTypeSchema inRepresentationSchema = RamlUtils
.generatePrimitiveSchema(inRepresentationPrimitive);
try {
ramlInRepresentation.setSchema(m
.writeValueAsString(inRepresentationSchema));
} catch (JsonProcessingException e) {
LOGGER.log(Level.WARNING,
"Error when setting mime type schema.", e);
}
} else {
ramlInRepresentation.setSchema(operation.getInputPayLoad()
.getType());
}
action.setBody(new HashMap<String, MimeType>());
MimeType ramlInRepresentationWithMediaType;
for (String mediaType : operation.getConsumes()) {
ramlInRepresentationWithMediaType = new MimeType();
ramlInRepresentationWithMediaType.setSchema(ramlInRepresentation
.getSchema());
try {
ramlInRepresentationWithMediaType
.setExample(getExampleFromPayLoad(
operation.getInputPayLoad(),
representationSamples, mediaType));
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error when writting sample.", e);
}
action.getBody().put(mediaType, ramlInRepresentationWithMediaType);
}
}
private static void fillSecuritySchemes(
List<Map<String, SecurityScheme>> securitySchemesList,
Endpoint endpoint) {
Map<String, SecurityScheme> securitySchemes = new HashMap<String, SecurityScheme>();
SecurityScheme securityScheme = new SecurityScheme();
if (endpoint != null) {
if (ChallengeScheme.HTTP_BASIC.equals(endpoint
.getAuthenticationProtocol())) {
securityScheme.setType(ChallengeScheme.HTTP_BASIC.getName());
securitySchemes.put(ChallengeScheme.HTTP_BASIC.getName(),
securityScheme);
} else if (ChallengeScheme.HTTP_OAUTH.equals(endpoint
.getAuthenticationProtocol())
|| ChallengeScheme.HTTP_OAUTH_BEARER.equals(endpoint
.getAuthenticationProtocol())
|| ChallengeScheme.HTTP_OAUTH_MAC.equals(endpoint
.getAuthenticationProtocol())) {
securityScheme.setType("Oauth 2.0");
securitySchemes.put("oauth_2_0", securityScheme);
} else if (ChallengeScheme.HTTP_DIGEST.equals(endpoint
.getAuthenticationProtocol())) {
securityScheme.setType(ChallengeScheme.HTTP_DIGEST.getName());
securitySchemes.put(ChallengeScheme.HTTP_DIGEST.getName(),
securityScheme);
} else if (ChallengeScheme.CUSTOM.equals(endpoint
.getAuthenticationProtocol())) {
securityScheme.setType(ChallengeScheme.CUSTOM.getName());
securitySchemes.put(ChallengeScheme.CUSTOM.getName(),
securityScheme);
}
securitySchemesList.add(securitySchemes);
}
}
/**
* Returns the representation given its name from the given list of
* representations.
*
* @param representations
* The list of representations.
* @param name
* The name of the representation.
* @return A representation.
*/
public static Representation getRepresentationByName(
List<Representation> representations, String name) {
if (name != null) {
for (Representation repr : representations) {
if (name.equals(repr.getName())) {
return repr;
}
}
}
return null;
}
/**
* Returns the list of Resources nested under a given Resource.
*
* @param resourceName
* The name of the generated resource, extracted from its path.
* @param resource
* The RAML Resource from which the list is extracted.
* @param rootPathVariables
* The path variables contained in the base URI.
* @return The list of Resources nested under resource.
*/
private static List<Resource> getResource(String resourceName,
org.raml.model.Resource resource,
List<PathVariable> rootPathVariables) {
List<Resource> rwadResources = new ArrayList<Resource>();
// Create one resource
Resource rwadResource = new Resource();
rwadResource.setDescription(resource.getDescription());
rwadResource.setName(resourceName);
rwadResource.setResourcePath(resource.getUri());
// Path Variables
rwadResource.setPathVariables(getPathVariables(resource));
rwadResource.getPathVariables().addAll(rootPathVariables);
// Operations
for (Entry<ActionType, Action> entry : resource.getActions().entrySet()) {
Action action = entry.getValue();
Operation operation = new Operation();
operation.setDescription(action.getDescription());
operation.setMethod(entry.getKey().name().toString());
}
rwadResources.add(rwadResource);
// Nested resources
for (Entry<String, org.raml.model.Resource> entry : resource
.getResources().entrySet()) {
rwadResources.addAll(getResource(
RamlUtils.processResourceName(entry.getValue().getUri()),
entry.getValue(), rootPathVariables));
}
return rwadResources;
}
/**
* Translates a RAML documentation to a Restlet definition.
*
* @param raml
* The RAML resource listing.
* @return The Restlet definition.
* @throws TranslationException
*/
public static Definition translate(Raml raml) throws TranslationException {
Definition definition = new Definition();
if (raml.getVersion() != null) {
definition.setVersion(raml.getVersion().substring(1));
// def.setEndpoint(raml.getBaseUri().replace("{version}",
// raml.getVersion()));
} else {
// def.setEndpoint(raml.getBaseUri());
}
Contract contract = new Contract();
definition.setContract(contract);
contract.setName(raml.getTitle());
// TODO add section sorting strategies
// TODO String defaultMediaType = raml.getMediaType();
List<PathVariable> rootPathVariables = new ArrayList<PathVariable>();
for (Entry<String, UriParameter> entry : raml.getBaseUriParameters()
.entrySet()) {
rootPathVariables.add(getPathVariable(entry.getKey(),
entry.getValue()));
}
for (Map<String, String> schema : raml.getSchemas()) {
for (Entry<String, String> entry : schema.entrySet()) {
Representation representation = new Representation();
representation.setName(entry.getKey());
representation.setDescription(entry.getValue());
// TODO get the schema !!!
// TODO set representations's sections
// representation.getSections().add(section.getName());
contract.getRepresentations().add(representation);
}
}
// Resources
for (Entry<String, org.raml.model.Resource> entry : raml.getResources()
.entrySet()) {
org.raml.model.Resource resource = entry.getValue();
contract.getResources().addAll(
getResource(
RamlUtils.processResourceName(resource.getUri()),
resource, rootPathVariables));
}
return definition;
}
/**
* Returns an example in provided media Type of the entity in the given
* PayLoad.
*
* @param payLoad
* The PayLoad.
* @param representationSamples
* The map of samples by representations.
* @param mediaType
* The media type as String.
* @return An example in provided media Type of the entity in the given
* PayLoad.
*/
private static String getExampleFromPayLoad(PayLoad payLoad,
Map<String, Map<String, Object>> representationSamples,
String mediaType) {
Object sample = (Types.isPrimitiveType(payLoad.getType())) ?
SampleUtils.getPropertyDefaultExampleValue(payLoad.getType(), "value") :
representationSamples.get(payLoad.getType());
if (payLoad.isArray()) {
sample = Arrays.asList(sample);
}
return SampleUtils.convertSampleAccordingToMediaType(sample, mediaType, payLoad.getType());
}
/**
* Private constructor to ensure that the class acts as a true utility class
* i.e. it isn't instantiable and extensible.
*/
private RamlTranslator() {
}
}