/*
* 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;
}
}