All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.effektif.workflow.impl.json.Mappings Maven / Gradle / Ivy

The newest version!
/* Copyright (c) 2014, Effektif GmbH.
 * 
 * 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 com.effektif.workflow.impl.json;

import com.effektif.workflow.api.json.*;
import com.effektif.workflow.api.types.DataType;
import com.effektif.workflow.api.types.JavaBeanType;
import com.effektif.workflow.api.types.ListType;
import com.effektif.workflow.impl.data.types.MapType;
import com.effektif.workflow.impl.json.types.BeanMapper;
import com.effektif.workflow.impl.json.types.PolymorphicBeanMapper;
import com.effektif.workflow.impl.util.Reflection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.*;
import java.util.*;

/**
 * Registry for static information used to map API model classes to and from JSON. The purpose of this class is to
 * provide a static cache of class information that is programmatically registered or discovered by reflection.
 *
 * @author Tom Baeyens
 */
public class Mappings {
  
  private static final Logger log = LoggerFactory.getLogger(Mappings.class);

  /** Initialized from the mapping builder information */
  protected Map fieldNames = new HashMap<>();
  
  /** Initialized from the mapping builder information */
  protected Map fieldsMappings;

  /** Initialized from the mapping builder information */
  protected Set inlineFields = new HashSet<>();
  
  /** Initialized from the mapping builder information */
  protected Set ignoredFields = new HashSet<>();
  
  /** Initialized from the mapping builder information */
  protected List jsonTypeMapperFactories = new ArrayList<>();
  
  /** Initialized from the mapping builder information */
  protected Map dataTypesByValueClass = new HashMap<>();

  /** Maps registered base classes (like e.g. Activity) to *unparameterized* polymorphic mappings.
   * Polymorphic parameterized types are not yet supported.
   * Initialized from the mapping builder information */
  protected Map, PolymorphicMapping> polymorphicMappings = new HashMap<>();

  /** Initialized from the mapping builder information in registerSubClass */
  protected Map, TypeField> typeFields = new HashMap<>();
  
  /** Type mappings contain the field mappings for each type.  
   * Types can be parameterized.
   * Dynamically initialized */ 
  protected Map typeMappings = new HashMap<>();

  /**
   * JSON type mappers are the SPI to plug in support for particular types.
   * Dynamically initialized.
   */
  protected Map typeMappers = new HashMap<>();
  
  /** dynamically initialized */
  protected Map, Map> fieldTypes = new HashMap<>();

  public Mappings(MappingsBuilder mappingsBuilder) {
    this.inlineFields = mappingsBuilder.inlineFields;
    this.ignoredFields = mappingsBuilder.ignoredFields;
    this.fieldNames = mappingsBuilder.fieldNames;
    this.fieldsMappings = mappingsBuilder.fieldsMappings;
    this.jsonTypeMapperFactories = mappingsBuilder.typeMapperFactories;
    this.dataTypesByValueClass = mappingsBuilder.dataTypesByValueClass;
    
    for (Class baseClass: mappingsBuilder.baseClasses.keySet()) {
      String typeField = mappingsBuilder.baseClasses.get(baseClass);
      PolymorphicMapping subclassMapping = new PolymorphicMapping(baseClass, typeField);
      polymorphicMappings.put(baseClass, subclassMapping);
    }
    for (Class subClass: mappingsBuilder.subClasses) {
      registerSubClass(subClass);
    }
  }

  public Mappings(Mappings other) {
    this.fieldNames = other.fieldNames;
    this.inlineFields = other.inlineFields;
    this.jsonTypeMapperFactories = other.jsonTypeMapperFactories;
    this.dataTypesByValueClass = other.dataTypesByValueClass;
    this.polymorphicMappings = other.polymorphicMappings;
    this.typeFields = other.typeFields;
    this.typeMappings = other.typeMappings;
    this.typeMappers = other.typeMappers;
    this.fieldTypes = other.fieldTypes;
  }

