/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * 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 org.apache.atlas.omrs.localrepository.repositorycontentmanager;

import org.apache.atlas.omrs.auditlog.OMRSAuditCode;
import org.apache.atlas.omrs.auditlog.OMRSAuditLog;
import org.apache.atlas.omrs.auditlog.OMRSAuditingComponent;
import org.apache.atlas.omrs.eventmanagement.*;
import org.apache.atlas.omrs.eventmanagement.events.OMRSTypeDefEventProcessor;
import org.apache.atlas.omrs.ffdc.OMRSErrorCode;
import org.apache.atlas.omrs.ffdc.exception.*;
import org.apache.atlas.omrs.localrepository.repositoryconnector.LocalOMRSRepositoryConnector;
import org.apache.atlas.omrs.metadatacollection.OMRSMetadataCollection;
import org.apache.atlas.omrs.metadatacollection.properties.instances.EntityDetail;
import org.apache.atlas.omrs.metadatacollection.properties.instances.InstanceStatus;
import org.apache.atlas.omrs.metadatacollection.properties.instances.InstanceType;
import org.apache.atlas.omrs.metadatacollection.properties.instances.Relationship;
import org.apache.atlas.omrs.metadatacollection.properties.typedefs.*;
import org.apache.atlas.omrs.metadatacollection.repositoryconnector.OMRSRepositoryConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * OMRSRepositoryContentManager supports an in-memory cache of TypeDefs for the local server.  It is used by the OMRS
 * components for constructing metadata instances with valid types.   It ensures that the TypeDefs used in other
 * members of the open metadata repository cohorts that the local server is also a member of are consistent with the
 * local repository.
 *
 * OMRSRepositoryContentManager plays a central role in ensuring the integrity of the metadata in the local repository.
 * It is called from multiple components at different points in the processing.  It presents a different interface
 * to each of these components that is specialized to their needs.
 * <ul>
 *     <li>
 *         OMRSTypeDefEventProcessor - processes inbound events from remote members of the open metadata
 *         repository cohorts that the local repository is connected to.  These incoming TypeDef events need to
 *         be validated against the types used locally and either saved or discarded depending on the exchange rule
 *         setting.
 *     </li>
 *     <li>
 *         OMRSTypeDefManager - provides maintenance methods for managing the TypeDefs in the local cache.
 *     </li>
 *     <li>
 *         OMRSTypeDefHelper - provides methods to help OMRS connectors and adapters manage TypeDefs.
 *     </li>
 *     <li>
 *         OMRSTypeDefValidator - provides methods to validate TypeDefs.
 *     </li>
 *     <li>
 *         OMRSInstanceValidator - provides methods to help validate instances.
 *     </li>
 * </ul>
 */
public class OMRSRepositoryContentManager implements OMRSTypeDefEventProcessor,
                                                     OMRSTypeDefManager,
                                                     OMRSTypeDefHelper,
                                                     OMRSTypeDefValidator,
                                                     OMRSInstanceValidator
{
    private LocalOMRSRepositoryConnector      localRepositoryConnector       = null;
    private OMRSRepositoryEventManager        outboundRepositoryEventManager = null;
    private OMRSRepositoryConnector           realLocalConnector             = null;
    private OMRSRepositoryEventExchangeRule   saveExchangeRule               = null;
    private String                            openTypesOriginGUID            = null;
    private HashMap<String, TypeDef>          knownTypes                     = new HashMap<>();
    private HashMap<String, AttributeTypeDef> knownAttributeTypes            = new HashMap<>();
    private HashMap<String, TypeDef>          activeTypes                    = new HashMap<>();
    private HashMap<String, AttributeTypeDef> activeAttributeTypes           = new HashMap<>();



    /*
     * The audit log provides a verifiable record of the open metadata archives that have been loaded into
     * the open metadata repository.  The Logger is for standard debug.
     */
    private static final OMRSAuditLog auditLog = new OMRSAuditLog(OMRSAuditingComponent.TYPEDEF_MANAGER);
    private static final Logger       log      = LoggerFactory.getLogger(OMRSRepositoryContentManager.class);



    /**
     * Default constructor
     */
    public OMRSRepositoryContentManager()
    {

    }


    /**
     * Saves all of the information necessary to process incoming TypeDef events.
     *
     * @param localRepositoryConnector - connector to the local repository
     * @param realLocalConnector - connector to the real local repository - used for processing TypeDef events
     * @param saveExchangeRule - rule that determines which events to process.
     * @param outboundRepositoryEventManager - event manager to call for outbound events - used to send out reports
     *                                       of conflicting TypeDefs
     */
    public void setupEventProcessor(LocalOMRSRepositoryConnector      localRepositoryConnector,
                                    OMRSRepositoryConnector           realLocalConnector,
                                    OMRSRepositoryEventExchangeRule   saveExchangeRule,
                                    OMRSRepositoryEventManager        outboundRepositoryEventManager)
    {
        this.localRepositoryConnector = localRepositoryConnector;
        this.realLocalConnector = realLocalConnector;
        this.saveExchangeRule = saveExchangeRule;
        this.outboundRepositoryEventManager = outboundRepositoryEventManager;
    }


    /**
     * Save the unique identifier of the open metadata archive.  This is stored in the origin property of
     * all of the open metadata types.  It is needed to support the isOpenType() method.
     *
     * @param openMetadataTypesGUID - unique identifier for the open metadata type's archive
     */
    public void setOpenMetadataTypesOriginGUID(String openMetadataTypesGUID)
    {
        openTypesOriginGUID = openMetadataTypesGUID;
    }


    /*
     * ========================
     * OMRSTypeDefManager
     */

    /**
     * Cache a definition of a new TypeDef.  This method assumes the TypeDef has been successfully added to the
     * local repository already and all that is needed is to maintain the cached list of types
     *
     * @param sourceName - source of the request (used for logging)
     * @param newTypeDef - TypeDef structure describing the new TypeDef.
     */
    public void addTypeDef(String  sourceName, TypeDef      newTypeDef)
    {
        if (this.validTypeDef(sourceName, newTypeDef))
        {
            knownTypes.put(newTypeDef.getName(), newTypeDef);
            if (localRepositoryConnector != null)
            {
                activeTypes.put(newTypeDef.getName(), newTypeDef);

                if (log.isDebugEnabled())
                {
                    log.debug("New Active Type " + newTypeDef.getName() + " from " + sourceName, newTypeDef);
                }
            }
        }
    }


    /**
     * Cache a definition of a new AttributeTypeDef.
     *
     * @param sourceName - source of the request (used for logging)
     * @param newAttributeTypeDef - AttributeTypeDef structure describing the new TypeDef.
     */
    public void addAttributeTypeDef(String  sourceName, AttributeTypeDef newAttributeTypeDef)
    {
        if (this.validAttributeTypeDef(sourceName, newAttributeTypeDef))
        {
            knownAttributeTypes.put(newAttributeTypeDef.getName(), newAttributeTypeDef);
            if (localRepositoryConnector != null)
            {
                activeAttributeTypes.put(newAttributeTypeDef.getName(), newAttributeTypeDef);

                if (log.isDebugEnabled())
                {
                    log.debug("New Active Attribute Type " + newAttributeTypeDef.getName() + " from " + sourceName, newAttributeTypeDef);
                }
            }
        }
    }


    /**
     * Update one or more properties of a cached TypeDef.  This method assumes the TypeDef has been successfully
     * updated in the local repository already and all that is needed is to maintain the cached list of types
     *
     * @param sourceName - source of the request (used for logging)
     * @param typeDef - TypeDef structure.
     */
    public void updateTypeDef(String  sourceName, TypeDef   typeDef)
    {
        if (this.validTypeDef(sourceName, typeDef))
        {
            knownTypes.put(typeDef.getName(), typeDef);
            if (localRepositoryConnector != null)
            {
                activeTypes.put(typeDef.getName(), typeDef);

                if (log.isDebugEnabled())
                {
                    log.debug("Updated Active Type " + typeDef.getName() + " from " + sourceName, typeDef);
                }
            }
        }
    }


    /**
     * Update one or more properties of a cached TypeDef.
     *
     * @param sourceName - source of the request (used for logging)
     * @param attributeTypeDef - AttributeTypeDef structure.
     */
    public void updateAttributeTypeDef(String  sourceName, AttributeTypeDef   attributeTypeDef)
    {
        if (this.validAttributeTypeDef(sourceName, attributeTypeDef))
        {
            knownAttributeTypes.put(attributeTypeDef.getName(), attributeTypeDef);
            if (localRepositoryConnector != null)
            {
                activeAttributeTypes.put(attributeTypeDef.getName(), attributeTypeDef);

                if (log.isDebugEnabled())
                {
                    log.debug("Updated Active AttributeType " + attributeTypeDef.getName() + " from " + sourceName, attributeTypeDef);
                }
            }
        }
    }



    /**
     * Delete a cached TypeDef.
     *
     * @param sourceName - source of the request (used for logging)
     * @param obsoleteTypeDefGUID - String unique identifier for the TypeDef.
     * @param obsoleteTypeDefName - String unique name for the TypeDef.
     */
    public void deleteTypeDef(String    sourceName,
                              String    obsoleteTypeDefGUID,
                              String    obsoleteTypeDefName)
    {
        if (this.validTypeId(sourceName, obsoleteTypeDefGUID, obsoleteTypeDefName))
        {
            knownTypes.remove(obsoleteTypeDefName);

            if (localRepositoryConnector != null)
            {
                activeTypes.remove(obsoleteTypeDefName);

                if (log.isDebugEnabled())
                {
                    log.debug("Deleted Active Type " + obsoleteTypeDefName + " from " + sourceName);
                }
            }
        }
    }


    /**
     * Delete a cached AttributeTypeDef.
     *
     * @param sourceName - source of the request (used for logging)
     * @param obsoleteTypeDefGUID - String unique identifier for the AttributeTypeDef.
     * @param obsoleteTypeDefName - String unique name for the AttributeTypeDef.
     */
    public void deleteAttributeTypeDef(String    sourceName,
                                       String    obsoleteTypeDefGUID,
                                       String    obsoleteTypeDefName)
    {

    }


    /**
     * Change the identifiers for a TypeDef.
     *
     * @param sourceName - source of the request (used for logging).
     * @param originalTypeDefGUID - TypeDef's original unique identifier.
     * @param originalTypeDefName - TypeDef's original unique name.
     * @param newTypeDef - updated TypeDef with new identifiers.
     */
    public void reIdentifyTypeDef(String   sourceName,
                                  String   originalTypeDefGUID,
                                  String   originalTypeDefName,
                                  TypeDef  newTypeDef)
    {
        // TODO
    }


    /**
     * Change the identifiers for an AttributeTypeDef.
     *
     * @param sourceName - source of the request (used for logging).
     * @param originalAttributeTypeDefGUID - AttributeTypeDef's original unique identifier.
     * @param originalAttributeTypeDefName - AttributeTypeDef's original unique name.
     * @param newAttributeTypeDef - updated AttributeTypeDef with new identifiers
     */
    public void reIdentifyAttributeTypeDef(String            sourceName,
                                           String            originalAttributeTypeDefGUID,
                                           String            originalAttributeTypeDefName,
                                           AttributeTypeDef  newAttributeTypeDef)
    {
        // TODO
    }


    /**
     * Return the list of property names defined for this TypeDef.
     *
     * @param sourceName - source of the request (used for logging)
     * @param typeDef - type definition to work with.
     * @return list of String property names
     * @throws TypeErrorException - there is an issue with the TypeDef.
     */
    private ArrayList<String>  getPropertyNames(String sourceName, TypeDef   typeDef) throws TypeErrorException
    {
        final  String                  methodName = "getPropertyNames()";
        ArrayList<String>              propertyNames = null;

        if (validTypeDef(sourceName, typeDef))
        {
            List<TypeDefAttribute>    propertiesDefinition = typeDef.getPropertiesDefinition();

            if ((propertiesDefinition != null) && (propertiesDefinition.size() > 0))
            {
                propertyNames = new ArrayList<>();

                for (TypeDefAttribute  propertyDefinition : propertiesDefinition)
                {
                    if (propertyDefinition != null)
                    {
                        String propertyName = propertyDefinition.getAttributeName();

                        if (propertyName != null)
                        {
                            if (log.isDebugEnabled())
                            {
                                log.debug(typeDef.getName()  + " from " + sourceName + " has property " + propertyName);
                            }
                            propertyNames.add(propertyName);
                        }
                        else
                        {
                            OMRSErrorCode errorCode = OMRSErrorCode.BAD_TYPEDEF_ATTRIBUTE_NAME;
                            String errorMessage = errorCode.getErrorMessageId()
                                                + errorCode.getFormattedErrorMessage(sourceName);

                            throw new TypeErrorException(errorCode.getHTTPErrorCode(),
                                                         this.getClass().getName(),
                                                         methodName,
                                                         errorMessage,
                                                         errorCode.getSystemAction(),
                                                         errorCode.getUserAction());
                        }
                    }
                    else
                    {
                        OMRSErrorCode errorCode = OMRSErrorCode.NULL_TYPEDEF_ATTRIBUTE;
                        String errorMessage = errorCode.getErrorMessageId()
                                            + errorCode.getFormattedErrorMessage(sourceName);

                        throw new TypeErrorException(errorCode.getHTTPErrorCode(),
                                                     this.getClass().getName(),
                                                     methodName,
                                                     errorMessage,
                                                     errorCode.getSystemAction(),
                                                     errorCode.getUserAction());
                    }
                }

                /*
                 * If no property names have been extracted then remove the array.
                 */
                if (propertyNames.size() == 0)
                {
                    propertyNames = null;
                }
            }
        }
        else
        {
            OMRSErrorCode errorCode = OMRSErrorCode.BAD_TYPEDEF;
            String errorMessage = errorCode.getErrorMessageId()
                                + errorCode.getFormattedErrorMessage(sourceName);

            throw new TypeErrorException(errorCode.getHTTPErrorCode(),
                                         this.getClass().getName(),
                                         methodName,
                                         errorMessage,
                                         errorCode.getSystemAction(),
                                         errorCode.getUserAction());
        }

        return propertyNames;
    }


    /**
     * Return identifiers for the TypeDef that matches the supplied type name.  If the type name is not recognized,
     * null is returned.
     *
     * @param sourceName - source of the request (used for logging)
     * @param category - category of the instance type required.
     * @param typeName - String type name.
     * @return InstanceType object containing TypeDef properties such as unique identifier (guid),
     *                             typeDef name and version name
     * @throws TypeErrorException - the type name is not a recognized type or is of the wrong category or there is
     *                              a problem with the cached TypeDef.
     */
    public InstanceType getInstanceType(String           sourceName,
                                        TypeDefCategory  category,
                                        String           typeName) throws TypeErrorException
    {
        final  String                  methodName = "getInstanceType()";

        if (isValidTypeCategory(sourceName, category, typeName))
        {
            TypeDef typeDef = knownTypes.get(typeName);

            if (typeDef != null)
            {
                InstanceType    instanceType = new InstanceType();

                instanceType.setTypeDefCategory(category);
                instanceType.setTypeDefGUID(typeDef.getGUID());
                instanceType.setTypeDefName(typeDef.getName());
                instanceType.setTypeDefVersion(typeDef.getVersion());
                instanceType.setTypeDefDescription(typeDef.getDescription());
                instanceType.setTypeDefDescriptionGUID(typeDef.getDescriptionGUID());

                /*
                 * Extract the properties for this TypeDef.  These will be augmented with property names
                 * from the super type(s).
                 */
                ArrayList<String>      propertyNames = this.getPropertyNames(sourceName, typeDef);

                /*
                 * If propertyNames is null, it means the TypeDef has no attributes.  However the superType
                 * may have attributes and so we need an array to accumulate the attributes into.
                 */
                if (propertyNames == null)
                {
                    propertyNames = new ArrayList<>();
                }

                /*
                 * Work up the TypeDef hierarchy extracting the property names and super type names.
                 */
                ArrayList<TypeDefLink> superTypes    = new ArrayList<>();
                TypeDefLink            superTypeLink = typeDef.getSuperType();

                while (superTypeLink != null)
                {
                    String                 superTypeName = superTypeLink.getName();

                    if (superTypeName != null)
                    {
                        if (log.isDebugEnabled())
                        {
                            log.debug(typeName + " from " + sourceName + " has super type " + superTypeName);
                        }

                        superTypes.add(superTypeLink);

                        TypeDef                superTypeDef  = knownTypes.get(superTypeName);

                        if (superTypeDef != null)
                        {
                            ArrayList<String>      superTypePropertyNames = this.getPropertyNames(sourceName, superTypeDef);

                            if (superTypePropertyNames != null)
                            {
                                propertyNames.addAll(0, superTypePropertyNames);
                            }
                        }

                        superTypeLink = superTypeDef.getSuperType();
                    }
                    else
                    {
                        superTypeLink = null;
                    }
                }

                /*
                 * Make sure empty lists are converted to nulls
                 */

                if (superTypes.size() > 0)
                {
                    instanceType.setTypeDefSuperTypes(superTypes);
                }

                if (propertyNames.size() > 0)
                {
                    instanceType.setValidInstanceProperties(propertyNames);
                }

                return instanceType;
            }
            else
            {
                OMRSErrorCode errorCode = OMRSErrorCode.TYPEDEF_NOT_KNOWN;
                String errorMessage = errorCode.getErrorMessageId()
                                    + errorCode.getFormattedErrorMessage(sourceName, typeName, category.getTypeName());

                throw new TypeErrorException(errorCode.getHTTPErrorCode(),
                                             this.getClass().getName(),
                                             methodName,
                                             errorMessage,
                                             errorCode.getSystemAction(),
                                             errorCode.getUserAction());
            }
        }
        else
        {
            OMRSErrorCode errorCode = OMRSErrorCode.BAD_CATEGORY_FOR_TYPEDEF_ATTRIBUTE;
            String errorMessage = errorCode.getErrorMessageId()
                                + errorCode.getFormattedErrorMessage(sourceName, typeName, category.getTypeName());

            throw new TypeErrorException(errorCode.getHTTPErrorCode(),
                                         this.getClass().getName(),
                                         methodName,
                                         errorMessage,
                                         errorCode.getSystemAction(),
                                         errorCode.getUserAction());
        }
    }


    /**
     * Return a boolean indicating that the type name matches the category.
     *
     * @param sourceName - source of the request (used for logging)
     * @param category - TypeDefCategory enum value to test
     * @param typeName - type name to test
     * @return - boolean flag indicating that the type name is of the specified category
     * @throws TypeErrorException - the type name is not a recognized type or there is
     *                              a problem with the cached TypeDef.
     */
    public boolean    isValidTypeCategory(String            sourceName,
                                          TypeDefCategory   category,
                                          String            typeName) throws TypeErrorException
    {
        if (category == null)
        {
            // TODO throw logic error
        }

        if (typeName == null)
        {
            // TODO throw logic error
        }

        TypeDef   typeDef = knownTypes.get(typeName);

        if (typeDef != null)
        {
            TypeDefCategory  retrievedTypeDefCategory = typeDef.getCategory();

            if (retrievedTypeDefCategory != null)
            {
                if (category.getTypeCode() == retrievedTypeDefCategory.getTypeCode())
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                // TODO Throw logic error
            }
        }

        return false;
    }


    /**
     * Return boolean indicating if a classification type can be applied to a specified entity.  This
     * uses the list of valid entity types located in the ClassificationDef.
     *
     * @param sourceName - source of the request (used for logging)
     * @param classificationTypeName - name of the classification's type (ClassificationDef)
     * @param entityTypeName - name of the entity's type (EntityDef)
     * @return boolean indicating if the classification is valid for the entity.
     */
    public boolean    isValidClassificationForEntity(String  sourceName,
                                                     String  classificationTypeName,
                                                     String  entityTypeName)
    {
        try
        {
            if ((isValidTypeCategory(sourceName, TypeDefCategory.CLASSIFICATION_DEF, classificationTypeName)) &&
                (isValidTypeCategory(sourceName, TypeDefCategory.ENTITY_DEF, entityTypeName)))
            {
                ClassificationDef  classificationTypeDef = (ClassificationDef)knownTypes.get(classificationTypeName);

                if (classificationTypeDef != null)
                {
                    List<TypeDefLink>   entityDefs = classificationTypeDef.getValidEntityDefs();

                    if (entityDefs == null)
                    {
                        /*
                         * The classification has no restrictions on which entities it can be attached to.
                         */
                        return true;
                    }
                    else
                    {
                        /*
                         * The classification can only be attached to the entities listed.  Note an empty list
                         * means the classification can not be attached to any entity and it is effectively useless.
                         */
                        for (TypeDefLink  allowedEntity : entityDefs)
                        {
                            if (allowedEntity != null)
                            {
                                if (entityTypeName.equals(allowedEntity.getName()))
                                {
                                    return true;
                                }
                            }
                        }

                        return false;
                    }
                }
                else
                {
                    // TODO log audit record - logic error
                    return false;
                }
            }
            else
            {
                return false;
            }
        }
        catch (TypeErrorException   typeError)
        {
            // TODO log audit record - invalid Types
            return false;
        }
        catch (ClassCastException   castError)
        {
            // TODO log audit record - logic error - category not matching TypeDef instance type
            return false;
        }
    }


    /**
     * Return the list of valid InstanceStatus states that instances of this type can handle.
     *
     * @param sourceName - source of the request (used for logging)
     * @param typeName - name of the type
     * @return list of InstanceStatus enums
     * @throws TypeErrorException - the type name is not recognized.
     */
    public List<InstanceStatus> getValidStatusList(String  sourceName, String typeName) throws TypeErrorException
    {
        if (typeName == null)
        {
            // TODO throw TypeError Exception
        }

        TypeDef   typeDef = knownTypes.get(typeName);

        if (typeDef == null)
        {
            // TODO throw TypeError exception
        }

        return typeDef.getValidInstanceStatusList();
    }


    /**
     * Return the initial status value to use for an instance of the supplied type.
     *
     * @param sourceName - source of the request (used for logging)
     * @param typeName - name of the type to extract the initial status from.
     * @return InstanceStatus enum
     * @throws TypeErrorException - the type name is not recognized.
     */
    public InstanceStatus getInitialStatus(String  sourceName, String typeName) throws TypeErrorException
    {
        if (typeName == null)
        {
            // TODO throw TypeError Exception
        }

        TypeDef   typeDef = knownTypes.get(typeName);

        if (typeDef == null)
        {
            // TODO throw TypeError exception
        }

        return typeDef.getInitialStatus();
    }


    /**
     * Return the URL string to use for direct access to the metadata instance.  This can be used for
     * entities and relationships.  However, not all servers support direct access, in which case, this
     * URL is null.
     *
     * @param sourceName - source of the request (used for logging)
     * @param guid - unique identifier for the instance.
     * @return String URL with placeholder for variables such as userId.
     */
    public String getInstanceURL(String  sourceName, String guid)
    {
        // TODO Need to work out what instance URL's look like for OMRS instances.  These URLs will be supported
        // TODO by the REST API
        return null;
    }


    /*
     * ========================
     * OMRSTypeDefHelper
     */


    /**
     * Return the list of typedefs known by the local repository.
     *
     * @return TypeDef gallery
     */
    public TypeDefGallery   getActiveTypeDefGallery()
    {
        TypeDefGallery               typeDefGallery               = new TypeDefGallery();

        if (! activeAttributeTypes.isEmpty())
        {
            typeDefGallery.setAttributeTypeDefs(new ArrayList<>(activeAttributeTypes.values()));
        }

        if (! activeTypes.isEmpty())
        {
            typeDefGallery.setTypeDefs(new ArrayList<>(activeTypes.values()));
        }

        return typeDefGallery;
    }


    /**
     * Return the list of typedefs known by the local repository.
     *
     * @return TypeDef gallery
     */
    public TypeDefGallery   getKnownTypeDefGallery()
    {
        TypeDefGallery               typeDefGallery               = new TypeDefGallery();

        if (! knownAttributeTypes.isEmpty())
        {
            typeDefGallery.setAttributeTypeDefs(new ArrayList<>(knownAttributeTypes.values()));
        }

        if (! knownTypes.isEmpty())
        {
            typeDefGallery.setTypeDefs(new ArrayList<>(knownTypes.values()));
        }

        return typeDefGallery;
    }


    /**
     * Return the TypeDef identified by the name supplied by the caller.  This is used in the connectors when
     * validating the actual types of the repository with the known open metadata types - looking specifically
     * for types of the same name but with different content.
     *
     * @param sourceName - source of the request (used for logging)
     * @param typeDefName - unique name for the TypeDef
     * @return TypeDef object or null if TypeDef is not known.
     */
    public TypeDef  getTypeDefByName (String    sourceName,
                                      String    typeDefName)
    {
        return knownTypes.get(typeDefName);
    }


    /**
     * Return the AttributeTypeDef identified by the name supplied by the caller.  This is used in the connectors when
     * validating the actual types of the repository with the known open metadata types - looking specifically
     * for types of the same name but with different content.
     *
     * @param sourceName - source of the request (used for logging)
     * @param attributeTypeDefName - unique name for the TypeDef
     * @return AttributeTypeDef object or null if AttributeTypeDef is not known.
     */
    public AttributeTypeDef getAttributeTypeDefByName (String    sourceName,
                                                       String    attributeTypeDefName)
    {
        return knownAttributeTypes.get(attributeTypeDefName);
    }


    /**
     * Return the TypeDefs identified by the name supplied by the caller.  The TypeDef name may have wild
     * card characters in it such as * and & which is why the results are returned in a list.
     *
     * @param sourceName - source of the request (used for logging)
     * @param typeDefName - unique name for the TypeDef
     * @return TypeDef object or null if TypeDef is not known.
     */
    public TypeDefGallery getActiveTypesByWildCardName (String    sourceName,
                                                        String    typeDefName)
    {
        // TODO
        return null;
    }


    /**
     * Return the TypeDef identified by the guid supplied by the caller.  This call is used when
     * retrieving a type that only the guid is known.
     *
     * @param sourceName - source of the request (used for logging)
     * @param typeDefGUID - unique identifier for the TypeDef
     * @return TypeDef object
     * @throws TypeErrorException - unknown or invalid type
     */
    public TypeDef  getTypeDef (String    sourceName,
                                String    typeDefGUID) throws TypeErrorException
    {
        // TODO
        return null;
    }


    /**
     * Return the AttributeTypeDef identified by the guid and name supplied by the caller.  This call is used when
     * retrieving a type that only the guid is known.
     *
     * @param sourceName - source of the request (used for logging)
     * @param attributeTypeDefGUID - unique identifier for the AttributeTypeDef
     * @return TypeDef object
     * @throws TypeErrorException - unknown or invalid type
     */
    public AttributeTypeDef  getAttributeTypeDef (String    sourceName,
                                                  String    attributeTypeDefGUID) throws TypeErrorException
    {
        // TODO
        return null;
    }

    /**
     * Return the TypeDef identified by the guid and name supplied by the caller.  This call is used when
     * retrieving a type that should exist.  For example, retrieving the type of a metadata instance.
     *
     * @param sourceName - source of the request (used for logging)
     * @param typeDefGUID - unique identifier for the TypeDef
     * @param typeDefName - unique name for the TypeDef
     * @return TypeDef object or null if the 
     * @throws TypeErrorException - invalid type
     */
    public TypeDef  getTypeDef (String    sourceName,
                                String    typeDefGUID,
                                String    typeDefName) throws TypeErrorException
    {
        final String methodName = "getTypeDef()";

        if (validTypeId(sourceName, typeDefGUID, typeDefName))
        {
            return knownTypes.get(typeDefName);
        }
        else
        {
            OMRSErrorCode errorCode = OMRSErrorCode.BAD_TYPEDEF;
            String errorMessage = errorCode.getErrorMessageId() + errorCode.getFormattedErrorMessage(sourceName);

            throw new TypeErrorException(errorCode.getHTTPErrorCode(),
                                         this.getClass().getName(),
                                         methodName,
                                         errorMessage,
                                         errorCode.getSystemAction(),
                                         errorCode.getUserAction());
        }
    }


    /**
     * Return the AttributeTypeDef identified by the guid and name supplied by the caller.  This call is used when
     * retrieving a type that should exist.  For example, retrieving the type definition of a metadata instance's
     * property.
     *
     * @param sourceName - source of the request (used for logging)
     * @param attributeTypeDefGUID - unique identifier for the AttributeTypeDef
     * @param attributeTypeDefName - unique name for the AttributeTypeDef
     * @return TypeDef object
     * @throws TypeErrorException - unknown or invalid type
     */
    public  AttributeTypeDef  getAttributeTypeDef (String    sourceName,
                                                   String    attributeTypeDefGUID,
                                                   String    attributeTypeDefName) throws TypeErrorException
    {
        final String methodName = "getAttributeTypeDef()";

        if (validTypeId(sourceName, attributeTypeDefGUID, attributeTypeDefName))
        {
            return knownAttributeTypes.get(attributeTypeDefName);
        }
        else
        {
            OMRSErrorCode errorCode = OMRSErrorCode.BAD_TYPEDEF;
            String errorMessage = errorCode.getErrorMessageId() + errorCode.getFormattedErrorMessage(sourceName);

            throw new TypeErrorException(errorCode.getHTTPErrorCode(),
                                         this.getClass().getName(),
                                         methodName,
                                         errorMessage,
                                         errorCode.getSystemAction(),
                                         errorCode.getUserAction());
        }
    }


    /*
     * =======================
     * OMRSTypeDefValidator
     */

    /**
     * Return a summary list of the TypeDefs supported by the local metadata repository.  This is
     * broadcast to the other servers/repositories in the cohort during the membership registration exchanges
     * managed by the cohort registries.
     *
     * @return TypeDefSummary list
     */
    public ArrayList<TypeDefSummary> getLocalTypeDefs()
    {
        ArrayList<TypeDefSummary> activeTypeDefSummaries = null;

        if (! activeTypes.isEmpty())
        {
            activeTypeDefSummaries = new ArrayList<>();

            for (TypeDef activeType : activeTypes.values())
            {
                activeTypeDefSummaries.add(activeType);
            }
        }

        return activeTypeDefSummaries;
    }


    /**
     * Return a boolean flag indicating whether the list of TypeDefs passed are compatible with the
     * local metadata repository.  A true response means it is ok; false means conflicts have been found.
     *
     * A valid TypeDef is one that:
     * <ul>
     *     <li>
     *         Matches name, GUID and version to a TypeDef in the local repository, or
     *     </li>
     *     <li>
     *         Is not defined in the local repository.
     *     </li>
     * </ul>
     *
     * @param sourceName - source of the request (used for logging)
     * @param typeDefSummaries - list of summary information about the TypeDefs.
     */
    public void validateAgainstLocalTypeDefs(String sourceName, List<TypeDefSummary> typeDefSummaries)
    {

        // TODO if invalid typeDefs are detected, they are logged and TypeDef conflict messages are sent to
        // TODO the typeDefEventProcessor methods to distributed
    }


    /**
     * Return a boolean flag indicating whether the list of TypeDefs passed are compatible with the
     * all known typedefs.
     *
     * A valid TypeDef is one that matches name, GUID and version to the full list of TypeDefs.
     * If a new TypeDef is present, it is added to the enterprise list.
     *
     * @param sourceName - source of the TypeDef (used for logging)
     * @param typeDefs - list of TypeDefs.
     * @return boolean flag
     */
    public boolean   validateEnterpriseTypeDefs(String sourceName, List<TypeDef> typeDefs)
    {
        boolean result = true;

        for (TypeDef  typeDef : typeDefs)
        {
            if (validTypeId(sourceName, typeDef.getGUID(), typeDef.getName()))
            {
                if (! isKnownType(sourceName, typeDef.getGUID(), typeDef.getName()))
                {
                    knownTypes.put(typeDef.getName(), typeDef);
                }
            }

        }

        return result;
    }


    /**
     * Return boolean indicating whether the TypeDef is one of the standard open metadata types.
     *
     * @param sourceName - source of the TypeDef (used for logging)
     * @param typeDefGUID - unique identifier of the type
     * @param typeDefName - unique name of the type
     * @return boolean result
     */
    public boolean isOpenType(String sourceName, String   typeDefGUID, String   typeDefName)
    {
        if (validTypeId(sourceName, typeDefGUID, typeDefName))
        {
            TypeDef typeDef = knownTypes.get(typeDefName);

            if (typeDef == null)
            {
                return false;
            }

            if (openTypesOriginGUID != null)
            {
                if (openTypesOriginGUID.equals(typeDef.getOrigin()))
                {
                    log.debug("TypeDef " + typeDefName + " (GUID = " + typeDefGUID + ") from " + sourceName + " is an open type");
                    return true;
                }
                else
                {
                    log.debug("TypeDef " + typeDefName + " (GUID = " + typeDefGUID + ") from " + sourceName + " is NOT an open type");
                }
            }
        }

        return false;
    }


    /**
     * Return boolean indicating whether the (Attribute)TypeDef is known, either as an open type, or one defined
     * by one or more of the members of the cohort.
     *
     * @param sourceName - source of the TypeDef (used for logging)
     * @param typeDefGUID - unique identifier of the type
     * @param typeDefName - unique name of the type
     * @return boolean result
     */
    public boolean isKnownType(String sourceName, String   typeDefGUID, String   typeDefName)
    {
        if (this.validTypeId(sourceName, typeDefGUID, typeDefName))
        {
            TypeDef  typeDef = knownTypes.get(typeDefName);

            if (typeDef == null)
            {
                AttributeTypeDef  attributeTypeDef = knownAttributeTypes.get(typeDefName);

                if (attributeTypeDef == null)
                {
                    log.debug("Unknown (Attribute)TypeDef " + typeDefName + " (GUID = " + typeDefGUID + ") from " + sourceName);
                    return false;
                }
                else
                {
                    log.debug("Known AttributeTypeDef " + typeDefName + " (GUID = " + typeDefGUID + ") from " + sourceName);
                    return true;
                }
            }
            else
            {
                log.debug("Known TypeDef " + typeDefName + " (GUID = " + typeDefGUID + ") from " + sourceName);
                return true;
            }
        }
        else
        {
            log.error("Invalid TypeDef  from " + sourceName + " so can not validate known type");

            return false;
        }
    }


    /**
     * Return boolean indicating whether the TypeDef is in use in the repository.
     *
     * @param sourceName - source of the TypeDef (used for logging)
     * @param typeDefGUID - unique identifier of the type
     * @param typeDefName - unique name of the type
     * @return boolean result
     */
    public boolean isActiveType(String sourceName, String   typeDefGUID, String   typeDefName)
    {
        if (this.validTypeId(sourceName, typeDefGUID, typeDefName))
        {
            TypeDef  typeDef = activeTypes.get(typeDefName);

            if (typeDef == null)
            {
                AttributeTypeDef  attributeTypeDef = activeAttributeTypes.get(typeDefName);

                if (attributeTypeDef == null)
                {
                    log.debug("Inactive (Attribute)TypeDef " + typeDefName + " (GUID = " + typeDefGUID + ") from " + sourceName);
                    return false;
                }
                else
                {
                    log.debug("Active AttributeTypeDef " + typeDefName + " (GUID = " + typeDefGUID + ") from " + sourceName);
                    return true;
                }
            }
            else
            {
                log.debug("Active TypeDef " + typeDefName + " (GUID = " + typeDefGUID + ") from " + sourceName);
                return true;
            }
        }
        else
        {
            log.error("Invalid TypeDef  from " + sourceName + " so can not validate active type");

            return false;
        }
    }


    /**
     * Return boolean indicating whether the (Attribute)TypeDef identifiers are valid or not.
     *
     * @param sourceName - source of the TypeDef (used for logging)
     * @param typeDefGUID - unique identifier of the TypeDef
     * @param typeDefName - unique name of the TypeDef
     * @return boolean result
     */
    public boolean validTypeId(String          sourceName,
                               String          typeDefGUID,
                               String          typeDefName)
    {
        if (typeDefName == null)
        {
            /*
             * A null TypeDef name is invalid
             */
            log.error("Null TypeDef Name from " + sourceName);

            return false;
        }

        if (typeDefGUID == null)
        {
            /*
             * A null guid is invalid
             */
            log.error("Null TypeDef GUID from " + sourceName);

            return false;
        }

        TypeDef typeDef = knownTypes.get(typeDefName);

        if (typeDef == null)
        {
            /*
             * This TypeDef is unknown so see if it is an AttributeTypeDef
             */
            AttributeTypeDef   attributeTypeDef = knownAttributeTypes.get(typeDefName);

            if (attributeTypeDef == null)
            {
                log.debug("Unknown (Attribute)TypeDef from " + sourceName);
            }
            else
            {
                if (!typeDefGUID.equals(attributeTypeDef.getGUID()))
                {
                    /*
                     * The requested guid does not equal the stored one.
                     */
                    log.error("GUID Mismatch in AttributeTypeDef " + typeDefName + " from " + sourceName + " received GUID is " + typeDefGUID + "; stored GUID is " + attributeTypeDef.getGUID());
                    return false;
                }

                log.debug("Valid AttributeTypeDef from " + sourceName);
                return true;
            }

            log.debug("Valid AttributeTypeDef from " + sourceName);
            return true;
        }

        if (! typeDefGUID.equals(typeDef.getGUID()))
        {
            /*
             * The requested guid does not equal the stored one.
             */
            log.error("GUID Mismatch in TypeDef " + typeDefName + " from " + sourceName + " received GUID is " + typeDefGUID + "; stored GUID is " + typeDef.getGUID());

            return false;
        }

        return true;
    }


    /**
     * Return boolean indicating whether the TypeDef identifiers are valid or not.
     *
     * @param sourceName - source of the TypeDef (used for logging)
     * @param typeDefGUID - unique identifier of the TypeDef
     * @param typeDefName - unique name of the TypeDef
     * @return boolean result
     */
    public boolean validTypeDefId(String          sourceName,
                                  String          typeDefGUID,
                                  String          typeDefName,
                                  TypeDefCategory category)
    {
        if (! validTypeId(sourceName, typeDefGUID, typeDefName))
        {
            return false;
        }

        TypeDef          typeDef = knownTypes.get(typeDefName);

        if (typeDef != null)
        {
            TypeDefCategory knownTypeDefCategory = typeDef.getCategory();

            if (knownTypeDefCategory == null)
            {
                log.error("Unknown TypeDef Category for " + typeDefName + " (GUID = " + typeDefGUID + ") from " + sourceName);
                return false;
            }

            if (category.getTypeCode() != knownTypeDefCategory.getTypeCode())
            {
                log.error("TypeDef category mismatch for TypeDef " + typeDefName + " (GUID = " + typeDefGUID + ") from "
                                  + sourceName + " received version number is " + category.getTypeDescription()
                                  + " and stored category is " + knownTypeDefCategory.getTypeDescription());

                return false;
            }
        }

        return true;
    }


    /**
     * Return boolean indicating whether the AttributeTypeDef identifiers are from a single known type or not.
     *
     * @param sourceName - source of the request (used for logging)
     * @param attributeTypeDefGUID - unique identifier of the AttributeTypeDef
     * @param attributeTypeDefName - unique name of the AttributeTypeDef
     * @param category - category for the AttributeTypeDef
     * @return boolean result
     */
    public boolean validAttributeTypeDefId(String                   sourceName,
                                           String                   attributeTypeDefGUID,
                                           String                   attributeTypeDefName,
                                           AttributeTypeDefCategory category)
    {
        if (! validTypeId(sourceName, attributeTypeDefGUID, attributeTypeDefName))
        {
            return false;
        }

        AttributeTypeDef          attributeTypeDef = knownAttributeTypes.get(attributeTypeDefName);

        if (attributeTypeDef != null)
        {
            AttributeTypeDefCategory knownAttributeTypeDefCategory = attributeTypeDef.getCategory();

            if (knownAttributeTypeDefCategory == null)
            {
                log.error("Unknown AttributeTypeDef Category for " + attributeTypeDefName + " (GUID = " + attributeTypeDefGUID + ") from " + sourceName);
                return false;
            }

            if (category.getTypeCode() != knownAttributeTypeDefCategory.getTypeCode())
            {
                log.error("TypeDef category mismatch for TypeDef " + attributeTypeDefName + " (GUID = " + attributeTypeDefGUID + ") from "
                                  + sourceName + " received version number is " + category.getTypeDescription()
                                  + " and stored category is " + knownAttributeTypeDefCategory.getTypeDescription());

                return false;
            }
        }

        return true;
    }


    /**
     * Return boolean indicating whether the TypeDef identifiers are valid or not.
     *
     * @param sourceName - source of the TypeDef (used for logging)
     * @param typeDefGUID - unique identifier of the TypeDef
     * @param typeDefName - unique name of the TypeDef
     * @param typeDefVersion - versionName of the type
     * @param typeDefCategory - category of the instance described by this TypeDef.
     * @return boolean result
     */
    public boolean validTypeDefId(String          sourceName,
                                  String          typeDefGUID,
                                  String          typeDefName,
                                  long            typeDefVersion,
                                  TypeDefCategory typeDefCategory)
    {
        if (! validTypeDefId(sourceName, typeDefGUID, typeDefName, typeDefCategory))
        {
            return false;
        }

        TypeDef   typeDef = knownTypes.get(typeDefName);

        if (typeDef == null)
        {
            log.debug("Unknown TypeDef " + typeDefName + " (GUID = " + typeDefGUID + ") from " + sourceName);

            return true;
        }

        if (typeDef.getVersion() != typeDefVersion)
        {
            log.error("Version mismatch for TypeDef " + typeDefName + " (GUID = " + typeDefGUID + ") from "
                              + sourceName + " received version number is " + Long.toString(typeDefVersion)
                              + " and stored version is " + Long.toString(typeDef.getVersion()));

            return false;
        }

        return true;
    }


    /**
     * Return boolean indicating whether the TypeDef identifiers are from a single known type or not.
     *
     * @param sourceName - source of the request (used for logging)
     * @param attributeTypeDefGUID - unique identifier of the TypeDef
     * @param attributeTypeDefName - unique name of the TypeDef
     * @param attributeTypeDefVersion - versionName of the type
     * @param category - category for the TypeDef
     * @return boolean result
     */
    public boolean validAttributeTypeDefId(String                   sourceName,
                                           String                   attributeTypeDefGUID,
                                           String                   attributeTypeDefName,
                                           long                     attributeTypeDefVersion,
                                           AttributeTypeDefCategory category)
    {
        if (! validAttributeTypeDefId(sourceName, attributeTypeDefGUID, attributeTypeDefName, category))
        {
            return false;
        }

        TypeDef   typeDef = knownTypes.get(attributeTypeDefName);

        if (typeDef == null)
        {
            log.debug("Unknown TypeDef " + attributeTypeDefName + " (GUID = " + attributeTypeDefGUID + ") from " + sourceName);

            return true;
        }

        if (typeDef.getVersion() != attributeTypeDefVersion)
        {
            log.error("Version mismatch for TypeDef " + attributeTypeDefName + " (GUID = " + attributeTypeDefGUID + ") from "
                              + sourceName + " received version number is " + Long.toString(attributeTypeDefVersion)
                              + " and stored version is " + Long.toString(typeDef.getVersion()));

            return false;
        }

        return true;
    }


    /**
     * Return boolean indicating whether the supplied TypeDef is valid or not.
     *
     * @param sourceName - source of the TypeDef (used for logging)
     * @param typeDef - TypeDef to test
     * @return boolean result
     */
    public boolean validTypeDef(String         sourceName,
                                TypeDef        typeDef)
    {
        if (typeDef == null)
        {
            log.error("Null typeDef from " + sourceName);
            return false;
        }

        if (validTypeDefId(sourceName,
                           typeDef.getGUID(),
                           typeDef.getName(),
                           typeDef.getVersion(),
                           typeDef.getCategory()))
        {
            log.debug("Good typeDef from " + sourceName);
            return true;
        }
        else
        {
            log.error("Bad typeDef from " + sourceName);
            return false;
        }
    }


    /**
     * Return boolean indicating whether the supplied AttributeTypeDef is valid or not.
     *
     * @param sourceName - source of the request (used for logging)
     * @param attributeTypeDef - TypeDef to test
     * @return boolean result
     */
    public boolean validAttributeTypeDef(String           sourceName,
                                         AttributeTypeDef attributeTypeDef)
    {
        if (attributeTypeDef == null)
        {
            log.error("Null attributeTypeDef from " + sourceName);
            return false;
        }

        if (validAttributeTypeDefId(sourceName,
                                    attributeTypeDef.getGUID(),
                                    attributeTypeDef.getName(),
                                    attributeTypeDef.getCategory()))
        {
            log.debug("Good attributeTypeDef from " + sourceName);
            return true;
        }
        else
        {
            log.error("Bad attributeTypeDef from " + sourceName);
            return false;
        }
    }


    /**
     * Return boolean indicating whether the supplied TypeDefSummary is valid or not.
     *
     * @param sourceName - source of the TypeDefSummary (used for logging)
     * @param typeDefSummary - TypeDefSummary to test.
     * @return boolean result.
     */
    public boolean validTypeDefSummary(String                sourceName,
                                       TypeDefSummary        typeDefSummary)
    {
        if (typeDefSummary != null)
        {
            if (validTypeDefId(sourceName,
                               typeDefSummary.getGUID(),
                               typeDefSummary.getName(),
                               typeDefSummary.getVersion(),
                               typeDefSummary.getCategory()))
            {
                return true;
            }
            else
            {
                log.error("Bad typeDefSummary from " + sourceName);
            }
        }

        log.error("Null typeDefSummary from " + sourceName);

        return false;
    }


    /*
     * ===========================
     * OMRSTypeDefEventProcessor
     */


    /**
     * A new TypeDef has been defined either in an archive, or in another member of the cohort.
     *
     * This new TypeDef can be added to the repository if it does not clash with an existing typeDef and the local
     * repository supports dynamic type definitions.
     *
     * @param sourceName - name of the source of the event.  It may be the cohort name for incoming events or the
     *                   local repository, or event mapper name.
     * @param originatorMetadataCollectionId - unique identifier for the metadata collection hosted by the server that
     *                                       sent the event.
     * @param originatorServerName - name of the server that the event came from.
     * @param originatorServerType - type of server that the event came from.
     * @param originatorOrganizationName - name of the organization that owns the server that sent the event.
     * @param typeDef - details of the new TypeDef
     */
    public void processNewTypeDefEvent(String       sourceName,
                                       String       originatorMetadataCollectionId,
                                       String       originatorServerName,
                                       String       originatorServerType,
                                       String       originatorOrganizationName,
                                       TypeDef      typeDef)
    {
        final String   actionDescription = "Process New TypeDef Event";

        OMRSMetadataCollection metadataCollection = null;

        if (localRepositoryConnector != null)
        {
            metadataCollection = localRepositoryConnector.getMetadataCollection();
        }

        if (metadataCollection != null)
        {
            try
            {
                /*
                 * VerifyTypeDef returns true if the typeDef is known and matches the supplied definition.
                 * It returns false if the type is supportable but has not yet been defined.
                 * It throws TypeDefNotSupportedException if the typeDef is not supported and can not
                 * be dynamically defined by the local repository.
                 */
                if (! metadataCollection.verifyTypeDef(sourceName, typeDef))
                {
                    metadataCollection.addTypeDef(sourceName, typeDef);

                    /*
                     * Update the active TypeDefs as this new TypeDef has been accepted by the local repository.
                     */
                    activeTypes.put(typeDef.getName(), typeDef);

                    OMRSAuditCode auditCode = OMRSAuditCode.NEW_TYPE_ADDED;
                    auditLog.logRecord(actionDescription,
                                       auditCode.getLogMessageId(),
                                       auditCode.getSeverity(),
                                       auditCode.getFormattedLogMessage(typeDef.getName(),
                                                                        typeDef.getGUID(),
                                                                        Long.toString(typeDef.getVersion()),
                                                                        sourceName),
                                       null,
                                       auditCode.getSystemAction(),
                                       auditCode.getUserAction());
                }
            }
            catch (TypeDefNotSupportedException fixedTypeSystemResponse)
            {
                OMRSAuditCode auditCode = OMRSAuditCode.NEW_TYPE_NOT_SUPPORTED;
                auditLog.logRecord(actionDescription,
                                   auditCode.getLogMessageId(),
                                   auditCode.getSeverity(),
                                   auditCode.getFormattedLogMessage(typeDef.getName(),
                                                                    typeDef.getGUID(),
                                                                    Long.toString(typeDef.getVersion())),
                                   null,
                                   auditCode.getSystemAction(),
                                   auditCode.getUserAction());

                if (log.isDebugEnabled())
                {
                    log.debug("TypeDef not added because repository does not support dynamic type definitions", typeDef);
                    log.debug("TypeDefNotSupportedException:", fixedTypeSystemResponse);

                }
            }
            catch (RepositoryErrorException error)
            {
                log.error("TypeDef " + typeDef.getName() + " not added because repository is not available", typeDef);
                log.error("RepositoryErrorException:", error);
            }
            catch (TypeDefConflictException error)
            {
                // TODO log an error to say that the TypeDef conflicts with a TypeDef already stored.


                log.error("TypeDef not added because it conflicts with another TypeDef already in the repository", typeDef);
                log.error("TypeDefConflictException:", error);

                outboundRepositoryEventManager.processTypeDefConflictEvent(sourceName,
                                                                           localRepositoryConnector.getMetadataCollectionId(),
                                                                           localRepositoryConnector.getLocalServerName(),
                                                                           localRepositoryConnector.getLocalServerType(),
                                                                           localRepositoryConnector.getOrganizationName(),
                                                                           typeDef,
                                                                           originatorMetadataCollectionId,
                                                                           knownTypes.get(typeDef.getName()),
                                                                           null);
            }
            catch (InvalidTypeDefException error)
            {
                // TODO log an error to say that the TypeDef contains bad values.

                if (log.isDebugEnabled())
                {
                    log.debug("TypeDef not added because repository is not available", typeDef);
                    log.debug("InvalidTypeDefException:", error);
                }
            }
            catch (TypeDefKnownException error)
            {
                // TODO log an error to say that a logic error has occurred

                if (log.isDebugEnabled())
                {
                    log.debug("TypeDef not added because repository has a logic error", typeDef);
                    log.debug("TypeDefKnownException:", error);

                }
            }
            catch (Throwable  error)
            {
                // TODO log an error to say that an unexpected error has occurred

                if (log.isDebugEnabled())
                {
                    log.debug("TypeDef not added because repository has an unexpected error", typeDef);
                    log.debug("Throwable:", error);
                }
            }
        }
    }


    /**
     * A new AttributeTypeDef has been defined in an open metadata repository.
     *
     * @param sourceName - name of the source of the event.  It may be the cohort name for incoming events or the
     *                   local repository, or event mapper name.
     * @param originatorMetadataCollectionId - unique identifier for the metadata collection hosted by the server that
     *                                       sent the event.
     * @param originatorServerName - name of the server that the event came from.
     * @param originatorServerType - type of server that the event came from.
     * @param originatorOrganizationName - name of the organization that owns the server that sent the event.
     * @param attributeTypeDef - details of the new AttributeTypeDef.
     */
    public void processNewAttributeTypeDefEvent(String           sourceName,
                                                String           originatorMetadataCollectionId,
                                                String           originatorServerName,
                                                String           originatorServerType,
                                                String           originatorOrganizationName,
                                                AttributeTypeDef attributeTypeDef)
    {
        final String   actionDescription = "Process New TypeDef Event";

        OMRSMetadataCollection metadataCollection = null;

        if (localRepositoryConnector != null)
        {
            metadataCollection = localRepositoryConnector.getMetadataCollection();
        }

        if (metadataCollection != null)
        {
            try
            {
                /*
                 * VerifyTypeDef returns true if the typeDef is known and matches the supplied definition.
                 * It returns false if the type is supportable but has not yet been defined.
                 * It throws TypeDefNotSupportedException if the typeDef is not supported and can not
                 * be dynamically defined by the local repository.
                 */
                if (! metadataCollection.verifyAttributeTypeDef(sourceName, attributeTypeDef))
                {
                    metadataCollection.addAttributeTypeDef(sourceName, attributeTypeDef);

                    /*
                     * Update the active TypeDefs as this new TypeDef has been accepted by the local repository.
                     */
                    activeAttributeTypes.put(attributeTypeDef.getName(), attributeTypeDef);

                    OMRSAuditCode auditCode = OMRSAuditCode.NEW_TYPE_ADDED;
                    auditLog.logRecord(actionDescription,
                                       auditCode.getLogMessageId(),
                                       auditCode.getSeverity(),
                                       auditCode.getFormattedLogMessage(attributeTypeDef.getName(),
                                                                        attributeTypeDef.getGUID(),
                                                                        Long.toString(attributeTypeDef.getVersion()),
                                                                        sourceName),
                                       null,
                                       auditCode.getSystemAction(),
                                       auditCode.getUserAction());
                }
            }
            catch (TypeDefNotSupportedException fixedTypeSystemResponse)
            {
                OMRSAuditCode auditCode = OMRSAuditCode.NEW_TYPE_NOT_SUPPORTED;
                auditLog.logRecord(actionDescription,
                                   auditCode.getLogMessageId(),
                                   auditCode.getSeverity(),
                                   auditCode.getFormattedLogMessage(attributeTypeDef.getName(),
                                                                    attributeTypeDef.getGUID(),
                                                                    Long.toString(attributeTypeDef.getVersion())),
                                   null,
                                   auditCode.getSystemAction(),
                                   auditCode.getUserAction());

                if (log.isDebugEnabled())
                {
                    log.debug("TypeDef not added because repository does not support dynamic type definitions", attributeTypeDef);
                    log.debug("TypeDefNotSupportedException:", fixedTypeSystemResponse);

                }
            }
            catch (RepositoryErrorException error)
            {
                log.error("TypeDef " + attributeTypeDef.getName() + " not added because repository is not available", attributeTypeDef);
                log.error("RepositoryErrorException:", error);
            }
            catch (TypeDefConflictException error)
            {
                // TODO log an error to say that the TypeDef conflicts with a TypeDef already stored.


                log.error("TypeDef not added because it conflicts with another TypeDef already in the repository", attributeTypeDef);
                log.error("TypeDefConflictException:", error);

                outboundRepositoryEventManager.processAttributeTypeDefConflictEvent(sourceName,
                                                                                    localRepositoryConnector.getMetadataCollectionId(),
                                                                                    localRepositoryConnector.getLocalServerName(),
                                                                                    localRepositoryConnector.getLocalServerType(),
                                                                                    localRepositoryConnector.getOrganizationName(),
                                                                                    attributeTypeDef,
                                                                                    originatorMetadataCollectionId,
                                                                                    knownAttributeTypes.get(
                                                                                            attributeTypeDef.getName()),
                                                                                    null);
            }
            catch (InvalidTypeDefException error)
            {
                // TODO log an error to say that the TypeDef contains bad values.

                if (log.isDebugEnabled())
                {
                    log.debug("TypeDef not added because repository is not available", attributeTypeDef);
                    log.debug("InvalidTypeDefException:", error);
                }
            }
            catch (TypeDefKnownException error)
            {
                // TODO log an error to say that a logic error has occurred

                if (log.isDebugEnabled())
                {
                    log.debug("TypeDef not added because repository has a logic error", attributeTypeDef);
                    log.debug("TypeDefKnownException:", error);

                }
            }
            catch (Throwable  error)
            {
                // TODO log an error to say that an unexpected error has occurred

                if (log.isDebugEnabled())
                {
                    log.debug("TypeDef not added because repository has an unexpected error", attributeTypeDef);
                    log.debug("Throwable:", error);
                }
            }
        }
    }


    /**
     * An existing TypeDef has been updated in a remote metadata repository.
     *
     * @param sourceName - name of the source of the event.  It may be the cohort name for incoming events or the
     *                   local repository, or event mapper name.
     * @param originatorMetadataCollectionId - unique identifier for the metadata collection hosted by the server that
     *                                       sent the event.
     * @param originatorServerName - name of the server that the event came from.
     * @param originatorServerType - type of server that the event came from.
     * @param originatorOrganizationName - name of the organization that owns the server that sent the event.
     * @param typeDefPatch - details of the new versionName of the TypeDef
     */
    public void processUpdatedTypeDefEvent(String       sourceName,
                                           String       originatorMetadataCollectionId,
                                           String       originatorServerName,
                                           String       originatorServerType,
                                           String       originatorOrganizationName,
                                           TypeDefPatch typeDefPatch)
    {
        OMRSMetadataCollection metadataCollection = localRepositoryConnector.getMetadataCollection();

        if (metadataCollection != null)
        {
            try
            {
                TypeDef updatedTypeDef = metadataCollection.updateTypeDef(null, typeDefPatch);

                if (log.isDebugEnabled())
                {
                    log.debug("Patch successfully applied", updatedTypeDef);
                }
            }
            catch (RepositoryErrorException  error)
            {
                // TODO log an error to say that the repository is not available

                if (log.isDebugEnabled())
                {
                    log.debug("Patch not applied because repository is not available", typeDefPatch);
                }
            }
            catch (TypeDefNotKnownException  error)
            {
                // TODO log an error to say that the TypeDef is not known

                if (log.isDebugEnabled())
                {
                    log.debug("Patch not applied because TypeDef does not exist", typeDefPatch);
                    log.debug("TypeDefNotKnownException:", error);
                }
            }
            catch (PatchErrorException  error)
            {
                // TODO log an error to say that the TypeDef patch is invalid

                if (log.isDebugEnabled())
                {
                    log.debug("Patch not applied because it is invalid", typeDefPatch);
                    log.debug("PatchErrorException:", error);
                }
            }
            catch (Throwable error)
            {
                // TODO log a generic error

                if (log.isDebugEnabled())
                {
                    log.debug("Patch not applied because of an error", typeDefPatch);
                    log.debug("Throwable:", error);
                }
            }
        }
    }


    /**
     * An existing TypeDef has been deleted in a remote metadata repository.  Both the name and the
     * GUID are provided to ensure the right TypeDef is deleted in other cohort member repositories.
     *
     * @param sourceName - name of the source of the event.  It may be the cohort name for incoming events or the
     *                   local repository, or event mapper name.
     * @param originatorMetadataCollectionId - unique identifier for the metadata collection hosted by the server that
     *                                       sent the event.
     * @param originatorServerName - name of the server that the event came from.
     * @param originatorServerType - type of server that the event came from.
     * @param originatorOrganizationName - name of the organization that owns the server that sent the event.
     * @param typeDefGUID - unique identifier of the TypeDef
     * @param typeDefName - unique name of the TypeDef
     */
    public void processDeletedTypeDefEvent(String      sourceName,
                                           String      originatorMetadataCollectionId,
                                           String      originatorServerName,
                                           String      originatorServerType,
                                           String      originatorOrganizationName,
                                           String      typeDefGUID,
                                           String      typeDefName)
    {
        // TODO
    }


    /**
     * An existing AttributeTypeDef has been deleted in an open metadata repository.  Both the name and the
     * GUID are provided to ensure the right AttributeTypeDef is deleted in other cohort member repositories.
     *
     * @param sourceName - name of the source of the event.  It may be the cohort name for incoming events or the
     *                   local repository, or event mapper name.
     * @param originatorMetadataCollectionId - unique identifier for the metadata collection hosted by the server that
     *                                       sent the event.
     * @param originatorServerName - name of the server that the event came from.
     * @param originatorServerType - type of server that the event came from.
     * @param originatorOrganizationName - name of the organization that owns the server that sent the event.
     * @param attributeTypeDefGUID - unique identifier of the AttributeTypeDef
     * @param attributeTypeDefName - unique name of the AttributeTypeDef
     */
    public void processDeletedAttributeTypeDefEvent(String      sourceName,
                                                    String      originatorMetadataCollectionId,
                                                    String      originatorServerName,
                                                    String      originatorServerType,
                                                    String      originatorOrganizationName,
                                                    String      attributeTypeDefGUID,
                                                    String      attributeTypeDefName)
    {
        // TODO
    }


    /**
     * Process an event that changes either the name or guid of a TypeDef.  It is resolving a Conflicting TypeDef Error.
     *
     * @param sourceName - name of the source of the event.  It may be the cohort name for incoming events or the
     *                   local repository, or event mapper name.
     * @param originatorMetadataCollectionId - unique identifier for the metadata collection hosted by the server that
     *                                       sent the event.
     * @param originatorServerName - name of the server that the event came from.
     * @param originatorServerType - type of server that the event came from.
     * @param originatorOrganizationName - name of the organization that owns the server that sent the event.
     * @param originalTypeDefSummary - details of the original TypeDef
     * @param typeDef - updated TypeDef with new identifiers inside.
     */
    public void processReIdentifiedTypeDefEvent(String         sourceName,
                                                String         originatorMetadataCollectionId,
                                                String         originatorServerName,
                                                String         originatorServerType,
                                                String         originatorOrganizationName,
                                                TypeDefSummary originalTypeDefSummary,
                                                TypeDef        typeDef)
    {

    }


    /**
     * Process an event that changes either the name or guid of an AttributeTypeDef.
     * It is resolving a Conflicting AttributeTypeDef Error.
     *
     * @param sourceName - name of the source of the event.  It may be the cohort name for incoming events or the
     *                   local repository, or event mapper name.
     * @param originatorMetadataCollectionId - unique identifier for the metadata collection hosted by the server that
     *                                       sent the event.
     * @param originatorServerName - name of the server that the event came from.
     * @param originatorServerType - type of server that the event came from.
     * @param originatorOrganizationName - name of the organization that owns the server that sent the event.
     * @param originalAttributeTypeDef - description of original AttributeTypeDef
     * @param attributeTypeDef - updated AttributeTypeDef with new identifiers inside.
     */
    public void processReIdentifiedAttributeTypeDefEvent(String           sourceName,
                                                         String           originatorMetadataCollectionId,
                                                         String           originatorServerName,
                                                         String           originatorServerType,
                                                         String           originatorOrganizationName,
                                                         AttributeTypeDef originalAttributeTypeDef,
                                                         AttributeTypeDef attributeTypeDef)
    {
        // TODO
    }


    /**
     * Process a detected conflict in type definitions (TypeDefs) used in the cohort.
     *
     * @param sourceName - name of the source of the event.  It may be the cohort name for incoming events or the
     *                   local repository, or event mapper name.
     * @param originatorMetadataCollectionId - unique identifier for the metadata collection hosted by the server that
     *                                       sent the event.
     * @param originatorServerName - name of the server that the event came from.
     * @param originatorServerType - type of server that the event came from.
     * @param originatorOrganizationName - name of the organization that owns the server that sent the event.
     * @param originatorTypeDefSummary - details of the TypeDef in the event originator
     * @param otherMetadataCollectionId - the metadataCollection using the conflicting TypeDef
     * @param conflictingTypeDefSummary - the details of the TypeDef in the other metadata collection
     * @param errorMessage - details of the error that occurs when the connection is used.
     */
    public void processTypeDefConflictEvent(String         sourceName,
                                            String         originatorMetadataCollectionId,
                                            String         originatorServerName,
                                            String         originatorServerType,
                                            String         originatorOrganizationName,
                                            TypeDefSummary originatorTypeDefSummary,
                                            String         otherMetadataCollectionId,
                                            TypeDefSummary conflictingTypeDefSummary,
                                            String         errorMessage)
    {
        // TODO
    }


    /**
     * Process a detected conflict in the attribute type definitions (AttributeTypeDefs) used in the cohort.
     *
     * @param sourceName - name of the source of the event.  It may be the cohort name for incoming events or the
     *                   local repository, or event mapper name.
     * @param originatorMetadataCollectionId - unique identifier for the metadata collection hosted by the server that
     *                                       sent the event.
     * @param originatorServerName - name of the server that the event came from.
     * @param originatorServerType - type of server that the event came from.
     * @param originatorOrganizationName - name of the organization that owns the server that sent the event.
     * @param originatorAttributeTypeDef- description of the AttributeTypeDef in the event originator.
     * @param otherMetadataCollectionId - the metadataCollection using the conflicting AttributeTypeDef.
     * @param conflictingAttributeTypeDef - description of the AttributeTypeDef in the other metadata collection.
     * @param errorMessage - details of the error that occurs when the connection is used.
     */
    public void processAttributeTypeDefConflictEvent(String           sourceName,
                                                     String           originatorMetadataCollectionId,
                                                     String           originatorServerName,
                                                     String           originatorServerType,
                                                     String           originatorOrganizationName,
                                                     AttributeTypeDef originatorAttributeTypeDef,
                                                     String           otherMetadataCollectionId,
                                                     AttributeTypeDef conflictingAttributeTypeDef,
                                                     String           errorMessage)
    {
        // TODO
    }


    /**
     * A TypeDef from another member in the cohort is at a different versionName than the local repository.  This may
     * create some inconsistencies in the different copies of instances of this type in different members of the
     * cohort.  The recommended action is to update all TypeDefs to the latest versionName.
     *
     * @param sourceName - name of the source of the event.  It may be the cohort name for incoming events or the
     *                   local repository, or event mapper name.
     * @param originatorMetadataCollectionId - unique identifier for the metadata collection hosted by the server that
     *                                       sent the event.
     * @param originatorServerName - name of the server that the event came from.
     * @param originatorServerType - type of server that the event came from.
     * @param originatorOrganizationName - name of the organization that owns the server that sent the event.
     * @param targetMetadataCollectionId - identifier of the metadata collection that is reporting a TypeDef at a
     *                                   different level to the local repository.
     * @param targetTypeDefSummary - details of the target TypeDef
     * @param otherTypeDef - details of the TypeDef in the local repository.
     */
    public void processTypeDefPatchMismatchEvent(String         sourceName,
                                                 String         originatorMetadataCollectionId,
                                                 String         originatorServerName,
                                                 String         originatorServerType,
                                                 String         originatorOrganizationName,
                                                 String         targetMetadataCollectionId,
                                                 TypeDefSummary targetTypeDefSummary,
                                                 TypeDef        otherTypeDef,
                                                 String         errorMessage)
    {

    }


    /*
     * =====================
     * OMRSInstanceValidator
     */

    /**
     * Test that the supplied entity is valid.
     *
     * @param sourceName - source of the entity (used for logging)
     * @param entity - entity to test
     * @return boolean result
     */
    public boolean validEntity(String       sourceName,
                               EntityDetail entity)
    {
        if (entity == null)
        {
            log.error("Null entity from " + sourceName);
            return false;
        }

        InstanceType instanceType = entity.getType();

        if (instanceType == null)
        {
            log.error("Null instance type in entity from " + sourceName);
            return false;
        }

        if (! validInstanceId(sourceName,
                              instanceType.getTypeDefGUID(),
                              instanceType.getTypeDefName(),
                              instanceType.getTypeDefCategory(),
                              entity.getGUID()))
        {
            log.error("Null entity guid from " + sourceName);
            return false;
        }

        String          homeMetadataCollectionId = entity.getMetadataCollectionId();

        if (homeMetadataCollectionId == null)
        {
            log.error("Null home metadata collection id for entity " + entity.getGUID() + " from " + sourceName);
            return false;
        }

        return true;
    }


    /**
     * Test that the supplied relationship is valid.
     *
     * @param sourceName - source of the relationship (used for logging)
     * @param relationship - relationship to test
     * @return boolean result
     */
    public boolean validRelationship(String       sourceName,
                                     Relationship relationship)
    {
        if (relationship == null)
        {
            log.error("Null relationship from " + sourceName);
            return false;
        }

        InstanceType instanceType = relationship.getType();

        if (instanceType == null)
        {
            log.error("Null instance type in relationship from " + sourceName);
            return false;
        }

        if (! validInstanceId(sourceName,
                              instanceType.getTypeDefGUID(),
                              instanceType.getTypeDefName(),
                              instanceType.getTypeDefCategory(),
                              relationship.getGUID()))
        {
            log.error("Null relationship guid from " + sourceName);
            return false;
        }

        String          homeMetadataCollectionId = relationship.getMetadataCollectionId();

        if (homeMetadataCollectionId == null)
        {
            log.error("Null home metadata collection id for relationship " + relationship.getGUID() + " from " + sourceName);
            return false;
        }

        return true;
    }


    /**
     * Verify that the identifiers for an instance are correct.
     *
     * @param sourceName - source of the instance (used for logging)
     * @param typeDefGUID - unique identifier for the type.
     * @param typeDefName - unique name for the type.
     * @param category - expected category of the instance.
     * @param instanceGUID - unique identifier for the instance.
     * @return boolean indicating whether the identifiers are ok.
     */
    public boolean validInstanceId(String           sourceName,
                                   String           typeDefGUID,
                                   String           typeDefName,
                                   TypeDefCategory  category,
                                   String           instanceGUID)
    {
        if (instanceGUID == null)
        {
            log.error("Null instance guid from " + sourceName);
            return false;
        }

        if (! validTypeDefId(sourceName,
                             typeDefGUID,
                             typeDefName,
                             category))
        {
            /*
             * Error messages already logged
             */
            return false;
        }

        return true;
    }
}
