org.candlepin.swagger.CandlepinSwaggerModelConverter.java Source code

Java tutorial

Introduction

Here is the source code for org.candlepin.swagger.CandlepinSwaggerModelConverter.java

Source

/**
 * 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 org.candlepin.common.jackson.HateoasInclude;

import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIdentityReference;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerator;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyMetadata;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.converter.ModelConverter;
import io.swagger.converter.ModelConverterContext;
import io.swagger.jackson.TypeNameResolver;
import io.swagger.models.ComposedModel;
import io.swagger.models.Model;
import io.swagger.models.ModelImpl;
import io.swagger.models.RefModel;
import io.swagger.models.Xml;
import io.swagger.models.properties.AbstractNumericProperty;
import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.IntegerProperty;
import io.swagger.models.properties.MapProperty;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.PropertyBuilder;
import io.swagger.models.properties.RefProperty;
import io.swagger.models.properties.StringProperty;
import io.swagger.models.properties.UUIDProperty;
import io.swagger.util.AllowableValues;
import io.swagger.util.AllowableValuesUtils;
import io.swagger.util.PrimitiveType;

/**
 * This custom model converter is adapted version of standard swagger
 * ModelResolver.
 *
 * The ModelResolver in Swagger has responsibility to introspect Models (in our
 * case Entities) and decide which fields to include in swagger.json. The
 * ModelResolver tries to account for various standard exlusion annotations such
 * as JsonIgnore and XmlTransient, however Candlepin has much more complex rules
 * for serialization that cannot bea easily captured just by adding new
 * ModelConverter into the chain of Converters. Thats why this ModelResolver is
 * being modified. The new capabilities of this ModelResolver are: Detection of
 * nested Model properties, Hateoas Serialization.
 *
 * <h2>Detection of nested Model properties</h2> Candlepin serialization is
 * configured in JsonProvider class. This class defines several JSON filters
 * that enable Hateoas Serialization for nested fields. That means that an
 * entity such as Owner will be serialized differently based on whether its
 * top-level entity returned as Response or nested property e.g. parentOwner
 * property inside an Owner. To allow for such behavior, this modified converter
 * is detecting such nested property and uses inheritance hack (see java doc of
 * NestedComplexType class) to make sure that a new model with prefix 'Nested'
 * (e.g. NestedOwner) is created for each such nested type. It is important to
 * note that not all entities have it's nested counterparts. Only those that
 * have JsonFilter annotation with appropriate filter (list of t hose is in
 * JsonProvider) should have the nested counterpart.
 *
 * <h2>Hateoas Serializatoin</h2> See JavaDoc of HateoasInclude annotation for
 * more details of how the serialization works. This converter make sure that
 * when a model is nested (e.g. NestedOwner) it will include only those fields
 * that are annotated for HateoasInclude
 *
 *
 * @author fnguyen
 *
 */
public class CandlepinSwaggerModelConverter extends AbstractModelConverter implements ModelConverter {
    private static final Logger log = LoggerFactory.getLogger(CandlepinSwaggerModelConverter.class);
    private Map<JavaType, NestedComplexType> nestedJavaTypes = new HashMap<JavaType, NestedComplexType>();

    @Inject
    public CandlepinSwaggerModelConverter(ObjectMapper mapper) {
        super(mapper);
    }

    public ObjectMapper objectMapper() {
        return pMapper;
    }

    protected boolean shouldIgnoreClass(Type type) {
        if (type instanceof Class) {
            Class<?> cls = (Class<?>) type;
            if (cls.getName().equals("javax.ws.rs.Response")) {
                return true;
            }
        } else {
            if (type instanceof com.fasterxml.jackson.core.type.ResolvedType) {
                com.fasterxml.jackson.core.type.ResolvedType rt = (com.fasterxml.jackson.core.type.ResolvedType) type;
                log.debug("Can't check class {}, {}", type, rt.getRawClass().getName());
                if (rt.getRawClass().equals(Class.class)) {
                    return true;
                }
            }
        }
        return false;
    }

    public Property resolveProperty(Type type, ModelConverterContext context, Annotation[] annotations,
            Iterator<ModelConverter> next) {
        JavaType propType = null;

        /**
         * See java doc of NestedComplexType. This unwrapping makes sure that a
         * real type of field is passed to _mapper
         */
        if (type instanceof NestedComplexType) {
            propType = pMapper.constructType(((NestedComplexType) type).getOriginalType());
        } else {
            propType = pMapper.constructType(type);
        }

        log.debug("resolveProperty {}", propType);

        Property property = null;
        if (propType.isContainerType()) {
            JavaType keyType = propType.getKeyType();
            JavaType valueType = propType.getContentType();
            if (keyType != null && valueType != null) {
                property = new MapProperty()
                        .additionalProperties(context.resolveProperty(valueType, new Annotation[] {}));
            } else if (valueType != null) {
                ArrayProperty arrayProperty = new ArrayProperty()
                        .items(context.resolveProperty(valueType, new Annotation[] {}));
                if (pIsSetType(propType.getRawClass())) {
                    arrayProperty.setUniqueItems(true);
                }
                property = arrayProperty;
            }
        } else {
            property = PrimitiveType.createProperty(propType);
        }

        if (property == null) {
            if (propType.isEnumType()) {
                property = new StringProperty();
                pAddEnumProps(propType.getRawClass(), property);
            } else if (pIsOptionalType(propType)) {
                property = context.resolveProperty(propType.containedType(0), null);
            } else {
                // complex type
                Model innerModel = context.resolve(type);
                if (innerModel instanceof ModelImpl) {
                    ModelImpl mi = (ModelImpl) innerModel;
                    property = new RefProperty(
                            StringUtils.isNotEmpty(mi.getReference()) ? mi.getReference() : mi.getName());
                }
            }
        }

        return property;
    }

    private boolean pIsOptionalType(JavaType propType) {
        return Arrays.asList("com.google.common.base.Optional", "java.util.Optional")
                .contains(propType.getRawClass().getCanonicalName());
    }

    protected void pAddEnumProps(Class<?> propClass, Property property) {
        final boolean useIndex = pMapper.isEnabled(SerializationFeature.WRITE_ENUMS_USING_INDEX);
        final boolean useToString = pMapper.isEnabled(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);

        @SuppressWarnings("unchecked")
        Class<Enum<?>> enumClass = (Class<Enum<?>>) propClass;
        for (Enum<?> en : enumClass.getEnumConstants()) {
            String n;
            if (useIndex) {
                n = String.valueOf(en.ordinal());
            } else if (useToString) {
                n = en.toString();
            } else {
                n = pIntr.findEnumValue(en);
            }
            if (property instanceof StringProperty) {
                StringProperty sp = (StringProperty) property;
                sp._enum(n);
            }
        }
    }

