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