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

yakworks.meta.MetaEntity.groovy Maven / Gradle / Ivy

/*
* Copyright 2020 original authors
* SPDX-License-Identifier: Apache-2.0
*/
package yakworks.meta

import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j

import org.codehaus.groovy.util.HashCodeHelper

import yakworks.commons.lang.NameUtils
import yakworks.commons.map.MapFlattener
import yakworks.openapi.OapiUtils

/**
 * Somewhat similiar to MetaClass.
 * This represents either the root object of the MetaMap or a property that is an object and has its own set of includes.
 * For exammple if Customer is the root object it might have an Address property that will be repesented with this.
 *
 * Customer  -> MetaEntity
 *   name    -> MetaProp
 *   address -> MetaEntity
 *     city  -> MetaProp
 *     etc....
 *
 * @see MetaProp
 */
@Slf4j
@CompileStatic
class MetaEntity extends MetaProp implements Serializable {

    //either a simple MetaProp or a ref to another MetaEntityProps
    Map metaProps = [:] as Map

    /** The source includes, these will be translated, for example '*' will be translated to the fields */
    Set includes
    /** The source excludes */
    Set excludes

    //if any special converters then can be set here and the MetaMap will get them
    public static Set CONVERTERS = [] as Set

    static {
        ServiceLoader loader = ServiceLoader.load(MetaMap.Converter)
        for (MetaMap.Converter converter : loader) {
            CONVERTERS.add(converter)
        }
    }

    MetaEntity(){}

    MetaEntity(Class type) {
        super(NameUtils.getShortName(type.name), type)
    }

    MetaEntity(String name, Class type) {
        super(name, type)
    }

    static MetaEntity of(List fields){
        def ment = new MetaEntity()
        fields.each { ment.metaProps[it] = new MetaProp(it, null) }
        return ment
    }

    /**
     * Filters the props to only the ones that are association and have a nested includes
     */
    Map getMetaEntityProps(){
        return metaProps.findAll {  it.value instanceof MetaEntity } as Map
    }

    /**
     * Filters the props to only the ones that dont have nested includes, basic types.
     */
    Set getBasicMetaProps(){
        return metaProps.findAll{ !(it.value instanceof MetaEntity) }.keySet() as Set
    }

    /**
     * gets the class name with out prefix so can lookup the openapi schema
     */
    String getShortClassName(){
        return NameUtils.getShortName(className)
    }

    void addBlacklist(Set excludeFields) {
        this.excludes = excludeFields
        this.metaProps.keySet().removeAll(excludeFields)
    }

    /**
     * merges another MetaEntity fields and nested includes
     */
    void merge(MetaEntity toMerge) {
        this.metaProps.putAll(toMerge.metaProps)
        // if(toMerge.nestedIncludes) this.nestedIncludes.putAll(toMerge.nestedIncludes)
    }

    /**
     * convert to Map of Maps or MetaProps. Can be used to use for flatting or rendering to json.
     * Its is not json-schema or openapi spec compliant.
     * For UI its easier to
     */
    Map toMap() {
        Map mmiProps = [:] as Map
        for (String key in metaProps.keySet()) {
            def val = metaProps[key]
            if(val instanceof MetaEntity) {
                mmiProps[key] = val.toMap()
            } else {
                mmiProps[key] = val
            }
        }
        return mmiProps
    }

    /**
     * convert to openApi schema in map form, ready to be dumped to json parser.
     *
     * Example:
     *    MetaEntity src                    Schema yml
     * -----------------------------------------------
     * name: "Org"                      ->    name: Org
     * title: "Org"                     ->    title: Organizations
     * classType: yak.rally.Org         ->    type: object
     * metaProps: [                     ->    properties:
     *  [num: [                         ->      num:
     *    classType: java.lang.String   ->        type: string
     *    schema.maxLength: 50          ->        maxLength: 50
     *  ], [name: etc..                 ->      name: ...
     *
     */
    Map toSchemaMap() {
        return OapiUtils.toSchemaMap(this)
    }

    Map flatten() {
        Map bmap = toMap() as Map
        Map flatMap = MapFlattener.of(bmap).convertObjectToString(false).convertEmptyStringsToNull(false).flatten()
        return flatMap as Map
    }

    /**
     * returns a "flattened" list of the properties with dot notation for nested.
     * so mmIncludes with ['id', 'thing':[name:""]] will return ['id', 'thing.name'] etc...
     */
    Set flattenProps() {
        return flatten().keySet()
    }

    /**
     * Returns a flattened openapi shema map, if schema exists
     * TODO need to mock up a test for this here. NOTE: it is tested and used in gorm-tools.
     */
    Map flattenSchema(){
        Map flatMap = flatten()
        Map map = [:] as Map
        //iterate over and convert Schema to Map
        flatMap.each{String k, MetaProp metaProp->
            Map schemaMap = [:] as Map
            for(String attr: OapiUtils.schemaAttrs){
                def schema = metaProp.schema
                if(schema && schema[attr] != null){
                    schemaMap[attr] = schema[attr]
                }
            }
            map[k] = schemaMap
        }
        return map
    }

    @Override
    boolean equals(Object other) {
        if (other == null) return false
        if (this.is(other)) return true
        if (other instanceof MetaEntity) {
            return other.className == className && other.metaProps == metaProps
        }
        return false
    }
    @Override
    int hashCode() {
        int hashCode = HashCodeHelper.initHash()
        if (className) { hashCode = HashCodeHelper.updateHash(hashCode, className) }
        if (metaProps) { hashCode = HashCodeHelper.updateHash(hashCode, metaProps) }
        hashCode
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy