/* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * Licensed 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/LICENSE2.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 com.sebastian_daschner.jaxrs_analyzer.backend.swagger; import com.sebastian_daschner.jaxrs_analyzer.model.rest.TypeIdentifier; import com.sebastian_daschner.jaxrs_analyzer.model.rest.TypeRepresentation; import com.sebastian_daschner.jaxrs_analyzer.model.rest.TypeRepresentationVisitor; import com.sebastian_daschner.jaxrs_analyzer.utils.Pair; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObject; import javax.json.JsonObjectBuilder; import java.util.HashMap; import java.util.Map; import static com.sebastian_daschner.jaxrs_analyzer.backend.ComparatorUtils.mapKeyComparator; import static com.sebastian_daschner.jaxrs_analyzer.model.Types.*; /** * Creates Swagger schema type definitions. * * @author Sebastian Daschner */ class SchemaBuilder { /** * The fully-qualified class name together with the JSON definitions identified by the definition names. */ private final Map<String, Pair<String, JsonObject>> jsonDefinitions = new HashMap<>(); /** * All known representation defined in the REST resources */ private final Map<TypeIdentifier, TypeRepresentation> typeRepresentations; SchemaBuilder(final Map<TypeIdentifier, TypeRepresentation> typeRepresentations) { this.typeRepresentations = typeRepresentations; } /** * Creates the schema object builder for the identifier. * The actual definitions are retrieved via {@link SchemaBuilder#getDefinitions} after all types have been declared. * * @param identifier The identifier * @return The schema JSON object builder with the needed properties */ JsonObjectBuilder build(final TypeIdentifier identifier) { final SwaggerType type = toSwaggerType(identifier.getType()); switch (type) { case BOOLEAN: case INTEGER: case NUMBER: case NULL: case STRING: final JsonObjectBuilder builder = Json.createObjectBuilder(); addPrimitive(builder, type); return builder; } final JsonObjectBuilder builder = Json.createObjectBuilder(); final TypeRepresentationVisitor visitor = new TypeRepresentationVisitor() { private boolean inCollection = false; @Override public void visit(final TypeRepresentation.ConcreteTypeRepresentation representation) { final JsonObjectBuilder nestedBuilder = inCollection ? Json.createObjectBuilder() : builder; add(nestedBuilder, representation); if (inCollection) { builder.add("items", nestedBuilder.build()); } } @Override public void visit(final TypeRepresentation.CollectionTypeRepresentation representation) { builder.add("type", "array"); inCollection = true; } @Override public void visit(final TypeRepresentation.EnumTypeRepresentation representation) { builder.add("type", "string"); if (!representation.getEnumValues().isEmpty()) { final JsonArrayBuilder array = representation.getEnumValues().stream().sorted().collect(Json::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::add); builder.add("enum", array); } } }; final TypeRepresentation representation = typeRepresentations.get(identifier); if (representation == null) builder.add("type", "object"); else representation.accept(visitor); return builder; } /** * Returns the stored schema definitions. This has to be called after all calls of {@link SchemaBuilder#build(TypeIdentifier)}. * * @return The schema JSON definitions */ JsonObject getDefinitions() { final JsonObjectBuilder builder = Json.createObjectBuilder(); jsonDefinitions.entrySet().stream().sorted(mapKeyComparator()).forEach(e -> builder.add(e.getKey(), e.getValue().getRight())); return builder.build(); } private void add(final JsonObjectBuilder builder, final TypeRepresentation.ConcreteTypeRepresentation representation) { final SwaggerType type = toSwaggerType(representation.getIdentifier().getType()); switch (type) { case BOOLEAN: case INTEGER: case NUMBER: case NULL: case STRING: addPrimitive(builder, type); return; } addObject(builder, representation.getIdentifier(), representation.getProperties()); } private void addObject(final JsonObjectBuilder builder, final TypeIdentifier identifier, final Map<String, TypeIdentifier> properties) { final String definition = buildDefinition(identifier.getName()); if (jsonDefinitions.containsKey(definition)) { builder.add("$ref", "#/definitions/" + definition); return; } // reserve definition jsonDefinitions.put(definition, Pair.of(identifier.getName(), Json.createObjectBuilder().build())); final JsonObjectBuilder nestedBuilder = Json.createObjectBuilder(); properties.entrySet().stream().sorted(mapKeyComparator()).forEach(e -> nestedBuilder.add(e.getKey(), build(e.getValue()))); jsonDefinitions.put(definition, Pair.of(identifier.getName(), Json.createObjectBuilder().add("properties", nestedBuilder).build())); builder.add("$ref", "#/definitions/" + definition); } private void addPrimitive(final JsonObjectBuilder builder, final SwaggerType type) { builder.add("type", type.toString()); } private String buildDefinition(final String typeName) { final String definition = typeName.startsWith(TypeIdentifier.DYNAMIC_TYPE_PREFIX) ? "JsonObject" : typeName.substring(typeName.lastIndexOf('/') + 1, typeName.length() - 1); final Pair<String, JsonObject> containedEntry = jsonDefinitions.get(definition); if (containedEntry == null || containedEntry.getLeft() != null && containedEntry.getLeft().equals(typeName)) return definition; if (!definition.matches("_\\d+$")) return definition + "_2"; final int separatorIndex = definition.lastIndexOf('_'); final int index = Integer.parseInt(definition.substring(separatorIndex + 1)); return definition.substring(0, separatorIndex + 1) + (index + 1); } /** * Converts the given Java type to the Swagger JSON type. * * @param type The Java type definition * @return The Swagger type */ private static SwaggerType toSwaggerType(final String type) { if (INTEGER_TYPES.contains(type)) return SwaggerType.INTEGER; if (DOUBLE_TYPES.contains(type)) return SwaggerType.NUMBER; if (BOOLEAN.equals(type) || PRIMITIVE_BOOLEAN.equals(type)) return SwaggerType.BOOLEAN; if (STRING.equals(type)) return SwaggerType.STRING; return SwaggerType.OBJECT; } /** * Represents the different Swagger types. * * @author Sebastian Daschner */ private enum SwaggerType { ARRAY, BOOLEAN, INTEGER, NULL, NUMBER, OBJECT, STRING; @Override public String toString() { return super.toString().toLowerCase(); } } }