    public Model resolve(Type rawType, ModelConverterContext context, Iterator<ModelConverter> next) {
        if (this.shouldIgnoreClass(rawType)) {
            return null;
        }

        /**
         * See java doc of NestedComplexType. This unwrapping makes sure that a
         * real type of field used throughout the method. At the same time flag
         * 'isNested' helps to indicate later in the method that this type may
         * be introspected as Hateoas enabled field
         */
        boolean isNested = false;
        if (rawType instanceof NestedComplexType) {
            isNested = true;
            NestedComplexType nested = (NestedComplexType) rawType;
            rawType = nested.getOriginalType();
        }
        JavaType type = pMapper.constructType(rawType);

        if (type.isEnumType() || PrimitiveType.fromType(type) != null) {
            // We don't build models for primitive types
            return null;
        }

        final BeanDescription beanDesc = pMapper.getSerializationConfig().introspect(type);
        // Couple of possibilities for defining
        String name = isNested ? "Nested" : "";
        name += pTypeName(type, beanDesc);

        if ("Object".equals(name)) {
            return new ModelImpl();
        }

        final ModelImpl model = new ModelImpl().type(ModelImpl.OBJECT).name(name)
                .description(pDescription(beanDesc.getClassInfo()));

        if (!type.isContainerType()) {
            // define the model here to support self/cyclic referencing of
            // models
            context.defineModel(name, model, type, null);
        }

        if (type.isContainerType()) {
            // We treat collections as primitive types, just need to add models
            // for values (if any)
            context.resolve(type.getContentType());
            return null;
        }
        // if XmlRootElement annotation, construct an Xml object and attach it
        // to the model
        XmlRootElement rootAnnotation = beanDesc.getClassAnnotations().get(XmlRootElement.class);
        if (rootAnnotation != null && !"".equals(rootAnnotation.name())
                && !"##default".equals(rootAnnotation.name())) {
            log.debug(rootAnnotation.toString());
            Xml xml = new Xml().name(rootAnnotation.name());
            if (rootAnnotation.namespace() != null && !"".equals(rootAnnotation.namespace())
                    && !"##default".equals(rootAnnotation.namespace())) {
                xml.namespace(rootAnnotation.namespace());
            }
            model.xml(xml);
        }

        // see if @JsonIgnoreProperties exist
        Set<String> propertiesToIgnore = new HashSet<String>();
        JsonIgnoreProperties ignoreProperties = beanDesc.getClassAnnotations().get(JsonIgnoreProperties.class);
        if (ignoreProperties != null) {
            propertiesToIgnore.addAll(Arrays.asList(ignoreProperties.value()));
        }

        final ApiModel apiModel = beanDesc.getClassAnnotations().get(ApiModel.class);
        String disc = (apiModel == null) ? "" : apiModel.discriminator();

        if (apiModel != null && StringUtils.isNotEmpty(apiModel.reference())) {
            model.setReference(apiModel.reference());
        }

        if (disc.isEmpty()) {
            // longer method would involve
            // AnnotationIntrospector.findTypeResolver(...) but:
            JsonTypeInfo typeInfo = beanDesc.getClassAnnotations().get(JsonTypeInfo.class);
            if (typeInfo != null) {
                disc = typeInfo.property();
            }
        }
        if (!disc.isEmpty()) {
            model.setDiscriminator(disc);
        }

        List<Property> props = new ArrayList<Property>();
        for (BeanPropertyDefinition propDef : beanDesc.findProperties()) {
            parseProperty(context, isNested, beanDesc, propertiesToIgnore, props, propDef);
        }

        Collections.sort(props, getPropertyComparator());

        Map<String, Property> modelProps = new LinkedHashMap<String, Property>();
        for (Property prop : props) {
            modelProps.put(prop.getName(), prop);
        }
        model.setProperties(modelProps);

        /**
         * This must be done after model.setProperties so that the model's set
         * of properties is available to filter from any subtypes
         **/
        if (!resolveSubtypes(model, beanDesc, context)) {
            model.setDiscriminator(null);
        }

        return model;
    }

