# Fri, 3 Jul 2026 11:20:33 GMT
---
version: 44
data:


- type: Credential
  attributes:
    displayName: XDM Lookup Table Credential
    description: Credential for XDM Lookup Table
    user: sa
    password: ""


- type: MappingTableContainer
  attributes:
    displayName: XDM Lookup Table
    description: Mapping table to use the xdm lookup tables
    variableName: lookupTable
    credential: XDM Lookup Table Credential


- type: AttributeGenerator
  attributes:
    displayName: ubsEmailGenerator
    description: |
          Generates an email address based on a seed, country and sex. Optionally a custom domain can be specified.
          The country parameter specifies the country/region of origin ('EN', 'DE', 'FR', 'IT', 'NL').
          The sex parameter specifies the gender ('M' for male, 'F' for female).
          Default domain is 'example.com'.
          *This generator requires the lookup tables to be installed.*
    scriptLanguage: "GROOVY"
    code: |
        import de.ubs.xdm3.batch.modification.CompositeKey
        import de.ubs.xdm3.batch.modification.MappingTableUtils
        
        def FIRST_NAME_TABLE = 'FIRST_NAMES'
        def LAST_NAME_TABLE = 'LAST_NAMES'
        def firstNameCountMap = [:]
        def lastNameCountMap = [:]
        def firstNameTotalCount = null
        def lastNameTotalCount = null
        def mappingTableUtils = null
        def seedVal
        def random
        
        void init() {
            mappingTableUtils = new MappingTableUtils(lookupTable)
            firstNameCountMap = mappingTableUtils.count(FIRST_NAME_TABLE, "KEY_01", "KEY_02")
            lastNameCountMap = mappingTableUtils.count(LAST_NAME_TABLE, "KEY_01")
            firstNameTotalCount = mappingTableUtils.count(FIRST_NAME_TABLE)
            lastNameTotalCount = mappingTableUtils.count(LAST_NAME_TABLE)
            seedVal = parseLong(parameters.seed, "seed")
            random = seedVal == null ? new Random() : new Random(seedVal)
        }
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            def seedHash = random.nextInt()
            def countryValue = parameters.country != null ? parameters.country.toString() : null
            def sexValue = parameters.sex != null ? parameters.sex.toString() : null
            def domain = parameters.domain == null ? "example.com" : parameters.domain.toString().toLowerCase()
        
            def firstNameResult
            if (countryValue != null && sexValue != null) {
                def firstNameCount = firstNameCountMap.get(new CompositeKey(countryValue, sexValue))
                if (firstNameCount == null || firstNameCount <= 0) {
                    throw new IllegalArgumentException("No entries for country ='" + countryValue + "' and sex ='" + sexValue + "'.")
                }
                def firstNameRowNumber = Math.floorMod(seedHash, firstNameCount) + 1
                firstNameResult = mappingTableUtils.get(FIRST_NAME_TABLE, firstNameRowNumber, countryValue, sexValue)
            } else {
                if (firstNameTotalCount == null || firstNameTotalCount <= 0) {
                    throw new IllegalArgumentException("No entries available in table " + FIRST_NAME_TABLE)
                }
                def globalFirstNameRowNumber = Math.floorMod(seedHash, firstNameTotalCount) + 1
                firstNameResult = resolveFirstNameWithoutCountrySex(globalFirstNameRowNumber)
            }
            if (!firstNameResult) throw new IllegalArgumentException("Failed to retrieve data from table " + FIRST_NAME_TABLE)
        
            def lastNameResult
            if (countryValue != null) {
                def lastNameCount = lastNameCountMap.get(new CompositeKey(countryValue))
                if (lastNameCount == null || lastNameCount <= 0) {
                    throw new IllegalArgumentException("No entries for country ='" + countryValue + "'.")
                }
                def lastNameRowNumber = Math.floorMod(seedHash, lastNameCount) + 1
                lastNameResult = mappingTableUtils.get(LAST_NAME_TABLE, lastNameRowNumber, countryValue)
            } else {
                if (lastNameTotalCount == null || lastNameTotalCount <= 0) {
                    throw new IllegalArgumentException("No entries available in table " + LAST_NAME_TABLE)
                }
                def globalLastNameRowNumber = Math.floorMod(seedHash, lastNameTotalCount) + 1
                lastNameResult = resolveLastNameWithoutCountry(globalLastNameRowNumber)
            }
            if (!lastNameResult) throw new IllegalArgumentException("Failed to retrieve data from table " + LAST_NAME_TABLE)
            return "${firstNameResult[0]}.${lastNameResult[0]}@${domain}"
        }
        
        private def resolveFirstNameWithoutCountrySex(def globalRowNumber) {
            int offset = 0
            for (entry in firstNameCountMap.entrySet().sort { e -> e.key.getData().collect { it.toString() }.join('|') }) {
                def count = entry.value
                if (count == null || count <= 0) {
                    continue
                }
                if (globalRowNumber <= offset + count) {
                    def localRowNumber = globalRowNumber - offset
                    def keyData = entry.key.getData()
                    return mappingTableUtils.get(FIRST_NAME_TABLE, localRowNumber, keyData[0], keyData[1])
                }
                offset += count
            }
            return null
        }
        
        private def resolveLastNameWithoutCountry(def globalRowNumber) {
            int offset = 0
            for (entry in lastNameCountMap.entrySet().sort { e -> e.key.getData()[0].toString() }) {
                def count = entry.value
                if (count == null || count <= 0) {
                    continue
                }
                if (globalRowNumber <= offset + count) {
                    def localRowNumber = globalRowNumber - offset
                    def keyData = entry.key.getData()
                    return mappingTableUtils.get(LAST_NAME_TABLE, localRowNumber, keyData[0])
                }
                offset += count
            }
            return null
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null || attribute.type != FieldDefinition.FieldType.STRING) {
                return 0
            }
        
            int baseScore = 5
            int nameBonus = getFieldNameBonus(attribute.name, ["email", "e-mail", "mail"])
            return baseScore + nameBonus
        }
        
        private int getFieldNameBonus(String fieldName, List<String> keywords) {
            if (fieldName == null || fieldName.isEmpty()) {
                return 0
            }
        
            String lowerName = fieldName.toLowerCase()
            for (String keyword : keywords) {
                String lowerKeyword = keyword.toLowerCase()
        
                if (lowerName.equals(lowerKeyword)) {
                    return 85
                }
        
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||
                        lowerName.startsWith(lowerKeyword) ||
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 40
                }
        
                if (lowerName.contains(lowerKeyword)) {
                    return 15
                }
            }
        
            return 0
        }
        
        private Long parseLong(def value, String parameterName) {
            if (value == null) {
                return null
            }
            if (value instanceof Number) {
                return value.longValue()
            }
            if (value instanceof String) {
                try {
                    return Long.parseLong(value)
                } catch (NumberFormatException e) {
                    return (long) value.hashCode()
                }
            }
            throw new IllegalArgumentException("Unsupported type for parameter '${parameterName}': ${value.getClass().name}")
        }
        
        def close() {
            mappingTableUtils.close()
        }
    mappingTableContainers:
      - "XDM Lookup Table"
    mappingTableAccessMode: READ

- type: AttributeGenerator
  attributes:
    displayName: ubsFirstNameGenerator
    description: |
          Generates a first name based on the specified seed value.
          The country parameter specifies the country/region of origin ('EN', 'DE', 'FR', 'IT', 'NL').
          The sex parameter specifies the gender ('M' for male, 'F' for female).
          *This generator requires the lookup tables to be installed.*
    scriptLanguage: "GROOVY"
    code: |
        import de.ubs.xdm3.batch.modification.CompositeKey
        import de.ubs.xdm3.batch.modification.MappingTableUtils
        
        def TABLE_NAME = 'FIRST_NAMES'
        def countMap = [:]
        def totalCount = null
        def mappingTableUtils = null
        def seedVal
        def random
        
        void init() {
            mappingTableUtils = new MappingTableUtils(lookupTable)
            countMap = mappingTableUtils.count(TABLE_NAME, "KEY_01", "KEY_02")
            totalCount = mappingTableUtils.count(TABLE_NAME)
            seedVal = parseLong(parameters.seed, "seed")
            random = seedVal == null ? new Random() : new Random(seedVal)
        }
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            def seedHash = random.nextInt()
            def countryValue = parameters.country != null ? parameters.country.toString() : null
            def sexValue = parameters.sex != null ? parameters.sex.toString() : null
            def result
            if (countryValue != null && sexValue != null) {
                def count = countMap.get(new CompositeKey(countryValue, sexValue))
                if (count == null || count <= 0) {
                    throw new IllegalArgumentException("No entries for country ='" + countryValue + "' and sex ='" + sexValue + "'.")
                }
                def rowNumber = Math.floorMod(seedHash, count) + 1
                result = mappingTableUtils.get(TABLE_NAME, rowNumber, countryValue, sexValue)
            } else {
                if (totalCount == null || totalCount <= 0) {
                    throw new IllegalArgumentException("No entries available in table '" + TABLE_NAME + "'.")
                }
                def globalRowNumber = Math.floorMod(seedHash, totalCount) + 1
                result = resolveRowWithoutCountrySex(globalRowNumber)
            }
            if (result == null) {
                throw new IllegalArgumentException("Failed to retrieve data from table '" + TABLE_NAME + "'.")
            }
            return result[0]
        }
        
        private def resolveRowWithoutCountrySex(def globalRowNumber) {
            int offset = 0
            for (entry in countMap.entrySet().sort { e -> e.key.getData().collect { it.toString() }.join('|') }) {
                def count = entry.value
                if (count == null || count <= 0) {
                    continue
                }
                if (globalRowNumber <= offset + count) {
                    def localRowNumber = globalRowNumber - offset
                    def keyData = entry.key.getData()
                    return mappingTableUtils.get(TABLE_NAME, localRowNumber, keyData[0], keyData[1])
                }
                offset += count
            }
            return null
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null || attribute.type != FieldDefinition.FieldType.STRING) {
                return 0
            }
        
            int baseScore = 5
            int nameBonus = getFieldNameBonus(attribute.name, ["firstname", "first_name", "givenname", "given_name", "forename", "vorname"])
            return baseScore + nameBonus
        }
        
        private int getFieldNameBonus(String fieldName, List<String> keywords) {
            if (fieldName == null || fieldName.isEmpty()) {
                return 0
            }
        
            String lowerName = fieldName.toLowerCase()
            for (String keyword : keywords) {
                String lowerKeyword = keyword.toLowerCase()
        
                if (lowerName.equals(lowerKeyword)) {
                    return 85
                }
        
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||
                        lowerName.startsWith(lowerKeyword) ||
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 40
                }
        
                if (lowerName.contains(lowerKeyword)) {
                    return 15
                }
            }
        
            return 0
        }
        
        private Long parseLong(def value, String parameterName) {
            if (value == null) {
                return null
            }
            if (value instanceof Number) {
                return value.longValue()
            }
            if (value instanceof String) {
                try {
                    return Long.parseLong(value)
                } catch (NumberFormatException e) {
                    return (long) value.hashCode()
                }
            }
            throw new IllegalArgumentException("Unsupported type for parameter '${parameterName}': ${value.getClass().name}")
        }
        
        def close() {
            mappingTableUtils.close()
        }
    mappingTableContainers:
      - "XDM Lookup Table"
    mappingTableAccessMode: READ

