/** * Copyright (c) 2009 - 2012 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package org.candlepin.swagger; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyName; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair; import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; import com.fasterxml.jackson.databind.jsontype.NamedType; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.xml.bind.annotation.XmlElement; import io.swagger.annotations.ApiModelProperty; import io.swagger.converter.ModelConverter; import io.swagger.converter.ModelConverterContext; import io.swagger.jackson.SwaggerAnnotationIntrospector; import io.swagger.jackson.TypeNameResolver; import io.swagger.models.Model; import io.swagger.models.properties.Property; /** * Class adopted from https://github.com/swagger-api. Converters are Swagger * extension points. * * @author fnguyen * */ public abstract class AbstractModelConverter implements ModelConverter { protected final ObjectMapper pMapper; protected final AnnotationIntrospector pIntr; protected final TypeNameResolver pTypeNameResolver = TypeNameResolver.std; /** * Minor optimization: no need to keep on resolving same types over and over * again. */ protected Map<JavaType, String> pResolvedTypeNames = new ConcurrentHashMap<JavaType, String>(); protected AbstractModelConverter(ObjectMapper mapper) { AnnotationIntrospector primary = new JacksonAnnotationIntrospector(); AnnotationIntrospector secondary = new JaxbAnnotationIntrospector(mapper.getTypeFactory()); AnnotationIntrospector pair = new AnnotationIntrospectorPair(primary, secondary); mapper.setAnnotationIntrospector(pair); mapper.registerModule(new SimpleModule("swagger", Version.unknownVersion()) { @Override public void setupModule(SetupContext context) { context.insertAnnotationIntrospector(new SwaggerAnnotationIntrospector()); } }); pMapper = mapper; pIntr = mapper.getSerializationConfig().getAnnotationIntrospector(); } protected static Comparator<Property> getPropertyComparator() { return new Comparator<Property>() { @Override public int compare(Property one, Property two) { if (one.getPosition() == null && two.getPosition() == null) { return 0; } if (one.getPosition() == null) { return -1; } if (two.getPosition() == null) { return 1; } return one.getPosition().compareTo(two.getPosition()); } }; } @Override public Property resolveProperty(Type type, ModelConverterContext context, Annotation[] annotations, Iterator<ModelConverter> chain) { if (chain.hasNext()) { return chain.next().resolveProperty(type, context, annotations, chain); } else { return null; } } protected String pDescription(Annotated ann) { // while name suggests it's only for properties, should work for any // Annotated thing. // also; with Swagger introspector's help, should get it from // ApiModel/ApiModelProperty return pIntr.findPropertyDescription(ann); } protected String pTypeName(JavaType type) { return pTypeName(type, null); } protected String pTypeName(JavaType type, BeanDescription beanDesc) { String name = pResolvedTypeNames.get(type); if (name != null) { return name; } name = pFindTypeName(type, beanDesc); pResolvedTypeNames.put(type, name); return name; } protected String pFindTypeName(JavaType type, BeanDescription beanDesc) { // First, handle container types; they require recursion if (type.isArrayType()) { return "Array"; } if (type.isMapLikeType()) { return "Map"; } if (type.isContainerType()) { if (Set.class.isAssignableFrom(type.getRawClass())) { return "Set"; } return "List"; } if (beanDesc == null) { beanDesc = pMapper.getSerializationConfig().introspectClassAnnotations(type); } PropertyName rootName = pIntr.findRootName(beanDesc.getClassInfo()); if (rootName != null && rootName.hasSimpleName()) { return rootName.getSimpleName(); } return pTypeNameResolver.nameForType(type); } protected String pTypeQName(JavaType type) { return type.getRawClass().getName(); } protected String pSubTypeName(NamedType type) { // !!! TODO: should this use 'name' instead? return type.getType().getName(); } protected String pFindDefaultValue(Annotated a) { XmlElement elem = a.getAnnotation(XmlElement.class); if (elem != null) { if (!elem.defaultValue().isEmpty() && !"\u0000".equals(elem.defaultValue())) { return elem.defaultValue(); } } return null; } protected String pFindExampleValue(Annotated a) { ApiModelProperty prop = a.getAnnotation(ApiModelProperty.class); if (prop != null) { if (!prop.example().isEmpty()) { return prop.example(); } } return null; } protected Boolean pFindReadOnly(Annotated a) { ApiModelProperty prop = a.getAnnotation(ApiModelProperty.class); if (prop != null) { return prop.readOnly(); } return null; } protected boolean pIsSetType(Class<?> cls) { if (cls != null) { if (java.util.Set.class.equals(cls)) { return true; } else { for (Class<?> a : cls.getInterfaces()) { // this is dirty and ugly and needs to be extended // into a scala model converter. But to avoid bringing in // scala runtime... if (java.util.Set.class.equals(a) || "interface scala.collection.Set".equals(a.toString())) { return true; } } } } return false; } @Override public Model resolve(Type type, ModelConverterContext context, Iterator<ModelConverter> chain) { if (chain.hasNext()) { return chain.next().resolve(type, context, chain); } else { return null; } } }