    private void parseProperty(ModelConverterContext context, boolean isNested, final BeanDescription beanDesc,
            Set<String> propertiesToIgnore, List<Property> props, BeanPropertyDefinition propDef) {
        Property property = null;
        String propName = propDef.getName();
        Annotation[] annotations = null;

        propName = getPropName(propDef, propName);

        PropertyMetadata md = propDef.getMetadata();

        boolean hasSetter = false, hasGetter = false;
        if (propDef.getSetter() == null) {
            hasSetter = false;
        } else {
            hasSetter = true;
        }
        if (propDef.getGetter() != null) {
            JsonProperty pd = propDef.getGetter().getAnnotation(JsonProperty.class);
            if (pd != null) {
                hasGetter = true;
            }
        }
        Boolean isReadOnly = null;
        if (!hasSetter & hasGetter) {
            isReadOnly = Boolean.TRUE;
        } else {
            isReadOnly = Boolean.FALSE;
        }

        final AnnotatedMember member = propDef.getPrimaryMember();

        if (member != null && !propertiesToIgnore.contains(propName) &&
        /**
         * If the owning type is nested than we should include only those
         * fields that have the Hateoas annotation.
         */
                !(isNested && !member.hasAnnotation(HateoasInclude.class))) {

            List<Annotation> annotationList = new ArrayList<Annotation>();
            for (Annotation a : member.annotations()) {
                annotationList.add(a);
            }

            annotations = annotationList.toArray(new Annotation[annotationList.size()]);

            ApiModelProperty mp = member.getAnnotation(ApiModelProperty.class);

            if (mp != null && mp.readOnly()) {
                isReadOnly = mp.readOnly();
            }

            Type nested = null;
            JavaType propType = member.getType(beanDesc.bindingsForBeanType());
            JsonFilter jsonFilter = propType.getRawClass().getAnnotation(JsonFilter.class);

            /**
             * At this point the propType is a type of some nested field of the
             * type that is being processed. The condition checks if this
             * particular type should have Hateoas serialization enabled. In
             * other words, if we should create a new Nested* model.
             */
            if (jsonFilter != null && (jsonFilter.value().equals("ConsumerFilter")
                    || jsonFilter.value().equals("EntitlementFilter") || jsonFilter.value().equals("OwnerFilter")
                    || jsonFilter.value().equals("GuestFilter"))) {
                if (!nestedJavaTypes.containsKey(propType)) {
                    nestedJavaTypes.put(propType, new NestedComplexType(propType));
                }
                nested = nestedJavaTypes.get(propType);
            } else {
                nested = propType;
            }

            // allow override of name from annotation
            if (mp != null && !mp.name().isEmpty()) {
                propName = mp.name();
            }

            if (mp != null && !mp.dataType().isEmpty()) {
                property = resolveApiAnnotated(context, property, annotations, mp, propType);
            }

            // no property from override, construct from propType
            if (property == null) {
                if (mp != null && StringUtils.isNotEmpty(mp.reference())) {
                    property = new RefProperty(mp.reference());
                } else if (member.getAnnotation(JsonIdentityInfo.class) != null) {
                    property = GeneratorWrapper.processJsonIdentity(propType, context, pMapper,
                            member.getAnnotation(JsonIdentityInfo.class),
                            member.getAnnotation(JsonIdentityReference.class));
                }
                if (property == null) {
                    property = context.resolveProperty(nested, annotations);
                }
            }

            if (property != null) {
                addMetadataToProperty(property, propName, md, isReadOnly, member, mp);
                applyBeanValidatorAnnotations(property, annotations);
                props.add(property);
            }
        }
    }

    private String getPropName(BeanPropertyDefinition propDef, String propName) {
        // hack to avoid clobbering properties with get/is names
        // it's ugly but gets around
        // https://github.com/swagger-api/swagger-core/issues/415
        if (propDef.getPrimaryMember() != null) {
            java.lang.reflect.Member member = propDef.getPrimaryMember().getMember();
            if (member != null) {
                String altName = member.getName();
                if (altName != null) {
                    final int length = altName.length();
                    for (String prefix : Arrays.asList("get", "is")) {
                        final int offset = prefix.length();
                        if (altName.startsWith(prefix) && length > offset
                                && !Character.isUpperCase(altName.charAt(offset))) {
                            propName = altName;
                            break;
                        }
                    }
                }
            }
        }
        return propName;
    }

    private void addMetadataToProperty(Property property, String propName, PropertyMetadata md, Boolean isReadOnly,
            final AnnotatedMember member, ApiModelProperty mp) {
        property.setName(propName);

        if (mp != null && !mp.access().isEmpty()) {
            property.setAccess(mp.access());
        }

        Boolean required = md.getRequired();
        if (required != null) {
            property.setRequired(required);
        }

        String description = pIntr.findPropertyDescription(member);
        if (description != null && !"".equals(description)) {
            property.setDescription(description);
        }

        Integer index = pIntr.findPropertyIndex(member);
        if (index != null) {
            property.setPosition(index);
        }
        property.setDefault(pFindDefaultValue(member));
        property.setExample(pFindExampleValue(member));
        property.setReadOnly(pFindReadOnly(member));

        if (property.getReadOnly() == null) {
            if (isReadOnly) {
                property.setReadOnly(isReadOnly);
            }
        }
        if (mp != null) {
            final AllowableValues allowableValues = AllowableValuesUtils.create(mp.allowableValues());
            if (allowableValues != null) {
                final Map<PropertyBuilder.PropertyId, Object> args = allowableValues.asPropertyArguments();
                PropertyBuilder.merge(property, args);
            }
        }
        JAXBAnnotationsHelper.apply(member, property);
    }

    private Property resolveApiAnnotated(ModelConverterContext context, Property property, Annotation[] annotations,
            ApiModelProperty mp, JavaType propType) {
        String or = mp.dataType();

        JavaType innerJavaType = null;
        log.debug("overriding datatype from {} to {}", propType, or);

        if (or.toLowerCase().startsWith("list[")) {
            String innerType = or.substring(5, or.length() - 1);
            ArrayProperty p = new ArrayProperty();
            Property primitiveProperty = PrimitiveType.createProperty(innerType);
            if (primitiveProperty != null) {
                p.setItems(primitiveProperty);
            } else {
                innerJavaType = getInnerType(innerType);
                p.setItems(context.resolveProperty(innerJavaType, annotations));
            }
            property = p;
        } else if (or.toLowerCase().startsWith("map[")) {
            int pos = or.indexOf(",");
            if (pos > 0) {
                String innerType = or.substring(pos + 1, or.length() - 1);
                MapProperty p = new MapProperty();
                Property primitiveProperty = PrimitiveType.createProperty(innerType);
                if (primitiveProperty != null) {
                    p.setAdditionalProperties(primitiveProperty);
                } else {
                    innerJavaType = getInnerType(innerType);
                    p.setAdditionalProperties(context.resolveProperty(innerJavaType, annotations));
                }
                property = p;
            }
        } else {
            Property primitiveProperty = PrimitiveType.createProperty(or);
            if (primitiveProperty != null) {
                property = primitiveProperty;
            } else {
                innerJavaType = getInnerType(or);
                property = context.resolveProperty(innerJavaType, annotations);
            }
        }
        if (innerJavaType != null) {
            context.resolve(innerJavaType);
        }
        return property;
    }

    private enum GeneratorWrapper {
        PROPERTY(ObjectIdGenerators.PropertyGenerator.class) {
            @Override
            protected Property processAsProperty(String propertyName, JavaType type, ModelConverterContext context,
                    ObjectMapper mapper) {
                /*
                 * When generator = ObjectIdGenerators.PropertyGenerator.class
                 * and
                 *
                 * @JsonIdentityReference(alwaysAsId = false) then property is
                 * serialized in the same way it is done
                 * without @JsonIdentityInfo annotation.
                 */
                return null;
            }

            @Override
            protected Property processAsId(String propertyName, JavaType type, ModelConverterContext context,
                    ObjectMapper mapper) {
                final BeanDescription beanDesc = mapper.getSerializationConfig().introspect(type);
                for (BeanPropertyDefinition def : beanDesc.findProperties()) {
                    final String name = def.getName();
                    if (name != null && name.equals(propertyName)) {
                        final AnnotatedMember propMember = def.getPrimaryMember();
                        final JavaType propType = propMember.getType(beanDesc.bindingsForBeanType());
                        if (PrimitiveType.fromType(propType) != null) {
                            return PrimitiveType.createProperty(propType);
                        } else {
                            return context.resolveProperty(propType,
                                    Iterables.toArray(propMember.annotations(), Annotation.class));
                        }
                    }
                }
                return null;
            }
        },
        INT(ObjectIdGenerators.IntSequenceGenerator.class) {
            @Override
            protected Property processAsProperty(String propertyName, JavaType type, ModelConverterContext context,
                    ObjectMapper mapper) {
                Property id = new IntegerProperty();
                return process(id, propertyName, type, context);
            }

            @Override
            protected Property processAsId(String propertyName, JavaType type, ModelConverterContext context,
                    ObjectMapper mapper) {
                return new IntegerProperty();
            }
        },
        UUID(ObjectIdGenerators.UUIDGenerator.class) {
            @Override
            protected Property processAsProperty(String propertyName, JavaType type, ModelConverterContext context,
                    ObjectMapper mapper) {
                Property id = new UUIDProperty();
                return process(id, propertyName, type, context);
            }

            @Override
            protected Property processAsId(String propertyName, JavaType type, ModelConverterContext context,
                    ObjectMapper mapper) {
                return new UUIDProperty();
            }
        },
        NONE(ObjectIdGenerators.None.class) {
            // When generator = ObjectIdGenerators.None.class property should be
            // processed as normal property.
            @Override
            protected Property processAsProperty(String propertyName, JavaType type, ModelConverterContext context,
                    ObjectMapper mapper) {
                return null;
            }

            @Override
            protected Property processAsId(String propertyName, JavaType type, ModelConverterContext context,
                    ObjectMapper mapper) {
                return null;
            }
        };

        private final Class<? extends ObjectIdGenerator> generator;

        GeneratorWrapper(Class<? extends ObjectIdGenerator> generator) {
            this.generator = generator;
        }

        protected abstract Property processAsProperty(String propertyName, JavaType type,
                ModelConverterContext context, ObjectMapper mapper);

        protected abstract Property processAsId(String propertyName, JavaType type, ModelConverterContext context,
                ObjectMapper mapper);

        public static Property processJsonIdentity(JavaType type, ModelConverterContext context,
                ObjectMapper mapper, JsonIdentityInfo identityInfo, JsonIdentityReference identityReference) {
            final GeneratorWrapper wrapper = identityInfo != null ? getWrapper(identityInfo.generator()) : null;
            if (wrapper == null) {
                return null;
            }
            if (identityReference != null && identityReference.alwaysAsId()) {
                return wrapper.processAsId(identityInfo.property(), type, context, mapper);
            } else {
                return wrapper.processAsProperty(identityInfo.property(), type, context, mapper);
            }
        }

        private static GeneratorWrapper getWrapper(Class<?> generator) {
            for (GeneratorWrapper value : GeneratorWrapper.values()) {
                if (value.generator.isAssignableFrom(generator)) {
                    return value;
                }
            }
            return null;
        }

        private static Property process(Property id, String propertyName, JavaType type,
                ModelConverterContext context) {
            id.setName(propertyName);
            final Model model = context.resolve(type);
            if (model instanceof ModelImpl) {
                ModelImpl mi = (ModelImpl) model;
                mi.getProperties().put(propertyName, id);
                return new RefProperty(
                        StringUtils.isNotEmpty(mi.getReference()) ? mi.getReference() : mi.getName());
            }
            return null;
        }
    }

