/** * 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.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.raml.model.ActionType; import org.raml.model.ParamType; import org.raml.model.Raml; import org.raml.model.Resource; import org.raml.parser.rule.ValidationResult; import org.raml.parser.visitor.RamlValidationService; import org.restlet.engine.util.StringUtils; import org.restlet.ext.apispark.internal.conversion.TranslationException; import org.restlet.ext.apispark.internal.introspection.util.Types; import org.restlet.ext.apispark.internal.model.Property; import org.restlet.ext.apispark.internal.model.Representation; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.module.jsonSchema.JsonSchema; import com.fasterxml.jackson.module.jsonSchema.types.ArraySchema; import com.fasterxml.jackson.module.jsonSchema.types.BooleanSchema; import com.fasterxml.jackson.module.jsonSchema.types.IntegerSchema; import com.fasterxml.jackson.module.jsonSchema.types.NumberSchema; import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema; import com.fasterxml.jackson.module.jsonSchema.types.SimpleTypeSchema; import com.fasterxml.jackson.module.jsonSchema.types.StringSchema; /** * Utility class for RAML java beans. * * @author Cyprien Quilici */ public class RamlUtils { /** * The list of java types that correspond to RAML's integer type. */ private static final List<String> integerTypesList = Arrays.asList( "integer", "int"); /** * The list of java types that correspond to RAML's number type. */ private static final List<String> numericTypesList = Arrays.asList( "integer", "int", "double", "long", "float"); /** * Returns the String passed as a parameter with a capital first letter. * Used to generate resource names in camel case * * @param str * The string to process * @return The String with a capital first letter */ public static String capFirst(String str) { if (str == null || str.isEmpty()) { return str; } return ("" + str.charAt(0)).toUpperCase() + str.substring(1); } /** * Generates the JsonSchema of a Representation's Property of primitive * type. * * @param property * The Property from which the JsonSchema is generated. * @return The JsonSchema of the given Property. */ protected static SimpleTypeSchema generatePrimitiveSchema(Property property) { SimpleTypeSchema result = null; String name = property.getName(); String type = (property.getType() != null) ? property.getType() .toLowerCase() : null; if (RamlUtils.integerTypesList.contains(type)) { IntegerSchema integerSchema = new IntegerSchema(); integerSchema.setTitle(name); if (property.getMin() != null) { integerSchema.setMinimum(Double.parseDouble(property.getMin())); } if (property.getMax() != null) { integerSchema.setMaximum(Double.parseDouble(property.getMax())); } result = integerSchema; } else if (RamlUtils.numericTypesList.contains(type)) { NumberSchema numberSchema = new NumberSchema(); numberSchema.setTitle(name); if (property.getMin() != null) { numberSchema.setMinimum(Double.parseDouble(property.getMin())); } if (property.getMax() != null) { numberSchema.setMaximum(Double.parseDouble(property.getMax())); } result = numberSchema; } else if ("boolean".equals(type)) { BooleanSchema booleanSchema = new BooleanSchema(); booleanSchema.setTitle(name); result = booleanSchema; } else if ("string".equals(type) || "date".equals(type)) { StringSchema stringSchema = new StringSchema(); stringSchema.setTitle(name); result = stringSchema; } return result; } /** * Generates the JsonSchema of a Representation. * * @param representation * The representation. * @param schemas * @param m * @throws JsonProcessingException */ public static void fillSchemas(Representation representation, Map<String, String> schemas, ObjectMapper m) throws JsonProcessingException { fillSchemas(representation.getName(), representation.getDescription(), representation.isRaw(), representation.getExtendedType(), representation.getProperties(), schemas, m); } public static void fillSchemas(String name, String description, boolean isRaw, String extendedType, List<Property> properties, Map<String, String> schemas, ObjectMapper m) throws JsonProcessingException { ObjectSchema objectSchema = new ObjectSchema(); objectSchema.setTitle(name); objectSchema.setDescription(description); if (!isRaw) { if (extendedType != null) { JsonSchema[] extended = new JsonSchema[1]; SimpleTypeSchema typeExtended = new ObjectSchema(); typeExtended.set$ref(extendedType); extended[0] = typeExtended; objectSchema.setExtends(extended); } objectSchema.setProperties(new HashMap<String, JsonSchema>()); for (Property property : properties) { String type = property.getType(); if (property.getMaxOccurs() != 1) { ArraySchema array = new ArraySchema(); array.setTitle(property.getName()); array.setRequired(property.getMinOccurs() > 0); array.setUniqueItems(property.isUniqueItems()); if (isPrimitiveType(type)) { Property prop = new Property(); prop.setName(property.getName()); prop.setType(type); array.setItemsSchema(generatePrimitiveSchema(prop)); } else { if (Types.isCompositeType(type)) { type = name + StringUtils.firstUpper(property.getName()); // add the new schema fillSchemas(type, null, false, null, property.getProperties(), schemas, m); } SimpleTypeSchema reference = new ObjectSchema(); reference.set$ref("#/schemas/" + type); array.setItemsSchema(reference); // array.setItemsSchema(generateSchema(RamlTranslator // .getRepresentationByName(representations, // property.getType()), representations)); } objectSchema.getProperties().put(array.getTitle(), array); } else if (isPrimitiveType(type)) { SimpleTypeSchema primitive = generatePrimitiveSchema(property); primitive.setRequired(property.getMinOccurs() > 0); if (property.getDefaultValue() != null) { primitive.setDefault(property.getDefaultValue()); } objectSchema.getProperties().put(property.getName(), primitive); } else { if (Types.isCompositeType(type)) { type = name + StringUtils.firstUpper(property.getName()); // add the new schema fillSchemas(type, null, false, null, property.getProperties(), schemas, m); } SimpleTypeSchema propertySchema = new ObjectSchema(); propertySchema.setTitle(property.getName()); propertySchema.set$ref("#/schemas/" + type); propertySchema.setRequired(property.getMinOccurs() > 0); objectSchema.getProperties().put(propertySchema.getTitle(), propertySchema); } } } schemas.put(name, m.writeValueAsString(objectSchema)); } /** * Returns the RAML {@link org.raml.model.ActionType} given an HTTP method * name. * * @param method * The HTTP method name as String. * @return The corresponding {@link org.raml.model.ActionType}. */ public static ActionType getActionType(String method) { String m = (method != null) ? method.toLowerCase() : null; if ("post".equals(m)) { return ActionType.POST; } else if ("get".equals(m)) { return ActionType.GET; } else if ("put".equals(m)) { return ActionType.PUT; } else if ("patch".equals(m)) { return ActionType.PATCH; } else if ("delete".equals(m)) { return ActionType.DELETE; } else if ("head".equals(m)) { return ActionType.HEAD; } else if ("options".equals(m)) { return ActionType.OPTIONS; } else if ("trace".equals(m)) { return ActionType.TRACE; } return null; } /** * Returns the RAML parameter type given a java primitive type. * * @param type * The Java type. * @return The RAML parameter type. */ public static ParamType getParamType(String type) { String t = (type != null) ? type.toLowerCase() : null; if (integerTypesList.contains(t)) { return ParamType.INTEGER; } else if (numericTypesList.contains(t)) { return ParamType.NUMBER; } else if ("boolean".equals(t)) { return ParamType.BOOLEAN; } else if ("date".equals(t)) { return ParamType.DATE; } // TODO add files // else if () { // return ParamType.FILE; // } return ParamType.STRING; } /** * Gets the parent resource of a Resource given its path and the list of * paths available on the API. * * @param paths * The list of paths available on the API. * @param resourcePath * The path of the resource the parent resource is searched for. * @param raml * The RAML representing the API. * @return The parent resource. */ public static Resource getParentResource(List<String> paths, String resourcePath, Raml raml) { List<String> parentPaths = new ArrayList<String>(); parentPaths.addAll(paths); parentPaths.add(resourcePath); Collections.sort(parentPaths); int index = parentPaths.indexOf(resourcePath); if (index != 0) { String parentPath = parentPaths.get(index - 1); if (resourcePath.startsWith(parentPath)) { return getResourceByCompletePath(raml, parentPath); } } return null; } /** * Returns a RAML Resource given its complete path. * * @param raml * The RAML in which the Resource is searched for. * @param path * The complete path of the resource. * @return The Resource. */ private static Resource getResourceByCompletePath(Raml raml, String path) { for (Entry<String, Resource> entry : raml.getResources().entrySet()) { if (path.equals(entry.getValue().getParentUri() + entry.getValue().getRelativeUri())) { return entry.getValue(); } } return null; } /** * Indicates if the given type is a primitive type. * * @param type * The type to check. * @return True if the given type is primitive, false otherwise. */ public static boolean isPrimitiveType(String type) { String t = (type != null) ? type.toLowerCase() : null; return ("string".equals(t) || "int".equals(t) || "integer".equals(t) || "long".equals(t) || "float".equals(t) || "double".equals(t) || "date".equals(t) || "boolean".equals(t) || "bool".equals(t)); } /** * Generates a name for a resource computed from its path. The name is * composed of all alphanumeric characters in camel case.<br/> * Ex: /contacts/{contactId} => ContactsContactId * * @param uri * The URI of the Resource * @return The Resource's name computed from the path. */ public static String processResourceName(String uri) { String processedUri = ""; String[] split = uri.replaceAll("\\{", "").replaceAll("\\}", "") .split("/"); for (String str : split) { processedUri += RamlUtils.capFirst(str); } return processedUri; } /** * Returns the primitive type as RAML expects them. * * @param type * The Java primitive type. * @return The primitive type expected by RAML. */ public static String toRamlType(String type) { if ("Integer".equals(type)) { return "int"; } else if ("String".equals(type)) { return "string"; } else if ("Boolean".equals(type)) { return "boolean"; } return type; } /** * Indicates if the given RAML definition is valid according to RAML * specifications. * * @param location * The RAML definition. * @throws TranslationException */ public static List<ValidationResult> validate(String location) throws TranslationException { // TODO see if needed as it requires lots of dependencies. return RamlValidationService.createDefault().validate(location); } }