- type: AttributeGenerator
  attributes:
    displayName: ubsLastNameGenerator
    description: |
          Generates a last name based on the specified seed value.
          The country parameter specifies the country/region of origin ('EN', 'DE', 'FR', 'IT', 'NL').
          Only a single country value can be specified (e.g. 'DE').
          *This generator requires the lookup tables to be installed.*
    scriptLanguage: "GROOVY"
    code: |
        import de.ubs.xdm3.batch.modification.CompositeKey
        import de.ubs.xdm3.batch.modification.MappingTableUtils
        
        def TABLE_NAME = 'LAST_NAMES'
        def countMap = [:]
        def totalCount = null
        def mappingTableUtils = null
        def seedVal
        def random
        
        void init() {
            mappingTableUtils = new MappingTableUtils(lookupTable)
            countMap = mappingTableUtils.count(TABLE_NAME, "KEY_01")
            totalCount = mappingTableUtils.count(TABLE_NAME)
            seedVal = parseLong(parameters.seed, "seed")
            random = seedVal == null ? new Random() : new Random(seedVal)
        }
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            def seedHash = random.nextInt()
            def countryValue = parameters.country != null ? parameters.country.toString() : null
            def result
            if (countryValue != null) {
                def count = countMap.get(new CompositeKey(countryValue))
                if (count == null || count <= 0) {
                    throw new IllegalArgumentException("No entries for country ='" + countryValue + "'.")
                }
                def rowNumber = Math.floorMod(seedHash, count) + 1
                result = mappingTableUtils.get(TABLE_NAME, rowNumber, countryValue)
            } else {
                if (totalCount == null || totalCount <= 0) {
                    throw new IllegalArgumentException("No entries available in table " + TABLE_NAME)
                }
                def globalRowNumber = Math.floorMod(seedHash, totalCount) + 1
                result = resolveRowWithoutCountry(globalRowNumber)
            }
            if (result == null) {
                throw new IllegalArgumentException("Failed to retrieve data from table " + TABLE_NAME)
            }
            return result[0]
        }
        
        private def resolveRowWithoutCountry(def globalRowNumber) {
            int offset = 0
            for (entry in countMap.entrySet().sort { e -> e.key.getData()[0].toString() }) {
                def count = entry.value
                if (count == null || count <= 0) {
                    continue
                }
                if (globalRowNumber <= offset + count) {
                    def localRowNumber = globalRowNumber - offset
                    def keyData = entry.key.getData()
                    return mappingTableUtils.get(TABLE_NAME, localRowNumber, keyData[0])
                }
                offset += count
            }
            return null
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null || attribute.type != FieldDefinition.FieldType.STRING) {
                return 0
            }
        
            int baseScore = 5
            int nameBonus = getFieldNameBonus(attribute.name, ["lastname", "last_name", "surname", "familyname", "family_name", "nachname"])
            return baseScore + nameBonus
        }
        
        private int getFieldNameBonus(String fieldName, List<String> keywords) {
            if (fieldName == null || fieldName.isEmpty()) {
                return 0
            }
        
            String lowerName = fieldName.toLowerCase()
            for (String keyword : keywords) {
                String lowerKeyword = keyword.toLowerCase()
        
                if (lowerName.equals(lowerKeyword)) {
                    return 85
                }
        
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||
                        lowerName.startsWith(lowerKeyword) ||
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 40
                }
        
                if (lowerName.contains(lowerKeyword)) {
                    return 15
                }
            }
        
            return 0
        }
        
        private Long parseLong(def value, String parameterName) {
            if (value == null) {
                return null
            }
            if (value instanceof Number) {
                return value.longValue()
            }
            if (value instanceof String) {
                try {
                    return Long.parseLong(value)
                } catch (NumberFormatException e) {
                    return (long) value.hashCode()
                }
            }
            throw new IllegalArgumentException("Unsupported type for parameter '${parameterName}': ${value.getClass().name}")
        }
        
        def close() {
            mappingTableUtils.close()
        }
    mappingTableContainers:
      - "XDM Lookup Table"
    mappingTableAccessMode: READ