    protected void applyBeanValidatorAnnotations(Property property, Annotation[] annotations) {
        Map<String, Annotation> annos = new HashMap<String, Annotation>();
        if (annotations != null) {
            for (Annotation anno : annotations) {
                annos.put(anno.annotationType().getName(), anno);
            }
        }
        if (annos.containsKey("javax.validation.constraints.NotNull")) {
            property.setRequired(true);
        }
        if (annos.containsKey("javax.validation.constraints.Min")) {
            if (property instanceof AbstractNumericProperty) {
                Min min = (Min) annos.get("javax.validation.constraints.Min");
                AbstractNumericProperty ap = (AbstractNumericProperty) property;
                ap.setMinimum(new Double(min.value()));
            }
        }
        if (annos.containsKey("javax.validation.constraints.Max")) {
            if (property instanceof AbstractNumericProperty) {
                Max max = (Max) annos.get("javax.validation.constraints.Max");
                AbstractNumericProperty ap = (AbstractNumericProperty) property;
                ap.setMaximum(new Double(max.value()));
            }
        }
        if (annos.containsKey("javax.validation.constraints.Size")) {
            Size size = (Size) annos.get("javax.validation.constraints.Size");
            if (property instanceof AbstractNumericProperty) {
                AbstractNumericProperty ap = (AbstractNumericProperty) property;
                ap.setMinimum(new Double(size.min()));
                ap.setMaximum(new Double(size.max()));
            } else if (property instanceof StringProperty) {
                StringProperty sp = (StringProperty) property;
                sp.minLength(new Integer(size.min()));
                sp.maxLength(new Integer(size.max()));
            } else if (property instanceof ArrayProperty) {
                ArrayProperty sp = (ArrayProperty) property;
                sp.setMinItems(size.min());
                sp.setMaxItems(size.max());
            }
        }
        if (annos.containsKey("javax.validation.constraints.DecimalMin")) {
            DecimalMin min = (DecimalMin) annos.get("javax.validation.constraints.DecimalMin");
            if (property instanceof AbstractNumericProperty) {
                AbstractNumericProperty ap = (AbstractNumericProperty) property;
                ap.setMinimum(new Double(min.value()));
            }
        }
        if (annos.containsKey("javax.validation.constraints.DecimalMax")) {
            DecimalMax max = (DecimalMax) annos.get("javax.validation.constraints.DecimalMax");
            if (property instanceof AbstractNumericProperty) {
                AbstractNumericProperty ap = (AbstractNumericProperty) property;
                ap.setMaximum(new Double(max.value()));

            }
        }
        if (annos.containsKey("javax.validation.constraints.Pattern")) {
            Pattern pattern = (Pattern) annos.get("javax.validation.constraints.Pattern");
            if (property instanceof StringProperty) {
                StringProperty ap = (StringProperty) property;
                ap.setPattern(pattern.regexp());
            }
        }
    }

    protected JavaType getInnerType(String innerType) {
        try {
            Class<?> innerClass = Class.forName(innerType);
            if (innerClass != null) {
                TypeFactory tf = pMapper.getTypeFactory();
                return tf.constructType(innerClass);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    private boolean resolveSubtypes(ModelImpl model, BeanDescription bean, ModelConverterContext context) {
        final List<NamedType> types = pIntr.findSubtypes(bean.getClassInfo());
        if (types == null) {
            return false;
        }
        int count = 0;
        final Class<?> beanClass = bean.getClassInfo().getAnnotated();
        for (NamedType subtype : types) {
            final Class<?> subtypeType = subtype.getType();
            if (!beanClass.isAssignableFrom(subtypeType)) {
                continue;
            }

            final Model subtypeModel = context.resolve(subtypeType);

            if (subtypeModel instanceof ModelImpl) {
                final ModelImpl impl = (ModelImpl) subtypeModel;

                // check if model name was inherited
                if (impl.getName().equals(model.getName())) {
                    impl.setName(pTypeNameResolver.nameForType(pMapper.constructType(subtypeType),
                            TypeNameResolver.Options.SKIP_API_MODEL));
                }

                // remove shared properties defined in the parent
                final Map<String, Property> baseProps = model.getProperties();
                final Map<String, Property> subtypeProps = impl.getProperties();
                if (baseProps != null && subtypeProps != null) {
                    for (Map.Entry<String, Property> entry : baseProps.entrySet()) {
                        if (entry.getValue().equals(subtypeProps.get(entry.getKey()))) {
                            subtypeProps.remove(entry.getKey());
                        }
                    }
                }

                impl.setDiscriminator(null);
                ComposedModel child = new ComposedModel().parent(new RefModel(model.getName())).child(impl);
                context.defineModel(impl.getName(), child);
                ++count;
            }
        }
        return count != 0;
    }
}