  public void registerSubClass(Class< ? > subClass) {
    TypeName typeName = subClass.getAnnotation(TypeName.class);
    if (typeName!=null) {
      registerSubClass(subClass, typeName.value(), subClass);
    } else {
      for (Class baseClass: polymorphicMappings.keySet()) {
        if (baseClass.isAssignableFrom(subClass)) {
          throw new RuntimeException(subClass.getName()+" does not declare "+TypeName.class.toString());
        }
      }
    }
  }
  
  protected void registerSubClass(Class baseClass, String typeName, Class subClass) {
    PolymorphicMapping polymorphicMapping = polymorphicMappings.get(baseClass);
    if (polymorphicMapping!=null) {
      TypeMapping typeMapping = getTypeMapping(subClass);
      polymorphicMapping.registerSubtypeMapping(typeName, subClass, typeMapping);
      typeFields.put(subClass, new TypeField(polymorphicMapping.getTypeField(), typeName));
    }
    Class< ? > superClass = baseClass.getSuperclass();
    if (superClass!=null) {
      registerSubClass(superClass, typeName, subClass);
    }
    for (Class i: baseClass.getInterfaces()) {
      registerSubClass(i, typeName, subClass);
    }
  }


  public void writeTypeField(JsonWriter jsonWriter, Object o) {
    TypeField typeField = typeFields.get(o.getClass());
    if (typeField!=null) {
      jsonWriter.writeFieldName(typeField.getTypeField());
      jsonWriter.writeString(typeField.getTypeName());
    }
  }
  
  public synchronized Type getFieldType(Class< ? > clazz, String fieldName) {
    // could be cached in this mappings object
    Type fieldType = getFieldTypeFromCache(clazz, fieldName);
    if (fieldType!=null) {
      return fieldType;
    }
    Map fieldTypesForClass = fieldTypes.get(clazz);
    if (fieldTypesForClass==null) {
      fieldTypesForClass = new HashMap<>();
      fieldTypes.put(clazz, fieldTypesForClass);
    }
    fieldType = findFieldType(clazz, fieldName);
    if (fieldType==null) {
      throw new RuntimeException("Field "+clazz.getName()+"."+fieldName+" not found");
    }
    fieldTypesForClass.put(fieldName, fieldType);
    return fieldType;
  }

  private Type findFieldType(Class< ? > clazz, String fieldName) {
    try {
      for (Field field: clazz.getDeclaredFields()) {
        if (field.getName().equals(fieldName)) {
          return field.getGenericType();
        }
      }
      if (clazz.getSuperclass()!=Object.class) {
        return findFieldType(clazz.getSuperclass(), fieldName);
      }
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    return null;
  }

  private Type getFieldTypeFromCache(Class< ? > type, String fieldName) {
    Map types = fieldTypes.get(type);
    if (types==null) {
      return null;
    }
    return types.get(fieldName);
  }
  
  public DataType getTypeByValue(Object value) {
    if (value==null) {
      return null;
    }
    if (value instanceof Collection) {
      return getTypeByCollection((Collection) value);
    }
    if (value instanceof Map) {
      return getTypeByMap((Map) value);
    }
    Class clazz = value.getClass();
    DataType dataType = dataTypesByValueClass.get(clazz);
    if (dataType!=null) {
      return dataType;
    }
    return new JavaBeanType(clazz);
  }

  private DataType getTypeByMap(Map map) {
    if (map==null || map.isEmpty()) {
      return null;
    }
    DataType valueType = getTypeByCollection(map.values());
    return new MapType(valueType);
  }

  private DataType getTypeByCollection(Collection collection) {
    if (collection==null || collection.isEmpty()) {
      return null;
    }
    Iterator iterator = collection.iterator();
    DataType commonDataType = getTypeByValue(iterator.next());
    if (commonDataType instanceof JavaBeanType) {
      JavaBeanType javaBeanType = (JavaBeanType) commonDataType; 
      while (iterator.hasNext()) {
        Object elementValue = iterator.next();
        Class elementValueClass = elementValue.getClass();
        Class javaBeanClass = javaBeanType.getJavaClass();
        while (!javaBeanClass.isAssignableFrom(elementValueClass)
               && javaBeanClass!=Object.class) {
          javaBeanType.setJavaClass(javaBeanClass.getSuperclass());
        }
      }
    }
    return new ListType(commonDataType);
  }

  public JsonTypeMapper getTypeMapper(Type type) {
    JsonTypeMapper jsonTypeMapper = typeMappers.get(type);
    if (jsonTypeMapper!=null) {
      return jsonTypeMapper;
    }

    Class clazz = Reflection.getRawClass(type);
    for (JsonTypeMapperFactory factory: jsonTypeMapperFactories) {
      jsonTypeMapper = factory.createTypeMapper(type, clazz, this);
      if (jsonTypeMapper!=null) {
        break;
      }
    }

    if (jsonTypeMapper==null) {
      PolymorphicMapping polymorphicMapping = getPolymorphicMapping(type);
      if (polymorphicMapping!=null) {
        polymorphicMapping = getParameterizedPolymorphicMapping(type, polymorphicMapping);
        jsonTypeMapper = new PolymorphicBeanMapper(polymorphicMapping);
      } else {
        TypeMapping typeMapping = getTypeMapping(type);
        jsonTypeMapper = new BeanMapper(typeMapping);
      }
    }

    jsonTypeMapper.setMappings(this);
    typeMappers.put(type, jsonTypeMapper);
    return jsonTypeMapper;
  }

  /** finds the most concrete polymorphic mapping that matches the given type. */
  public PolymorphicMapping getPolymorphicMapping(Type type) {
    Class clazz = Reflection.getRawClass(type);
    PolymorphicMapping polymorphicMapping = polymorphicMappings.get(clazz);
    while (polymorphicMapping==null && clazz!=null && clazz!=Object.class) {
      clazz = clazz.getSuperclass();
      polymorphicMapping = polymorphicMappings.get(clazz);
    }
    return polymorphicMapping;
  }


  private PolymorphicMapping getParameterizedPolymorphicMapping(Type type, PolymorphicMapping untypedPolymorphicMapping) {
    if (!Reflection.isParameterized(type)) {
      return untypedPolymorphicMapping;
    }
    throw new RuntimeException("TODO polymorphic, parameterized types are not yet supported");
  }

  public TypeMapping getTypeMapping(Type type) {
    TypeMapping typeMapping = typeMappings.get(type);
    if (typeMapping!=null) {
      // log.debug("Found type mapping "+typeMapping+" in cache for type "+Reflection.getSimpleName(type));
      return typeMapping;
    }
    // log.debug("Creating type mapping for "+Reflection.getSimpleName(type));
    typeMapping = new TypeMapping(type);
    typeMappings.put(type, typeMapping);
    scanFieldMappings(type, typeMapping);
    // log.debug("Creating type mapping "+typeMapping);
    return typeMapping;
  }

  public void scanFieldMappings(Type type, TypeMapping typeMapping) {
    List fieldMappings = new ArrayList<>();
    scanFields(fieldMappings, type);
    Class clazz = Reflection.getRawClass(type);
    Set inlineFieldMappings = new HashSet<>();
    for (FieldMapping fieldMapping: fieldMappings) {
      // apply the json field name overwriting
      String jsonFieldName = fieldNames.get(fieldMapping.field);
      if (jsonFieldName!=null) {
        fieldMapping.jsonFieldName = jsonFieldName;
      }
      // capture the inline field mappings in a collection
      if (inlineFields.contains(fieldMapping.field)) {
        inlineFieldMappings.add(fieldMapping);
      }
    }
    if (!inlineFieldMappings.isEmpty()) {
      List fieldNames = new ArrayList<>();
      for (FieldMapping fieldMapping: fieldMappings) {
        fieldNames.add(fieldMapping.jsonFieldName);
      }
      for (FieldMapping inlineFieldMapping: inlineFieldMappings) {
        inlineFieldMapping.inline = fieldNames;
      }
    }
    JsonPropertyOrder jsonPropertyOrder = clazz.getAnnotation(JsonPropertyOrder.class);
    if (jsonPropertyOrder!=null) {
      String[] fieldNamesOrder = jsonPropertyOrder.value();
      for (int i=fieldNamesOrder.length-1; i>=0; i--) {
        String fieldName = fieldNamesOrder[i];
        FieldMapping fieldMapping = removeField(fieldMappings, fieldName);
        if (fieldMapping!=null) {
          fieldMappings.add(0, fieldMapping);
        }
      }
    }
    typeMapping.setFieldMappings(fieldMappings);
  }

  private FieldMapping removeField(List fieldMappings, String fieldName) {
    Iterator iterator = fieldMappings.iterator();
    while (iterator.hasNext()) {
      FieldMapping fieldMapping = iterator.next();
      if (fieldMapping.getFieldName().equals(fieldName)) {
        iterator.remove();
        return fieldMapping;
      }
    }
    return null;
  }
  
  public static Type resolveFieldType(TypeVariable fieldType, Class clazz, Type type) {
    Map typeArgs = new HashMap<>();
    TypeVariable< ? >[] typeParameters = clazz.getTypeParameters();
    Type[] actualTypeArguments = null;
    if (type instanceof ParameterizedType) {
      actualTypeArguments = ((ParameterizedType)type).getActualTypeArguments(); 
    } else if (type instanceof GenericType) {
      actualTypeArguments = ((GenericType)type).getTypeArgs(); 
    } else {
      return null;
    }
    for (int i=0; i fieldMappings, Type type) {
    if (type == null) {
      throw new IllegalArgumentException("type may not be null");
    }
    Class clazz = Reflection.getRawClass(type);
    Map typeArgs = Reflection.getTypeArgsMap(type);
    Field[] declaredFields = clazz.getDeclaredFields();
    if (declaredFields!=null) {
      int index = 0;
      for (Field field: declaredFields) {
        if (!Modifier.isStatic(field.getModifiers())
            && field.getAnnotation(JsonIgnore.class)==null
            && !ignoredFields.contains(field)) {
          field.setAccessible(true);
          FieldMapping fieldMapping = fieldsMappings.get(field);
          if (fieldMapping==null) {
            // log.debug("  Scanning "+Reflection.getSimpleName(field));
            Type fieldType = field.getGenericType();
            if (fieldType instanceof TypeVariable) {
              fieldType = typeArgs!=null ? typeArgs.get((TypeVariable)fieldType) : Object.class;
            }
            JsonTypeMapper fieldTypeMapper = getTypeMapper(fieldType);
            fieldMapping = new FieldMapping(field, fieldTypeMapper);
          }
          // Annotation-based field name override.
          JsonFieldName jsonFieldNameAnnotation = field.getAnnotation(JsonFieldName.class);
          if (jsonFieldNameAnnotation != null) {
            fieldMapping.setJsonFieldName(jsonFieldNameAnnotation.value());
          }
          fieldMappings.add(index, fieldMapping);
          index++;
        }
      }
    }
    if (clazz.isEnum()) {
      return;
    }
    Class superclass = clazz.getSuperclass();
    if (superclass!=null && superclass!=Object.class) {
      Type supertype = Reflection.getSuperclass(type);
      if (supertype!=null) {
        scanFields(fieldMappings, supertype);
      } else {
        // TODO find out which field is not handled properly
        throw new RuntimeException("null supertype for " + type );
      }
    }
  }

  
  public Map getDataTypesByValueClass() {
    return dataTypesByValueClass;
  }

  
  public void setDataTypesByValueClass(Map dataTypesByValueClass) {
    this.dataTypesByValueClass = dataTypesByValueClass;
  }

  
  public Map getFieldNames() {
    return fieldNames;
  }

  
  public Map getFieldsMappings() {
    return fieldsMappings;
  }

  
  public Set getInlineFields() {
    return inlineFields;
  }

  
  public Set getIgnoredFields() {
    return ignoredFields;
  }

  
  public List getJsonTypeMapperFactories() {
    return jsonTypeMapperFactories;
  }

  
  public Map, PolymorphicMapping> getPolymorphicMappings() {
    return polymorphicMappings;
  }

  
  public Map, TypeField> getTypeFields() {
    return typeFields;
  }

  
  public Map getTypeMappings() {
    return typeMappings;
  }

  
  public Map getTypeMappers() {
    return typeMappers;
  }

  
  public Map, Map> getFieldTypes() {
    return fieldTypes;
  }

  public boolean isIgnored(Field field) {
    return ignoredFields.contains(field);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy