/* * Copyright 2015 herd contributors * * 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/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.finra.herd.swaggergen; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlEnum; import javax.xml.bind.annotation.XmlType; import javax.xml.datatype.XMLGregorianCalendar; import io.swagger.models.ModelImpl; import io.swagger.models.Swagger; import io.swagger.models.properties.ArrayProperty; import io.swagger.models.properties.BooleanProperty; import io.swagger.models.properties.DateTimeProperty; import io.swagger.models.properties.DecimalProperty; import io.swagger.models.properties.IntegerProperty; import io.swagger.models.properties.LongProperty; import io.swagger.models.properties.Property; import io.swagger.models.properties.RefProperty; import io.swagger.models.properties.StringProperty; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; /** * Generates Swagger definitions. */ public class DefinitionGenerator { // The log to use for logging purposes. @SuppressWarnings("PMD.ProperLogger") // Logger is passed into this method from Mojo base class. private Log log; // The Swagger metadata. private Swagger swagger; // The classes that we will create examples for. private Set<String> exampleClassNames; // The model classes. private Set<Class<?>> modelClasses; // The XSD parser private XsdParser xsdParser; /** * Instantiates a Swagger definition generator which generates the definitions based on the specified parameters. * * @param log the log. * @param swagger the Swagger metadata. * @param exampleClassNames the example class names. * @param modelClasses the model classes. * @param xsdParser the XSD parser * * @throws MojoExecutionException if any problems were encountered. */ public DefinitionGenerator(Log log, Swagger swagger, Set<String> exampleClassNames, Set<Class<?>> modelClasses, XsdParser xsdParser) throws MojoExecutionException { this.log = log; this.swagger = swagger; this.exampleClassNames = exampleClassNames; this.modelClasses = modelClasses; this.xsdParser = xsdParser; generateDefinitions(); } /** * Generates definitions for the set of model classes. * * @throws MojoExecutionException if any errors were encountered. */ private void generateDefinitions() throws MojoExecutionException { for (Class<?> clazz : modelClasses) { processDefinitionClass(clazz); } } /** * Processes a model class which can be converted into a Swagger definition. A model class must be a JAXB XmlType. This method may be called recursively. * * @param clazz the class to process. * * @throws MojoExecutionException if the class isn't an XmlType. */ private void processDefinitionClass(Class<?> clazz) throws MojoExecutionException { log.debug("Processing model class \"" + clazz.getName() + "\""); XmlType xmlType = clazz.getAnnotation(XmlType.class); if (xmlType == null) { log.debug("Model class \"" + clazz.getName() + "\" is not an XmlType so it will be skipped."); } else { String name = xmlType.name(); if (!swagger.getDefinitions().containsKey(name)) { ModelImpl model = new ModelImpl(); if (exampleClassNames.contains(clazz.getSimpleName())) { // Only provide examples for root elements. If we do them for child elements, the JSON examples use the XML examples which is a problem. model.setExample(new ExampleXmlGenerator(log, clazz).getExampleXml()); } swagger.addDefinition(name, model); model.name(name); if (xsdParser != null) { model.setDescription(xsdParser.getAnnotation(name)); } for (Field field : clazz.getDeclaredFields()) { processField(field, model); } } } } /** * Processes a Field of a model class which can be converted into a Swagger definition property. The property is added into the given model. This method may * be called recursively. * * @param field the field to process. * @param model model the model. * * @throws MojoExecutionException if any problems were encountered. */ private void processField(Field field, ModelImpl model) throws MojoExecutionException { log.debug("Processing field \"" + field.getName() + "\"."); if (!Modifier.isStatic(field.getModifiers())) { Property property; Class<?> fieldClass = field.getType(); if (Collection.class.isAssignableFrom(fieldClass)) { property = new ArrayProperty(getPropertyFromType(FieldUtils.getCollectionType(field))); } else { property = getPropertyFromType(fieldClass); } // Set the required field based on the XmlElement that comes from the XSD. XmlElement xmlElement = field.getAnnotation(XmlElement.class); if (xmlElement != null) { property.setRequired(xmlElement.required()); } if (xsdParser != null) { property.setDescription(xsdParser.getAnnotation(model.getName(), field.getName())); } // Set the property on model. model.property(field.getName(), property); } } /** * Gets a property from the given fieldType. This method may be called recursively. * * @param fieldType the field type class. * * @return the property. * @throws MojoExecutionException if any problems were encountered. */ private Property getPropertyFromType(Class<?> fieldType) throws MojoExecutionException { Property property; if (String.class.isAssignableFrom(fieldType)) { property = new StringProperty(); } else if (Integer.class.isAssignableFrom(fieldType) || int.class.isAssignableFrom(fieldType)) { property = new IntegerProperty(); } else if( Long.class.isAssignableFrom(fieldType) || long.class.isAssignableFrom(fieldType)) { property = new LongProperty(); } else if (BigDecimal.class.isAssignableFrom(fieldType)) { property = new DecimalProperty(); } else if (XMLGregorianCalendar.class.isAssignableFrom(fieldType)) { property = new DateTimeProperty(); } else if (Boolean.class.isAssignableFrom(fieldType) || boolean.class.isAssignableFrom(fieldType)) { property = new BooleanProperty(); } else if (Collection.class.isAssignableFrom(fieldType)) { property = new ArrayProperty(new StringProperty()); } else if (fieldType.getAnnotation(XmlEnum.class) != null) { /* * Enums are a string property which have enum constants */ List<String> enums = new ArrayList<>(); for (Enum<?> anEnum : (Enum<?>[]) fieldType.getEnumConstants()) { enums.add(anEnum.name()); } property = new StringProperty()._enum(enums); } /* * Recursively process complex objects which is a XmlType */ else if (fieldType.getAnnotation(XmlType.class) != null) { processDefinitionClass(fieldType); property = new RefProperty(fieldType.getAnnotation(XmlType.class).name()); } else { // Default to a string property in other cases. property = new StringProperty(); } log.debug("Field type \"" + fieldType.getName() + "\" is a property type \"" + property.getType() + "\"."); return property; } }