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

org.odpi.openmetadata.commonservices.generichandlers.OpenMetadataAPIGenericHandler Maven / Gradle / Ivy

There is a newer version: 5.1
Show newest version
/* SPDX-License-Identifier: Apache 2.0 */
/* Copyright Contributors to the ODPi Egeria project. */
package org.odpi.openmetadata.commonservices.generichandlers;

import org.odpi.openmetadata.commonservices.ffdc.InvalidParameterHandler;
import org.odpi.openmetadata.commonservices.repositoryhandler.*;
import org.odpi.openmetadata.commonservices.generichandlers.ffdc.*;
import org.odpi.openmetadata.frameworks.auditlog.AuditLog;
import org.odpi.openmetadata.frameworks.connectors.ffdc.InvalidParameterException;
import org.odpi.openmetadata.frameworks.connectors.ffdc.PropertyServerException;
import org.odpi.openmetadata.frameworks.connectors.ffdc.UserNotAuthorizedException;
import org.odpi.openmetadata.metadatasecurity.properties.*;
import org.odpi.openmetadata.metadatasecurity.server.OpenMetadataServerSecurityVerifier;
import org.odpi.openmetadata.repositoryservices.connectors.stores.metadatacollectionstore.properties.MatchCriteria;
import org.odpi.openmetadata.repositoryservices.connectors.stores.metadatacollectionstore.properties.SequencingOrder;
import org.odpi.openmetadata.repositoryservices.connectors.stores.metadatacollectionstore.properties.instances.*;
import org.odpi.openmetadata.repositoryservices.connectors.stores.metadatacollectionstore.properties.search.SearchClassifications;
import org.odpi.openmetadata.repositoryservices.connectors.stores.metadatacollectionstore.properties.search.SearchProperties;
import org.odpi.openmetadata.repositoryservices.connectors.stores.metadatacollectionstore.properties.typedefs.PrimitiveDefCategory;
import org.odpi.openmetadata.repositoryservices.connectors.stores.metadatacollectionstore.properties.typedefs.TypeDef;
import org.odpi.openmetadata.repositoryservices.connectors.stores.metadatacollectionstore.repositoryconnector.OMRSRepositoryHelper;
import org.odpi.openmetadata.repositoryservices.ffdc.exception.ClassificationErrorException;
import org.odpi.openmetadata.repositoryservices.ffdc.exception.TypeErrorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * OpenMetadataAPIGenericHandler manages the exchange of Open Metadata API Bean content with the repository services
 * (via the repository handler).
 */
public class OpenMetadataAPIGenericHandler
{
    protected OpenMetadataAPIGenericConverter converter;
    protected Class                           beanClass;

    protected String                             serviceName;
    protected String                             serverName;
    protected OMRSRepositoryHelper               repositoryHelper;
    protected RepositoryHandler                  repositoryHandler;
    protected InvalidParameterHandler            invalidParameterHandler;

    protected String                             localServerUserId;
    protected OpenMetadataServerSecurityVerifier securityVerifier = new OpenMetadataServerSecurityVerifier();


    protected List                       supportedZones;
    protected List                       publishZones;
    protected List                       defaultZones;

    protected RepositoryErrorHandler             errorHandler;

    private static final Logger log = LoggerFactory.getLogger(OpenMetadataAPIGenericHandler.class);

    protected AuditLog                           auditLog;

    private final static String supplementaryPropertiesQualifiedNamePostFix = " Supplementary Properties";
    private final static String supplementaryPropertiesQualifiedNameParameterName = "elementQualifiedName";
    private final static String supplementaryPropertiesGlossaryName = "Supplementary Properties Glossary";
    private final static String supplementaryPropertiesGlossaryParameterName = "supplementaryPropertiesGlossaryName";
    private final static String supplementaryPropertiesGlossaryDescription =
            "This glossary contains glossary terms containing the business-oriented descriptive names and related properties for " +
                    "open metadata assets.";

    private List qualifiedNamePropertyNamesList;


    /**
     * Construct the handler information needed to interact with the repository services
     *
     * @param converter specific converter for this bean class
     * @param beanClass name of bean class that is represented by the generic class B
     * @param serviceName      name of this service
     * @param serverName       name of the local server
     * @param invalidParameterHandler handler for managing parameter errors
     * @param repositoryHandler     manages calls to the repository services
     * @param repositoryHelper provides utilities for manipulating the repository services objects
     * @param localServerUserId userId for this server
     * @param securityVerifier open metadata security services verifier
     * @param supportedZones list of zones that the access service is allowed to serve Asset instances from.
     * @param defaultZones list of zones that the access service should set in all new Asset instances.
     * @param publishZones list of zones that the access service sets up in published Asset instances.
     * @param auditLog destination for audit log events.
     */
    public OpenMetadataAPIGenericHandler(OpenMetadataAPIGenericConverter converter,
                                         Class                           beanClass,
                                         String                             serviceName,
                                         String                             serverName,
                                         InvalidParameterHandler            invalidParameterHandler,
                                         RepositoryHandler                  repositoryHandler,
                                         OMRSRepositoryHelper               repositoryHelper,
                                         String                             localServerUserId,
                                         OpenMetadataServerSecurityVerifier securityVerifier,
                                         List                       supportedZones,
                                         List                       defaultZones,
                                         List                       publishZones,
                                         AuditLog                           auditLog)
    {
        this.converter               = converter;
        this.beanClass               = beanClass;

        this.serviceName             = serviceName;
        this.serverName              = serverName;
        this.invalidParameterHandler = invalidParameterHandler;
        this.repositoryHandler       = repositoryHandler;
        this.repositoryHelper        = repositoryHelper;
        this.localServerUserId       = localServerUserId;

        if (securityVerifier != null)
        {
            this.securityVerifier    = securityVerifier;
        }

        this.supportedZones          = supportedZones;
        this.defaultZones            = defaultZones;
        this.publishZones            = publishZones;

        this.auditLog                = auditLog;

        this.errorHandler            = new RepositoryErrorHandler(repositoryHelper, serviceName, serverName, auditLog);

        this.qualifiedNamePropertyNamesList = new ArrayList<>();
        this.qualifiedNamePropertyNamesList.add(OpenMetadataAPIMapper.QUALIFIED_NAME_PROPERTY_NAME);
    }


    /**
     * Set up a new security verifier (the handler runs with a default verifier until this method is called).
     *
     * The security verifier provides authorization checks for access and maintenance
     * changes to open metadata.  Authorization checks are enabled through the
     * OpenMetadataServerSecurityConnector.
     *
     * @param securityVerifier new security verifier
     */
    public void setSecurityVerifier(OpenMetadataServerSecurityVerifier securityVerifier)
    {
        if (securityVerifier != null)
        {
            this.securityVerifier = securityVerifier;
        }
    }


    /**
     * Return the list of supported zones for this asset.  This originates from the configuration of the access server.
     * but may be changed by the security verifier.
     *
     * @param userId calling user
     * @param suppliedSupportedZones supported zones from caller
     * @param serviceName called service
     * @return list of zone names
     * @throws InvalidParameterException invalid parameter
     * @throws PropertyServerException problem from the verifier
     */
    private List getSupportedZones(String       userId,
                                           List suppliedSupportedZones,
                                           String       serviceName) throws InvalidParameterException, PropertyServerException
    {
        return securityVerifier.setSupportedZonesForUser(suppliedSupportedZones, serviceName, userId);
    }


    /**
     * Return the repository helper for this server.
     *
     * @return repository helper object
     */
    public OMRSRepositoryHelper getRepositoryHelper()
    {
        return repositoryHelper;
    }


    /**
     * Return the repository handler for this server.
     *
     * @return repository handler object
     */
    public RepositoryHandler getRepositoryHandler()
    {
        return repositoryHandler;
    }


    /**
     * Return the type definition for the named type.
     *
     * @param suppliedTypeName caller's subtype (or null)
     * @param defaultTypeName common super type
     *
     * @return type definition
     */
    public TypeDef getTypeDefByName(String suppliedTypeName,
                                    String defaultTypeName)
    {
        String resultsTypeName = defaultTypeName;

        if (suppliedTypeName != null)
        {
            resultsTypeName = suppliedTypeName;
        }

        return repositoryHelper.getTypeDefByName(serviceName, resultsTypeName);
    }


    /**
     * Return the name of this service.
     *
     * @return string name
     */
    public String getServiceName()
    {
        return serviceName;
    }

    /**
     * Return the name of this server.
     *
     * @return string name
     */
    public String getServerName()
    {
        return serverName;
    }


    /**
     * Add the requested classification to the matching entity in the repository.
     *
     * @param userId calling user
     * @param beanGUID unique identifier of the entity in the repositories
     * @param beanGUIDParameterName parameter name that passed the beanGUID
     * @param beanGUIDTypeName type of bean
     * @param classificationTypeGUID unique identifier of classification type
     * @param classificationTypeName unique name of classification type
     * @param classificationProperties properties to save in the classification
     * @param methodName calling method
     * @throws InvalidParameterException the classification name is null
     * @throws PropertyServerException there is a problem with the repositories
     * @throws UserNotAuthorizedException the user is not allowed to update the security tags
     */
    public void setClassificationInRepository(String             userId,
                                              String             beanGUID,
                                              String             beanGUIDParameterName,
                                              String             beanGUIDTypeName,
                                              String             classificationTypeGUID,
                                              String             classificationTypeName,
                                              InstanceProperties classificationProperties,
                                              String             methodName) throws InvalidParameterException,
                                                                                    PropertyServerException,
                                                                                    UserNotAuthorizedException
    {
        setClassificationInRepository(userId,
                                      null,
                                      null,
                                      beanGUID,
                                      beanGUIDParameterName,
                                      beanGUIDTypeName,
                                      classificationTypeGUID,
                                      classificationTypeName,
                                      classificationProperties,
                                      false,
                                      methodName);
    }



    /**
     * Add the requested classification to the matching entity in the repository.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param beanGUID unique identifier of the entity in the repositories
     * @param beanGUIDParameterName parameter name that passed the beanGUID
     * @param beanGUIDTypeName type of bean
     * @param classificationTypeGUID unique identifier of classification type
     * @param classificationTypeName unique name of classification type
     * @param classificationProperties properties to save in the classification
     * @param isMergeUpdate should the properties be merged with the existing properties or completely over-write them
     * @param methodName calling method
     * @throws InvalidParameterException the classification name is null
     * @throws PropertyServerException there is a problem with the repositories
     * @throws UserNotAuthorizedException the user is not allowed to update the security tags
     */
    public void setClassificationInRepository(String             userId,
                                              String             externalSourceGUID,
                                              String             externalSourceName,
                                              String             beanGUID,
                                              String             beanGUIDParameterName,
                                              String             beanGUIDTypeName,
                                              String             classificationTypeGUID,
                                              String             classificationTypeName,
                                              InstanceProperties classificationProperties,
                                              boolean            isMergeUpdate,
                                              String             methodName) throws InvalidParameterException,
                                                                                    PropertyServerException,
                                                                                    UserNotAuthorizedException
    {
        setClassificationInRepository(userId,
                                      externalSourceGUID,
                                      externalSourceName,
                                      beanGUID,
                                      beanGUIDParameterName,
                                      beanGUIDTypeName,
                                      classificationTypeGUID,
                                      classificationTypeName,
                                      classificationProperties,
                                      isMergeUpdate,
                                      false,
                                      false,
                                      null,
                                      methodName);
    }


    /**
     * Add the requested classification to the matching entity in the repository.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param beanGUID unique identifier of the entity in the repositories
     * @param beanGUIDParameterName parameter name that passed the beanGUID
     * @param beanGUIDTypeName type of bean
     * @param classificationTypeGUID unique identifier of classification type
     * @param classificationTypeName unique name of classification type
     * @param classificationProperties properties to save in the classification
     * @param isMergeUpdate should the properties be merged with the existing properties or completely over-write them
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     * @throws InvalidParameterException the classification name is null
     * @throws PropertyServerException there is a problem with the repositories
     * @throws UserNotAuthorizedException the user is not allowed to update the security tags
     */
    public void setClassificationInRepository(String             userId,
                                              String             externalSourceGUID,
                                              String             externalSourceName,
                                              String             beanGUID,
                                              String             beanGUIDParameterName,
                                              String             beanGUIDTypeName,
                                              String             classificationTypeGUID,
                                              String             classificationTypeName,
                                              InstanceProperties classificationProperties,
                                              boolean            isMergeUpdate,
                                              boolean            forLineage,
                                              boolean            forDuplicateProcessing,
                                              Date               effectiveTime,
                                              String             methodName) throws InvalidParameterException,
                                                                                    PropertyServerException,
                                                                                    UserNotAuthorizedException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(beanGUID, beanGUIDParameterName, methodName);

        EntityDetail beanEntity = getEntityFromRepository(userId,
                                                          beanGUID,
                                                          beanGUIDParameterName,
                                                          beanGUIDTypeName,
                                                          null,
                                                          null,
                                                          forLineage,
                                                          forDuplicateProcessing,
                                                          supportedZones,
                                                          effectiveTime,
                                                          methodName);


        setClassificationInRepository(userId,
                                      externalSourceGUID,
                                      externalSourceName,
                                      beanEntity,
                                      beanGUIDParameterName,
                                      beanGUIDTypeName,
                                      classificationTypeGUID,
                                      classificationTypeName,
                                      classificationProperties,
                                      isMergeUpdate,
                                      forLineage,
                                      forDuplicateProcessing,
                                      effectiveTime,
                                      methodName);
    }


    /**
     * Add the requested classification to the matching entity in the repository.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param beanEntity entity that the classification is for
     * @param beanGUIDParameterName parameter name that passed the beanGUID
     * @param beanGUIDTypeName type of bean
     * @param classificationTypeGUID unique identifier of classification type
     * @param classificationTypeName unique name of classification type
     * @param classificationProperties properties to save in the classification
     * @param isMergeUpdate should the properties be merged with the existing properties or completely over-write them
     * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     * @throws InvalidParameterException the classification name is null
     * @throws PropertyServerException there is a problem with the repositories
     * @throws UserNotAuthorizedException the user is not allowed to update the security tags
     */
    public void setClassificationInRepository(String             userId,
                                              String             externalSourceGUID,
                                              String             externalSourceName,
                                              EntityDetail       beanEntity,
                                              String             beanGUIDParameterName,
                                              String             beanGUIDTypeName,
                                              String             classificationTypeGUID,
                                              String             classificationTypeName,
                                              InstanceProperties classificationProperties,
                                              boolean            isMergeUpdate,
                                              boolean            forDuplicateProcessing,
                                              boolean            forLineage,
                                              Date               effectiveTime,
                                              String             methodName) throws InvalidParameterException,
                                                                                    PropertyServerException,
                                                                                    UserNotAuthorizedException
    {
        if (beanEntity != null)
        {
            EntityDetail anchorEntity = validateAnchorEntity(userId,
                                                             beanEntity.getGUID(),
                                                             beanGUIDTypeName,
                                                             beanEntity,
                                                             beanGUIDParameterName,
                                                             true,
                                                             forLineage,
                                                             forDuplicateProcessing,
                                                             supportedZones,
                                                             effectiveTime,
                                                             methodName);

            Classification existingClassification = this.getExistingClassification(beanEntity, classificationTypeName);

            /*
             * Classify the asset
             */
            int latestChangeActionOrdinal;
            if (existingClassification == null)
            {
                latestChangeActionOrdinal = OpenMetadataAPIMapper.CREATED_LATEST_CHANGE_ACTION_ORDINAL;
                repositoryHandler.classifyEntity(userId,
                                                 externalSourceGUID,
                                                 externalSourceName,
                                                 beanEntity.getGUID(),
                                                 beanEntity,
                                                 beanGUIDParameterName,
                                                 beanGUIDTypeName,
                                                 classificationTypeGUID,
                                                 classificationTypeName,
                                                 ClassificationOrigin.ASSIGNED,
                                                 null,
                                                 classificationProperties,
                                                 forLineage,
                                                 forDuplicateProcessing,
                                                 effectiveTime,
                                                 methodName);
            }
            else
            {
                latestChangeActionOrdinal = OpenMetadataAPIMapper.UPDATED_LATEST_CHANGE_ACTION_ORDINAL;

                InstanceProperties newProperties = setUpNewProperties(isMergeUpdate,
                                                                      classificationProperties,
                                                                      existingClassification.getProperties());

                repositoryHandler.reclassifyEntity(userId,
                                                   externalSourceGUID,
                                                   externalSourceName,
                                                   beanEntity.getGUID(),
                                                   beanGUIDParameterName,
                                                   beanGUIDTypeName,
                                                   classificationTypeGUID,
                                                   classificationTypeName,
                                                   existingClassification,
                                                   newProperties,
                                                   forLineage,
                                                   forDuplicateProcessing,
                                                   effectiveTime,
                                                   methodName);
            }

            if (anchorEntity != null)
            {
                final String actionDescriptionTemplate = "Adding %s classification to %s %s";
                String actionDescription = String.format(actionDescriptionTemplate, classificationTypeName, beanGUIDTypeName, beanEntity.getGUID());

                int latestChangeTargetOrdinal;
                String attachmentGUID = null;
                String attachmentTypeName = null;

                if (beanEntity.getGUID().equals(anchorEntity.getGUID()))
                {
                    latestChangeTargetOrdinal = OpenMetadataAPIMapper.ENTITY_CLASSIFICATION_LATEST_CHANGE_TARGET_ORDINAL;
                }
                else
                {
                    latestChangeTargetOrdinal = OpenMetadataAPIMapper.ATTACHMENT_CLASSIFICATION_LATEST_CHANGE_TARGET_ORDINAL;
                    attachmentGUID = beanEntity.getGUID();
                    attachmentTypeName = beanGUIDTypeName;
                }

                this.addLatestChangeToAnchor(anchorEntity,
                                             latestChangeTargetOrdinal,
                                             latestChangeActionOrdinal,
                                             classificationTypeName,
                                             attachmentGUID,
                                             attachmentTypeName,
                                             null,
                                             userId,
                                             actionDescription,
                                             methodName);
            }
        }
    }


    /**
     * Update the effectivity dates of a specific classification attached to a metadata element.
     * The effectivity dates control the visibility of the classification through specific APIs.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param beanGUID unique identifier of the entity in the repositories
     * @param beanGUIDParameterName parameter name that passed the beanGUID
     * @param beanGUIDTypeName type of bean
     * @param classificationTypeGUID unique identifier of classification type
     * @param classificationTypeName unique name of classification type
     * @param forLineage the query is to support lineage retrieval
     * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate
     * @param effectiveFrom the date when this element is active - null for active now
     * @param effectiveTo the date when this element becomes inactive - null for active until deleted
     * @param methodName calling method
     *
     * @throws InvalidParameterException either the unique identifier or the status are invalid in some way
     * @throws UserNotAuthorizedException the governance action service is not authorized to update this element
     * @throws PropertyServerException there is a problem with the metadata store
     */
    public void updateClassificationEffectivityDates(String  userId,
                                                     String  externalSourceGUID,
                                                     String  externalSourceName,
                                                     String  beanGUID,
                                                     String  beanGUIDParameterName,
                                                     String  beanGUIDTypeName,
                                                     String  classificationTypeGUID,
                                                     String  classificationTypeName,
                                                     boolean forLineage,
                                                     boolean forDuplicateProcessing,
                                                     Date    effectiveFrom,
                                                     Date    effectiveTo,
                                                     String  methodName) throws InvalidParameterException,
                                                                                UserNotAuthorizedException,
                                                                                PropertyServerException
    {
        this.updateClassificationEffectivityDates(userId,
                                                  externalSourceGUID,
                                                  externalSourceName,
                                                  beanGUID,
                                                  beanGUIDParameterName,
                                                  beanGUIDTypeName,
                                                  classificationTypeGUID,
                                                  classificationTypeName,
                                                  forLineage,
                                                  forDuplicateProcessing,
                                                  effectiveFrom,
                                                  effectiveTo,
                                                  this.getEffectiveTime(effectiveFrom, effectiveTo),
                                                  methodName);
    }


    /**
     * Update the effectivity dates of a specific classification attached to a metadata element.
     * The effectivity dates control the visibility of the classification through specific APIs.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param beanGUID unique identifier of the entity in the repositories
     * @param beanGUIDParameterName parameter name that passed the beanGUID
     * @param beanGUIDTypeName type of bean
     * @param classificationTypeGUID unique identifier of classification type
     * @param classificationTypeName unique name of classification type
     * @param forLineage the query is to support lineage retrieval
     * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate
     * @param effectiveFrom the date when this element is active - null for active now
     * @param effectiveTo the date when this element becomes inactive - null for active until deleted
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @throws InvalidParameterException either the unique identifier or the status are invalid in some way
     * @throws UserNotAuthorizedException the governance action service is not authorized to update this element
     * @throws PropertyServerException there is a problem with the metadata store
     */
    public void updateClassificationEffectivityDates(String  userId,
                                                     String  externalSourceGUID,
                                                     String  externalSourceName,
                                                     String  beanGUID,
                                                     String  beanGUIDParameterName,
                                                     String  beanGUIDTypeName,
                                                     String  classificationTypeGUID,
                                                     String  classificationTypeName,
                                                     boolean forLineage,
                                                     boolean forDuplicateProcessing,
                                                     Date    effectiveFrom,
                                                     Date    effectiveTo,
                                                     Date    effectiveTime,
                                                     String  methodName) throws InvalidParameterException,
                                                                                UserNotAuthorizedException,
                                                                                PropertyServerException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(beanGUID, beanGUIDParameterName, methodName);

        EntityDetail  beanEntity = getEntityFromRepository(userId,
                                                           beanGUID,
                                                           beanGUIDParameterName,
                                                           beanGUIDTypeName,
                                                           null,
                                                           null,
                                                           forLineage,
                                                           forDuplicateProcessing,
                                                           supportedZones,
                                                           effectiveTime,
                                                           methodName);

        EntityDetail  anchorEntity = validateAnchorEntity(userId,
                                                          beanGUID,
                                                          beanGUIDTypeName,
                                                          beanEntity,
                                                          beanGUIDParameterName,
                                                          true,
                                                          forLineage,
                                                          forDuplicateProcessing,
                                                          supportedZones,
                                                          effectiveTime,
                                                          methodName);

        if (beanEntity != null)
        {
            Classification existingClassification = this.getExistingClassification(beanEntity, classificationTypeName);

            invalidParameterHandler.validateObject(existingClassification, classificationTypeName, methodName);

            int latestChangeActionOrdinal = OpenMetadataAPIMapper.UPDATED_LATEST_CHANGE_ACTION_ORDINAL;

            InstanceProperties newProperties = existingClassification.getProperties();

            if (newProperties == null)
            {
                newProperties = new InstanceProperties();
            }

            newProperties.setEffectiveFromTime(effectiveFrom);
            newProperties.setEffectiveToTime(effectiveTo);

            repositoryHandler.reclassifyEntity(userId,
                                               externalSourceGUID,
                                               externalSourceName,
                                               beanEntity.getGUID(),
                                               beanGUIDParameterName,
                                               beanGUIDTypeName,
                                               classificationTypeGUID,
                                               classificationTypeName,
                                               existingClassification,
                                               newProperties,
                                               forLineage,
                                               forDuplicateProcessing,
                                               this.getEffectiveTime(effectiveFrom, effectiveTo),
                                               methodName);

            if (anchorEntity != null)
            {
                final String actionDescriptionTemplate = "Updating effectivity dates for %s classification to %s %s";
                String actionDescription = String.format(actionDescriptionTemplate, classificationTypeName, beanGUIDTypeName, beanGUID);

                int latestChangeTargetOrdinal;
                String attachmentGUID = null;
                String attachmentTypeName = null;

                if (beanGUID.equals(anchorEntity.getGUID()))
                {
                    latestChangeTargetOrdinal = OpenMetadataAPIMapper.ENTITY_CLASSIFICATION_LATEST_CHANGE_TARGET_ORDINAL;
                }
                else
                {
                    latestChangeTargetOrdinal = OpenMetadataAPIMapper.ATTACHMENT_CLASSIFICATION_LATEST_CHANGE_TARGET_ORDINAL;
                    attachmentGUID = beanGUID;
                    attachmentTypeName = beanGUIDTypeName;
                }

                this.addLatestChangeToAnchor(anchorEntity,
                                             latestChangeTargetOrdinal,
                                             latestChangeActionOrdinal,
                                             classificationTypeName,
                                             attachmentGUID,
                                             attachmentTypeName,
                                             null,
                                             userId,
                                             actionDescription,
                                             methodName);
            }
        }
    }



    /**
     * Update the effectivity dates of a specific entity .
     * The effectivity dates control the visibility of the entity through specific APIs.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param beanGUID unique identifier of the entity in the repositories
     * @param beanGUIDParameterName parameter name that passed the beanGUID
     * @param beanGUIDTypeGUID type of bean
     * @param beanGUIDTypeName type of bean
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveFrom the date when this element is active - null for active now
     * @param effectiveTo the date when this element becomes inactive - null for active until deleted
     * @param methodName calling method
     *
     * @throws InvalidParameterException either the unique identifier or the status are invalid in some way
     * @throws UserNotAuthorizedException the governance action service is not authorized to update this element
     * @throws PropertyServerException there is a problem with the metadata store
     */
    public void updateBeanEffectivityDates(String  userId,
                                           String  externalSourceGUID,
                                           String  externalSourceName,
                                           String  beanGUID,
                                           String  beanGUIDParameterName,
                                           String  beanGUIDTypeGUID,
                                           String  beanGUIDTypeName,
                                           boolean forLineage,
                                           boolean forDuplicateProcessing,
                                           Date    effectiveFrom,
                                           Date    effectiveTo,
                                           String  methodName) throws InvalidParameterException,
                                                                      UserNotAuthorizedException,
                                                                      PropertyServerException
    {
        this.updateBeanEffectivityDates(userId,
                                        externalSourceGUID,
                                        externalSourceName,
                                        beanGUID,
                                        beanGUIDParameterName,
                                        beanGUIDTypeGUID,
                                        beanGUIDTypeName,
                                        forLineage,
                                        forDuplicateProcessing,
                                        effectiveFrom,
                                        effectiveTo,
                                        this.getEffectiveTime(effectiveFrom, effectiveTo),
                                        methodName);
    }


    /**
     * Update the effectivity dates of a specific entity .
     * The effectivity dates control the visibility of the entity through specific APIs.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param beanGUID unique identifier of the entity in the repositories
     * @param beanGUIDParameterName parameter name that passed the beanGUID
     * @param beanGUIDTypeGUID type of bean
     * @param beanGUIDTypeName type of bean
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveFrom the date when this element is active - null for active now
     * @param effectiveTo the date when this element becomes inactive - null for active until deleted
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @throws InvalidParameterException either the unique identifier or the status are invalid in some way
     * @throws UserNotAuthorizedException the governance action service is not authorized to update this element
     * @throws PropertyServerException there is a problem with the metadata store
     */
    public void updateBeanEffectivityDates(String  userId,
                                           String  externalSourceGUID,
                                           String  externalSourceName,
                                           String  beanGUID,
                                           String  beanGUIDParameterName,
                                           String  beanGUIDTypeGUID,
                                           String  beanGUIDTypeName,
                                           boolean forLineage,
                                           boolean forDuplicateProcessing,
                                           Date    effectiveFrom,
                                           Date    effectiveTo,
                                           Date    effectiveTime,
                                           String  methodName) throws InvalidParameterException,
                                                                      UserNotAuthorizedException,
                                                                      PropertyServerException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(beanGUID, beanGUIDParameterName, methodName);

        EntityDetail  beanEntity = getEntityFromRepository(userId,
                                                           beanGUID,
                                                           beanGUIDParameterName,
                                                           beanGUIDTypeName,
                                                           null,
                                                           null,
                                                           forLineage,
                                                           forDuplicateProcessing,
                                                           supportedZones,
                                                           effectiveTime,
                                                           methodName);

        EntityDetail  anchorEntity = validateAnchorEntity(userId,
                                                          beanGUID,
                                                          beanGUIDTypeName,
                                                          beanEntity,
                                                          beanGUIDParameterName,
                                                          true,
                                                          forLineage,
                                                          forDuplicateProcessing,
                                                          supportedZones,
                                                          effectiveTime,
                                                          methodName);

        if (beanEntity != null)
        {
            InstanceProperties newProperties = beanEntity.getProperties();

            if (newProperties == null)
            {
                newProperties = new InstanceProperties();
            }

            newProperties.setEffectiveFromTime(effectiveFrom);
            newProperties.setEffectiveToTime(effectiveTo);

            repositoryHandler.updateEntityProperties(userId,
                                                     externalSourceGUID,
                                                     externalSourceName,
                                                     beanEntity.getGUID(),
                                                     beanEntity,
                                                     beanGUIDTypeGUID,
                                                     beanGUIDTypeName,
                                                     newProperties,
                                                     methodName);

            if (anchorEntity != null)
            {
                final String actionDescriptionTemplate = "Updating effectivity dates for %s %s";
                String actionDescription = String.format(actionDescriptionTemplate, beanGUIDTypeName, beanGUID);

                int latestChangeTargetOrdinal;
                String attachmentGUID = null;
                String attachmentTypeName = null;

                if (beanGUID.equals(anchorEntity.getGUID()))
                {
                    latestChangeTargetOrdinal = OpenMetadataAPIMapper.ENTITY_PROPERTY_LATEST_CHANGE_TARGET_ORDINAL;
                }
                else
                {
                    latestChangeTargetOrdinal = OpenMetadataAPIMapper.ATTACHMENT_PROPERTY_LATEST_CHANGE_TARGET_ORDINAL;
                    attachmentGUID = beanGUID;
                    attachmentTypeName = beanGUIDTypeName;
                }

                this.addLatestChangeToAnchor(anchorEntity,
                                             latestChangeTargetOrdinal,
                                             OpenMetadataAPIMapper.UPDATED_LATEST_CHANGE_ACTION_ORDINAL,
                                             null,
                                             attachmentGUID,
                                             attachmentTypeName,
                                             null,
                                             userId,
                                             actionDescription,
                                             methodName);
            }
        }
    }


    /**
     * Update the effectivity dates of a specific relationship.
     * The effectivity dates control the visibility of the relationship through specific APIs.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param relationshipGUID unique identifier of the entity in the repositories
     * @param relationshipGUIDParameterName parameter name that passed the relationshipGUID
     * @param relationshipGUIDTypeName type of relationship
     * @param effectiveFrom the date when this element is active - null for active now
     * @param effectiveTo the date when this element becomes inactive - null for active until deleted
     * @param methodName calling method
     *
     * @throws InvalidParameterException either the unique identifier or the status are invalid in some way
     * @throws UserNotAuthorizedException the governance action service is not authorized to update this element
     * @throws PropertyServerException there is a problem with the metadata store
     */
    public void updateRelationshipEffectivityDates(String userId,
                                                   String externalSourceGUID,
                                                   String externalSourceName,
                                                   String relationshipGUID,
                                                   String relationshipGUIDParameterName,
                                                   String relationshipGUIDTypeName,
                                                   Date   effectiveFrom,
                                                   Date   effectiveTo,
                                                   String methodName) throws InvalidParameterException,
                                                                             UserNotAuthorizedException,
                                                                             PropertyServerException
    {
        updateRelationshipEffectivityDates(userId,
                                           externalSourceGUID,
                                           externalSourceName,
                                           relationshipGUID,
                                           relationshipGUIDParameterName,
                                           relationshipGUIDTypeName,
                                           effectiveFrom,
                                           effectiveTo,
                                           null,
                                           methodName);
    }

    /**
     * Update the effectivity dates of a specific relationship.
     * The effectivity dates control the visibility of the relationship through specific APIs.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param relationshipGUID unique identifier of the entity in the repositories
     * @param relationshipGUIDParameterName parameter name that passed the relationshipGUID
     * @param relationshipGUIDTypeName type of relationship
     * @param effectiveFrom the date when this element is active - null for active now
     * @param effectiveTo the date when this element becomes inactive - null for active until deleted
     * @param methodName calling method
     *
     * @throws InvalidParameterException either the unique identifier or the status are invalid in some way
     * @throws UserNotAuthorizedException the governance action service is not authorized to update this element
     * @throws PropertyServerException there is a problem with the metadata store
     */
    public void updateRelationshipEffectivityDates(String userId,
                                                   String externalSourceGUID,
                                                   String externalSourceName,
                                                   String relationshipGUID,
                                                   String relationshipGUIDParameterName,
                                                   String relationshipGUIDTypeName,
                                                   Date   effectiveFrom,
                                                   Date   effectiveTo,
                                                   Date   effectiveTime,
                                                   String methodName) throws InvalidParameterException,
                                                                             UserNotAuthorizedException,
                                                                             PropertyServerException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(relationshipGUID, relationshipGUIDParameterName, methodName);

        Relationship relationship = repositoryHandler.getRelationshipByGUID(userId,
                                                                            relationshipGUID,
                                                                            relationshipGUIDParameterName,
                                                                            relationshipGUIDTypeName,
                                                                            effectiveTime,
                                                                            methodName);

        if (relationship != null)
        {
            final String entityOneParameterName = "relationship.getEntityOneProxy().getGUID()";
            final String entityTwoParameterName = "relationship.getEntityTwoProxy().getGUID()";

            this.validateAnchorEntity(userId,
                                      relationship.getEntityOneProxy().getGUID(),
                                      entityOneParameterName,
                                      OpenMetadataAPIMapper.OPEN_METADATA_ROOT_TYPE_NAME,
                                      false,
                                      false,
                                      false,
                                      supportedZones,
                                      this.getEffectiveTime(effectiveFrom, effectiveTo),
                                      methodName);

            this.validateAnchorEntity(userId,
                                      relationship.getEntityTwoProxy().getGUID(),
                                      entityTwoParameterName,
                                      OpenMetadataAPIMapper.OPEN_METADATA_ROOT_TYPE_NAME,
                                      false,
                                      false,
                                      false,
                                      supportedZones,
                                      this.getEffectiveTime(effectiveFrom, effectiveTo),
                                      methodName);

            InstanceProperties newProperties = relationship.getProperties();

            if (newProperties == null)
            {
                newProperties = new InstanceProperties();
            }

            newProperties.setEffectiveFromTime(effectiveFrom);
            newProperties.setEffectiveToTime(effectiveTo);

            repositoryHandler.updateRelationshipProperties(userId,
                                                           externalSourceGUID,
                                                           externalSourceName,
                                                           relationshipGUID,
                                                           newProperties,
                                                           methodName);

        }
    }


    /**
     * Create the new properties to hand to the repository helper based on the supplied properties,
     * existing properties and whether this is a merge update or not.
     * The effectivity dates are always preserved unless they are over-written by the caller.  If they need clearing then use the separate call.
     *
     * @param isMergeUpdate should the supplied updateProperties be merged with existing properties (true) by replacing the just the properties with
     *                      matching names, or should the entire properties of the instance be replaced?
     * @param suppliedProperties properties from the caller
     * @param existingProperties properties from the repository
     * @return properties to store
     */
    private InstanceProperties setUpNewProperties(boolean            isMergeUpdate,
                                                  InstanceProperties suppliedProperties,
                                                  InstanceProperties existingProperties)
    {
        /*
         * Sort out the properties
         */
        InstanceProperties newProperties;

        if (isMergeUpdate)
        {
            newProperties = existingProperties;

            if (newProperties == null)
            {
                newProperties = suppliedProperties;
            }
            else if (suppliedProperties != null)
            {
                Map  propertyMap = suppliedProperties.getInstanceProperties();

                for (String propertyName : propertyMap.keySet())
                {
                    if (propertyName != null)
                    {
                        newProperties.setProperty(propertyName, propertyMap.get(propertyName));
                    }
                }

                if (suppliedProperties.getEffectiveFromTime() != null)
                {
                    newProperties.setEffectiveFromTime(suppliedProperties.getEffectiveFromTime());
                }

                if (suppliedProperties.getEffectiveToTime() != null)
                {
                    newProperties.setEffectiveToTime(suppliedProperties.getEffectiveToTime());
                }
            }
        }
        else
        {
            newProperties = suppliedProperties;

            /*
             * Preserve the effectivity dates even though the properties are being replaced.
             */
            if (existingProperties != null)
            {
                if (existingProperties.getEffectiveFromTime() != null)
                {
                    newProperties.setEffectiveFromTime(existingProperties.getEffectiveFromTime());
                }
                if (existingProperties.getEffectiveToTime() != null)
                {
                    newProperties.setEffectiveToTime(existingProperties.getEffectiveToTime());
                }
            }
        }

        return newProperties;
    }


    /**
     * Update the properties associated with a relationship.  Effectivity dates are unchanged.
     *
     * @param userId caller's userId
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param relationshipGUID unique identifier of the relationship to update
     * @param relationshipGUIDParameterName  name of the parameter supplying the relationshipGUID
     * @param relationshipTypeName type name of relationship if known (null is ok)
     * @param isMergeUpdate should the supplied updateProperties be merged with existing properties (true) by replacing the just the properties with
     *                      matching names, or should the entire properties of the instance be replaced?
     * @param relationshipProperties new properties for the relationship
     * @param methodName calling method
     *
     * @throws InvalidParameterException the unique identifier of the relationship is null or invalid in some way; the properties are
     *                                    not valid for this type of relationship
     * @throws UserNotAuthorizedException the governance action service is not authorized to update this relationship
     * @throws PropertyServerException there is a problem with the metadata store
     */
    public void updateRelationshipProperties(String             userId,
                                             String             externalSourceGUID,
                                             String             externalSourceName,
                                             String             relationshipGUID,
                                             String             relationshipGUIDParameterName,
                                             String             relationshipTypeName,
                                             boolean            isMergeUpdate,
                                             InstanceProperties relationshipProperties,
                                             String             methodName) throws InvalidParameterException,
                                                                                   UserNotAuthorizedException,
                                                                                   PropertyServerException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(relationshipGUID, relationshipGUIDParameterName, methodName);

        Relationship relationship = repositoryHandler.getRelationshipByGUID(userId,
                                                                            relationshipGUID,
                                                                            relationshipGUIDParameterName,
                                                                            relationshipTypeName,
                                                                            null,
                                                                            methodName);

        if (this.visibleToUserThroughRelationship(userId, relationship, methodName))
        {
            /*
             * Sort out the properties
             */
            InstanceProperties newProperties = setUpNewProperties(isMergeUpdate,
                                                                  relationshipProperties,
                                                                  relationship.getProperties());

            final String entityOneParameterName = "relationship.getEntityOneProxy().getGUID()";
            final String entityTwoParameterName = "relationship.getEntityTwoProxy().getGUID()";

            Date effectiveTime = this.getEffectiveTime(newProperties);

            this.validateAnchorEntity(userId,
                                      relationship.getEntityOneProxy().getGUID(),
                                      entityOneParameterName,
                                      OpenMetadataAPIMapper.OPEN_METADATA_ROOT_TYPE_NAME,
                                      false,
                                      false,
                                      false,
                                      supportedZones,
                                      effectiveTime,
                                      methodName);

            this.validateAnchorEntity(userId,
                                      relationship.getEntityTwoProxy().getGUID(),
                                      entityTwoParameterName,
                                      OpenMetadataAPIMapper.OPEN_METADATA_ROOT_TYPE_NAME,
                                      false,
                                      false,
                                      false,
                                      supportedZones,
                                      effectiveTime,
                                      methodName);

            repositoryHandler.updateRelationshipProperties(userId,
                                                           externalSourceGUID,
                                                           externalSourceName,
                                                           relationship,
                                                           newProperties,
                                                           methodName);
        }
    }


    /**
     * Locate the requested classification in the supplied entity.
     *
     * @param beanEntity entity potentially containing the classification
     * @param classificationTypeName unique name of classification type
     */
    private Classification getExistingClassification(EntityDetail beanEntity,
                                                     String       classificationTypeName)
    {
        Classification  existingClassification  = null;

        if (beanEntity != null)
        {
            /*
             * Look to see if there is an existing classification
             */
            List existingClassifications = beanEntity.getClassifications();
            if (existingClassifications != null)
            {
                for (Classification classification : existingClassifications)
                {
                    if (classification != null)
                    {
                        if (classificationTypeName.equals(classification.getName()))
                        {
                            existingClassification = classification;
                        }
                    }
                }
            }
        }

        return existingClassification;
    }


    /**
     * Remove the requested classification from the matching entity in the repository.
     *
     * @param userId calling user
     * @param beanGUID unique identifier of the entity in the repositories
     * @param beanGUIDParameterName parameter name that passed the beanGUID
     * @param beanGUIDTypeName type of bean
     * @param classificationTypeGUID unique identifier of classification type
     * @param classificationTypeName unique name of classification type
     * @param methodName calling method
     * @throws InvalidParameterException the classification name is null
     * @throws PropertyServerException there is a problem with the repositories
     * @throws UserNotAuthorizedException the user is not allowed to update the security tags
     */
    public void removeClassificationFromRepository(String userId,
                                                   String beanGUID,
                                                   String beanGUIDParameterName,
                                                   String beanGUIDTypeName,
                                                   String classificationTypeGUID,
                                                   String classificationTypeName,
                                                   String methodName) throws InvalidParameterException,
                                                                             PropertyServerException,
                                                                             UserNotAuthorizedException
    {
        removeClassificationFromRepository(userId,
                                           null,
                                           null,
                                           beanGUID,
                                           beanGUIDParameterName,
                                           beanGUIDTypeName,
                                           classificationTypeGUID,
                                           classificationTypeName,
                                           false,
                                           false,
                                           new Date(),
                                           methodName);
    }


    /**
     * Remove the requested classification from the matching entity in the repository.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param beanGUID unique identifier of the entity in the repositories
     * @param beanGUIDParameterName parameter name that passed the beanGUID
     * @param beanGUIDTypeName type of bean
     * @param classificationTypeGUID unique identifier of classification type
     * @param classificationTypeName unique name of classification type
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     * @throws InvalidParameterException the classification name is null
     * @throws PropertyServerException there is a problem with the repositories
     * @throws UserNotAuthorizedException the user is not allowed to update the security tags
     */
    public void removeClassificationFromRepository(String  userId,
                                                   String  externalSourceGUID,
                                                   String  externalSourceName,
                                                   String  beanGUID,
                                                   String  beanGUIDParameterName,
                                                   String  beanGUIDTypeName,
                                                   String  classificationTypeGUID,
                                                   String  classificationTypeName,
                                                   boolean forLineage,
                                                   boolean forDuplicateProcessing,
                                                   Date    effectiveTime,
                                                   String  methodName) throws InvalidParameterException,
                                                                              PropertyServerException,
                                                                              UserNotAuthorizedException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(beanGUID, beanGUIDParameterName, methodName);

        EntityDetail  beanEntity = getEntityFromRepository(userId,
                                                           beanGUID,
                                                           beanGUIDParameterName,
                                                           beanGUIDTypeName,
                                                           null,
                                                           null,
                                                           forLineage,
                                                           forDuplicateProcessing,
                                                           supportedZones,
                                                           effectiveTime,
                                                           methodName);

        EntityDetail  anchorEntity = validateAnchorEntity(userId,
                                                          beanGUID,
                                                          beanGUIDTypeName,
                                                          beanEntity,
                                                          beanGUIDParameterName,
                                                          true,
                                                          forLineage,
                                                          forDuplicateProcessing,
                                                          supportedZones,
                                                          effectiveTime,
                                                          methodName);

        if (beanEntity != null)
        {
            /*
             * Look to see if there is an existing classification
             */
            InstanceAuditHeader existingClassification = this.getExistingClassification(beanEntity, classificationTypeName);

            if (existingClassification != null)
            {
                repositoryHandler.declassifyEntity(userId,
                                                   externalSourceGUID,
                                                   externalSourceName,
                                                   beanEntity.getGUID(),
                                                   beanGUIDParameterName,
                                                   beanGUIDTypeName,
                                                   classificationTypeGUID,
                                                   classificationTypeName,
                                                   existingClassification,
                                                   forLineage,
                                                   forDuplicateProcessing,
                                                   effectiveTime,
                                                   methodName);

                if (anchorEntity != null)
                {
                    final String actionDescriptionTemplate = "Removing %s classification from %s %s";
                    String actionDescription = String.format(actionDescriptionTemplate, classificationTypeName, beanGUIDTypeName, beanGUID);

                    int latestChangeTargetOrdinal;
                    String attachmentGUID = null;
                    String attachmentTypeName = null;

                    if (beanGUID.equals(anchorEntity.getGUID()))
                    {
                        latestChangeTargetOrdinal = OpenMetadataAPIMapper.ENTITY_CLASSIFICATION_LATEST_CHANGE_TARGET_ORDINAL;
                    }
                    else
                    {
                        latestChangeTargetOrdinal = OpenMetadataAPIMapper.ATTACHMENT_CLASSIFICATION_LATEST_CHANGE_TARGET_ORDINAL;
                        attachmentGUID = beanGUID;
                        attachmentTypeName = beanGUIDTypeName;
                    }

                    this.addLatestChangeToAnchor(anchorEntity,
                                                 latestChangeTargetOrdinal,
                                                 OpenMetadataAPIMapper.DELETED_LATEST_CHANGE_ACTION_ORDINAL,
                                                 classificationTypeName,
                                                 attachmentGUID,
                                                 attachmentTypeName,
                                                 null,
                                                 userId,
                                                 actionDescription,
                                                 methodName);
                }
            }
        }
    }






    /**
     * Fill in information about an asset from an entity.  This is to pass to the Open Metadata Security verifier.
     *
     * @param entity properties to add to the bean
     * @param methodName calling method
     */
    private Asset getAssetBeanFromEntity(EntityDetail entity,
                                         String       methodName)
    {
        if ((entity != null) && (entity.getType() != null))
        {
            Asset assetBean = new Asset();

            String              typeId                    = entity.getType().getTypeDefGUID();
            String              typeName                  = entity.getType().getTypeDefName();
            InstanceStatus      instanceStatus            = entity.getStatus();
            String              assetGUID                 = entity.getGUID();
            InstanceProperties  entityProperties          = entity.getProperties();
            InstanceProperties  securityTagProperties     = null;
            InstanceProperties  confidentialityProperties = null;
            InstanceProperties  confidenceProperties      = null;
            InstanceProperties  criticalityProperties     = null;
            InstanceProperties  impactProperties          = null;
            InstanceProperties  retentionProperties       = null;
            InstanceProperties  ownershipProperties       = null;
            InstanceProperties  zoneProperties            = null;
            InstanceProperties  originProperties          = null;

            if (entity.getClassifications() != null)
            {
                for (Classification classification : entity.getClassifications())
                {
                    if (classification != null)
                    {
                        if (OpenMetadataAPIMapper.SECURITY_TAG_CLASSIFICATION_TYPE_NAME.equals(classification.getName()))
                        {
                            securityTagProperties = classification.getProperties();
                        }
                        else if (OpenMetadataAPIMapper.CONFIDENTIALITY_CLASSIFICATION_TYPE_NAME.equals(classification.getName()))
                        {
                            confidentialityProperties = classification.getProperties();
                        }
                        else if (OpenMetadataAPIMapper.CONFIDENCE_CLASSIFICATION_TYPE_NAME.equals(classification.getName()))
                        {
                            confidenceProperties = classification.getProperties();
                        }
                        else if (OpenMetadataAPIMapper.CRITICALITY_CLASSIFICATION_TYPE_NAME.equals(classification.getName()))
                        {
                            criticalityProperties = classification.getProperties();
                        }
                        else if (OpenMetadataAPIMapper.IMPACT_CLASSIFICATION_TYPE_NAME.equals(classification.getName()))
                        {
                            criticalityProperties = classification.getProperties();
                        }
                        else if (OpenMetadataAPIMapper.RETENTION_CLASSIFICATION_TYPE_NAME.equals(classification.getName()))
                        {
                            retentionProperties = classification.getProperties();
                        }
                        else if (OpenMetadataAPIMapper.ASSET_OWNERSHIP_CLASSIFICATION_NAME.equals(classification.getName()))
                        {
                            ownershipProperties = classification.getProperties();
                        }
                        else if (OpenMetadataAPIMapper.ASSET_ZONES_CLASSIFICATION_NAME.equals(classification.getName()))
                        {
                            zoneProperties = classification.getProperties();
                        }
                        else if (OpenMetadataAPIMapper.ASSET_ORIGIN_CLASSIFICATION_NAME.equals(classification.getName()))
                        {
                            originProperties = classification.getProperties();
                        }
                    }
                }
            }

            setupAssetBeanWithEntityProperties(assetBean,
                                               typeId,
                                               typeName,
                                               instanceStatus,
                                               assetGUID,
                                               entityProperties,
                                               securityTagProperties,
                                               confidentialityProperties,
                                               confidenceProperties,
                                               criticalityProperties,
                                               impactProperties,
                                               retentionProperties,
                                               ownershipProperties,
                                               zoneProperties,
                                               originProperties,
                                               methodName);
            return assetBean;
        }

        return null;
    }


    /**
     * Convert an OMRS InstanceStatus enum into a metadata security Referenceable Status enum.
     *
     * @param instanceStatus value from the entity
     * @return mapped enum (default is ReferenceableStatus.UNKNOWN)
     */
    private ReferenceableStatus getReferenceableStatus(InstanceStatus instanceStatus)
    {
        if (instanceStatus != null)
        {
            for (ReferenceableStatus referenceableStatus : ReferenceableStatus.values())
            {
                if (referenceableStatus.getOMRSOrdinal() == instanceStatus.getOrdinal())
                {
                    return referenceableStatus;
                }
            }
        }

        return ReferenceableStatus.UNKNOWN;
    }



    /**
     * Fill in information about an asset from an entity.  This is to pass to the Open Metadata Security verifier.
     *
     * @param assetBean bean to fill out
     * @param typeId unique identifier for the type of the entity
     * @param typeName unique name for the type of the entity
     * @param instanceStatus status from the entity
     * @param assetGUID unique identifier for the entity
     * @param entityProperties properties from the entity
     * @param securityTagProperties properties from the SecurityTags classification
     * @param confidentialityProperties properties from the Confidentiality classification
     * @param confidenceProperties properties from the Confidence classification
     * @param criticalityProperties properties from the Criticality classification
     * @param impactProperties properties from the Impact classification
     * @param retentionProperties properties from the Retention classification
     * @param ownershipProperties properties from the AssetOwnership classification
     * @param zoneProperties properties from the AssetZoneMembership classification
     * @param originProperties properties from the AssetOrigin classification
     * @param methodName calling method
     */
    private void setupAssetBeanWithEntityProperties(Asset              assetBean,
                                                    String             typeId,
                                                    String             typeName,
                                                    InstanceStatus     instanceStatus,
                                                    String             assetGUID,
                                                    InstanceProperties entityProperties,
                                                    InstanceProperties securityTagProperties,
                                                    InstanceProperties confidentialityProperties,
                                                    InstanceProperties confidenceProperties,
                                                    InstanceProperties criticalityProperties,
                                                    InstanceProperties impactProperties,
                                                    InstanceProperties retentionProperties,
                                                    InstanceProperties ownershipProperties,
                                                    InstanceProperties zoneProperties,
                                                    InstanceProperties originProperties,
                                                    String             methodName)
    {
        assetBean.setTypeGUID(typeId);
        assetBean.setTypeName(typeName);
        assetBean.setStatus(this.getReferenceableStatus(instanceStatus));
        assetBean.setGUID(assetGUID);

        InstanceProperties properties = new InstanceProperties(entityProperties);

        assetBean.setQualifiedName(repositoryHelper.removeStringProperty(serviceName,
                                                                         OpenMetadataAPIMapper.QUALIFIED_NAME_PROPERTY_NAME,
                                                                         properties,
                                                                         methodName));
        assetBean.setAdditionalProperties(repositoryHelper.removeStringMapFromProperty(serviceName,
                                                                                       OpenMetadataAPIMapper.ADDITIONAL_PROPERTIES_PROPERTY_NAME,
                                                                                       properties,
                                                                                       methodName));
        assetBean.setDisplayName(repositoryHelper.removeStringProperty(serviceName,
                                                                       OpenMetadataAPIMapper.NAME_PROPERTY_NAME,
                                                                       properties,
                                                                       methodName));
        assetBean.setDescription(repositoryHelper.removeStringProperty(serviceName,
                                                                       OpenMetadataAPIMapper.DESCRIPTION_PROPERTY_NAME,
                                                                       properties,
                                                                       methodName));
        assetBean.setOwner(repositoryHelper.removeStringProperty(serviceName,
                                                                 OpenMetadataAPIMapper.OWNER_PROPERTY_NAME,
                                                                 properties,
                                                                 methodName));
        assetBean.setOwnerType(repositoryHelper.removeEnumPropertyOrdinal(serviceName,
                                                                          OpenMetadataAPIMapper.OWNER_TYPE_PROPERTY_NAME,
                                                                          properties,
                                                                          methodName));
        assetBean.setZoneMembership(repositoryHelper.removeStringArrayProperty(serviceName,
                                                                               OpenMetadataAPIMapper.ZONE_MEMBERSHIP_PROPERTY_NAME,
                                                                               properties,
                                                                               methodName));

        assetBean.setExtendedProperties(repositoryHelper.getInstancePropertiesAsMap(properties));

        if (securityTagProperties != null)
        {
            assetBean.setSecurityLabels(repositoryHelper.getStringArrayProperty(serviceName,
                                                                                OpenMetadataAPIMapper.SECURITY_LABELS_PROPERTY_NAME,
                                                                                securityTagProperties,
                                                                                methodName));
            assetBean.setSecurityProperties(repositoryHelper.getMapFromProperty(serviceName,
                                                                                OpenMetadataAPIMapper.SECURITY_PROPERTIES_PROPERTY_NAME,
                                                                                securityTagProperties,
                                                                                methodName));
        }

        if (confidentialityProperties != null)
        {
            ConfidentialityGovernanceClassification classification = new ConfidentialityGovernanceClassification();

            classification.setStatus(repositoryHelper.getIntProperty(serviceName,
                                                                     OpenMetadataAPIMapper.STATUS_IDENTIFIER_PROPERTY_NAME,
                                                                     confidentialityProperties,
                                                                     methodName));
            classification.setConfidence(repositoryHelper.getIntProperty(serviceName,
                                                                         OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_CONFIDENCE_PROPERTY_NAME,
                                                                         confidentialityProperties,
                                                                         methodName));
            classification.setSteward(repositoryHelper.getStringProperty(serviceName,
                                                                         OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_STEWARD_PROPERTY_NAME,
                                                                         confidentialityProperties,
                                                                         methodName));
            classification.setSource(repositoryHelper.getStringProperty(serviceName,
                                                                        OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_SOURCE_PROPERTY_NAME,
                                                                        confidentialityProperties,
                                                                        methodName));
            classification.setNotes(repositoryHelper.getStringProperty(serviceName,
                                                                       OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_NOTES_PROPERTY_NAME,
                                                                       confidentialityProperties,
                                                                       methodName));
            classification.setConfidentialityLevel(repositoryHelper.getIntProperty(serviceName,
                                                                                   OpenMetadataAPIMapper.LEVEL_IDENTIFIER_PROPERTY_NAME,
                                                                                   confidentialityProperties,
                                                                                   methodName));
        }

        if (confidenceProperties != null)
        {
            ConfidenceGovernanceClassification classification = new ConfidenceGovernanceClassification();

            classification.setStatus(repositoryHelper.getIntProperty(serviceName,
                                                                     OpenMetadataAPIMapper.STATUS_IDENTIFIER_PROPERTY_NAME,
                                                                     confidenceProperties,
                                                                     methodName));
            classification.setConfidence(repositoryHelper.getIntProperty(serviceName,
                                                                         OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_CONFIDENCE_PROPERTY_NAME,
                                                                         confidenceProperties,
                                                                         methodName));
            classification.setSteward(repositoryHelper.getStringProperty(serviceName,
                                                                         OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_STEWARD_PROPERTY_NAME,
                                                                         confidenceProperties,
                                                                         methodName));
            classification.setSource(repositoryHelper.getStringProperty(serviceName,
                                                                        OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_SOURCE_PROPERTY_NAME,
                                                                        confidenceProperties,
                                                                        methodName));
            classification.setNotes(repositoryHelper.getStringProperty(serviceName,
                                                                       OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_NOTES_PROPERTY_NAME,
                                                                       confidenceProperties,
                                                                       methodName));
            classification.setConfidenceLevel(repositoryHelper.getIntProperty(serviceName,
                                                                              OpenMetadataAPIMapper.LEVEL_IDENTIFIER_PROPERTY_NAME,
                                                                              confidenceProperties,
                                                                              methodName));
        }

        if (criticalityProperties != null)
        {
            CriticalityGovernanceClassification classification = new CriticalityGovernanceClassification();

            classification.setStatus(repositoryHelper.getIntProperty(serviceName,
                                                                     OpenMetadataAPIMapper.STATUS_IDENTIFIER_PROPERTY_NAME,
                                                                     criticalityProperties,
                                                                     methodName));
            classification.setConfidence(repositoryHelper.getIntProperty(serviceName,
                                                                         OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_CONFIDENCE_PROPERTY_NAME,
                                                                         criticalityProperties,
                                                                         methodName));
            classification.setSteward(repositoryHelper.getStringProperty(serviceName,
                                                                         OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_STEWARD_PROPERTY_NAME,
                                                                         criticalityProperties,
                                                                         methodName));
            classification.setSource(repositoryHelper.getStringProperty(serviceName,
                                                                        OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_SOURCE_PROPERTY_NAME,
                                                                        criticalityProperties,
                                                                        methodName));
            classification.setNotes(repositoryHelper.getStringProperty(serviceName,
                                                                       OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_NOTES_PROPERTY_NAME,
                                                                       criticalityProperties,
                                                                       methodName));
            classification.setCriticalityLevel(repositoryHelper.getIntProperty(serviceName,
                                                                               OpenMetadataAPIMapper.LEVEL_IDENTIFIER_PROPERTY_NAME,
                                                                               criticalityProperties,
                                                                               methodName));
        }

        if (impactProperties != null)
        {
            ImpactGovernanceClassification classification = new ImpactGovernanceClassification();

            classification.setStatus(repositoryHelper.getIntProperty(serviceName,
                                                                     OpenMetadataAPIMapper.STATUS_IDENTIFIER_PROPERTY_NAME,
                                                                     impactProperties,
                                                                     methodName));
            classification.setConfidence(repositoryHelper.getIntProperty(serviceName,
                                                                         OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_CONFIDENCE_PROPERTY_NAME,
                                                                         impactProperties,
                                                                         methodName));
            classification.setSteward(repositoryHelper.getStringProperty(serviceName,
                                                                         OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_STEWARD_PROPERTY_NAME,
                                                                         impactProperties,
                                                                         methodName));
            classification.setSource(repositoryHelper.getStringProperty(serviceName,
                                                                        OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_SOURCE_PROPERTY_NAME,
                                                                        impactProperties,
                                                                        methodName));
            classification.setNotes(repositoryHelper.getStringProperty(serviceName,
                                                                       OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_NOTES_PROPERTY_NAME,
                                                                       impactProperties,
                                                                       methodName));
            classification.setImpactSeverityLevel(repositoryHelper.getIntProperty(serviceName,
                                                                                  OpenMetadataAPIMapper.SEVERITY_LEVEL_IDENTIFIER_PROPERTY_NAME,
                                                                                  impactProperties,
                                                                                  methodName));
        }

        if (retentionProperties != null)
        {
            RetentionGovernanceClassification classification = new RetentionGovernanceClassification();

            classification.setStatus(repositoryHelper.getIntProperty(serviceName,
                                                                     OpenMetadataAPIMapper.STATUS_IDENTIFIER_PROPERTY_NAME,
                                                                     retentionProperties,
                                                                     methodName));
            classification.setConfidence(repositoryHelper.getIntProperty(serviceName,
                                                                         OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_CONFIDENCE_PROPERTY_NAME,
                                                                         retentionProperties,
                                                                         methodName));
            classification.setSteward(repositoryHelper.getStringProperty(serviceName,
                                                                         OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_STEWARD_PROPERTY_NAME,
                                                                         retentionProperties,
                                                                         methodName));
            classification.setSource(repositoryHelper.getStringProperty(serviceName,
                                                                        OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_SOURCE_PROPERTY_NAME,
                                                                        retentionProperties,
                                                                        methodName));
            classification.setNotes(repositoryHelper.getStringProperty(serviceName,
                                                                       OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_NOTES_PROPERTY_NAME,
                                                                       retentionProperties,
                                                                       methodName));
            classification.setRetentionBasis(repositoryHelper.getIntProperty(serviceName,
                                                                             OpenMetadataAPIMapper.BASIS_IDENTIFIER_PROPERTY_NAME,
                                                                             retentionProperties,
                                                                             methodName));
            classification.setAssociatedGUID(repositoryHelper.getStringProperty(serviceName,
                                                                                OpenMetadataAPIMapper.RETENTION_ASSOCIATED_GUID_PROPERTY_NAME,
                                                                                retentionProperties,
                                                                                methodName));
            classification.setArchiveAfter(repositoryHelper.getDateProperty(serviceName,
                                                                            OpenMetadataAPIMapper.RETENTION_ARCHIVE_AFTER_PROPERTY_NAME,
                                                                            retentionProperties,
                                                                            methodName));
            classification.setDeleteAfter(repositoryHelper.getDateProperty(serviceName,
                                                                           OpenMetadataAPIMapper.RETENTION_DELETE_AFTER_PROPERTY_NAME,
                                                                           retentionProperties,
                                                                           methodName));
        }

        if (ownershipProperties != null)
        {
            assetBean.setOwner(repositoryHelper.getStringProperty(serviceName,
                                                                  OpenMetadataAPIMapper.OWNER_PROPERTY_NAME,
                                                                  ownershipProperties,
                                                                  methodName));
            assetBean.setOwnerType(repositoryHelper.getEnumPropertyOrdinal(serviceName,
                                                                           OpenMetadataAPIMapper.OWNER_TYPE_PROPERTY_NAME,
                                                                           ownershipProperties,
                                                                           methodName));
        }

        if (zoneProperties != null)
        {
            assetBean.setZoneMembership(repositoryHelper.getStringArrayProperty(serviceName,
                                                                                OpenMetadataAPIMapper.ZONE_MEMBERSHIP_PROPERTY_NAME,
                                                                                zoneProperties,
                                                                                methodName));
        }

        if (originProperties != null)
        {
            Map origins = new HashMap<>();
            String               propertyValue = repositoryHelper.getStringProperty(serviceName,
                                                                                    OpenMetadataAPIMapper.ORGANIZATION_PROPERTY_NAME,
                                                                                    originProperties,
                                                                                    methodName);

            if (propertyValue != null)
            {
                origins.put(OpenMetadataAPIMapper.ORGANIZATION_PROPERTY_NAME, propertyValue);
            }

            propertyValue = repositoryHelper.getStringProperty(serviceName,
                                                               OpenMetadataAPIMapper.BUSINESS_CAPABILITY_PROPERTY_NAME,
                                                               originProperties,
                                                               methodName);

            if (propertyValue != null)
            {
                origins.put(OpenMetadataAPIMapper.BUSINESS_CAPABILITY_PROPERTY_NAME, propertyValue);
            }

            Map propertyMap = repositoryHelper.getStringMapFromProperty(serviceName,
                                                                                        OpenMetadataAPIMapper.OTHER_ORIGIN_VALUES_PROPERTY_NAME,
                                                                                        originProperties,
                                                                                        methodName);

            if (propertyMap != null)
            {
                for (String propertyName : propertyMap.keySet())
                {
                    if (propertyName != null)
                    {
                        origins.put(propertyName, propertyMap.get(propertyName));
                    }
                }
            }

            if (! origins.isEmpty())
            {
                assetBean.setOrigin(origins);
            }
        }
    }


    /**
     * Return the enum value that matches the ordinal from the classification properties.  If the ordinal is not recognized,
     * the enum returned is null.
     *
     * @param governanceClassificationProperties properties from classification (not null)
     * @param methodName calling methodName
     * @return status level identifier
     */
    private int getGovernanceClassificationStatus(InstanceProperties governanceClassificationProperties,
                                                  String             methodName)
    {
        int enumOrdinal = repositoryHelper.getEnumPropertyOrdinal(serviceName,
                                                                  OpenMetadataAPIMapper.GOVERNANCE_CLASSIFICATION_STATUS_PROPERTY_NAME,
                                                                  governanceClassificationProperties,
                                                                  methodName);

        if (enumOrdinal >= 0)
        {
            GovernanceClassificationStatus[] enums = GovernanceClassificationStatus.values();

            for (GovernanceClassificationStatus status : enums)
            {
                if (status.getOpenTypeOrdinal() == enumOrdinal)
                {
                    return status.getOpenTypeOrdinal();
                }
            }
        }

        return repositoryHelper.getIntProperty(serviceName,
                                               OpenMetadataAPIMapper.STATUS_IDENTIFIER_PROPERTY_NAME,
                                               governanceClassificationProperties,
                                               methodName);
    }


    /**
     * Validate that the user is able to perform the requested action on an attachment.  This method should be used by the other
     * handlers to verify whether or not the element they are working with is attached to a visible asset
     * (ie is a member of one of the supported zones) that can be operated on by the calling user.
     *
     * @param userId calling user
     * @param assetGUID unique identifier of the asset
     * @param assetGUIDParameterName name of parameter supplying the assetGUID
     * @param assetEntity entity storing the the asset's properties
     * @param suppliedSupportedZones list of supported zones from the caller.
     * @param methodName calling method
     * @throws InvalidParameterException the bean properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException problem accessing the property server
     */
    private void validateUserForAssetRead(String       userId,
                                          String       assetGUID,
                                          String       assetGUIDParameterName,
                                          EntityDetail assetEntity,
                                          List suppliedSupportedZones,
                                          String       methodName) throws InvalidParameterException,
                                                                          PropertyServerException,
                                                                          UserNotAuthorizedException
    {
        /*
         * This method will throw an exception if the asset is not in the supported zones - it will look like
         * the asset is not known.
         */
        invalidParameterHandler.validateAssetInSupportedZone(assetGUID,
                                                             assetGUIDParameterName,
                                                             suppliedSupportedZones,
                                                             this.getSupportedZones(userId, suppliedSupportedZones, serviceName),
                                                             serviceName,
                                                             methodName);

        /*
         * Create the bean for the security module then call the appropriate security method.
         */
        Asset assetBean = this.getAssetBeanFromEntity(assetEntity, methodName);

        securityVerifier.validateUserForAssetRead(userId, assetBean);
    }


    /**
     * Validate that the user is able to perform the requested action on an attachment.  This method should be used by the other
     * handlers to verify whether or not the element they are working with is attached to a visible asset
     * (ie is a member of one of the supported zones) that can be operated on by the calling user.
     *
     * @param userId calling user
     * @param originalAssetEntity entity storing the current asset
     * @param updatedAssetProperties  properties after the update has completed
     * @param newInstanceStatus status of the entity once the update is complete
     * @param methodName calling method
     * @throws UserNotAuthorizedException user not authorized to issue this request
     */
    private void validateUserForAssetUpdate(String             userId,
                                            EntityDetail       originalAssetEntity,
                                            InstanceProperties updatedAssetProperties,
                                            InstanceStatus     newInstanceStatus,
                                            String             methodName) throws UserNotAuthorizedException
    {
        /*
         * Creates the beans for the security module then calls to appropriate security method.
         */
        Asset originalAsset = this.getAssetBeanFromEntity(originalAssetEntity, methodName);

        AssetAuditHeader assetAuditHeader = new AssetAuditHeader();
        assetAuditHeader.setCreatedBy(originalAssetEntity.getCreatedBy());
        assetAuditHeader.setCreateTime(originalAssetEntity.getCreateTime());
        assetAuditHeader.setMaintainedBy(originalAssetEntity.getMaintainedBy());
        assetAuditHeader.setUpdatedBy(originalAssetEntity.getUpdatedBy());
        assetAuditHeader.setUpdateTime(originalAssetEntity.getUpdateTime());
        assetAuditHeader.setVersion(assetAuditHeader.getVersion());

        EntityDetail updatedAssetEntity = new EntityDetail(originalAssetEntity);
        updatedAssetEntity.setProperties(updatedAssetProperties);
        updatedAssetEntity.setStatus(newInstanceStatus);
        Asset updatedAsset = this.getAssetBeanFromEntity(updatedAssetEntity, methodName);

        securityVerifier.validateUserForAssetDetailUpdate(userId, originalAsset, assetAuditHeader, updatedAsset);
    }


    /**
     * Validate that the user is able to perform the requested action on an attachment.  This method should be used by the other
     * handlers to verify whether or not the element they are working with is attached to a visible asset
     * (ie is a member of one of the supported zones) that can be operated on by the calling user.
     *
     * @param userId calling user
     * @param assetGUID unique identifier of the asset
     * @param assetGUIDParameterName name of parameter supplying the assetGUID
     * @param assetEntity entity storing the root of the  asset
     * @param isFeedback       is this request related to a feedback element (comment, like, rating) or an attachment
     * @param isUpdate         is this an update request?
     * @param suppliedSupportedZones list of supported zones from the caller.
     * @param methodName calling method
     * @throws InvalidParameterException the bean properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException problem accessing the property server
     */
    private void validateUserForAssetAttachment(String       userId,
                                                String       assetGUID,
                                                String       assetGUIDParameterName,
                                                EntityDetail assetEntity,
                                                boolean      isFeedback,
                                                boolean      isUpdate,
                                                List suppliedSupportedZones,
                                                String       methodName) throws InvalidParameterException,
                                                                                PropertyServerException,
                                                                                UserNotAuthorizedException
    {
        /*
         * This method will throw an exception if the asset is not in the supported zones - it will look like
         * the asset is not known.
         */
        invalidParameterHandler.validateAssetInSupportedZone(assetGUID,
                                                             assetGUIDParameterName,
                                                             suppliedSupportedZones,
                                                             this.getSupportedZones(userId,
                                                                                    suppliedSupportedZones,
                                                                                    serviceName),
                                                             serviceName,
                                                             methodName);

        /*
         * Creates the entity and checks that it is in at least one of the supporting zones.
         */
        Asset asset = this.getAssetBeanFromEntity(assetEntity, methodName);

        /*
         * Now validate the security.
         */
        if (isUpdate)
        {
            if (isFeedback)
            {
                securityVerifier.validateUserForAssetFeedback(userId, asset);
            }
            else
            {
                securityVerifier.validateUserForAssetAttachmentUpdate(userId, asset);
            }
        }
        else
        {
            securityVerifier.validateUserForAssetRead(userId, asset);
        }
    }


    /**
     * Fill in information about an connection from an entity.  This is to pass to the Open Metadata Security verifier.
     *
     * @param entity properties fill out
     * @param methodName calling method
     */
    Connection getConnectionFromEntity(EntityDetail       entity,
                                       String             methodName)
    {
        if ((entity != null) && (entity.getType() != null))
        {
            Connection connectionBean = new Connection();

            connectionBean.setTypeGUID(entity.getType().getTypeDefGUID());
            connectionBean.setTypeName(entity.getType().getTypeDefName());

            connectionBean.setGUID(entity.getGUID());

            InstanceProperties properties = new InstanceProperties(entity.getProperties());

            connectionBean.setQualifiedName(repositoryHelper.removeStringProperty(serviceName,
                                                                                  OpenMetadataAPIMapper.QUALIFIED_NAME_PROPERTY_NAME,
                                                                                  properties,
                                                                                  methodName));

            connectionBean.setDisplayName(repositoryHelper.removeStringProperty(serviceName,
                                                                                OpenMetadataAPIMapper.DISPLAY_NAME_PROPERTY_NAME,
                                                                                properties,
                                                                                methodName));
            connectionBean.setDescription(repositoryHelper.removeStringProperty(serviceName,
                                                                                OpenMetadataAPIMapper.DESCRIPTION_PROPERTY_NAME,
                                                                                properties,
                                                                                methodName));

            connectionBean.setUserId(repositoryHelper.removeStringProperty(serviceName,
                                                                           OpenMetadataAPIMapper.USER_ID_PROPERTY_NAME,
                                                                           properties,
                                                                           methodName));

            connectionBean.setClearPassword(repositoryHelper.removeStringProperty(serviceName,
                                                                                  OpenMetadataAPIMapper.CLEAR_PASSWORD_PROPERTY_NAME,
                                                                                  properties,
                                                                                  methodName));

            connectionBean.setEncryptedPassword(repositoryHelper.removeStringProperty(serviceName,
                                                                                      OpenMetadataAPIMapper.ENCRYPTED_PASSWORD_PROPERTY_NAME,
                                                                                      properties,
                                                                                      methodName));

            connectionBean.setAdditionalProperties(repositoryHelper.removeStringMapFromProperty(serviceName,
                                                                                                OpenMetadataAPIMapper.ADDITIONAL_PROPERTIES_PROPERTY_NAME,
                                                                                                properties,
                                                                                                methodName));

            connectionBean.setConfigurationProperties(repositoryHelper.removeMapFromProperty(serviceName,
                                                                                             OpenMetadataAPIMapper.CONFIGURATION_PROPERTIES_PROPERTY_NAME,
                                                                                             properties,
                                                                                             methodName));

            connectionBean.setSecuredProperties(repositoryHelper.removeMapFromProperty(serviceName,
                                                                                       OpenMetadataAPIMapper.SECURED_PROPERTIES_PROPERTY_NAME,
                                                                                       properties,
                                                                                       methodName));

            connectionBean.setExtendedProperties(repositoryHelper.getInstancePropertiesAsMap(properties));

            return connectionBean;
        }

        return null;
    }


    /**
     * Validate that the user is able to retrieve the requested connection.
     *
     * @param userId calling user
     * @param entity entity storing the connection's properties
     * @param methodName calling method
     * @throws UserNotAuthorizedException user not authorized to access this connection
     */
    private void validateUserForConnection(String       userId,
                                           EntityDetail entity,
                                           String       methodName) throws UserNotAuthorizedException
    {
        Connection connectionBean = this.getConnectionFromEntity(entity, methodName);

        securityVerifier.validateUserForConnection(userId, connectionBean);
    }


    /**
     * Work out whether the relationship permits the requesting user to traverse along the relationship.  This is determined by the
     * isPublic property on relationships that represent feedback on another object.
     *
     * @param userId calling user
     * @param relationship relationship to the feedback content
     * @param methodName calling method
     * @return boolean - true if allowed
     */
    private boolean visibleToUserThroughRelationship(String        userId,
                                                     Relationship  relationship,
                                                     String        methodName)
    {
        if (relationship == null)
        {
            return false;
        }

        String relationshipTypeName = null;

        if (relationship.getType() != null)
        {
            relationshipTypeName = relationship.getType().getTypeDefName();
        }

        if (relationshipTypeName == null)
        {
            /*
             * Strictly speaking this relationship is in error. Returning false will cause it to be ignored.
             */
            return false;
        }

        /*
         * These are the feedback relationships.  They have a property called "isPrivate".  If it is set to true, only the user
         * that created it can see it (or update or delete it).
         */
        if ((repositoryHelper.isTypeOf(serviceName, relationshipTypeName, OpenMetadataAPIMapper.REFERENCEABLE_TO_LIKE_TYPE_NAME)) ||
            (repositoryHelper.isTypeOf(serviceName, relationshipTypeName, OpenMetadataAPIMapper.REFERENCEABLE_TO_TAG_TYPE_NAME)) ||
            (repositoryHelper.isTypeOf(serviceName, relationshipTypeName, OpenMetadataAPIMapper.REFERENCEABLE_TO_RATING_TYPE_NAME)) ||
            (repositoryHelper.isTypeOf(serviceName, relationshipTypeName, OpenMetadataAPIMapper.REFERENCEABLE_TO_COMMENT_TYPE_NAME)))
        {
            if (userId.equals(relationship.getCreatedBy()))
            {
                return true;
            }

            return repositoryHelper.getBooleanProperty(serviceName,
                                                       OpenMetadataAPIMapper.IS_PUBLIC_PROPERTY_NAME,
                                                       relationship.getProperties(),
                                                       methodName);
        }

        return true;
    }



    /**
     * Retrieve the anchorGUID property from the Anchors classification if present.  A null is returned if the Anchors classification
     * is missing, or the property is missing from the classification or is null.
     *
     * @param connectToEntity entity containing the classifications
     * @param methodName calling method
     * @return anchorGUID or null
     */
    public String getAnchorGUIDFromAnchorsClassification(EntityDetail connectToEntity,
                                                         String       methodName)
    {
        /*
         * Metadata maintained by Egeria Access Service modules should have the Anchors classification.
         */
        Classification anchorsClassification;
        String         anchorGUID = null;

        try
        {
            anchorsClassification = repositoryHelper.getClassificationFromEntity(serviceName,
                                                                                 connectToEntity,
                                                                                 OpenMetadataAPIMapper.ANCHORS_CLASSIFICATION_TYPE_NAME,
                                                                                 methodName);

            if (anchorsClassification != null)
            {
                /*
                 * If the connectTo entity has the Anchors classification then this should contain the anchor guid
                 */
                if (anchorsClassification.getProperties() != null)
                {
                    anchorGUID = repositoryHelper.getStringProperty(serviceName,
                                                                    OpenMetadataAPIMapper.ANCHOR_GUID_PROPERTY_NAME,
                                                                    anchorsClassification.getProperties(),
                                                                    methodName);

                    /*
                     * This is an attempt to trap an intermittent error recorded in issue #4680.
                     */
                    if ("".equals(anchorGUID))
                    {
                        final String localMethodName = "getAnchorGUIDFromAnchorsClassification";

                        throw new PropertyServerException(GenericHandlersErrorCode.UNKNOWN_ANCHOR_GUID.getMessageDefinition(localMethodName,
                                                                                                                            serviceName,
                                                                                                                            methodName),
                                                          this.getClass().getName(),
                                                          localMethodName);
                    }
                }
            }
        }
        catch (ClassificationErrorException noAnchorsClassification)
        {
            /*
             * No Anchors Classification - this is not an error - it means the connectTo entity is either an anchor itself, or was not created by an
             * Egeria component.
             */
        }
        catch (PropertyServerException error)
        {
            try
            {
                errorHandler.handleUnsupportedAnchorsType(error, methodName, OpenMetadataAPIMapper.ANCHORS_CLASSIFICATION_TYPE_NAME);
            }
            catch (PropertyServerException secondError)
            {
                // Not able to log exception
            }
        }

        return anchorGUID;
    }


    /**
     * Set up the anchors classification in an entity (and any child anchored entity connected to it).  This is done using the local server's user Id
     * and assumes these classifications are maintained in the local cohort.
     *
     * @param targetGUID unique identifier for the entity
     * @param targetGUIDParameterName parameter name supplying targetGUID
     * @param targetTypeName type of entity
     * @param targetEntity contents of the entity from the repository
     * @param anchorGUID unique identifier of the anchor
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @throws InvalidParameterException problem with one of the properties
     * @throws PropertyServerException problem within the repository services
     * @throws UserNotAuthorizedException local server user not allowed to issue the request.
     */
    private void maintainAnchorGUIDInClassification(String        targetGUID,
                                                    String        targetGUIDParameterName,
                                                    String        targetTypeName,
                                                    EntitySummary targetEntity,
                                                    String        anchorGUID,
                                                    boolean       forLineage,
                                                    boolean       forDuplicateProcessing,
                                                    Date          effectiveTime,
                                                    String        methodName) throws InvalidParameterException,
                                                                                     PropertyServerException,
                                                                                     UserNotAuthorizedException
    {
        Classification     anchorsClassification;
        InstanceProperties anchorsProperties = null;
        String             currentAnchorGUID = null;

        /*
         * This is an attempt to trap an intermittent error recorded in issue #4680.
         */
        if ("".equals(anchorGUID))
        {
            final String localMethodName = "maintainAnchorGUIDInClassification";

            throw new PropertyServerException(GenericHandlersErrorCode.UNKNOWN_ANCHOR_GUID.getMessageDefinition(localMethodName,
                                                                                                                serviceName,
                                                                                                                methodName),
                                              this.getClass().getName(),
                                              localMethodName);
        }

        /*
         * It is necessary to retrieve any existing classification to know whether it is a classify or reclassify method required.
         */
        try
        {
            anchorsClassification = repositoryHelper.getClassificationFromEntity(serviceName,
                                                                                 targetEntity,
                                                                                 OpenMetadataAPIMapper.ANCHORS_CLASSIFICATION_TYPE_NAME,
                                                                                 methodName);
            if (anchorsClassification != null)
            {
                /*
                 * If the target entity has the Anchors classification then this should contain the anchor guid
                 */
                anchorsProperties = anchorsClassification.getProperties();
                if (anchorsProperties != null)
                {
                    currentAnchorGUID = repositoryHelper.getStringProperty(serviceName,
                                                                           OpenMetadataAPIMapper.ANCHOR_GUID_PROPERTY_NAME,
                                                                           anchorsClassification.getProperties(),
                                                                           methodName);
                }
            }
        }
        catch (ClassificationErrorException noAnchorsClassification)
        {
            /*
             * No Anchors Classification - this is not an error - it means the target entity is either an anchor itself, or was not created by an
             * Egeria component.
             */
            anchorsClassification = null;
        }


        /*
         * This is an attempt to trap an intermittent error recorded in issue #4680.
         */
        if ("".equals(currentAnchorGUID))
        {
            final String localMethodName = "maintainAnchorGUIDInClassification";

            throw new PropertyServerException(GenericHandlersErrorCode.UNKNOWN_ANCHOR_GUID.getMessageDefinition(localMethodName,
                                                                                                                serviceName,
                                                                                                                methodName),
                                              this.getClass().getName(),
                                              localMethodName);
        }

        /*
         * If the anchor guid has changed then update the value in the classification
         */
        if (((currentAnchorGUID == null) && (anchorGUID != null)) ||
            ((currentAnchorGUID != null) && (! currentAnchorGUID.equals(anchorGUID))))
        {
            try
            {
                anchorsProperties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                                 anchorsProperties,
                                                                                 OpenMetadataAPIMapper.ANCHOR_GUID_PROPERTY_NAME,
                                                                                 anchorGUID,
                                                                                 methodName);

                if (anchorsClassification == null)
                {

                    if (anchorGUID != null)
                    {
                        repositoryHandler.classifyEntity(localServerUserId,
                                                         null,
                                                         null,
                                                         targetGUID,
                                                         null,
                                                         targetGUIDParameterName,
                                                         targetEntity.getType().getTypeDefName(),
                                                         OpenMetadataAPIMapper.ANCHORS_CLASSIFICATION_TYPE_GUID,
                                                         OpenMetadataAPIMapper.ANCHORS_CLASSIFICATION_TYPE_NAME,
                                                         ClassificationOrigin.ASSIGNED,
                                                         null,
                                                         anchorsProperties,
                                                         forLineage,
                                                         forDuplicateProcessing,
                                                         effectiveTime,
                                                         methodName);
                    }
                }
                else
                {
                    if (anchorGUID != null)
                    {
                        repositoryHandler.reclassifyEntity(localServerUserId,
                                                           null,
                                                           null,
                                                           targetGUID,
                                                           targetGUIDParameterName,
                                                           targetTypeName,
                                                           OpenMetadataAPIMapper.ANCHORS_CLASSIFICATION_TYPE_GUID,
                                                           OpenMetadataAPIMapper.ANCHORS_CLASSIFICATION_TYPE_NAME,
                                                           anchorsClassification,
                                                           anchorsProperties,
                                                           forLineage,
                                                           forDuplicateProcessing,
                                                           effectiveTime,
                                                           methodName);
                    }
                    else
                    {
                        repositoryHandler.declassifyEntity(localServerUserId,
                                                           null,
                                                           null,
                                                           targetGUID,
                                                           targetGUIDParameterName,
                                                           targetEntity.getType().getTypeDefName(),
                                                           OpenMetadataAPIMapper.ANCHORS_CLASSIFICATION_TYPE_GUID,
                                                           OpenMetadataAPIMapper.ANCHORS_CLASSIFICATION_TYPE_NAME,
                                                           anchorsClassification,
                                                           forLineage,
                                                           forDuplicateProcessing,
                                                           effectiveTime,
                                                           methodName);
                    }
                }
            }
            catch (Exception   error)
            {
                String typeName = "";
                String typeGUID = "";

                if (targetEntity.getType() != null)
                {
                    typeName = targetEntity.getType().getTypeDefName();
                    typeGUID = targetEntity.getType().getTypeDefGUID();
                }
                auditLog.logException(methodName,
                                      GenericHandlersAuditCode.UNABLE_TO_SET_ANCHORS.getMessageDefinition(serviceName,
                                                                                                          targetGUID,
                                                                                                          typeName,
                                                                                                          typeGUID,
                                                                                                          methodName,
                                                                                                          error.getClass().getName(),
                                                                                                          error.getMessage()),
                                      error);
            }

            /*
             * Now need to ensure that the anchor's classification is pushed down to the dependent elements.  This is done by retrieving the
             * relationships.
             */
            RepositoryRelationshipsIterator iterator = new RepositoryRelationshipsIterator(repositoryHandler,
                                                                                           localServerUserId,
                                                                                           targetGUID,
                                                                                           targetEntity.getType().getTypeDefName(),
                                                                                           null,
                                                                                           null,
                                                                                           false,
                                                                                           0,
                                                                                           invalidParameterHandler.getMaxPagingSize(),
                                                                                           effectiveTime,
                                                                                           methodName);

            while (iterator.moreToReceive())
            {
                Relationship relationship = iterator.getNext();

                /*
                 * This if statement should always be true.
                 */
                if ((relationship != null) && (relationship.getType() != null) &&
                    (relationship.getEntityOneProxy() != null) && (relationship.getEntityTwoProxy() != null))
                {
                    String entityOneGUID = relationship.getEntityOneProxy().getGUID();
                    String entityTwoGUID = relationship.getEntityTwoProxy().getGUID();

                    switch (relationship.getType().getTypeDefGUID())
                    {
                        /*
                         * These relationships all have the parent at end one.  We then need to push the anchor guid down to the entity at end two.
                         */
                        case OpenMetadataAPIMapper.ASSET_TO_CONNECTION_TYPE_GUID:
                        case OpenMetadataAPIMapper.ASSET_TO_SCHEMA_TYPE_TYPE_GUID:
                        case OpenMetadataAPIMapper.REFERENCEABLE_TO_COMMENT_TYPE_GUID:
                        case OpenMetadataAPIMapper.REFERENCEABLE_TO_EXTERNAL_ID_TYPE_GUID:
                        case OpenMetadataAPIMapper.REFERENCEABLE_TO_LIKE_TYPE_GUID:
                        case OpenMetadataAPIMapper.REFERENCEABLE_TO_RATING_TYPE_GUID:
                        case OpenMetadataAPIMapper.TYPE_TO_ATTRIBUTE_RELATIONSHIP_TYPE_GUID:
                        case OpenMetadataAPIMapper.ATTRIBUTE_TO_TYPE_RELATIONSHIP_TYPE_GUID:
                        case OpenMetadataAPIMapper.NESTED_ATTRIBUTE_RELATIONSHIP_TYPE_GUID:
                        case OpenMetadataAPIMapper.SCHEMA_TYPE_OPTION_RELATIONSHIP_TYPE_GUID:
                        case OpenMetadataAPIMapper.API_OPERATIONS_RELATIONSHIP_TYPE_GUID:
                        case OpenMetadataAPIMapper.API_HEADER_RELATIONSHIP_TYPE_GUID:
                        case OpenMetadataAPIMapper.API_REQUEST_RELATIONSHIP_TYPE_GUID:
                        case OpenMetadataAPIMapper.API_RESPONSE_RELATIONSHIP_TYPE_GUID:
                        case OpenMetadataAPIMapper.MAP_TO_RELATIONSHIP_TYPE_GUID:
                        case OpenMetadataAPIMapper.MAP_FROM_RELATIONSHIP_TYPE_GUID:
                        case OpenMetadataAPIMapper.PERSONAL_CONTRIBUTION_RELATIONSHIP_TYPE_GUID:
                            if (entityOneGUID.equals(targetGUID))
                            {
                                final String proxyTwoGUIDParameterName = "proxyTwoGUID";

                                this.maintainAnchorGUIDInClassification(entityTwoGUID,
                                                                        proxyTwoGUIDParameterName,
                                                                        relationship.getEntityTwoProxy().getType().getTypeDefName(),
                                                                        relationship.getEntityTwoProxy(),
                                                                        anchorGUID,
                                                                        forLineage,
                                                                        forDuplicateProcessing,
                                                                        effectiveTime,
                                                                        methodName);
                            }
                            break;

                        /*
                         * These relationships point to entities of types that may or may not be anchored to the parent.
                         * It is necessary to retrieve the entity and only update its Anchors classification if it is anchored.
                         * It is only allowed to be connected to one entity if it is anchored.  If it could be anchored to one entity and then
                         * connected to another then this logic does not work.
                         */
                        case OpenMetadataAPIMapper.COLLECTION_MEMBERSHIP_TYPE_GUID:
                        case OpenMetadataAPIMapper.REFERENCEABLE_TO_REFERENCE_VALUE_TYPE_GUID:
                        case OpenMetadataAPIMapper.REFERENCEABLE_TO_NOTE_LOG_TYPE_GUID:
                            if (entityOneGUID.equals(targetGUID))
                            {
                                final String proxyTwoGUIDParameterName = "proxyTwoGUID";
                                EntityDetail proxyTwoAnchor = this.validateAnchorEntity(localServerUserId,
                                                                                        entityTwoGUID,
                                                                                        proxyTwoGUIDParameterName,
                                                                                        relationship.getEntityTwoProxy().getType().getTypeDefName(),
                                                                                        true,
                                                                                        forLineage,
                                                                                        forDuplicateProcessing,
                                                                                        supportedZones,
                                                                                        effectiveTime,
                                                                                        methodName);
                                if (proxyTwoAnchor != null)
                                {
                                    this.maintainAnchorGUIDInClassification(entityTwoGUID,
                                                                            proxyTwoGUIDParameterName,
                                                                            relationship.getEntityTwoProxy().getType().getTypeDefName(),
                                                                            relationship.getEntityTwoProxy(),
                                                                            anchorGUID,
                                                                            forLineage,
                                                                            forDuplicateProcessing,
                                                                            effectiveTime,
                                                                            methodName);
                                }
                            }

                            break;
                    }
                }
            }
        }
    }


    /**
     * Walk the graph to locate the anchor for a schema type.  Schemas are attached to each other through various levels of nesting, ports (for
     * process assets) and asset through the schema type.  It is also possible that the schema is not attached to anything.  This is common if the
     * schema is a template.
     *
     * @param userId calling user
     * @param schemaTypeGUID unique identifier of schema type (it is assumed that the anchorGUID property of this type is null)
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return unique identifier of attached asset or null if there is no attached asset
     *
     * @throws InvalidParameterException  the GUID is not recognized or the paging values are invalid or
     * @throws PropertyServerException  there is a problem retrieving the instances from the property server or
     * @throws UserNotAuthorizedException  the requesting user is not authorized to issue this request.
     */
    private String getAnchorGUIDForSchemaType(String  userId,
                                              String  schemaTypeGUID,
                                              boolean forDuplicateProcessing,
                                              Date    effectiveTime,
                                              String  methodName) throws InvalidParameterException,
                                                                         PropertyServerException,
                                                                         UserNotAuthorizedException
    {
        /*
         * The most obvious test is that this schema type is attached directly to the asset.
         */
        Relationship relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                                  schemaTypeGUID,
                                                                                  OpenMetadataAPIMapper.SCHEMA_TYPE_TYPE_NAME,
                                                                                  false,
                                                                                  OpenMetadataAPIMapper.ASSET_TO_SCHEMA_TYPE_TYPE_GUID,
                                                                                  OpenMetadataAPIMapper.ASSET_TO_SCHEMA_TYPE_TYPE_NAME,
                                                                                  forDuplicateProcessing,
                                                                                  effectiveTime,
                                                                                  methodName);
        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();

            return proxy.getGUID();
        }

        /*
         * Next test to see if the type is connected to an attribute.
         */
        relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                     schemaTypeGUID,
                                                                     OpenMetadataAPIMapper.SCHEMA_TYPE_TYPE_NAME,
                                                                     false,
                                                                     OpenMetadataAPIMapper.ATTRIBUTE_TO_TYPE_RELATIONSHIP_TYPE_GUID,
                                                                     OpenMetadataAPIMapper.ATTRIBUTE_TO_TYPE_RELATIONSHIP_TYPE_NAME,
                                                                     forDuplicateProcessing,
                                                                     effectiveTime,
                                                                     methodName);
        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();

            return getAnchorGUIDForSchemaAttribute(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);
        }

        /*
         * Next test to see if the type is connected to a SchemaTypeChoice.
         */
        relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                     schemaTypeGUID,
                                                                     OpenMetadataAPIMapper.SCHEMA_TYPE_TYPE_NAME,
                                                                     false,
                                                                     OpenMetadataAPIMapper.SCHEMA_TYPE_OPTION_RELATIONSHIP_TYPE_GUID,
                                                                     OpenMetadataAPIMapper.SCHEMA_TYPE_OPTION_RELATIONSHIP_TYPE_NAME,
                                                                     forDuplicateProcessing,
                                                                     effectiveTime,
                                                                     methodName);
        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();

            return getAnchorGUIDForSchemaType(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);
        }

        /*
         * Next test to see if the type is connected to a MapSchemaType.
         */
        relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                     schemaTypeGUID,
                                                                     OpenMetadataAPIMapper.SCHEMA_TYPE_TYPE_NAME,
                                                                     false,
                                                                     OpenMetadataAPIMapper.MAP_FROM_RELATIONSHIP_TYPE_GUID,
                                                                     OpenMetadataAPIMapper.MAP_FROM_RELATIONSHIP_TYPE_NAME,
                                                                     forDuplicateProcessing,
                                                                     effectiveTime,
                                                                     methodName);
        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();

            return getAnchorGUIDForSchemaType(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);
        }
        relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                     schemaTypeGUID,
                                                                     OpenMetadataAPIMapper.SCHEMA_TYPE_TYPE_NAME,
                                                                     false,
                                                                     OpenMetadataAPIMapper.MAP_TO_RELATIONSHIP_TYPE_GUID,
                                                                     OpenMetadataAPIMapper.MAP_TO_RELATIONSHIP_TYPE_NAME,
                                                                     forDuplicateProcessing,
                                                                     effectiveTime,
                                                                     methodName);
        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();

            return getAnchorGUIDForSchemaType(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);
        }

        /*
         * Next test to see if the type is connected to an API operation or API schema type.
         */
        relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                     schemaTypeGUID,
                                                                     OpenMetadataAPIMapper.SCHEMA_TYPE_TYPE_NAME,
                                                                     false,
                                                                     OpenMetadataAPIMapper.API_OPERATIONS_RELATIONSHIP_TYPE_GUID,
                                                                     OpenMetadataAPIMapper.API_OPERATIONS_RELATIONSHIP_TYPE_NAME,
                                                                     forDuplicateProcessing,
                                                                     effectiveTime,
                                                                     methodName);
        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();

            return getAnchorGUIDForSchemaType(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);
        }
        relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                     schemaTypeGUID,
                                                                     OpenMetadataAPIMapper.SCHEMA_TYPE_TYPE_NAME,
                                                                     false,
                                                                     OpenMetadataAPIMapper.API_HEADER_RELATIONSHIP_TYPE_GUID,
                                                                     OpenMetadataAPIMapper.API_HEADER_RELATIONSHIP_TYPE_NAME,
                                                                     forDuplicateProcessing,
                                                                     effectiveTime,
                                                                     methodName);
        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();

            return getAnchorGUIDForSchemaType(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);
        }

        relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                     schemaTypeGUID,
                                                                     OpenMetadataAPIMapper.SCHEMA_TYPE_TYPE_NAME,
                                                                     false,
                                                                     OpenMetadataAPIMapper.API_REQUEST_RELATIONSHIP_TYPE_GUID,
                                                                     OpenMetadataAPIMapper.API_REQUEST_RELATIONSHIP_TYPE_NAME,
                                                                     forDuplicateProcessing,
                                                                     effectiveTime,
                                                                     methodName);
        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();

            return getAnchorGUIDForSchemaType(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);
        }

        relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                     schemaTypeGUID,
                                                                     OpenMetadataAPIMapper.SCHEMA_TYPE_TYPE_NAME,
                                                                     false,
                                                                     OpenMetadataAPIMapper.API_RESPONSE_RELATIONSHIP_TYPE_GUID,
                                                                     OpenMetadataAPIMapper.API_RESPONSE_RELATIONSHIP_TYPE_NAME,
                                                                     forDuplicateProcessing,
                                                                     effectiveTime,
                                                                     methodName);
        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();

            return getAnchorGUIDForSchemaType(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);
        }

        /*
         * Finally test that this schema type is attached directly to a port.
         */
        relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                     schemaTypeGUID,
                                                                     OpenMetadataAPIMapper.SCHEMA_TYPE_TYPE_NAME,
                                                                     false,
                                                                     OpenMetadataAPIMapper.PORT_SCHEMA_RELATIONSHIP_TYPE_GUID,
                                                                     OpenMetadataAPIMapper.PORT_SCHEMA_RELATIONSHIP_TYPE_NAME,
                                                                     forDuplicateProcessing,
                                                                     effectiveTime,
                                                                     methodName);
        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();

            return proxy.getGUID();
        }


        /*
         * If none of these relationships are present, then this schema type has no anchor.
         */
        return null;
    }



    /**
     * Walk the graph to locate the anchor for a schema attribute.  Schemas are attached to each other through various levels of nesting, ports (for
     * process assets) and asset through the schema type.  It is also possible that the schema is not attached to anything.  This is common if the
     * schema is a template.
     *
     * @param userId calling user
     * @param attributeGUID unique identifier of attribute (it is assumed that the anchorGUID property of this attribute is null)
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return unique identifier of attached asset or null if there is no attached asset
     *
     * @throws InvalidParameterException - the GUID is not recognized or the paging values are invalid or
     * @throws PropertyServerException - there is a problem retrieving the asset properties from the property server or
     * @throws UserNotAuthorizedException - the requesting user is not authorized to issue this request.
     */
    private String getAnchorGUIDForSchemaAttribute(String  userId,
                                                   String  attributeGUID,
                                                   boolean forDuplicateProcessing,
                                                   Date    effectiveTime,
                                                   String  methodName) throws InvalidParameterException,
                                                                              PropertyServerException,
                                                                              UserNotAuthorizedException
    {
        /*
         * Is the schema attribute connected to a type.
         */
        Relationship relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                                  attributeGUID,
                                                                                  OpenMetadataAPIMapper.SCHEMA_ATTRIBUTE_TYPE_NAME,
                                                                                  false,
                                                                                  OpenMetadataAPIMapper.TYPE_TO_ATTRIBUTE_RELATIONSHIP_TYPE_GUID,
                                                                                  OpenMetadataAPIMapper.TYPE_TO_ATTRIBUTE_RELATIONSHIP_TYPE_NAME,
                                                                                  forDuplicateProcessing,
                                                                                  effectiveTime,
                                                                                  methodName);
        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();

            return getAnchorGUIDForSchemaType(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);
        }

        /*
         * Is the attribute nested in another attribute?  Note that because schema attributes can be nested through multiple levels,
         * the retrieval of the parent needs to take account of which end the attributeGUID is connected to.
         */

        relationship = repositoryHandler.getUniqueParentRelationshipByType(userId,
                                                                           attributeGUID,
                                                                           OpenMetadataAPIMapper.SCHEMA_ATTRIBUTE_TYPE_NAME,
                                                                           OpenMetadataAPIMapper.NESTED_ATTRIBUTE_RELATIONSHIP_TYPE_GUID,
                                                                           OpenMetadataAPIMapper.NESTED_ATTRIBUTE_RELATIONSHIP_TYPE_NAME,
                                                                           true,
                                                                           forDuplicateProcessing,
                                                                           effectiveTime,
                                                                           methodName);
        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();

            return getAnchorGUIDForSchemaAttribute(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);
        }

        return null;
    }


    /**
     * Walk the graph to locate the anchor for a connection.  Connections are attached to each other through various levels of nesting in
     * a VirtualConnection.  They are also typically attached to an Asset.  The Asset is the anchor.  Because embedded connections
     * can be used in multiple VirtualConnections, they are only anchored to a virtual connection if this is done explicitly.
     *
     * @param userId calling user
     * @param connectionGUID unique identifier of the connection (it is assumed that the anchorGUID property of this instance is null)
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return unique identifier of attached asset or null if there is no attached asset
     *
     * @throws PropertyServerException - there is a problem retrieving the asset properties from the property server or
     * @throws UserNotAuthorizedException - the requesting user is not authorized to issue this request.
     */
    private String getAnchorGUIDForConnection(String  userId,
                                              String  connectionGUID,
                                              boolean forDuplicateProcessing,
                                              Date    effectiveTime,
                                              String  methodName) throws PropertyServerException,
                                                                         UserNotAuthorizedException
    {
        /*
         * Is the connection connected to an asset?
         */
        Relationship relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                                  connectionGUID,
                                                                                  OpenMetadataAPIMapper.CONNECTION_TYPE_NAME,
                                                                                  false,
                                                                                  OpenMetadataAPIMapper.ASSET_TO_CONNECTION_TYPE_GUID,
                                                                                  OpenMetadataAPIMapper.ASSET_TO_CONNECTION_TYPE_NAME,
                                                                                  forDuplicateProcessing,
                                                                                  effectiveTime,
                                                                                  methodName);

        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();

            if ((proxy != null) && (proxy.getGUID() != null))
            {
                return proxy.getGUID();
            }
        }

        return null;
    }


    /**
     * Walk the graph to locate the anchor for a Like.  Likes are attached directly to a referenceable.
     *
     * @param userId calling user
     * @param likeGUID unique identifier of the connection (it is assumed that the anchorGUID property of this instance is null)
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return unique identifier of attached asset or null if there is no attached asset
     *
     * @throws PropertyServerException - there is a problem retrieving the asset properties from the property server or
     * @throws UserNotAuthorizedException - the requesting user is not authorized to issue this request.
     */
    private String getAnchorGUIDForLike(String  userId,
                                        String  likeGUID,
                                        boolean forDuplicateProcessing,
                                        Date    effectiveTime,
                                        String  methodName) throws PropertyServerException,
                                                                   UserNotAuthorizedException
    {
        /*
         * Is the like connected to a Referenceable?
         */
        Relationship relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                                  likeGUID,
                                                                                  OpenMetadataAPIMapper.LIKE_TYPE_NAME,
                                                                                  false,
                                                                                  OpenMetadataAPIMapper.REFERENCEABLE_TO_LIKE_TYPE_GUID,
                                                                                  OpenMetadataAPIMapper.REFERENCEABLE_TO_LIKE_TYPE_NAME,
                                                                                  forDuplicateProcessing,
                                                                                  effectiveTime,
                                                                                  methodName);

        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();
            if ((proxy != null) && (proxy.getGUID() != null))
            {
                return proxy.getGUID();
            }
        }

        return null;
    }


    /**
     * Walk the graph to locate the anchor for a Rating.  Ratings are attached directly to a referenceable.
     *
     * @param userId calling user
     * @param ratingGUID unique identifier of the connection (it is assumed that the anchorGUID property of this instance is null)
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return unique identifier of attached asset or null if there is no attached asset
     *
     * @throws PropertyServerException - there is a problem retrieving the asset properties from the property server or
     * @throws UserNotAuthorizedException - the requesting user is not authorized to issue this request.
     */
    private String getAnchorGUIDForRating(String  userId,
                                          String  ratingGUID,
                                          boolean forDuplicateProcessing,
                                          Date    effectiveTime,
                                          String  methodName) throws PropertyServerException,
                                                                     UserNotAuthorizedException
    {
        /*
         * Is the rating connected to a Referenceable?
         */
        Relationship relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                                  ratingGUID,
                                                                                  OpenMetadataAPIMapper.RATING_TYPE_NAME,
                                                                                  false,
                                                                                  OpenMetadataAPIMapper.REFERENCEABLE_TO_RATING_TYPE_GUID,
                                                                                  OpenMetadataAPIMapper.REFERENCEABLE_TO_RATING_TYPE_NAME,
                                                                                  forDuplicateProcessing,
                                                                                  effectiveTime,
                                                                                  methodName);

        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();
            if ((proxy != null) && (proxy.getGUID() != null))
            {
                return proxy.getGUID();
            }
        }

        return null;
    }


    /**
     * Walk the graph to locate the anchor for a Rating.  Ratings are attached directly to a referenceable.
     *
     * @param userId calling user
     * @param reportGUID unique identifier of the connection (it is assumed that the anchorGUID property of this instance is null)
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return unique identifier of attached asset or null if there is no attached asset
     *
     * @throws PropertyServerException - there is a problem retrieving the asset properties from the property server or
     * @throws UserNotAuthorizedException - the requesting user is not authorized to issue this request.
     */
    private String getAnchorGUIDForOpenDiscoveryAnalysisReport(String  userId,
                                                               String  reportGUID,
                                                               boolean forDuplicateProcessing,
                                                               Date    effectiveTime,
                                                               String  methodName) throws PropertyServerException,
                                                                                          UserNotAuthorizedException
    {
        /*
         * Is the report connected to an Asset?
         */
        Relationship relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                                  reportGUID,
                                                                                  OpenMetadataAPIMapper.DISCOVERY_ANALYSIS_REPORT_TYPE_NAME,
                                                                                  false,
                                                                                  OpenMetadataAPIMapper.REPORT_TO_ASSET_TYPE_GUID,
                                                                                  OpenMetadataAPIMapper.REPORT_TO_ASSET_TYPE_NAME,
                                                                                  forDuplicateProcessing,
                                                                                  effectiveTime,
                                                                                  methodName);

        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();
            if ((proxy != null) && (proxy.getGUID() != null))
            {
                return proxy.getGUID();
            }
        }

        return null;
    }


    /**
     * Walk the graph to locate the anchor for an annotation.  Annotations are attached to each other through various levels of nesting
     * and eventually anchored to an asset via an OpenDiscoveryAnalysisReport.   The asset is the anchor.
     *
     * @param userId calling user
     * @param annotationGUID unique identifier of the comment (it is assumed that the anchorGUID property of this instance is null)
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return unique identifier of attached asset or null if there is no attached asset
     *
     * @throws PropertyServerException - there is a problem retrieving the asset properties from the property server or
     * @throws UserNotAuthorizedException - the requesting user is not authorized to issue this request.
     */
    private String getAnchorGUIDForAnnotation(String  userId,
                                              String  annotationGUID,
                                              boolean forDuplicateProcessing,
                                              Date    effectiveTime,
                                              String  methodName) throws PropertyServerException,
                                                                         UserNotAuthorizedException
    {
        /*
         * Is the comment connected to anything?
         */
        List relationships = repositoryHandler.getRelationshipsByType(userId,
                                                                                    annotationGUID,
                                                                                    OpenMetadataAPIMapper.ANNOTATION_TYPE_NAME,
                                                                                    null,
                                                                                    null,
                                                                                    forDuplicateProcessing,
                                                                                    0, 0,
                                                                                    effectiveTime,
                                                                                    methodName);

        if (relationships != null)
        {
            for (Relationship relationship : relationships)
            {
                if (relationship != null)
                {
                    EntityProxy proxy = relationship.getEntityOneProxy();
                    if ((proxy != null) && (proxy.getGUID() != null) && (proxy.getType() != null) && (! annotationGUID.equals(proxy.getGUID())))
                    {
                        if (repositoryHelper.isTypeOf(serviceName, proxy.getType().getTypeDefName(), OpenMetadataAPIMapper.DISCOVERY_ANALYSIS_REPORT_TYPE_NAME))
                        {
                            return this.getAnchorGUIDForOpenDiscoveryAnalysisReport(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);
                        }
                        else if (repositoryHelper.isTypeOf(serviceName, proxy.getType().getTypeDefName(), OpenMetadataAPIMapper.DATA_FIELD_TYPE_NAME))
                        {
                            String parentAnchorGUID = this.getAnchorGUIDForDataField(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);

                            if (parentAnchorGUID != null)
                            {
                                return parentAnchorGUID;
                            }
                        }
                        else if (repositoryHelper.isTypeOf(serviceName, proxy.getType().getTypeDefName(), OpenMetadataAPIMapper.ANNOTATION_TYPE_NAME))
                        {
                            return this.getAnchorGUIDForAnnotation(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);
                        }
                    }
                }
            }
        }

        return null;
    }


    /**
     * Walk the graph to locate the anchor for an annotation review.  AnnotationReviews are attached to annotations which have levels of nesting
     * and eventually anchored to an asset via an OpenDiscoveryAnalysisReport.   The asset is the anchor.
     *
     * @param userId calling user
     * @param annotationReviewGUID unique identifier of the annotation review (it is assumed that the anchorGUID property of this instance is null)
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return unique identifier of attached asset or null if there is no attached asset
     *
     * @throws PropertyServerException - there is a problem retrieving the asset properties from the property server or
     * @throws UserNotAuthorizedException - the requesting user is not authorized to issue this request.
     */
    private String getAnchorGUIDForAnnotationReview(String  userId,
                                                    String  annotationReviewGUID,
                                                    boolean forDuplicateProcessing,
                                                    Date    effectiveTime,
                                                    String  methodName) throws PropertyServerException,
                                                                               UserNotAuthorizedException
    {
        /*
         * Is the annotation review connected to an annotation?
         */
        Relationship relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                                  annotationReviewGUID,
                                                                                  OpenMetadataAPIMapper.ANNOTATION_REVIEW_TYPE_NAME,
                                                                                  false,
                                                                                  OpenMetadataAPIMapper.ANNOTATION_REVIEW_LINK_TYPE_GUID,
                                                                                  OpenMetadataAPIMapper.ANNOTATION_REVIEW_LINK_TYPE_NAME,
                                                                                  forDuplicateProcessing,
                                                                                  effectiveTime,
                                                                                  methodName);

        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();
            if ((proxy != null) && (proxy.getGUID() != null))
            {
                return this.getAnchorGUIDForAnnotation(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);
            }
        }

        return null;
    }


    /**
     * Walk the graph to locate the anchor for an annotation.  AnnotationReviews are attached to annotations which have various levels of nesting
     * and eventually anchored to an asset via an OpenDiscoveryAnalysisReport.   The asset is the anchor.
     *
     * @param userId calling user
     * @param dataFieldGUID unique identifier of the data field (it is assumed that the anchorGUID property of this instance is null)
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return unique identifier of attached asset or null if there is no attached asset
     *
     * @throws PropertyServerException - there is a problem retrieving the asset properties from the repositories or
     * @throws UserNotAuthorizedException - the requesting user is not authorized to issue this request.
     */
    private String getAnchorGUIDForDataField(String  userId,
                                             String  dataFieldGUID,
                                             boolean forDuplicateProcessing,
                                             Date    effectiveTime,
                                             String  methodName) throws PropertyServerException,
                                                                       UserNotAuthorizedException
    {
        /*
         * Is the data field connected to an annotation?
         */
        Relationship relationship = repositoryHandler.getUniqueRelationshipByType(userId,
                                                                                  dataFieldGUID,
                                                                                  OpenMetadataAPIMapper.DATA_FIELD_TYPE_NAME,
                                                                                  false,
                                                                                  OpenMetadataAPIMapper.DISCOVERED_DATA_FIELD_TYPE_GUID,
                                                                                  OpenMetadataAPIMapper.DISCOVERED_DATA_FIELD_TYPE_NAME,
                                                                                  forDuplicateProcessing,
                                                                                  effectiveTime,
                                                                                  methodName);

        if (relationship != null)
        {
            EntityProxy proxy = relationship.getEntityOneProxy();
            if ((proxy != null) && (proxy.getGUID() != null))
            {
                return this.getAnchorGUIDForAnnotation(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);
            }
        }

        return null;
    }


    /**
     * Walk the graph to locate the anchor for a comment.  Comments are attached to each other through various levels of nesting
     * and eventually anchored to a referenceable.   The referenceable is the anchor.  Care has to be taken because a
     * comment is a referenceable too
     *
     * @param userId calling user
     * @param commentGUID unique identifier of the comment (it is assumed that the anchorGUID property of this instance is null)
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return unique identifier of attached anchor or null if there is no attached anchor
     *
     * @throws PropertyServerException  there is a problem retrieving the properties from the repositories
     * @throws UserNotAuthorizedException  the requesting user is not authorized to issue this request
     */
    private String getAnchorGUIDForComment(String  userId,
                                           String  commentGUID,
                                           boolean forDuplicateProcessing,
                                           Date    effectiveTime,
                                           String  methodName) throws PropertyServerException,
                                                                      UserNotAuthorizedException
    {
        /*
         * Is the comment connected to anything?
         */
        List relationships = repositoryHandler.getRelationshipsByType(userId,
                                                                                    commentGUID,
                                                                                    OpenMetadataAPIMapper.COMMENT_TYPE_NAME,
                                                                                    OpenMetadataAPIMapper.REFERENCEABLE_TO_COMMENT_TYPE_GUID,
                                                                                    OpenMetadataAPIMapper.REFERENCEABLE_TO_COMMENT_TYPE_NAME,
                                                                                    forDuplicateProcessing,
                                                                                    0, 0,
                                                                                    effectiveTime,
                                                                                    methodName);

        if (relationships != null)
        {
            for (Relationship relationship : relationships)
            {
                if (relationship != null)
                {
                    EntityProxy proxy = relationship.getEntityOneProxy();
                    if ((proxy != null) && (proxy.getGUID() != null) && (proxy.getType() != null))
                    {
                        if ((! commentGUID.equals(proxy.getGUID())) &&
                            (repositoryHelper.isTypeOf(serviceName, proxy.getType().getTypeDefName(), OpenMetadataAPIMapper.COMMENT_TYPE_NAME)))
                        {
                            String parentAnchorGUID = this.getAnchorGUIDForComment(userId, proxy.getGUID(), forDuplicateProcessing, effectiveTime, methodName);

                            /*
                             * If the parent has no anchor then it is a detached chain of comments and the parent is the anchor.
                             */
                            if (parentAnchorGUID != null)
                            {
                                return parentAnchorGUID;
                            }
                        }

                        return proxy.getGUID();
                    }
                }
            }
        }

        return null;
    }


    /**
     * Walk the graph to locate the anchor for a Glossary Term.  Glossary Terms are connected directly to their anchor
     * Glossary via a TermAnchor relationship.
     *
     * @param userId calling user
     * @param glossaryTermGUID unique identifier of the Glossary Term (it is assumed that the anchorGUID property of this instance is null)
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return unique identifier of attached anchor or null if there is no attached anchor
     *
     * @throws PropertyServerException  there is a problem retrieving the properties from the repositories
     * @throws UserNotAuthorizedException  the requesting user is not authorized to issue this request
     */
    private String getAnchorGUIDForGlossaryTerm(String  userId,
                                                String  glossaryTermGUID,
                                                boolean forDuplicateProcessing,
                                                Date    effectiveTime,
                                                String  methodName) throws PropertyServerException,
                                                                           UserNotAuthorizedException
    {
        /*
         * Is the Glossary Term connected to anything?
         */
        List relationships = repositoryHandler.getRelationshipsByType(userId,
                                                                                    glossaryTermGUID,
                                                                                    OpenMetadataAPIMapper.GLOSSARY_TERM_TYPE_NAME,
                                                                                    OpenMetadataAPIMapper.TERM_ANCHOR_TYPE_GUID,
                                                                                    OpenMetadataAPIMapper.TERM_ANCHOR_TYPE_NAME,
                                                                                    forDuplicateProcessing,
                                                                                    0, 0,
                                                                                    effectiveTime,
                                                                                    methodName);

        if (relationships != null)
        {
            for (Relationship relationship : relationships)
            {
                if (relationship != null)
                {
                    EntityProxy proxy = relationship.getEntityOneProxy();
                    if ((proxy != null) && (proxy.getGUID() != null) && (proxy.getType() != null))
                    {
                        return proxy.getGUID();
                    }
                }
            }
        }

        return null;
    }


    /**
     * Walk the graph to locate the anchor for a Glossary Category.  Glossary Categories are connected directly to their anchor Glossary
     * via a CategoryAnchor relationship.
     *
     * @param userId calling user
     * @param glossaryCategoryGUID unique identifier of the Glossary Category (it is assumed that the anchorGUID property of this instance is null)
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return unique identifier of attached anchor or null if there is no attached anchor
     *
     * @throws PropertyServerException  there is a problem retrieving the properties from the repositories
     * @throws UserNotAuthorizedException  the requesting user is not authorized to issue this request
     */
    private String getAnchorGUIDForGlossaryCategory(String  userId,
                                                    String  glossaryCategoryGUID,
                                                    boolean forDuplicateProcessing,
                                                    Date    effectiveTime,
                                                    String  methodName) throws PropertyServerException,
                                                                               UserNotAuthorizedException
    {
        /*
         * Is the Glossary Category connected to anything?
         */
        List relationships = repositoryHandler.getRelationshipsByType(userId,
                                                                                    glossaryCategoryGUID,
                                                                                    OpenMetadataAPIMapper.GLOSSARY_CATEGORY_TYPE_NAME,
                                                                                    OpenMetadataAPIMapper.CATEGORY_ANCHOR_TYPE_GUID,
                                                                                    OpenMetadataAPIMapper.CATEGORY_ANCHOR_TYPE_NAME,
                                                                                    forDuplicateProcessing,
                                                                                    0, 0,
                                                                                    effectiveTime,
                                                                                    methodName);

        if (relationships != null)
        {
            for (Relationship relationship : relationships)
            {
                if (relationship != null)
                {
                    EntityProxy proxy = relationship.getEntityOneProxy();
                    if ((proxy != null) && (proxy.getGUID() != null) && (proxy.getType() != null))
                    {
                        return proxy.getGUID();
                    }
                }
            }
        }

        return null;
    }


    /**
     * This method walks the relationships to determine if the entity identified by the targetGUID has an anchor.  It returns the GUID of
     * this anchor if it exists or null if it does not.  This method is used both as part of setting up the anchorGUID in a newly linked entity,
     * or to verify that the existing anchorGUID value is still valid.
     *
     * @param targetGUID unique identifier for the entity to test
     * @param targetTypeName type of the entity to test
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     * @return anchorGUID or null
     * @throws InvalidParameterException on of the properties is invalid which is suspicious since they have been validated earlier
     * @throws UserNotAuthorizedException the local server userId does not have access to the repository instances
     * @throws PropertyServerException something is wrong with the repository
     */
    private String deriveAnchorGUID(String  targetGUID,
                                    String  targetTypeName,
                                    boolean forLineage,
                                    boolean forDuplicateProcessing,
                                    Date    effectiveTime,
                                    String  methodName) throws InvalidParameterException,
                                                               UserNotAuthorizedException,
                                                               PropertyServerException
    {
        String anchorGUID = null;

        /*
         * This group of calls walks the chain of entities to detect the anchorGUID for specific types of entities.  There is scope for more
         * method calls added here, for example, for comments, note logs, connections etc.
         */
        if (repositoryHelper.isTypeOf(serviceName, targetTypeName, OpenMetadataAPIMapper.SCHEMA_TYPE_TYPE_NAME))
        {
            anchorGUID = this.getAnchorGUIDForSchemaType(localServerUserId, targetGUID, forDuplicateProcessing, effectiveTime, methodName);
        }
        else if (repositoryHelper.isTypeOf(serviceName, targetTypeName, OpenMetadataAPIMapper.SCHEMA_ATTRIBUTE_TYPE_NAME))
        {
            anchorGUID = this.getAnchorGUIDForSchemaAttribute(localServerUserId, targetGUID, forDuplicateProcessing, effectiveTime, methodName);
        }
        else if (repositoryHelper.isTypeOf(serviceName, targetTypeName, OpenMetadataAPIMapper.CONNECTION_TYPE_NAME))
        {
            anchorGUID = this.getAnchorGUIDForConnection(localServerUserId, targetGUID, forDuplicateProcessing, effectiveTime, methodName);
        }
        else if (repositoryHelper.isTypeOf(serviceName, targetTypeName, OpenMetadataAPIMapper.COMMENT_TYPE_NAME))
        {
            anchorGUID = this.getAnchorGUIDForComment(localServerUserId, targetGUID, forDuplicateProcessing, effectiveTime, methodName);
        }
        else if (repositoryHelper.isTypeOf(serviceName, targetTypeName, OpenMetadataAPIMapper.RATING_TYPE_NAME))
        {
            anchorGUID = this.getAnchorGUIDForRating(localServerUserId, targetGUID, forDuplicateProcessing, effectiveTime, methodName);
        }
        else if (repositoryHelper.isTypeOf(serviceName, targetTypeName, OpenMetadataAPIMapper.LIKE_TYPE_NAME))
        {
            anchorGUID = this.getAnchorGUIDForLike(localServerUserId, targetGUID, forDuplicateProcessing, effectiveTime, methodName);
        }
        else if (repositoryHelper.isTypeOf(serviceName, targetTypeName, OpenMetadataAPIMapper.DISCOVERY_ANALYSIS_REPORT_TYPE_NAME))
        {
            anchorGUID = this.getAnchorGUIDForOpenDiscoveryAnalysisReport(localServerUserId, targetGUID, forDuplicateProcessing, effectiveTime, methodName);
        }
        else if (repositoryHelper.isTypeOf(serviceName, targetTypeName, OpenMetadataAPIMapper.ANNOTATION_TYPE_NAME))
        {
            anchorGUID = this.getAnchorGUIDForAnnotation(localServerUserId, targetGUID, forDuplicateProcessing, effectiveTime, methodName);
        }
        else if (repositoryHelper.isTypeOf(serviceName, targetTypeName, OpenMetadataAPIMapper.ANNOTATION_REVIEW_TYPE_NAME))
        {
            anchorGUID = this.getAnchorGUIDForAnnotationReview(localServerUserId, targetGUID, forDuplicateProcessing, effectiveTime, methodName);
        }
        else if (repositoryHelper.isTypeOf(serviceName, targetTypeName, OpenMetadataAPIMapper.DATA_FIELD_TYPE_NAME))
        {
            anchorGUID = this.getAnchorGUIDForDataField(localServerUserId, targetGUID, forDuplicateProcessing, effectiveTime, methodName);
        }
        else if (repositoryHelper.isTypeOf(serviceName, targetTypeName, OpenMetadataAPIMapper.GLOSSARY_TERM_TYPE_NAME))
        {
            anchorGUID = this.getAnchorGUIDForGlossaryTerm(localServerUserId, targetGUID, forDuplicateProcessing, effectiveTime, methodName);
        }
        else if (repositoryHelper.isTypeOf(serviceName, targetTypeName, OpenMetadataAPIMapper.GLOSSARY_CATEGORY_TYPE_NAME))
        {
            anchorGUID = this.getAnchorGUIDForGlossaryCategory(localServerUserId, targetGUID, forDuplicateProcessing, effectiveTime, methodName);
        }

        return anchorGUID;
    }


    /**
     * Validates that the current anchorGUID is correct and updates it if it is not.
     *
     * @param targetGUID unique identifier of the element to validate
     * @param targetGUIDParameterName parameter that provided the guid
     * @param targetTypeName type of entity to validate
     * @param originalAnchorGUID the original anchor guid - may be null
     * @param forLineage             the query is to support lineage retrieval
     * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return newAnchorGUID we derive the anchor from the the target element and then return it. Note this value can be null if there is no anchor.
     *
     * @throws InvalidParameterException probably the type of the entity is not correct
     * @throws PropertyServerException there is a problem with the repository
     * @throws UserNotAuthorizedException the local server user id is not able to update the entity
     */
    private String reEvaluateAnchorGUID(String  targetGUID,
                                        String  targetGUIDParameterName,
                                        String  targetTypeName,
                                        String  originalAnchorGUID,
                                        boolean forLineage,
                                        boolean forDuplicateProcessing,
                                        Date    effectiveTime,
                                        String  methodName) throws InvalidParameterException,
                                                                   PropertyServerException,
                                                                   UserNotAuthorizedException
    {
        /*
         * Find out the anchorGUID by following the relationships
         */
        String newAnchorGUID = this.deriveAnchorGUID(targetGUID, targetTypeName, forLineage, forDuplicateProcessing, effectiveTime, methodName);

        /*
         * The anchorGUID has changed
         */
        if (((newAnchorGUID == null) && (originalAnchorGUID != null)) ||
            ((newAnchorGUID != null) && (! newAnchorGUID.equals(originalAnchorGUID))))
        {
            EntityDetail targetElement = repositoryHandler.getEntityByGUID(localServerUserId,
                                                                           targetGUID,
                                                                           targetGUIDParameterName,
                                                                           targetTypeName,
                                                                           forLineage,
                                                                           forDuplicateProcessing,
                                                                           effectiveTime,
                                                                           methodName);

            if (targetElement != null)
            {
                this.maintainAnchorGUIDInClassification(targetElement.getGUID(),
                                                        targetGUIDParameterName,
                                                        targetTypeName,
                                                        targetElement,
                                                        newAnchorGUID,
                                                        forLineage,
                                                        forDuplicateProcessing,
                                                        effectiveTime,
                                                        methodName);
            }
        }
        return newAnchorGUID;
    }



    /**
     * Validates that the current anchorGUID is correct and updates it if it is not.
     *
     * @param targetGUID unique identifier of the element to validate
     * @param targetGUIDParameterName parameter name for target GUID
     * @param targetElement target entity already retrieved
     * @param targetTypeName type of entity to validate
     * @param originalAnchorGUID the original anchor guid - may be null
     * @param forLineage             the query is to support lineage retrieval
     * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return the new anchor GUID
     *
     * @throws InvalidParameterException probably the type of the entity is not correct
     * @throws PropertyServerException there is a problem with the repository
     * @throws UserNotAuthorizedException the local server user id is not able to update the entity
     */
    private String reEvaluateAnchorGUID(String       targetGUID,
                                        String       targetGUIDParameterName,
                                        String       targetTypeName,
                                        EntityDetail targetElement,
                                        String       originalAnchorGUID,
                                        boolean      forLineage,
                                        boolean      forDuplicateProcessing,
                                        Date         effectiveTime,
                                        String       methodName) throws InvalidParameterException,
                                                                        PropertyServerException,
                                                                        UserNotAuthorizedException
    {
        /*
         * Find out the anchorGUID by following the relationships
         */
        String newAnchorGUID = this.deriveAnchorGUID(targetGUID, targetTypeName, forLineage, forDuplicateProcessing, effectiveTime, methodName);

        /*
         * The anchorGUID has changed
         */
        if (((newAnchorGUID == null) && (originalAnchorGUID != null)) ||
            ((newAnchorGUID != null) && (! newAnchorGUID.equals(originalAnchorGUID))))
        {
            if (targetElement != null)
            {
                this.maintainAnchorGUIDInClassification(targetElement.getGUID(),
                                                        targetGUIDParameterName,
                                                        targetTypeName,
                                                        targetElement,
                                                        newAnchorGUID,
                                                        forLineage,
                                                        forDuplicateProcessing,
                                                        effectiveTime,
                                                        methodName);
            }
        }

        return newAnchorGUID;
    }


    /**
     * Validates whether an operation is valid based on the type of entity it is connecting to, who the user is and whether it is a read or an update.
     *
     * @param userId           userId of user making request.
     * @param connectToGUID       unique id for the object to connect the attachment to.
     * @param connectToGUIDParameterName  name of the parameter that passed the connect to guid
     * @param connectToType       type of the connect to element.
     * @param isUpdate         is this an update request?
     * @param forLineage             the query is to support lineage retrieval
     * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate
     * @param suppliedSupportedZones supported zone list from calling service
     * @param effectiveTime    the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName       calling method
     * @return anchor entity or null.  The anchor entity is used by the caller to set the LatestChange classification
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem adding the asset properties to the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public EntityDetail validateAnchorEntity(String       userId,
                                             String       connectToGUID,
                                             String       connectToGUIDParameterName,
                                             String       connectToType,
                                             boolean      isUpdate,
                                             boolean      forLineage,
                                             boolean      forDuplicateProcessing,
                                             List suppliedSupportedZones,
                                             Date         effectiveTime,
                                             String       methodName) throws InvalidParameterException,
                                                                             PropertyServerException,
                                                                             UserNotAuthorizedException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(connectToGUID, connectToGUIDParameterName, methodName);

        /*
         * This returns the entity for the connect to element and validates it is of the correct type.
         */
        EntityDetail  connectToEntity = repositoryHandler.getEntityByGUID(userId,
                                                                          connectToGUID,
                                                                          connectToGUIDParameterName,
                                                                          connectToType,
                                                                          forLineage,
                                                                          forDuplicateProcessing,
                                                                          effectiveTime,
                                                                          methodName);

        return this.validateAnchorEntity(userId,
                                         connectToGUID,
                                         connectToType,
                                         connectToEntity,
                                         connectToGUIDParameterName,
                                         isUpdate,
                                         forLineage,
                                         forDuplicateProcessing,
                                         suppliedSupportedZones,
                                         effectiveTime,
                                         methodName);
    }


    /**
     * Validates whether an operation is valid based on the type of entity it is connecting to, who the user is and whether it is a read or an
     * update.
     *
     * The first part of this method is looking to see if the connectToEntity is an anchor entity. In which case it calls any specific validation
     * for that entity and returns the connectToEntity, assuming all is ok - exceptions are thrown if the entity is not valid or the user does not
     * have access to it.
     *
     * If the connectToEntity is of a type that has a lifecycle that is linked to the lifecycle of another entity - typically a referenceable -
     * then that other entity is its anchor (examples are schema elements, comments, connections).  The anchor entity needs to be retrieved and
     * validated.
     *
     * Some anchor entities have specific validation to perform.
     *
     * @param userId           userId of user making request.
     * @param connectToGUID    unique id for the object to connect the attachment to
     * @param connectToType    name of type of connectToEntity
     * @param connectToEntity  entity retrieved from the repository
     * @param connectToGUIDParameterName  name of the parameter that passed the connect to guid
     * @param isUpdate         is this an update request?
     * @param forLineage             the query is to support lineage retrieval
     * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate
     * @param suppliedSupportedZones list of supported zones from the caller.
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName       calling method
     *
     * @return anchor entity or null if this entity is an anchor or does not have an anchor.  The anchor entity is used by the
     * caller to set the LatestChange classification
     *
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem accessing the properties in the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public EntityDetail validateAnchorEntity(String        userId,
                                             String        connectToGUID,
                                             String        connectToType,
                                             EntityDetail  connectToEntity,
                                             String        connectToGUIDParameterName,
                                             boolean       isUpdate,
                                             boolean       forLineage,
                                             boolean       forDuplicateProcessing,
                                             List  suppliedSupportedZones,
                                             Date          effectiveTime,
                                             String        methodName) throws InvalidParameterException,
                                                                              PropertyServerException,
                                                                              UserNotAuthorizedException
    {
        invalidParameterHandler.validateObject(connectToEntity, connectToGUIDParameterName, methodName);

        /*
         * This first processing looks at the retrieved entity itself to ensure it is visible.
         */
        if (repositoryHelper.isTypeOf(serviceName, connectToType, OpenMetadataAPIMapper.INFORMAL_TAG_TYPE_NAME))
        {
            /*
             * InformalTags have a property that says whether they are public or private
             */
            if (! repositoryHelper.getBooleanProperty(serviceName,
                                                      OpenMetadataAPIMapper.IS_PUBLIC_PROPERTY_NAME,
                                                      connectToEntity.getProperties(),
                                                      methodName))
            {
                /*
                 * This is a private tag - if the user did not create this we pretend it is not known.
                 */
                if (!userId.equals(connectToEntity.getCreatedBy()))
                {
                    invalidParameterHandler.throwUnknownElement(userId,
                                                                connectToGUID,
                                                                connectToType,
                                                                serviceName,
                                                                serverName,
                                                                methodName);
                }
            }
        }
        else if (repositoryHelper.isTypeOf(serviceName, connectToType, OpenMetadataAPIMapper.CONNECTION_TYPE_NAME))
        {
            this.validateUserForConnection(userId, connectToEntity, methodName);
        }
        else if (repositoryHelper.isTypeOf(serviceName, connectToType, OpenMetadataAPIMapper.ASSET_TYPE_NAME))
        {
            /*
             * Even if the request is an update request, the security module is first called for read - the update
             * is validated once the properties have been updated.
             */
            this.validateUserForAssetRead(userId,
                                          connectToGUID,
                                          connectToGUIDParameterName,
                                          connectToEntity,
                                          suppliedSupportedZones,
                                          methodName);
        }

        /*
         * Most referenceables have an independent lifecycle.  They are their own anchor.  This method is handling the special cases.
         */
        EntityDetail anchorEntity = null;

        /*
         * If an entity has an anchor, the unique identifier of the anchor should be in the Anchors classifications.
         * The exception occurs where the entity is not being managed by this handler, or something equivalent that maintains the Anchors
         * classification.
         */
        String anchorGUID = this.getAnchorGUIDFromAnchorsClassification(connectToEntity, methodName);

        if (anchorGUID == null)
        {
            /*
             * The classification is missing - so walk the relationships to find the anchor if it exists.
             */
            anchorGUID = deriveAnchorGUID(connectToGUID, connectToEntity.getType().getTypeDefName(), forLineage, forDuplicateProcessing, effectiveTime, methodName);

            if (anchorGUID != null)
            {
                /*
                 * The anchor has been found so store it in the classification so it is easy to find next time.
                 */
                maintainAnchorGUIDInClassification(connectToGUID,
                                                   connectToGUIDParameterName,
                                                   connectToType,
                                                   connectToEntity,
                                                   anchorGUID,
                                                   forLineage,
                                                   forDuplicateProcessing,
                                                   effectiveTime,
                                                   methodName);
            }
        }

        /*
         * If an anchor GUID has been found then validate it by retrieving the identified entity.  Note - anchorGUID may be null if the connectToEntity
         * is actually an anchor.
         */
        if (anchorGUID != null)
        {
            final String anchorGUIDParameterName = "anchorGUID";

            if (! anchorGUID.equals(connectToEntity.getGUID()))
            {
                anchorEntity = repositoryHandler.getEntityByGUID(userId,
                                                                 anchorGUID,
                                                                 anchorGUIDParameterName,
                                                                 OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME,
                                                                 forLineage,
                                                                 forDuplicateProcessing,
                                                                 effectiveTime,
                                                                 methodName);
            }
        }

        /*
         * Perform any special processing on the anchor entity
         */
        if (anchorEntity != null)
        {
            InstanceType anchorEntityType = anchorEntity.getType();

            if (anchorEntityType != null)
            {
                /*
                 * Determine if the element is attached directly or indirectly to an asset (or is an asset) so it is possible to determine
                 * if this asset is in a supported zone or if the user is allowed to change its attachments.
                 */
                if (OpenMetadataAPIMapper.ASSET_TYPE_NAME.equals(anchorEntityType.getTypeDefName()))
                {
                    boolean isFeedbackEntity = false;

                    if ((repositoryHelper.isTypeOf(serviceName, connectToType, OpenMetadataAPIMapper.INFORMAL_TAG_TYPE_NAME)) ||
                        (repositoryHelper.isTypeOf(serviceName, connectToType, OpenMetadataAPIMapper.COMMENT_TYPE_NAME)) ||
                        (repositoryHelper.isTypeOf(serviceName, connectToType, OpenMetadataAPIMapper.RATING_TYPE_NAME)) ||
                        (repositoryHelper.isTypeOf(serviceName, connectToType, OpenMetadataAPIMapper.LIKE_TYPE_NAME)))
                    {
                        isFeedbackEntity = true;
                    }

                    this.validateUserForAssetAttachment(userId,
                                                        connectToGUID,
                                                        connectToGUIDParameterName,
                                                        anchorEntity,
                                                        isFeedbackEntity,
                                                        isUpdate,
                                                        suppliedSupportedZones,
                                                        methodName);
                }

                /*
                 * This list is likely to expand as more anchor types get specialized visibility/security mechanisms.
                 */
            }
        }

        return anchorEntity;
    }


    /**
     * Validates that the unique property is not already in use.
     *
     * @param entityGUID existing entity (or null if this is a create)
     * @param entityTypeGUID the unique identifier of type of the entity
     * @param entityTypeName the unique name of the type of the entity
     * @param uniqueParameterValue the value of the unique parameter
     * @param uniqueParameterName the name of the unique parameter
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method for exceptions and error messages
     *
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem accessing the properties in the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public void validateUniqueProperty(String entityGUID,
                                       String entityTypeGUID,
                                       String entityTypeName,
                                       String uniqueParameterValue,
                                       String uniqueParameterName,
                                       Date   effectiveTime,
                                       String methodName) throws InvalidParameterException,
                                                                 PropertyServerException,
                                                                 UserNotAuthorizedException
    {
        List propertyNames = new ArrayList<>();
        propertyNames.add(uniqueParameterName);

        /*
         * An entity with the Memento classification set is ignored
         */
        List existingEntities = this.getEntitiesByValue(localServerUserId,
                                                                      uniqueParameterValue,
                                                                      uniqueParameterName,
                                                                      entityTypeGUID,
                                                                      entityTypeName,
                                                                      propertyNames,
                                                                      true,
                                                                      null,
                                                                      OpenMetadataAPIMapper.MEMENTO_CLASSIFICATION_TYPE_NAME,
                                                                      false,
                                                                      false,
                                                                      supportedZones,
                                                                      null,
                                                                      0,
                                                                      invalidParameterHandler.getMaxPagingSize(),
                                                                      effectiveTime,
                                                                      methodName);

        if ((existingEntities != null) && (! existingEntities.isEmpty()))
        {
            if (entityGUID != null)
            {
                for (EntityDetail existingEntity : existingEntities)
                {
                    if ((existingEntity != null) && (! entityGUID.equals(existingEntity.getGUID())))
                    {
                        invalidParameterHandler.throwUniqueNameInUse(uniqueParameterValue,
                                                                     uniqueParameterName,
                                                                     entityTypeName,
                                                                     serviceName,
                                                                     methodName);
                    }
                }
            }
            else
            {
                invalidParameterHandler.throwUniqueNameInUse(uniqueParameterValue,
                                                             uniqueParameterName,
                                                             entityTypeName,
                                                             serviceName,
                                                             methodName);
            }
        }
    }


    /**
     * Validate that new properties fo an entity do not have unique properties that class with other instances.
     *
     * @param entityGUID unique identifier of the entity to be updated (or null for a new entity).
     * @param entityTypeGUID the unique identifier of type of the entity
     * @param entityTypeName the unique name of the type of the entity
     * @param newProperties properties to test
     * @param effectiveTime  the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method for exceptions and error messages
     *
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem accessing the properties in the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    private void validateUniqueProperties(String             entityGUID,
                                          String             entityTypeGUID,
                                          String             entityTypeName,
                                          InstanceProperties newProperties,
                                          Date               effectiveTime,
                                          String             methodName) throws InvalidParameterException,
                                                                                PropertyServerException,
                                                                                UserNotAuthorizedException
    {
        InstanceProperties uniqueProperties = repositoryHelper.getUniqueProperties(serviceName,
                                                                                   entityTypeName,
                                                                                   newProperties);

        if ((uniqueProperties != null) && (uniqueProperties.getPropertyCount() > 0))
        {
            Iterator uniquePropertyNames = uniqueProperties.getPropertyNames();

            if (uniquePropertyNames != null)
            {
                while (uniquePropertyNames.hasNext())
                {
                    String uniquePropertyName = uniquePropertyNames.next();

                    if (uniquePropertyName != null)
                    {
                        /*
                         * This code assumes that the unique property is a string - which is fine for all current open metadata types.
                         */
                        InstancePropertyValue uniquePropertyValue = uniqueProperties.getPropertyValue(uniquePropertyName);

                        validateUniqueProperty(entityGUID,
                                               entityTypeGUID,
                                               entityTypeName,
                                               uniquePropertyValue.valueAsString(),
                                               uniquePropertyName,
                                               effectiveTime,
                                               methodName);
                    }
                }
            }
        }
    }


    /**
     * Validate that the user has permission to create a new entity
     *
     * @param userId           userId of user making request.
     * @param entityTypeGUID unique identifier of the type of entity to create
     * @param entityTypeName unique name of the type of entity to create
     * @param newObjectBuilder builder to create new entity
     * @param methodName calling method
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem adding the asset properties to the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    private void validateNewEntityRequest(String                         userId,
                                          String                         entityTypeGUID,
                                          String                         entityTypeName,
                                          OpenMetadataAPIGenericBuilder  newObjectBuilder,
                                          String                         methodName) throws InvalidParameterException,
                                                                                            PropertyServerException,
                                                                                            UserNotAuthorizedException
    {
        invalidParameterHandler.validateUserId(userId, methodName);

        InstanceProperties newProperties = newObjectBuilder.getInstanceProperties(methodName);

        validateUniqueProperties(null, entityTypeGUID, entityTypeName, newProperties, this.getEffectiveTime(newProperties), methodName);

        if (repositoryHelper.isTypeOf(serviceName, entityTypeName, OpenMetadataAPIMapper.ASSET_TYPE_NAME))
        {
            /*
             * Need to build a description of the asset to pass to the metadata security object.
             */
            Asset assetBeanForMetadataSecurity = new Asset();

            setupAssetBeanWithEntityProperties(assetBeanForMetadataSecurity,
                                               entityTypeGUID,
                                               entityTypeName,
                                               newObjectBuilder.getInstanceStatus(),
                                               null,
                                               newObjectBuilder.getInstanceProperties(methodName),
                                               newObjectBuilder.getEntityClassificationProperties(OpenMetadataAPIMapper.SECURITY_TAG_CLASSIFICATION_TYPE_NAME, methodName),
                                               newObjectBuilder.getEntityClassificationProperties(OpenMetadataAPIMapper.CONFIDENTIALITY_CLASSIFICATION_TYPE_NAME, methodName),
                                               newObjectBuilder.getEntityClassificationProperties(OpenMetadataAPIMapper.CONFIDENCE_CLASSIFICATION_TYPE_NAME, methodName),
                                               newObjectBuilder.getEntityClassificationProperties(OpenMetadataAPIMapper.CRITICALITY_CLASSIFICATION_TYPE_NAME, methodName),
                                               newObjectBuilder.getEntityClassificationProperties(OpenMetadataAPIMapper.IMPACT_CLASSIFICATION_TYPE_NAME, methodName),
                                               newObjectBuilder.getEntityClassificationProperties(OpenMetadataAPIMapper.RETENTION_CLASSIFICATION_TYPE_NAME, methodName),
                                               newObjectBuilder.getEntityClassificationProperties(OpenMetadataAPIMapper.ASSET_OWNERSHIP_CLASSIFICATION_NAME, methodName),
                                               newObjectBuilder.getEntityClassificationProperties(OpenMetadataAPIMapper.ASSET_ZONES_CLASSIFICATION_NAME, methodName),
                                               newObjectBuilder.getEntityClassificationProperties(OpenMetadataAPIMapper.ASSET_ORIGIN_CLASSIFICATION_NAME, methodName),
                                               methodName);

            assetBeanForMetadataSecurity.setZoneMembership(securityVerifier.setAssetZonesToDefault(defaultZones, assetBeanForMetadataSecurity));

            securityVerifier.validateUserForAssetCreate(userId, assetBeanForMetadataSecurity);
        }
    }


    /**
     * Create the instance properties object for the LatestChange classification.
     *
     * @param latestChangeTargetOrdinal The relationship of element that has been changed to the anchor
     * @param latestChangeActionOrdinal The type of change
     * @param classificationName If a classification name changed, this is its name
     * @param attachmentGUID If an attached entity or relationship changed, this is its unique identifier
     * @param attachmentTypeName If an attached entity or relationship changed, this is its unique type name
     * @param relationshipTypeName if a new relationship has been established, what is the type name of the relationship
     * @param userId The user identifier for the person/system making the change
     * @param actionDescription Description of the change
     * @param methodName calling method
     * @return instance properties object to null
     * @throws TypeErrorException there is a problem with one of the types used in this method - probably a logic error.
     */
    private InstanceProperties getLatestChangeClassificationProperties(int    latestChangeTargetOrdinal,
                                                                       int    latestChangeActionOrdinal,
                                                                       String classificationName,
                                                                       String attachmentGUID,
                                                                       String attachmentTypeName,
                                                                       String relationshipTypeName,
                                                                       String userId,
                                                                       String actionDescription,
                                                                       String methodName) throws TypeErrorException
    {
        InstanceProperties properties =  repositoryHelper.addEnumPropertyToInstance(serviceName,
                                                                                    null,
                                                                                    OpenMetadataAPIMapper.CHANGE_TARGET_PROPERTY_NAME,
                                                                                    OpenMetadataAPIMapper.LATEST_CHANGE_TARGET_ENUM_TYPE_GUID,
                                                                                    OpenMetadataAPIMapper.LATEST_CHANGE_TARGET_ENUM_TYPE_NAME,
                                                                                    latestChangeTargetOrdinal,
                                                                                    methodName);

        properties = repositoryHelper.addEnumPropertyToInstance(serviceName,
                                                                properties,
                                                                OpenMetadataAPIMapper.CHANGE_ACTION_PROPERTY_NAME,
                                                                OpenMetadataAPIMapper.LATEST_CHANGE_ACTION_ENUM_TYPE_GUID,
                                                                OpenMetadataAPIMapper.LATEST_CHANGE_ACTION_ENUM_TYPE_NAME,
                                                                latestChangeActionOrdinal,
                                                                methodName);

        if (classificationName != null)
        {
            properties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                      properties,
                                                                      OpenMetadataAPIMapper.CLASSIFICATION_NAME_PROPERTY_NAME,
                                                                      classificationName,
                                                                      methodName);
        }

        if (attachmentGUID != null)
        {
            properties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                      properties,
                                                                      OpenMetadataAPIMapper.ATTACHMENT_GUID_PROPERTY_NAME,
                                                                      attachmentGUID,
                                                                      methodName);
        }

        if (attachmentTypeName != null)
        {
            properties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                      properties,
                                                                      OpenMetadataAPIMapper.ATTACHMENT_TYPE_PROPERTY_NAME,
                                                                      attachmentTypeName,
                                                                      methodName);
        }

        if (attachmentTypeName != null)
        {
            properties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                      properties,
                                                                      OpenMetadataAPIMapper.RELATIONSHIP_TYPE_PROPERTY_NAME,
                                                                      relationshipTypeName,
                                                                      methodName);
        }

        if (userId != null)
        {
            properties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                      properties,
                                                                      OpenMetadataAPIMapper.USER_PROPERTY_NAME,
                                                                      userId,
                                                                      methodName);
        }

        if (actionDescription != null)
        {
            properties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                      properties,
                                                                      OpenMetadataAPIMapper.ACTION_DESCRIPTION_PROPERTY_NAME,
                                                                      actionDescription,
                                                                      methodName);
        }


        return properties;
    }


    /**
     * Classify an element with the anchors classification.
     *
     * @param userId calling user
     * @param beanGUID unique identifier of bean
     * @param beanGUIDParameterName anchorGUID of parameter supplying the beanGUID
     * @param beanGUIDTypeName type of bean
     * @param anchorGUID unique identifier of the anchor
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @throws InvalidParameterException asset or element not known, null userId or guid
     * @throws PropertyServerException problem accessing property server
     * @throws UserNotAuthorizedException security access problem
     */
    public void addAnchorsClassification(String  userId,
                                         String  beanGUID,
                                         String  beanGUIDParameterName,
                                         String  beanGUIDTypeName,
                                         String  anchorGUID,
                                         boolean forLineage,
                                         boolean forDuplicateProcessing,
                                         Date    effectiveTime,
                                         String  methodName) throws InvalidParameterException,
                                                                    UserNotAuthorizedException,
                                                                    PropertyServerException
    {
        ReferenceableBuilder builder = new ReferenceableBuilder(OpenMetadataAPIMapper.REFERENCEABLE_TYPE_GUID,
                                                                OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME,
                                                                repositoryHelper,
                                                                serviceName,
                                                                serverName);

        this.setClassificationInRepository(userId,
                                           null,
                                           null,
                                           beanGUID,
                                           beanGUIDParameterName,
                                           beanGUIDTypeName,
                                           OpenMetadataAPIMapper.ANCHORS_CLASSIFICATION_TYPE_GUID,
                                           OpenMetadataAPIMapper.ANCHORS_CLASSIFICATION_TYPE_NAME,
                                           builder.getAnchorsProperties(anchorGUID, methodName),
                                           false,
                                           forLineage,
                                           forDuplicateProcessing,
                                           effectiveTime,
                                           methodName);
    }


    /**
     * Add details of an update to the anchor entity in the latest change classification.
     *
     * @param anchorEntity the entity to update
     * @param latestChangeTargetOrdinal the type of instance
     * @param latestChangeActionOrdinal the type of change
     * @param classificationName if a classification has changed, what is its name
     * @param attachmentGUID if a new relationship has been established, what is the unique identifier of the entity it is connecting to
     * @param attachmentTypeName if a new relationship has been established, what is the type name of the entity it is connecting to
     * @param relationshipTypeName if a new relationship has been established, what is the type name of the relationship
     * @param userId who is the calling user?
     * @param actionDescription what is the description of the activity
     * @param methodName calling method
     * @throws UserNotAuthorizedException local server user id not authorized to update latest change
     * @throws PropertyServerException logic error because classification type not recognized
     */
    private void addLatestChangeToAnchor(EntityDetail anchorEntity,
                                         int          latestChangeTargetOrdinal,
                                         int          latestChangeActionOrdinal,
                                         String       classificationName,
                                         String       attachmentGUID,
                                         String       attachmentTypeName,
                                         String       relationshipTypeName,
                                         String       userId,
                                         String       actionDescription,
                                         String       methodName) throws UserNotAuthorizedException,
                                                                         PropertyServerException
    {
        final String  guidParameterName = "anchorEntity.getGUID()";

        InstanceProperties newProperties = null;

        try
        {
            invalidParameterHandler.validateObject(anchorEntity, guidParameterName, methodName);

            newProperties = this.getLatestChangeClassificationProperties(latestChangeTargetOrdinal,
                                                                         latestChangeActionOrdinal,
                                                                         classificationName,
                                                                         attachmentGUID,
                                                                         attachmentTypeName,
                                                                         relationshipTypeName,
                                                                         userId,
                                                                         actionDescription,
                                                                         methodName);

            Classification classification = repositoryHelper.getClassificationFromEntity(serviceName,
                                                                                         anchorEntity,
                                                                                         OpenMetadataAPIMapper.LATEST_CHANGE_CLASSIFICATION_TYPE_NAME,
                                                                                         methodName);
            if (classification != null)
            {
                repositoryHandler.reclassifyEntity(localServerUserId,
                                                   null,
                                                   null,
                                                   anchorEntity.getGUID(),
                                                   guidParameterName,
                                                   anchorEntity.getType().getTypeDefName(),
                                                   OpenMetadataAPIMapper.LATEST_CHANGE_CLASSIFICATION_TYPE_GUID,
                                                   OpenMetadataAPIMapper.LATEST_CHANGE_CLASSIFICATION_TYPE_NAME,
                                                   classification,
                                                   newProperties,
                                                   false,
                                                   false,
                                                   new Date(),
                                                   methodName);
            }
        }
        catch (ClassificationErrorException newClassificationNeeded)
        {
            /*
             * This is not an error - it just means that the classification is not present on the anchor entity.
             */
            repositoryHandler.classifyEntity(localServerUserId,
                                             null,
                                             null,
                                             anchorEntity.getGUID(),
                                             anchorEntity,
                                             guidParameterName,
                                             anchorEntity.getType().getTypeDefName(),
                                             OpenMetadataAPIMapper.LATEST_CHANGE_CLASSIFICATION_TYPE_GUID,
                                             OpenMetadataAPIMapper.LATEST_CHANGE_CLASSIFICATION_TYPE_NAME,
                                             ClassificationOrigin.ASSIGNED,
                                             null,
                                             newProperties,
                                             false,
                                             false,
                                             new Date(),
                                             methodName);
        }
        catch (InvalidParameterException | TypeErrorException error)
        {
            throw new PropertyServerException(error);
        }
    }


    /**
     * Retrieve the supplementary properties glossary object.  This is the anchor of all the supplementary properties
     * glossary terms.
     *
     * @param methodName calling method
     *
     * @return unique identifier of the supplementary properties glossary
     *
     * @throws InvalidParameterException  the parameters are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem detected in the repository services
     */
    private String getSupplementaryPropertiesGlossary(String methodName) throws InvalidParameterException,
                                                                                UserNotAuthorizedException,
                                                                                PropertyServerException
    {

        String glossaryGUID = this.getEntityGUIDByValue(localServerUserId,
                                                        supplementaryPropertiesGlossaryName,
                                                        supplementaryPropertiesGlossaryParameterName,
                                                        OpenMetadataAPIMapper.GLOSSARY_TYPE_GUID,
                                                        OpenMetadataAPIMapper.GLOSSARY_TYPE_NAME,
                                                        qualifiedNamePropertyNamesList,
                                                        false,
                                                        false,
                                                        new Date(),
                                                        methodName);

        if (glossaryGUID == null)
        {
            InstanceProperties properties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                                         null,
                                                                                         OpenMetadataAPIMapper.QUALIFIED_NAME_PROPERTY_NAME,
                                                                                         supplementaryPropertiesGlossaryName,
                                                                                         methodName);

            properties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                      properties,
                                                                      OpenMetadataAPIMapper.DISPLAY_NAME_PROPERTY_NAME,
                                                                      supplementaryPropertiesGlossaryDescription,
                                                                      methodName);

            properties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                      properties,
                                                                      OpenMetadataAPIMapper.DESCRIPTION_PROPERTY_NAME,
                                                                      supplementaryPropertiesGlossaryDescription,
                                                                      methodName);

            glossaryGUID = repositoryHandler.createEntity(localServerUserId,
                                                          OpenMetadataAPIMapper.GLOSSARY_TYPE_GUID,
                                                          OpenMetadataAPIMapper.GLOSSARY_TYPE_NAME,
                                                          null,
                                                          null,
                                                          properties,
                                                          null,
                                                          InstanceStatus.ACTIVE,
                                                          methodName);
        }

        return glossaryGUID;
    }


    /**
     * Set up the instance properties for a supplementary properties glossary term.
     *
     * @param existingProperties properties to add the new properties to
     * @param displayName  display name for the term
     * @param summary short description
     * @param description description of the term
     * @param abbreviation abbreviation used for the term
     * @param usage illustrations of how the term is used
     * @param methodName calling method
     * @return properties object or null
     */
    private InstanceProperties getSupplementaryInstanceProperties(InstanceProperties existingProperties,
                                                                  String             displayName,
                                                                  String             summary,
                                                                  String             description,
                                                                  String             abbreviation,
                                                                  String             usage,
                                                                  String             methodName)
    {
        InstanceProperties properties = existingProperties;

        properties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                  properties,
                                                                  OpenMetadataAPIMapper.DISPLAY_NAME_PROPERTY_NAME,
                                                                  displayName,
                                                                  methodName);
        properties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                  properties,
                                                                  OpenMetadataAPIMapper.SUMMARY_PROPERTY_NAME,
                                                                  summary,
                                                                  methodName);
        properties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                  properties,
                                                                  OpenMetadataAPIMapper.DESCRIPTION_PROPERTY_NAME,
                                                                  description,
                                                                  methodName);
        properties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                  properties,
                                                                  OpenMetadataAPIMapper.ABBREVIATION_PROPERTY_NAME,
                                                                  abbreviation,
                                                                  methodName);
        properties = repositoryHelper.addStringPropertyToInstance(serviceName,
                                                                  properties,
                                                                  OpenMetadataAPIMapper.USAGE_PROPERTY_NAME,
                                                                  usage,
                                                                  methodName);
        return properties;
    }



    /**
     * Maintain the supplementary properties of a technical metadata element in a glossary term linked to the supplied element.
     * The glossary term needs to be connected to a glossary which may need to be created.  There is no use of effective time
     * on these elements because they are effective all of the time that the asset is effective
     *
     * @param userId calling user
     * @param elementGUID element for the
     * @param elementQualifiedName qualified name of the linked element
     * @param displayName  display name for the term
     * @param summary short description
     * @param description description of the term
     * @param abbreviation abbreviation used for the term
     * @param usage illustrations of how the term is used
     * @param isMergeUpdate should the new properties be merged with the existing properties or completely replace them?
     * @param methodName calling method
     *
     * @throws InvalidParameterException  the parameters are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem detected in the repository services
     */
    public void maintainSupplementaryProperties(String  userId,
                                                String  elementGUID,
                                                String  elementQualifiedName,
                                                String  displayName,
                                                String  summary,
                                                String  description,
                                                String  abbreviation,
                                                String  usage,
                                                boolean isMergeUpdate,
                                                String  methodName) throws InvalidParameterException,
                                                                           UserNotAuthorizedException,
                                                                           PropertyServerException
    {
        EntityDetail glossaryTerm = this.getEntityByValue(localServerUserId,
                                                          elementQualifiedName + supplementaryPropertiesQualifiedNamePostFix,
                                                          supplementaryPropertiesQualifiedNameParameterName,
                                                          OpenMetadataAPIMapper.GLOSSARY_TERM_TYPE_GUID,
                                                          OpenMetadataAPIMapper.GLOSSARY_TERM_TYPE_NAME,
                                                          qualifiedNamePropertyNamesList,
                                                          false,
                                                          false,
                                                          new Date(),
                                                          methodName);

        if (glossaryTerm == null)
        {
            String glossaryGUID = this.getSupplementaryPropertiesGlossary(methodName);

            if (glossaryGUID != null)
            {
                InstanceProperties glossaryTermProperties = this.getSupplementaryInstanceProperties(null,
                                                                                                    displayName,
                                                                                                    summary,
                                                                                                    description,
                                                                                                    abbreviation,
                                                                                                    usage,
                                                                                                    methodName);

                /*
                 * The glossary term is anchored to the element rather than the glossary.  This means that it deleted if/when
                 * the element is deleted.
                 */
                List initialClassifications = new ArrayList<>();
                try
                {
                    Classification classification = repositoryHelper.getNewClassification(serviceName,
                                                                                          null,
                                                                                          null,
                                                                                          InstanceProvenanceType.LOCAL_COHORT,
                                                                                          userId,
                                                                                          OpenMetadataAPIMapper.ANCHORS_CLASSIFICATION_TYPE_NAME,
                                                                                          OpenMetadataAPIMapper.GLOSSARY_TERM_TYPE_NAME,
                                                                                          ClassificationOrigin.ASSIGNED,
                                                                                          null,
                                                                                          repositoryHelper.addStringPropertyToInstance(
                                                                                                  serviceName,
                                                                                                  null,
                                                                                                  OpenMetadataAPIMapper.ANCHOR_GUID_PROPERTY_NAME,
                                                                                                  elementGUID,
                                                                                                  methodName));
                    initialClassifications.add(classification);

                    classification = repositoryHelper.getNewClassification(serviceName,
                                                                           null,
                                                                           null,
                                                                           InstanceProvenanceType.LOCAL_COHORT,
                                                                           userId,
                                                                           OpenMetadataAPIMapper.ELEMENT_SUPPLEMENT_CLASSIFICATION_TYPE_NAME,
                                                                           OpenMetadataAPIMapper.GLOSSARY_TERM_TYPE_NAME,
                                                                           ClassificationOrigin.ASSIGNED,
                                                                           null,
                                                                           null);
                    initialClassifications.add(classification);
                }
                catch (TypeErrorException error)
                {
                    throw new PropertyServerException(error);
                }

                String glossaryTermGUID = repositoryHandler.createEntity(localServerUserId,
                                                                         OpenMetadataAPIMapper.GLOSSARY_TERM_TYPE_GUID,
                                                                         OpenMetadataAPIMapper.GLOSSARY_TERM_TYPE_NAME,
                                                                         null,
                                                                         null,
                                                                         glossaryTermProperties,
                                                                         initialClassifications,
                                                                         InstanceStatus.ACTIVE,
                                                                         methodName);

                repositoryHandler.createRelationship(userId,
                                                     OpenMetadataAPIMapper.TERM_ANCHOR_TYPE_GUID,
                                                     null,
                                                     null,
                                                     glossaryGUID,
                                                     glossaryTermGUID,
                                                     null,
                                                     methodName);

                repositoryHandler.createRelationship(userId,
                                                     OpenMetadataAPIMapper.SUPPLEMENTARY_PROPERTIES_TYPE_GUID,
                                                     null,
                                                     null,
                                                     elementGUID,
                                                     glossaryTermGUID,
                                                     null,
                                                     methodName);
            }
        }
        else
        {
            InstanceProperties glossaryTermProperties;

            if (isMergeUpdate)
            {
                glossaryTermProperties = this.getSupplementaryInstanceProperties(glossaryTerm.getProperties(),
                                                                                 displayName,
                                                                                 summary,
                                                                                 description,
                                                                                 abbreviation,
                                                                                 usage,
                                                                                 methodName);
            }
            else
            {
                glossaryTermProperties = this.getSupplementaryInstanceProperties(null,
                                                                                 displayName,
                                                                                 summary,
                                                                                 description,
                                                                                 abbreviation,
                                                                                 usage,
                                                                                 methodName);
            }

            repositoryHandler.updateEntityProperties(userId,
                                                     null,
                                                     null,
                                                     glossaryTerm.getGUID(),
                                                     glossaryTerm,
                                                     null,
                                                     null,
                                                     glossaryTermProperties,
                                                     methodName);

        }
    }


    /**
     * Count up the number of elements of a certain type that are attached to a specific entity.
     *
     * @param userId calling user
     * @param elementGUID unique identifier for the entity that the object is attached to (anchor entity)
     * @param elementTypeName type of the anchor entity
     * @param attachmentTypeGUID unique identifier of the attachment relationship's type
     * @param attachmentTypeName unique name of the attachment's type
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return count of attached objects that are effective now
     *
     * @throws InvalidParameterException  the parameters are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public int countAttachments(String userId,
                                String elementGUID,
                                String elementTypeName,
                                String attachmentTypeGUID,
                                String attachmentTypeName,
                                Date   effectiveTime,
                                String methodName) throws InvalidParameterException,
                                                               PropertyServerException,
                                                               UserNotAuthorizedException
    {
        final String guidParameter = "elementGUID";

        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(elementGUID, guidParameter, methodName);

        int count = 0;

        RepositoryRelationshipsIterator iterator = new RepositoryRelationshipsIterator(repositoryHandler,
                                                                                       userId,
                                                                                       elementGUID,
                                                                                       elementTypeName,
                                                                                       attachmentTypeGUID,
                                                                                       attachmentTypeName,
                                                                                       false,
                                                                                       0,
                                                                                       invalidParameterHandler.getMaxPagingSize(),
                                                                                       effectiveTime,
                                                                                       methodName);


        while (iterator.moreToReceive())
        {
            Relationship relationship = iterator.getNext();

            if (visibleToUserThroughRelationship(userId, relationship, methodName))
            {
                count ++;
            }
        }

        return count;
    }


    /**
     * Return the entity for the required relationship attached to a specific entity.  This method assumes the starting entity has
     * a validated anchor
     *
     * @param userId     calling user
     * @param startingElementGUID identifier for the entity that the identifier is attached to
     * @param startingElementGUIDParameterName name of the parameter used to pass the guid
     * @param startingElementTypeName type name for anchor
     * @param relationshipTypeGUID unique identifier of the attachment's relationship type
     * @param relationshipTypeName unique name of the attachment's relationship type
     * @param resultingElementTypeName unique name of the attached entity's type
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of retrieved objects or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public EntityDetail getAttachedEntity(String  userId,
                                          String  startingElementGUID,
                                          String  startingElementGUIDParameterName,
                                          String  startingElementTypeName,
                                          String  relationshipTypeGUID,
                                          String  relationshipTypeName,
                                          String  resultingElementTypeName,
                                          boolean forLineage,
                                          boolean forDuplicateProcessing,
                                          Date    effectiveTime,
                                          String  methodName) throws InvalidParameterException,
                                                                     PropertyServerException,
                                                                     UserNotAuthorizedException
    {
        return this.getAttachedEntity(userId,
                                      startingElementGUID,
                                      startingElementGUIDParameterName,
                                      startingElementTypeName,
                                      relationshipTypeGUID,
                                      relationshipTypeName,
                                      resultingElementTypeName,
                                      forLineage,
                                      forDuplicateProcessing,
                                      supportedZones,
                                      effectiveTime,
                                      methodName);
    }


    /**
     * Return the entity for the required relationship attached to a specific entity.  This method assumes the starting entity has
     * a validated anchor
     *
     * @param userId     calling user
     * @param startingElementGUID identifier for the entity that the identifier is attached to
     * @param startingElementGUIDParameterName name of the parameter used to pass the guid
     * @param startingElementTypeName type name for anchor
     * @param relationshipTypeGUID unique identifier of the attachment's relationship type
     * @param relationshipTypeName unique name of the attachment's relationship type
     * @param resultingElementTypeName unique name of the attached entity's type
     * @param forLineage is this part of a lineage request?
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param serviceSupportedZones supported zones for calling service
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of retrieved objects or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public EntityDetail getAttachedEntity(String       userId,
                                          String       startingElementGUID,
                                          String       startingElementGUIDParameterName,
                                          String       startingElementTypeName,
                                          String       relationshipTypeGUID,
                                          String       relationshipTypeName,
                                          String       resultingElementTypeName,
                                          boolean      forLineage,
                                          boolean      forDuplicateProcessing,
                                          List serviceSupportedZones,
                                          Date         effectiveTime,
                                          String       methodName) throws InvalidParameterException,
                                                                          PropertyServerException,
                                                                          UserNotAuthorizedException
    {
        EntityDetail startingEntity = this.getEntityFromRepository(userId,
                                                                   startingElementGUID,
                                                                   startingElementGUIDParameterName,
                                                                   startingElementTypeName,
                                                                   null,
                                                                   null,
                                                                   forLineage,
                                                                   forDuplicateProcessing,
                                                                   serviceSupportedZones,
                                                                   effectiveTime,
                                                                   methodName);

        EntityDetail entity = repositoryHandler.getEntityForRelationshipType(userId,
                                                                             startingEntity,
                                                                             startingElementTypeName,
                                                                             relationshipTypeGUID,
                                                                             relationshipTypeName,
                                                                             null,
                                                                             0,
                                                                             resultingElementTypeName,
                                                                             forLineage,
                                                                             forDuplicateProcessing,
                                                                             effectiveTime,
                                                                             methodName);

        if (entity != null)
        {
            validateAnchorEntity(userId,
                                 startingElementGUID,
                                 startingElementTypeName,
                                 entity,
                                 startingElementGUIDParameterName,
                                 false,
                                 forLineage,
                                 forDuplicateProcessing,
                                 serviceSupportedZones,
                                 effectiveTime,
                                 methodName);

            if (repositoryHelper.isTypeOf(serviceName, entity.getType().getTypeDefName(), resultingElementTypeName))
            {
                if (! forLineage)
                {
                    try
                    {
                        if (repositoryHelper.getClassificationFromEntity(serviceName, entity, OpenMetadataAPIMapper.MEMENTO_CLASSIFICATION_TYPE_NAME, methodName) != null)
                        {
                            entity = null;
                        }
                    }
                    catch (ClassificationErrorException error)
                    {
                        /*
                         * Since this classification is not supported, it can not be attached to the entity
                         */
                    }
                }

                return entity;
            }
        }

        return null;
    }



    /**
     * Return the list of entities at the other end of the requested relationship type that were created or edited by
     * the requesting user.
     *
     * @param userId  user making the request
     * @param startingEntityGUID  starting entity's GUID
     * @param startingEntityTypeName  starting entity's type name
     * @param relationshipTypeGUID  identifier for the relationship to follow
     * @param relationshipTypeName  type name for the relationship to follow
     * @param attachedEntityTypeGUID  identifier for the relationship to follow
     * @param attachedEntityTypeName  type name for the relationship to follow
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName  name of calling method
     * @return retrieved entities or null
     * @throws InvalidParameterException the entity at the other end is not of the expected type
     * @throws PropertyServerException problem accessing the property server
     * @throws UserNotAuthorizedException security access problem
     */
    public EntityDetail getAttachedEntityFromUser(String  userId,
                                                  String  startingEntityGUID,
                                                  String  startingEntityTypeName,
                                                  String  relationshipTypeGUID,
                                                  String  relationshipTypeName,
                                                  String  attachedEntityTypeGUID,
                                                  String  attachedEntityTypeName,
                                                  Date    effectiveTime,
                                                  String  methodName) throws InvalidParameterException,
                                                                             UserNotAuthorizedException,
                                                                             PropertyServerException
    {
        final String localMethodName = "getAttachedEntityFromUser";

        RepositoryRelatedEntitiesIterator iterator = new RepositoryRelatedEntitiesIterator(repositoryHandler,
                                                                                           userId,
                                                                                           startingEntityGUID,
                                                                                           startingEntityTypeName,
                                                                                           relationshipTypeGUID,
                                                                                           relationshipTypeName,
                                                                                           null,
                                                                                           false,
                                                                                           false,
                                                                                           0,
                                                                                           invalidParameterHandler.getMaxPagingSize(),
                                                                                           effectiveTime,
                                                                                           methodName);

        while (iterator.moreToReceive())
        {
            EntityDetail entity = iterator.getNext();

            if (entity != null)
            {
                if ((userId.equals(entity.getCreatedBy()) || (userId.equals(entity.getUpdatedBy())) || ((entity.getMaintainedBy() != null) && (entity.getMaintainedBy().contains(userId)))))
                {
                    errorHandler.validateInstanceType(entity, attachedEntityTypeName, methodName, localMethodName);
                    return entity;
                }
            }
        }

        return null;
    }


    /**
     * Return the list of entities at the other end of the requested relationship type that were created or
     * edited by the requesting user.
     *
     * @param userId  user making the request
     * @param startingEntityGUID  starting entity's GUID
     * @param startingEntityTypeName  starting entity's type name
     * @param relationshipTypeGUID  identifier for the relationship to follow
     * @param relationshipTypeName  type name for the relationship to follow
     * @param attachedEntityTypeGUID  identifier for the relationship to follow
     * @param attachedEntityTypeName  type name for the relationship to follow
     * @param sequencingPropertyName name of property used to sequence the results - needed for paging
     * @param startingFrom initial position in the stored list
     * @param pageSize maximum number of definitions to return on this call.
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName  name of calling method
     * @return retrieved entities or null
     * @throws PropertyServerException problem accessing the property server
     * @throws UserNotAuthorizedException security access problem
     * @throws InvalidParameterException one of the parameters is in error
     */
    public List getAttachedEntitiesFromUser(String  userId,
                                                          String  startingEntityGUID,
                                                          String  startingEntityTypeName,
                                                          String  relationshipTypeGUID,
                                                          String  relationshipTypeName,
                                                          String  attachedEntityTypeGUID,
                                                          String  attachedEntityTypeName,
                                                          String  sequencingPropertyName,
                                                          int     startingFrom,
                                                          int     pageSize,
                                                          Date    effectiveTime,
                                                          String  methodName) throws InvalidParameterException,
                                                                                     UserNotAuthorizedException,
                                                                                     PropertyServerException
    {
        final String localMethodName = "getAttachedEntitiesFromUser";

        List results = new ArrayList<>();

        RepositoryRelatedEntitiesIterator iterator = new RepositoryRelatedEntitiesIterator(repositoryHandler,
                                                                                           userId,
                                                                                           startingEntityGUID,
                                                                                           startingEntityTypeName,
                                                                                           relationshipTypeGUID,
                                                                                           relationshipTypeName,
                                                                                           sequencingPropertyName,
                                                                                           false,
                                                                                           false,
                                                                                           startingFrom,
                                                                                           pageSize,
                                                                                           effectiveTime,
                                                                                           methodName);

        while ((iterator.moreToReceive() && ((pageSize == 0) || (results.size() < pageSize))))
        {
            EntityDetail entity = iterator.getNext();

            if (entity != null)
            {
                if ((userId.equals(entity.getCreatedBy()) ||
                             (userId.equals(entity.getUpdatedBy())) ||
                             ((entity.getMaintainedBy() != null) && (entity.getMaintainedBy().contains(userId)))))
                {
                    errorHandler.validateInstanceType(entity, attachedEntityTypeName, methodName, localMethodName);
                    results.add(entity);
                }
            }
        }

        if (results.isEmpty())
        {
            return null;
        }
        else
        {
            return results;
        }
    }


    /**
     * Return the entities for the required relationships attached to a specific entity.
     *
     * @param userId     calling user
     * @param startingElementGUID identifier for the entity that the identifier is attached to
     * @param startingElementGUIDParameterName name of the parameter used to pass the guid
     * @param startingElementTypeName type name for anchor
     * @param relationshipTypeGUID unique identifier of the attachment's relationship type
     * @param relationshipTypeName unique name of the attachment's relationship type
     * @param resultingElementTypeName unique name of the attached entity's type
     * @param startingFrom start position for results
     * @param pageSize     maximum number of results
     * @param methodName calling method
     *
     * @return list of retrieved objects or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public List getAttachedEntities(String       userId,
                                                  String       startingElementGUID,
                                                  String       startingElementGUIDParameterName,
                                                  String       startingElementTypeName,
                                                  String       relationshipTypeGUID,
                                                  String       relationshipTypeName,
                                                  String       resultingElementTypeName,
                                                  int          startingFrom,
                                                  int          pageSize,
                                                  String       methodName) throws InvalidParameterException,
                                                                                  PropertyServerException,
                                                                                  UserNotAuthorizedException
    {
        return this.getAttachedEntities(userId,
                                        startingElementGUID,
                                        startingElementGUIDParameterName,
                                        startingElementTypeName,
                                        relationshipTypeGUID,
                                        relationshipTypeName,
                                        resultingElementTypeName,
                                        null,
                                        null,
                                        false,
                                        false,
                                        supportedZones,
                                        startingFrom,
                                        pageSize,
                                        new Date(),
                                        methodName);
    }


    /**
     * Return the entities for the required relationships attached to a specific entity.
     *
     * @param userId     calling user
     * @param startingElementGUID identifier for the entity that the identifier is attached to
     * @param startingElementGUIDParameterName name of the parameter used to pass the guid
     * @param startingElementTypeName type name for anchor
     * @param relationshipTypeGUID unique identifier of the attachment's relationship type
     * @param relationshipTypeName unique name of the attachment's relationship type
     * @param resultingElementTypeName unique name of the attached entity's type
     * @param requiredClassificationName name of a classification that must be on the entity for a match
     * @param omittedClassificationName name of a classification that must NOT be on the entity for a match
     * @param forLineage is this part of a lineage request?
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param serviceSupportedZones supported zones for calling service
     * @param startingFrom start position for results
     * @param pageSize     maximum number of results
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of retrieved objects or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public List getAttachedEntities(String       userId,
                                                  String       startingElementGUID,
                                                  String       startingElementGUIDParameterName,
                                                  String       startingElementTypeName,
                                                  String       relationshipTypeGUID,
                                                  String       relationshipTypeName,
                                                  String       resultingElementTypeName,
                                                  String       requiredClassificationName,
                                                  String       omittedClassificationName,
                                                  boolean      forLineage,
                                                  boolean      forDuplicateProcessing,
                                                  List serviceSupportedZones,
                                                  int          startingFrom,
                                                  int          pageSize,
                                                  Date         effectiveTime,
                                                  String       methodName) throws InvalidParameterException,
                                                                                  PropertyServerException,
                                                                                  UserNotAuthorizedException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(startingElementGUID, startingElementGUIDParameterName, methodName);

        this.validateAnchorEntity(userId,
                                  startingElementGUID,
                                  startingElementGUIDParameterName,
                                  startingElementTypeName,
                                  false,
                                  forLineage,
                                  forDuplicateProcessing,
                                  serviceSupportedZones,
                                  effectiveTime,
                                  methodName);

        List visibleRelationships = this.getAttachmentLinks(userId,
                                                                          startingElementGUID,
                                                                          startingElementGUIDParameterName,
                                                                          startingElementTypeName,
                                                                          relationshipTypeGUID,
                                                                          relationshipTypeName,
                                                                          resultingElementTypeName,
                                                                          startingFrom,
                                                                          pageSize,
                                                                          effectiveTime,
                                                                          methodName);

        if (visibleRelationships != null)
        {
            List visibleEntities = new ArrayList<>();

            for (Relationship  relationship : visibleRelationships)
            {
                if (relationship != null)
                {
                    EntityProxy entityProxy = repositoryHandler.getOtherEnd(startingElementGUID, startingElementTypeName, relationship, methodName);

                    if ((entityProxy != null) && (entityProxy.getType() != null)  &&
                        (repositoryHelper.isTypeOf(serviceName, entityProxy.getType().getTypeDefName(), resultingElementTypeName)))
                    {
                        try
                        {
                            final String guidParameterName = "entityProxy.getGUID()";

                            visibleEntities.add(this.getEntityFromRepository(userId,
                                                                             entityProxy.getGUID(),
                                                                             guidParameterName,
                                                                             resultingElementTypeName,
                                                                             requiredClassificationName,
                                                                             omittedClassificationName,
                                                                             forLineage,
                                                                             forDuplicateProcessing,
                                                                             supportedZones,
                                                                             effectiveTime,
                                                                             methodName));
                        }
                        catch (InvalidParameterException | UserNotAuthorizedException | PropertyServerException inaccessibleEntity)
                        {
                            // skip entities that are not visible to this user
                            if (log.isDebugEnabled())
                            {
                                log.debug("Skipping inaccessible entity", inaccessibleEntity);
                            }
                        }
                    }
                }
            }

            if (! visibleEntities.isEmpty())
            {
                return visibleEntities;
            }
        }

        return null;
    }


    /**
     * Return the relationship between the requested elements - there should be only one.  Note that the entities are not checked.
     *
     * @param userId     calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of the parameter used to pass the guid
     * @param startingTypeName type name for anchor
     * @param attachmentRelationshipTypeGUID unique identifier of the relationship type connect to the attachment
     * @param attachmentRelationshipTypeName unique name of the relationship type connect to the attachment
     * @param attachmentEntityGUID unique identifier of the entity on the other end or null if unknown
     * @param attachmentEntityTypeName unique name of the attached entity's type
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of retrieved relationships or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public Relationship  getUniqueAttachmentLink(String   userId,
                                                 String   startingGUID,
                                                 String   startingGUIDParameterName,
                                                 String   startingTypeName,
                                                 String   attachmentRelationshipTypeGUID,
                                                 String   attachmentRelationshipTypeName,
                                                 String   attachmentEntityGUID,
                                                 String   attachmentEntityTypeName,
                                                 Date     effectiveTime,
                                                 String   methodName) throws InvalidParameterException,
                                                                             PropertyServerException,
                                                                             UserNotAuthorizedException
    {
        return this.getUniqueAttachmentLink(userId,
                                            startingGUID,
                                            startingGUIDParameterName,
                                            startingTypeName,
                                            attachmentRelationshipTypeGUID,
                                            attachmentRelationshipTypeName,
                                            attachmentEntityGUID,
                                            attachmentEntityTypeName,
                                            0,
                                            false,
                                            effectiveTime,
                                            methodName);
    }


    /**
     * Return the relationship between the requested elements - there should be only one.  Note that the entities are not checked.
     *
     * @param userId     calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of the parameter used to pass the guid
     * @param startingTypeName type name for anchor
     * @param attachmentRelationshipTypeGUID unique identifier of the relationship type connect to the attachment
     * @param attachmentRelationshipTypeName unique name of the relationship type connect to the attachment
     * @param attachmentEntityGUID unique identifier of the entity on the other end or null if unknown
     * @param attachmentEntityTypeName unique name of the attached entity's type
     * @param attachmentEntityEnd which relationship end should the attached entity be located? 0=either end; 1=end1; 2=end2
     * @param forDuplicateProcessing is this processing part of duplicate processing?
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of retrieved relationships or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public Relationship  getUniqueAttachmentLink(String   userId,
                                                 String   startingGUID,
                                                 String   startingGUIDParameterName,
                                                 String   startingTypeName,
                                                 String   attachmentRelationshipTypeGUID,
                                                 String   attachmentRelationshipTypeName,
                                                 String   attachmentEntityGUID,
                                                 String   attachmentEntityTypeName,
                                                 int      attachmentEntityEnd,
                                                 boolean  forDuplicateProcessing,
                                                 Date     effectiveTime,
                                                 String   methodName) throws InvalidParameterException,
                                                                             PropertyServerException,
                                                                             UserNotAuthorizedException
    {
        List relationships = this.getAttachmentLinks(userId,
                                                                   startingGUID,
                                                                   startingGUIDParameterName,
                                                                   startingTypeName,
                                                                   attachmentRelationshipTypeGUID,
                                                                   attachmentRelationshipTypeName,
                                                                   attachmentEntityGUID,
                                                                   attachmentEntityTypeName,
                                                                   attachmentEntityEnd,
                                                                   forDuplicateProcessing,
                                                                   0,
                                                                   invalidParameterHandler.getMaxPagingSize(),
                                                                   effectiveTime,
                                                                   methodName);

        if ((relationships == null) || (relationships.isEmpty()))
        {
            return null;
        }
        else if (relationships.size() == 1)
        {
            return relationships.get(0);
        }
        else
        {
            errorHandler.handleAmbiguousRelationships(startingGUID,
                                                      startingTypeName,
                                                      attachmentRelationshipTypeName,
                                                      relationships,
                                                      methodName);

            return null;
        }
    }


    /**
     * Return all relationships attached to a specific entity.
     *
     * @param userId     calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of the parameter used to pass the guid
     * @param startingTypeName type name for anchor
     * @param forDuplicateProcessing is this processing part of duplicate processing?
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of retrieved relationships or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public List  getAllAttachmentLinks(String   userId,
                                                     String   startingGUID,
                                                     String   startingGUIDParameterName,
                                                     String   startingTypeName,
                                                     boolean  forDuplicateProcessing,
                                                     Date     effectiveTime,
                                                     String   methodName) throws InvalidParameterException,
                                                                                 PropertyServerException,
                                                                                 UserNotAuthorizedException
    {
        return this.getAttachmentLinks(userId,
                                       startingGUID,
                                       startingGUIDParameterName,
                                       startingTypeName,
                                       null,
                                       null,
                                       null,
                                       null,
                                       0,
                                       forDuplicateProcessing,
                                       0,
                                       invalidParameterHandler.getMaxPagingSize(),
                                       effectiveTime,
                                       methodName);
    }


    /**
     * Return the relationships to required elements attached to a specific entity.  Note that the entities are not checked.
     *
     * @param userId     calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of the parameter used to pass the guid
     * @param startingTypeName type name for anchor
     * @param attachmentRelationshipTypeGUID unique identifier of the relationship type connect to the attachment
     * @param attachmentRelationshipTypeName unique name of the relationship type connect to the attachment
     * @param attachmentEntityTypeName unique name of the attached entity's type
     * @param startingFrom start position for results
     * @param pageSize     maximum number of results
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of retrieved relationships or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public List  getAttachmentLinks(String   userId,
                                                  String   startingGUID,
                                                  String   startingGUIDParameterName,
                                                  String   startingTypeName,
                                                  String   attachmentRelationshipTypeGUID,
                                                  String   attachmentRelationshipTypeName,
                                                  String   attachmentEntityTypeName,
                                                  int      startingFrom,
                                                  int      pageSize,
                                                  Date     effectiveTime,
                                                  String   methodName) throws InvalidParameterException,
                                                                              PropertyServerException,
                                                                              UserNotAuthorizedException
    {
        return this.getAttachmentLinks(userId,
                                       startingGUID,
                                       startingGUIDParameterName,
                                       startingTypeName,
                                       attachmentRelationshipTypeGUID,
                                       attachmentRelationshipTypeName,
                                       null,
                                       attachmentEntityTypeName,
                                       0,
                                       false,
                                       startingFrom,
                                       pageSize,
                                       effectiveTime,
                                       methodName);
    }


    /**
     * Return the relationships to required elements attached to a specific entity.  Note that the entities are not checked.
     *
     * @param userId     calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of the parameter used to pass the guid
     * @param startingTypeName type name for anchor
     * @param attachmentRelationshipTypeGUID unique identifier of the relationship type connect to the attachment
     * @param attachmentRelationshipTypeName unique name of the relationship type connect to the attachment
     * @param attachmentEntityGUID unique identifier of the entity on the other end or null if unknown
     * @param attachmentEntityTypeName unique name of the attached entity's type
     * @param startingFrom start position for results
     * @param pageSize     maximum number of results
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of retrieved relationships or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public List  getAttachmentLinks(String   userId,
                                                  String   startingGUID,
                                                  String   startingGUIDParameterName,
                                                  String   startingTypeName,
                                                  String   attachmentRelationshipTypeGUID,
                                                  String   attachmentRelationshipTypeName,
                                                  String   attachmentEntityGUID,
                                                  String   attachmentEntityTypeName,
                                                  int      startingFrom,
                                                  int      pageSize,
                                                  Date     effectiveTime,
                                                  String   methodName) throws InvalidParameterException,
                                                                              PropertyServerException,
                                                                              UserNotAuthorizedException
    {
        return this.getAttachmentLinks(userId,
                                       startingGUID,
                                       startingGUIDParameterName,
                                       startingTypeName,
                                       attachmentRelationshipTypeGUID,
                                       attachmentRelationshipTypeName,
                                       attachmentEntityGUID,
                                       attachmentEntityTypeName,
                                       0,
                                       false,
                                       startingFrom,
                                       pageSize,
                                       effectiveTime,
                                       methodName);
    }


    /**
     * Return the relationships to required elements attached to a specific entity.  Note that the entities are not checked.
     *
     * @param userId     calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of the parameter used to pass the guid
     * @param startingTypeName type name for anchor
     * @param attachmentRelationshipTypeGUID unique identifier of the relationship type connect to the attachment
     * @param attachmentRelationshipTypeName unique name of the relationship type connect to the attachment
     * @param attachmentEntityGUID unique identifier of the entity on the other end or null if unknown
     * @param attachmentEntityTypeName unique name of the attached entity's type
     * @param attachmentEntityEnd which relationship end should the attached entity be located? 0=either end; 1=end1; 2=end2
     * @param forDuplicateProcessing is this processing part of duplicate processing?
     * @param startingFrom start position for results
     * @param pageSize     maximum number of results
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of retrieved relationships or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public List  getAttachmentLinks(String   userId,
                                                  String   startingGUID,
                                                  String   startingGUIDParameterName,
                                                  String   startingTypeName,
                                                  String   attachmentRelationshipTypeGUID,
                                                  String   attachmentRelationshipTypeName,
                                                  String   attachmentEntityGUID,
                                                  String   attachmentEntityTypeName,
                                                  int      attachmentEntityEnd,
                                                  boolean  forDuplicateProcessing,
                                                  int      startingFrom,
                                                  int      pageSize,
                                                  Date     effectiveTime,
                                                  String   methodName) throws InvalidParameterException,
                                                                              PropertyServerException,
                                                                              UserNotAuthorizedException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(startingGUID, startingGUIDParameterName, methodName);

        int queryPageSize = invalidParameterHandler.validatePaging(startingFrom, pageSize, methodName);

        RepositoryRelationshipsIterator iterator = new RepositoryRelationshipsIterator(repositoryHandler,
                                                                                       userId,
                                                                                       startingGUID,
                                                                                       startingTypeName,
                                                                                       attachmentRelationshipTypeGUID,
                                                                                       attachmentRelationshipTypeName,
                                                                                       forDuplicateProcessing,
                                                                                       startingFrom,
                                                                                       queryPageSize,
                                                                                       effectiveTime,
                                                                                       methodName);


        List visibleRelationships = new ArrayList<>();

        while ((iterator.moreToReceive() && ((queryPageSize == 0) || (visibleRelationships.size() < queryPageSize))))
        {
            Relationship relationship = iterator.getNext();

            if (this.visibleToUserThroughRelationship(userId, relationship, methodName))
            {
                EntityProxy otherEnd;

                if (attachmentEntityEnd == 1)
                {
                    otherEnd = relationship.getEntityOneProxy();

                    if (startingGUID.equals(otherEnd.getGUID()))
                    {
                        otherEnd = null;
                    }
                }
                else if (attachmentEntityEnd == 2)
                {
                    otherEnd = relationship.getEntityTwoProxy();

                    if (startingGUID.equals(otherEnd.getGUID()))
                    {
                        otherEnd = null;
                    }
                }
                else
                {
                    otherEnd = repositoryHandler.getOtherEnd(startingGUID, startingTypeName, relationship, methodName);
                }

                if (otherEnd != null)
                {
                    /*
                     * Does the relationship point to to appropriate type of entity?
                     */
                    if (attachmentEntityTypeName != null)
                    {
                        if (otherEnd.getType() != null)
                        {
                            if (repositoryHelper.isTypeOf(serviceName, otherEnd.getType().getTypeDefName(), attachmentEntityTypeName))
                            {
                                if ((attachmentEntityGUID == null) || (attachmentEntityGUID.equals(otherEnd.getGUID())))
                                {
                                    visibleRelationships.add(relationship);
                                }
                            }
                        }
                    }
                    else
                    {
                        /*
                         * Any type of entity attachment will do
                         */
                        visibleRelationships.add(relationship);
                    }
                }
            }
        }

        if (! visibleRelationships.isEmpty())
        {
            return visibleRelationships;
        }

        return null;
    }


    /**
     * Return a list of relationships that match the supplied criteria.  The results can be returned over many pages.
     * Note: the visibility of the relationship and both entities is checked before returning a relationship to the caller.
     *
     * @param userId caller's userId
     * @param relationshipTypeName type of interest (null means any element type)
     * @param searchProperties Optional list of entity property conditions to match.
     * @param limitResultsByStatus By default, relationships in all statuses (other than DELETE) are returned.  However, it is possible
     *                             to specify a list of statuses (eg ACTIVE) to restrict the results.  Null means all status values.
     * @param asOfTime Requests a historical query of the entity.  Null means return the present values.
     * @param sequencingProperty String name of the property that is to be used to sequence the results.
     *                           Null means do not sequence on a property name (see SequencingOrder).
     * @param sequencingOrder Enum defining how the results should be ordered.
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param startingFrom paging start point
     * @param pageSize maximum results that can be returned
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return a list of elements matching the supplied criteria; null means no matching elements in the metadata store.
     * @throws InvalidParameterException one of the search parameters is invalid
     * @throws UserNotAuthorizedException the governance action service is not able to access the elements
     * @throws PropertyServerException there is a problem accessing the metadata store
     */
    public List findAttachmentLinks(String                userId,
                                                  String                relationshipTypeName,
                                                  SearchProperties      searchProperties,
                                                  List  limitResultsByStatus,
                                                  Date                  asOfTime,
                                                  String                sequencingProperty,
                                                  SequencingOrder       sequencingOrder,
                                                  boolean               forLineage,
                                                  boolean               forDuplicateProcessing,
                                                  int                   startingFrom,
                                                  int                   pageSize,
                                                  Date                  effectiveTime,
                                                  String                methodName) throws InvalidParameterException,
                                                                                           UserNotAuthorizedException,
                                                                                           PropertyServerException
    {
        invalidParameterHandler.validateUserId(userId, methodName);

        int queryPageSize = invalidParameterHandler.validatePaging(startingFrom, pageSize, methodName);

        String relationshipTypeGUID = null;

        if (relationshipTypeName != null)
        {
            relationshipTypeGUID = invalidParameterHandler.validateTypeName(relationshipTypeName,
                                                                            null,
                                                                            serviceName,
                                                                            methodName,
                                                                            repositoryHelper);
        }


        RepositoryFindRelationshipsIterator iterator = new RepositoryFindRelationshipsIterator(repositoryHandler,
                                                                                               userId,
                                                                                               relationshipTypeGUID,
                                                                                               null,
                                                                                               searchProperties,
                                                                                               limitResultsByStatus,
                                                                                               asOfTime,
                                                                                               sequencingProperty,
                                                                                               sequencingOrder,
                                                                                               startingFrom,
                                                                                               queryPageSize,
                                                                                               forDuplicateProcessing,
                                                                                               effectiveTime,
                                                                                               methodName);

        List results = new ArrayList<>();

        while ((iterator.moreToReceive()) && ((queryPageSize == 0) || (results.size() < queryPageSize)))
        {
            Relationship relationship = iterator.getNext();

            if (this.visibleToUserThroughRelationship(userId, relationship, methodName))
            {
                try
                {
                    final String entityOneParameterName = "relationship.getEntityOneProxy().getGUID()";
                    final String entityTwoParameterName = "relationship.getEntityTwoProxy().getGUID()";

                    this.validateAnchorEntity(userId,
                                              relationship.getEntityOneProxy().getGUID(),
                                              entityOneParameterName,
                                              OpenMetadataAPIMapper.OPEN_METADATA_ROOT_TYPE_NAME,
                                              false,
                                              forLineage,
                                              forDuplicateProcessing,
                                              supportedZones,
                                              effectiveTime,
                                              methodName);

                    this.validateAnchorEntity(userId,
                                              relationship.getEntityTwoProxy().getGUID(),
                                              entityTwoParameterName,
                                              OpenMetadataAPIMapper.OPEN_METADATA_ROOT_TYPE_NAME,
                                              false,
                                              forLineage,
                                              forDuplicateProcessing,
                                              supportedZones,
                                              effectiveTime,
                                              methodName);

                    results.add(relationship);
                }
                catch (Exception error)
                {
                    // ignore an element that is not visible to the caller
                }
            }
        }

        if (! results.isEmpty())
        {
            return results;
        }

        return null;
    }



    /**
     * Create a new entity in the repository assuming all parameters are ok.
     *
     * @param userId           userId of user making request.
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityTypeGUID unique identifier of the type of entity to create
     * @param entityTypeName unique name of the type of entity to create
     * @param uniqueParameterValue value of unique parameter (or null if no unique properties)
     * @param uniqueParameterName name of unique parameter (or null if no unique properties)
     * @param propertyBuilder builder pre-populated with the properties and classifications of the new entity
     * @param methodName calling method
     * @return unique identifier of new entity
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem adding the properties to the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public String createBeanInRepository(String                        userId,
                                         String                        externalSourceGUID,
                                         String                        externalSourceName,
                                         String                        entityTypeGUID,
                                         String                        entityTypeName,
                                         String                        uniqueParameterValue,
                                         String                        uniqueParameterName,
                                         OpenMetadataAPIGenericBuilder propertyBuilder,
                                         String                        methodName) throws InvalidParameterException,
                                                                                          PropertyServerException,
                                                                                          UserNotAuthorizedException
    {
        invalidParameterHandler.validateUserId(userId, methodName);

        validateNewEntityRequest(userId,
                                 entityTypeGUID,
                                 entityTypeName,
                                 propertyBuilder,
                                 methodName);

        if (uniqueParameterName != null)
        {
            validateUniqueProperty(null,
                                   entityTypeGUID,
                                   entityTypeName,
                                   uniqueParameterValue,
                                   uniqueParameterValue,
                                   this.getEffectiveTime(propertyBuilder.getInstanceProperties(methodName)),
                                   methodName);
        }

        return repositoryHandler.createEntity(userId,
                                              entityTypeGUID,
                                              entityTypeName,
                                              externalSourceGUID,
                                              externalSourceName,
                                              propertyBuilder.getInstanceProperties(methodName),
                                              propertyBuilder.getEntityClassifications(),
                                              propertyBuilder.getInstanceStatus(),
                                              methodName);
    }


    /**
     * Create a new entity in the repository based on the contents of an existing entity (the template). The supplied builder is pre-loaded with
     * properties that should override the properties from the the template.  This is the method to call from the specific handlers.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param templateGUID unique identifier of existing entity to use
     * @param templateGUIDParameterName name of parameter passing the templateGUID
     * @param entityTypeGUID unique identifier of the type for the entity
     * @param entityTypeName unique name of the type for the entity
     * @param uniqueParameterValue the value of a unique property (eg qualifiedName) in the new entity - this is used to create unique names in the
     *                             attachments.
     * @param uniqueParameterName name of the property where the unique value is stored.
     * @param propertyBuilder this property builder has the new properties supplied by the caller.  They will be augmented by the template
     *                        properties and classification.
     * @param methodName calling method
     * @return unique identifier of the the new bean
     * @throws InvalidParameterException one of the parameters is invalid
     * @throws PropertyServerException there is a problem in the repository services
     * @throws UserNotAuthorizedException the user is not authorized to access one of the elements.
     */
    public String createBeanFromTemplate(String                        userId,
                                         String                        externalSourceGUID,
                                         String                        externalSourceName,
                                         String                        templateGUID,
                                         String                        templateGUIDParameterName,
                                         String                        entityTypeGUID,
                                         String                        entityTypeName,
                                         String                        uniqueParameterValue,
                                         String                        uniqueParameterName,
                                         OpenMetadataAPIGenericBuilder propertyBuilder,
                                         String                        methodName) throws InvalidParameterException,
                                                                                          PropertyServerException,
                                                                                          UserNotAuthorizedException
    {
        TemplateProgress templateProgress = createBeanFromTemplate(userId,
                                                                   externalSourceGUID,
                                                                   externalSourceName,
                                                                   true,
                                                                   new TemplateProgress(),
                                                                   templateGUID,
                                                                   templateGUIDParameterName,
                                                                   entityTypeGUID,
                                                                   entityTypeName,
                                                                   uniqueParameterValue,
                                                                   uniqueParameterName,
                                                                   propertyBuilder,
                                                                   methodName);

        if (templateProgress != null)
        {
            /*
             * This relationship shows where the property values for the new bean came from.  It enables traceability.  Also, if the template is
             * updated, there is a possibility of making complementary changes to the entities that were derived from it.
             */
            repositoryHandler.createRelationship(localServerUserId,
                                                 OpenMetadataAPIMapper.SOURCED_FROM_RELATIONSHIP_TYPE_GUID,
                                                 externalSourceGUID,
                                                 externalSourceName,
                                                 templateProgress.newBeanGUID,
                                                 templateGUID,
                                                 null,
                                                 methodName);

            return templateProgress.newBeanGUID;
        }

        return null;
    }


    /**
     * Template process is used to pass around the status of the template replicating process.
     * The purpose of taking note of the parts of the template graph processed is to prevent
     * situations where elements are processed more than once - creating distorted or "infinite" results.
     */
    static class TemplateProgress
    {
        String              newBeanGUID          = null; /* GUID of last new entity created - ultimately this is returned to the original caller*/
        String              previousTemplateGUID = null; /* GUID of last template entity processed - prevents processing a relationship twice */
        Map coveredGUIDMap       = new HashMap<>(); /* Map of template GUIDs to new bean GUIDs that have been processed - prevents replicating the same entity twice */
        String              beanAnchorGUID       = null; /* value of the anchor to set into the new beans */
    }


    /**
     * Create a new entity in the repository based on the contents of an existing entity (the template). The supplied builder is pre-loaded with
     * properties that should override the properties from the the template.  This method is called iterative for each entity anchored to the
     * original template.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param firstIteration is this the first call to this method?
     * @param templateProgress current new bean, previous GUID and list of entities from the template that have been processed (so we only create new elements one-to-one when there are cyclic relationships)
     * @param templateGUID unique identifier of existing entity to use
     * @param templateGUIDParameterName name of parameter passing the templateGUID
     * @param entityTypeGUID unique identifier of the type for the entity
     * @param entityTypeName unique name of the type for the entity
     * @param uniqueParameterValue the value of a unique property (eg qualifiedName) in the new entity - this is used to create unique names in the
     *                             attachments.
     * @param uniqueParameterName name of the property where the unique value is stored.
     * @param propertyBuilder this property builder has the new properties supplied by the caller.  They will be augmented by the template
     *                        properties and classification.
     * @param methodName calling method
     *
     * @return current progress of the template replication
     * @throws InvalidParameterException one of the parameters is invalid
     * @throws PropertyServerException there is a problem in the repository services
     * @throws UserNotAuthorizedException the user is not authorized to access one of the elements.
     */
    private TemplateProgress createBeanFromTemplate(String                        userId,
                                                    String                        externalSourceGUID,
                                                    String                        externalSourceName,
                                                    boolean                       firstIteration,
                                                    TemplateProgress              templateProgress,
                                                    String                        templateGUID,
                                                    String                        templateGUIDParameterName,
                                                    String                        entityTypeGUID,
                                                    String                        entityTypeName,
                                                    String                        uniqueParameterValue,
                                                    String                        uniqueParameterName,
                                                    OpenMetadataAPIGenericBuilder propertyBuilder,
                                                    String                        methodName) throws InvalidParameterException,
                                                                                                     PropertyServerException,
                                                                                                     UserNotAuthorizedException
    {
        final String newEntityParameterName = "newEntityGUID";

        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(templateGUID, templateGUIDParameterName, methodName);

        boolean forLineage = true;
        boolean forDuplicateProcessing = false;
        Date    effectiveTime = null;

        /*
         * This call ensures the template exists and is the correct type. An exception will be thrown if there are any problems.
         */
        EntityDetail templateEntity = repositoryHandler.getEntityByGUID(userId,
                                                                        templateGUID,
                                                                        templateGUIDParameterName,
                                                                        entityTypeName,
                                                                        forLineage,
                                                                        forDuplicateProcessing,
                                                                        effectiveTime,
                                                                        methodName);

        if (templateEntity != null)
        {
            /*
             * Check that the template is visible to the calling user.  If the template is an anchor, its own entity is returned.
             */
            EntityDetail templateAnchorEntity = this.validateAnchorEntity(userId,
                                                                          templateGUID,
                                                                          entityTypeName,
                                                                          templateEntity,
                                                                          templateGUIDParameterName,
                                                                          false,
                                                                          forLineage,
                                                                          forDuplicateProcessing,
                                                                          supportedZones,
                                                                          effectiveTime,
                                                                          methodName);
            String templateAnchorGUID = null;
            if (templateAnchorEntity != null)
            {
                templateAnchorGUID = templateAnchorEntity.getGUID();
            }


            /*
             * Verify that the user is permitted to create a new bean.
             */
            validateNewEntityRequest(userId,
                                     entityTypeGUID,
                                     entityTypeName,
                                     propertyBuilder,
                                     methodName);

            /*
             * All OK to create the new bean, now work out the classifications.  Start with the classifications from the template (ignoring Anchors
             * and LatestChange) and then overlay the classifications set up in the builder and the appropriate anchor.
             */
            Map newClassificationMap = new HashMap<>();

            if (templateEntity.getClassifications() != null)
            {
                for (Classification templateClassification : templateEntity.getClassifications())
                {
                    if (templateClassification != null)
                    {
                        if ((! OpenMetadataAPIMapper.LATEST_CHANGE_CLASSIFICATION_TYPE_NAME.equals(templateClassification.getName())) &&
                            (! OpenMetadataAPIMapper.ANCHORS_CLASSIFICATION_TYPE_NAME.equals(templateClassification.getName())))
                        {
                            newClassificationMap.put(templateClassification.getName(), templateClassification);
                        }
                    }
                }
            }

            /*
             * If the template has another anchor set up then this same anchor needs to be established for the new bean.
             */
            if ((firstIteration) && (templateAnchorGUID != null) && (! templateGUID.equals(templateAnchorGUID)))
            {
                /*
                 * Need to use the same anchor as the template.  This occurs the first time through the iteration if the initial
                 * template object has an anchor.
                 */
                propertyBuilder.setAnchors(userId, templateAnchorGUID, methodName);
            }
            else if (! firstIteration)
            {
                /*
                 * A bean anchor has been set up on a previous iteration.
                 */
                propertyBuilder.setAnchors(userId, templateProgress.beanAnchorGUID, methodName);
            }


            List builderClassifications = propertyBuilder.getEntityClassifications();
            if (builderClassifications != null)
            {
                for (Classification builderClassification : builderClassifications)
                {
                    if (builderClassification != null)
                    {
                        newClassificationMap.put(builderClassification.getName(), builderClassification);
                    }
                }
            }

            List newClassifications = null;

            if (! newClassificationMap.isEmpty())
            {
                newClassifications = new ArrayList<>(newClassificationMap.values());
            }

            /*
             * Ready to create the new bean
             */
            String newEntityGUID = repositoryHandler.createEntity(userId,
                                                                  entityTypeGUID,
                                                                  entityTypeName,
                                                                  externalSourceGUID,
                                                                  externalSourceName,
                                                                  propertyBuilder.getInstanceProperties(methodName),
                                                                  newClassifications,
                                                                  propertyBuilder.getInstanceStatus(),
                                                                  methodName);

            /*
             * This is the first time through the iteration and so we need to capture the top level bean's guid to act as the anchor for all other
             * beans that are created as a result of this templated creation.
             */
            if (firstIteration)
            {
                templateProgress.beanAnchorGUID = newEntityGUID;
            }

            /*
             * The real value of templates is that they cover the creation of a cluster of metadata instances.  The last step is to explore
             * the graph of linked elements and replicate the structure for the new bean.
             */
            templateProgress = this.addAttachmentsFromTemplate(userId,
                                                               externalSourceGUID,
                                                               externalSourceName,
                                                               templateProgress,
                                                               newEntityGUID,
                                                               newEntityParameterName,
                                                               templateGUID,
                                                               templateAnchorGUID,
                                                               entityTypeName,
                                                               uniqueParameterValue,
                                                               forLineage,
                                                               forDuplicateProcessing,
                                                               effectiveTime,
                                                               methodName);


            templateProgress.newBeanGUID = newEntityGUID;
        }
        else
        {
            templateProgress.newBeanGUID = null;
        }

        return templateProgress;
    }


    /**
     * Create equivalent relationships to attachments for a new element that has been created from a template.
     * The element and template have already been checked to be visible to the calling user.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source - null for local
     * @param templateProgress current new bean, previous GUID and list of entities from the template that have been processed (so we only create new elements one-to-one when there are cyclic relationships)
     * @param startingGUID unique identifier of the newly created element
     * @param startingGUIDParameterName parameter providing the startingGUID value
     * @param templateGUID unique identifier of the template element
     * @param templateAnchorGUID unique identifier of the template's anchor
     * @param expectedTypeName type name of the new bean
     * @param qualifiedName unique name for this new bean - must not be null
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return current progress of the template replication
     * @throws InvalidParameterException the guids or something related are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException problem accessing the repositories
     */
    private TemplateProgress addAttachmentsFromTemplate(String           userId,
                                                        String           externalSourceGUID,
                                                        String           externalSourceName,
                                                        TemplateProgress templateProgress,
                                                        String           startingGUID,
                                                        String           startingGUIDParameterName,
                                                        String           templateGUID,
                                                        String           templateAnchorGUID,
                                                        String           expectedTypeName,
                                                        String           qualifiedName,
                                                        boolean          forLineage,
                                                        boolean          forDuplicateProcessing,
                                                        Date             effectiveTime,
                                                        String           methodName) throws InvalidParameterException,
                                                                                            PropertyServerException,
                                                                                            UserNotAuthorizedException
    {
        final String nextTemplateEntityGUIDParameterName = "nextTemplateEntity.getGUID";
        final String nextQualifiedNameParameterName = "nextQualifiedName";
        final String nextBeanEntityGUIDParameterName = "nextBeanEntityGUID";

        Map qualifiedNameUsageCount = new HashMap<>();
        boolean              relationshipOneToTwo;

        /*
         * Save the previousTemplateGUID for this round.
         */
        String previousTemplateGUID = templateProgress.previousTemplateGUID;

        /*
         * Record that the templateGUID has already been processed. This is passed to subsequent iterative calls to this method.
         */
        templateProgress.previousTemplateGUID = templateGUID;
        templateProgress.coveredGUIDMap.put(templateGUID, startingGUID);

        /*
         * Begin by retrieving all of the relationships attached to the template.
         */
        RepositoryRelationshipsIterator iterator = new RepositoryRelationshipsIterator(repositoryHandler,
                                                                                       userId,
                                                                                       templateGUID,
                                                                                       expectedTypeName,
                                                                                       null,
                                                                                       null,
                                                                                       forDuplicateProcessing,
                                                                                       0,
                                                                                       invalidParameterHandler.getMaxPagingSize(),
                                                                                       effectiveTime,
                                                                                       methodName);

        /*
         * For each relationship, it is necessary to determine if the attached entity is anchored to the template or not.  If it is
         * anchored, the attached entity needs to be copied and the copy attached to the new bean.  If it is not anchored then it is sufficient to
         * create a new relationship between the new bean and the attached entity.
         */
        while (iterator.moreToReceive())
        {
            Relationship relationship = iterator.getNext();
            EntityProxy  entityProxy  = relationship.getEntityOneProxy();

            if (templateGUID.equals(entityProxy.getGUID()))
            {
                entityProxy = relationship.getEntityTwoProxy();
                relationshipOneToTwo = true;
            }
            else
            {
                relationshipOneToTwo = false;
            }

            if ((entityProxy != null) && (entityProxy.getType() != null) && (! entityProxy.getGUID().equals(previousTemplateGUID)))
            {
                EntityDetail nextTemplateEntity = repositoryHandler.getEntityByGUID(userId,
                                                                                    entityProxy.getGUID(),
                                                                                    nextTemplateEntityGUIDParameterName,
                                                                                    OpenMetadataAPIMapper.OPEN_METADATA_ROOT_TYPE_NAME,
                                                                                    forLineage,
                                                                                    forDuplicateProcessing,
                                                                                    effectiveTime,
                                                                                    methodName);

                if ((nextTemplateEntity != null) && (nextTemplateEntity.getType() != null))
                {
                    String nextTemplateEntityTypeGUID = nextTemplateEntity.getType().getTypeDefGUID();
                    String nextTemplateEntityTypeName = nextTemplateEntity.getType().getTypeDefName();

                    EntityDetail nextTemplateEntityAnchor = this.validateAnchorEntity(userId,
                                                                                      nextTemplateEntity.getGUID(),
                                                                                      null,
                                                                                      nextTemplateEntity,
                                                                                      nextTemplateEntityGUIDParameterName,
                                                                                      false,
                                                                                      forLineage,
                                                                                      forDuplicateProcessing,
                                                                                      supportedZones,
                                                                                      effectiveTime,
                                                                                      methodName);

                    String nextTemplateAnchorGUID = null;
                    if (nextTemplateEntityAnchor != null)
                    {
                        nextTemplateAnchorGUID = nextTemplateEntityAnchor.getGUID();
                    }
                    String nextBeanEntityGUID;

                    if (templateProgress.coveredGUIDMap.keySet().contains(nextTemplateEntity.getGUID()))
                    {
                        /*
                         * The template entity has already been replicated and so we just need to create the
                         * relationship from the equivalent new bean to the start bean.
                         */
                        nextBeanEntityGUID = templateProgress.coveredGUIDMap.get(nextTemplateEntity.getGUID());
                    }
                    else if ((nextTemplateAnchorGUID == null) || (! nextTemplateAnchorGUID.equals(templateAnchorGUID)))
                    {
                        /*
                         * The linked entity is either not got an anchorGUID or has a different anchorGUID.
                         * However we still need to create the relationship between the start bean and the linked entity.
                         */
                        nextBeanEntityGUID = nextTemplateEntity.getGUID();
                    }
                    else
                    {
                        /*
                         * This linked entity has the same anchorGUID so it need to be copied.
                         */
                        OpenMetadataAPIGenericBuilder builder;
                        String nextQualifiedName = null;
                        if (repositoryHelper.isTypeOf(serviceName, nextTemplateEntityTypeName, OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME))
                        {

                            nextQualifiedName = qualifiedName + "::" + nextTemplateEntityTypeName;
                            int    nextQualifiedNameCount = 0;

                            if (qualifiedNameUsageCount.get(nextQualifiedName) != null)
                            {
                                nextQualifiedNameCount = qualifiedNameUsageCount.get(nextQualifiedName);
                            }
                            qualifiedNameUsageCount.put(nextQualifiedName, nextQualifiedNameCount + 1);

                            if (nextQualifiedNameCount > 0)
                            {
                                nextQualifiedName = nextQualifiedName + "_" + nextQualifiedNameCount;
                            }

                            builder = new ReferenceableBuilder(nextQualifiedName,
                                                               null,
                                                               nextTemplateEntityTypeGUID,
                                                               nextTemplateEntityTypeName,
                                                               null,
                                                               nextTemplateEntity.getStatus(),
                                                               repositoryHelper,
                                                               serviceName,
                                                               serverName);
                        }
                        else
                        {
                            builder = new OpenMetadataAPIGenericBuilder(nextTemplateEntityTypeGUID,
                                                                        nextTemplateEntityTypeName,
                                                                        null,
                                                                        nextTemplateEntity.getStatus(),
                                                                        null,
                                                                        repositoryHelper,
                                                                        serviceName,
                                                                        serverName);
                        }

                        builder.setTemplateProperties(nextTemplateEntity.getProperties());
                        builder.setTemplateClassifications(userId,
                                                           externalSourceGUID,
                                                           externalSourceName,
                                                           nextTemplateEntity.getClassifications(),
                                                           methodName);

                        templateProgress = this.createBeanFromTemplate(userId,
                                                                         externalSourceGUID,
                                                                         externalSourceName,
                                                                         false,
                                                                         templateProgress,
                                                                         nextTemplateEntity.getGUID(),
                                                                         nextTemplateEntityGUIDParameterName,
                                                                         nextTemplateEntity.getType().getTypeDefGUID(),
                                                                         nextTemplateEntityTypeName,
                                                                         nextQualifiedName,
                                                                         nextQualifiedNameParameterName,
                                                                         builder,
                                                                         methodName);

                        nextBeanEntityGUID = templateProgress.newBeanGUID;
                    }

                    /*
                     * Link the previously created bean to the next bean - making sure end one and end two are correctly set up.
                     */
                    if (relationshipOneToTwo)
                    {
                        this.linkElementToElement(userId,
                                                  externalSourceGUID,
                                                  externalSourceName,
                                                  startingGUID,
                                                  startingGUIDParameterName,
                                                  expectedTypeName,
                                                  nextBeanEntityGUID,
                                                  nextBeanEntityGUIDParameterName,
                                                  nextTemplateEntityTypeName,
                                                  forLineage,
                                                  forDuplicateProcessing,
                                                  supportedZones,
                                                  relationship.getType().getTypeDefGUID(),
                                                  relationship.getType().getTypeDefName(),
                                                  relationship.getProperties(),
                                                  methodName);
                    }
                    else
                    {
                        this.linkElementToElement(userId,
                                                  externalSourceGUID,
                                                  externalSourceName,
                                                  nextBeanEntityGUID,
                                                  nextBeanEntityGUIDParameterName,
                                                  nextTemplateEntityTypeName,
                                                  startingGUID,
                                                  startingGUIDParameterName,
                                                  expectedTypeName,
                                                  forLineage,
                                                  forDuplicateProcessing,
                                                  supportedZones,
                                                  relationship.getType().getTypeDefGUID(),
                                                  relationship.getType().getTypeDefName(),
                                                  relationship.getProperties(),
                                                  methodName);
                    }
                }
            }
        }

        return templateProgress;
    }



    /**
     * Update one or more properties in the requested entity.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityGUID unique identifier of object to update
     * @param entityGUIDParameterName name of parameter supplying the GUID
     * @param entityTypeGUID unique identifier of the entity's type
     * @param entityTypeName unique name of the entity's type
     * @param propertyName name of bean property to update
     * @param propertyValue new value for bean property
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem updating the properties tin the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public void updateBeanPropertyInRepository(String  userId,
                                               String  externalSourceGUID,
                                               String  externalSourceName,
                                               String  entityGUID,
                                               String  entityGUIDParameterName,
                                               String  entityTypeGUID,
                                               String  entityTypeName,
                                               String  propertyName,
                                               String  propertyValue,
                                               boolean forLineage,
                                               boolean forDuplicateProcessing,
                                               Date    effectiveTime,
                                               String  methodName) throws InvalidParameterException,
                                                                          PropertyServerException,
                                                                          UserNotAuthorizedException
    {
        final String propertyParameterName = "propertyValue";
        final String propertyNameParameterName = "propertyName";

        invalidParameterHandler.validateName(propertyName, propertyNameParameterName, methodName);
        invalidParameterHandler.validateObject(propertyValue, propertyParameterName, methodName);

        InstanceProperties properties = repositoryHelper.addStringPropertyToInstance(serviceName, null, propertyName, propertyValue, methodName);

        this.updateBeanInRepository(userId,
                                    externalSourceGUID,
                                    externalSourceName,
                                    entityGUID,
                                    entityGUIDParameterName,
                                    entityTypeGUID,
                                    entityTypeName,
                                    forLineage,
                                    forDuplicateProcessing,
                                    supportedZones,
                                    properties,
                                    true,
                                    effectiveTime,
                                    methodName);
    }


    /**
     * Update one or more properties in the requested entity.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityGUID unique identifier of object to update
     * @param entityGUIDParameterName name of parameter supplying the GUID
     * @param entityTypeGUID unique identifier of the entity's type
     * @param entityTypeName unique name of the entity's type
     * @param properties object containing the properties for the repository instances based on the properties of the bean
     * @param isMergeUpdate should the supplied properties be merged with existing properties (true) only replacing the properties with
     *                      matching names, or should the entire properties of the instance be replaced?
     * @param methodName calling method
     *
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem updating the properties in the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public void updateBeanInRepository(String             userId,
                                       String             externalSourceGUID,
                                       String             externalSourceName,
                                       String             entityGUID,
                                       String             entityGUIDParameterName,
                                       String             entityTypeGUID,
                                       String             entityTypeName,
                                       InstanceProperties properties,
                                       boolean            isMergeUpdate,
                                       String             methodName) throws InvalidParameterException,
                                                                             PropertyServerException,
                                                                             UserNotAuthorizedException
    {
        this.updateBeanInRepository(userId,
                                    externalSourceGUID,
                                    externalSourceName,
                                    entityGUID,
                                    entityGUIDParameterName,
                                    entityTypeGUID,
                                    entityTypeName,
                                    false,
                                    false,
                                    supportedZones,
                                    properties,
                                    isMergeUpdate,
                                    new Date(),
                                    methodName);
    }



    /**
     * Update one or more updateProperties in the requested entity.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityGUID unique identifier of object to update
     * @param entityGUIDParameterName name of parameter supplying the GUID
     * @param entityTypeGUID unique identifier of the entity's type
     * @param entityTypeName unique name of the entity's type
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param updateProperties object containing the properties
     * @param isMergeUpdate should the supplied properties be merged with existing properties (true) by replacing the just the properties with
     *                      matching names, or should the entire properties of the instance be replaced?
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem adding the new properties to the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public void updateBeanInRepository(String             userId,
                                       String             externalSourceGUID,
                                       String             externalSourceName,
                                       String             entityGUID,
                                       String             entityGUIDParameterName,
                                       String             entityTypeGUID,
                                       String             entityTypeName,
                                       boolean            forLineage,
                                       boolean            forDuplicateProcessing,
                                       InstanceProperties updateProperties,
                                       boolean            isMergeUpdate,
                                       Date               effectiveTime,
                                       String             methodName) throws InvalidParameterException,
                                                                             PropertyServerException,
                                                                             UserNotAuthorizedException
    {
        this.updateBeanInRepository(userId,
                                    externalSourceGUID,
                                    externalSourceName,
                                    entityGUID,
                                    entityGUIDParameterName,
                                    entityTypeGUID,
                                    entityTypeName,
                                    forLineage,
                                    forDuplicateProcessing,
                                    supportedZones,
                                    updateProperties,
                                    isMergeUpdate,
                                    effectiveTime,
                                    methodName);
    }


    /**
     * Update one or more updateProperties in the requested entity.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityGUID unique identifier of object to update
     * @param entityGUIDParameterName name of parameter supplying the GUID
     * @param entityTypeGUID unique identifier of the entity's type
     * @param entityTypeName unique name of the entity's type
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param serviceSupportedZones supported zones for calling service
     * @param updateProperties object containing the properties
     * @param isMergeUpdate should the supplied properties be merged with existing properties (true) by replacing the just the properties with
     *                      matching names, or should the entire properties of the instance be replaced?
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem adding the new properties to the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public void updateBeanInRepository(String             userId,
                                       String             externalSourceGUID,
                                       String             externalSourceName,
                                       String             entityGUID,
                                       String             entityGUIDParameterName,
                                       String             entityTypeGUID,
                                       String             entityTypeName,
                                       boolean            forLineage,
                                       boolean            forDuplicateProcessing,
                                       List       serviceSupportedZones,
                                       InstanceProperties updateProperties,
                                       boolean            isMergeUpdate,
                                       Date               effectiveTime,
                                       String             methodName) throws InvalidParameterException,
                                                                             PropertyServerException,
                                                                             UserNotAuthorizedException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(entityGUID, entityGUIDParameterName, methodName);

        /*
         * This returns the entity for the connect to element and validates it is of the correct type.
         */
        EntityDetail  originalEntity = repositoryHandler.getEntityByGUID(userId,
                                                                         entityGUID,
                                                                         entityGUIDParameterName,
                                                                         entityTypeName,
                                                                         forLineage,
                                                                         forDuplicateProcessing,
                                                                         effectiveTime,
                                                                         methodName);

        if ((originalEntity != null) && (originalEntity.getType() != null))
        {
            EntityDetail anchorEntity = this.validateAnchorEntity(userId,
                                                                  entityGUID,
                                                                  entityTypeName,
                                                                  originalEntity,
                                                                  entityGUIDParameterName,
                                                                  true,
                                                                  forLineage,
                                                                  forDuplicateProcessing,
                                                                  serviceSupportedZones,
                                                                  effectiveTime,
                                                                  methodName);

            /*
             * Sort out the properties
             */
            InstanceProperties newProperties = setUpNewProperties(isMergeUpdate,
                                                                  updateProperties,
                                                                  originalEntity.getProperties());

            /*
             * Validate that any changes to the unique properties do not clash with other entities.
             */
            validateUniqueProperties(entityGUID,
                                     entityTypeGUID,
                                     entityTypeName,
                                     newProperties,
                                     effectiveTime,
                                     methodName);

            /*
             * There is an extra security check if the update is for an asset.
             */
            if (repositoryHelper.isTypeOf(serviceName, originalEntity.getType().getTypeDefName(), OpenMetadataAPIMapper.ASSET_TYPE_NAME))
            {
                this.validateUserForAssetUpdate(userId,
                                                originalEntity,
                                                newProperties,
                                                originalEntity.getStatus(),
                                                methodName);
            }

            repositoryHandler.updateEntityProperties(userId,
                                                     externalSourceGUID,
                                                     externalSourceName,
                                                     entityGUID,
                                                     originalEntity,
                                                     entityTypeGUID,
                                                     entityTypeName,
                                                     newProperties,
                                                     methodName);

            /*
             * Update is OK so record that it occurred in the LatestChange classification if there is an anchor entity.
             */
            final String actionDescriptionTemplate = "Updating properties in %s %s";
            String actionDescription = String.format(actionDescriptionTemplate, entityTypeName, entityGUID);

            if (anchorEntity != null)
            {
                this.addLatestChangeToAnchor(anchorEntity,
                                             OpenMetadataAPIMapper.ATTACHMENT_PROPERTY_LATEST_CHANGE_TARGET_ORDINAL,
                                             OpenMetadataAPIMapper.UPDATED_LATEST_CHANGE_ACTION_ORDINAL,
                                             null,
                                             entityGUID,
                                             entityTypeName,
                                             null,
                                             userId,
                                             actionDescription,
                                             methodName);
            }
            else if (repositoryHelper.isTypeOf(serviceName, entityTypeName, OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME))
            {
                this.addLatestChangeToAnchor(originalEntity,
                                             OpenMetadataAPIMapper.ENTITY_PROPERTY_LATEST_CHANGE_TARGET_ORDINAL,
                                             OpenMetadataAPIMapper.UPDATED_LATEST_CHANGE_ACTION_ORDINAL,
                                             null,
                                             null,
                                             null,
                                             null,
                                             userId,
                                             actionDescription,
                                             methodName);
            }
        }
        else
        {
            invalidParameterHandler.throwUnknownElement(userId,
                                                        entityGUID,
                                                        entityTypeName,
                                                        serviceName,
                                                        serverName,
                                                        methodName);
        }
    }


    /**
     * Update the instance status in the requested entity.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityGUID unique identifier of object to update
     * @param entityGUIDParameterName name of parameter supplying the GUID
     * @param entityTypeGUID unique identifier of the entity's type
     * @param entityTypeName unique name of the entity's type
     * @param newStatus new status value
     * @param newStatusParameterName parameter providing the new status value
     * @param methodName calling method
     *
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem adding the new properties to the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public void updateBeanStatusInRepository(String             userId,
                                             String             externalSourceGUID,
                                             String             externalSourceName,
                                             String             entityGUID,
                                             String             entityGUIDParameterName,
                                             String             entityTypeGUID,
                                             String             entityTypeName,
                                             InstanceStatus     newStatus,
                                             String             newStatusParameterName,
                                             String             methodName) throws InvalidParameterException,
                                                                                   PropertyServerException,
                                                                                   UserNotAuthorizedException
    {
        updateBeanStatusInRepository(userId,
                                     externalSourceGUID,
                                     externalSourceName,
                                     entityGUID,
                                     entityGUIDParameterName,
                                     entityTypeGUID,
                                     entityTypeName,
                                     false,
                                     false,
                                     supportedZones,
                                     newStatus,
                                     newStatusParameterName,
                                     new Date(),
                                     methodName);
    }




    /**
     * Update the instance status in the requested entity.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityGUID unique identifier of object to update
     * @param entityGUIDParameterName name of parameter supplying the GUID
     * @param entityTypeGUID unique identifier of the entity's type
     * @param entityTypeName unique name of the entity's type
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param newStatus new status value
     * @param newStatusParameterName parameter providing the new status value
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem adding the new properties to the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public void updateBeanStatusInRepository(String         userId,
                                             String         externalSourceGUID,
                                             String         externalSourceName,
                                             String         entityGUID,
                                             String         entityGUIDParameterName,
                                             String         entityTypeGUID,
                                             String         entityTypeName,
                                             boolean        forLineage,
                                             boolean        forDuplicateProcessing,
                                             InstanceStatus newStatus,
                                             String         newStatusParameterName,
                                             Date           effectiveTime,
                                             String         methodName) throws InvalidParameterException,
                                                                               PropertyServerException,
                                                                               UserNotAuthorizedException
    {
        updateBeanStatusInRepository(userId,
                                     externalSourceGUID,
                                     externalSourceName,
                                     entityGUID,
                                     entityGUIDParameterName,
                                     entityTypeGUID,
                                     entityTypeName,
                                     forLineage,
                                     forDuplicateProcessing,
                                     supportedZones,
                                     newStatus,
                                     newStatusParameterName,
                                     effectiveTime,
                                     methodName);
    }


    /**
     * Update the instance status in the requested entity.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityGUID unique identifier of object to update
     * @param entityGUIDParameterName name of parameter supplying the GUID
     * @param entityTypeGUID unique identifier of the entity's type
     * @param entityTypeName unique name of the entity's type
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param serviceSupportedZones supported zones for calling service
     * @param newStatus new status value
     * @param newStatusParameterName parameter providing the new status value
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem adding the new properties to the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public void updateBeanStatusInRepository(String             userId,
                                             String             externalSourceGUID,
                                             String             externalSourceName,
                                             String             entityGUID,
                                             String             entityGUIDParameterName,
                                             String             entityTypeGUID,
                                             String             entityTypeName,
                                             boolean            forLineage,
                                             boolean            forDuplicateProcessing,
                                             List       serviceSupportedZones,
                                             InstanceStatus     newStatus,
                                             String             newStatusParameterName,
                                             Date               effectiveTime,
                                             String             methodName) throws InvalidParameterException,
                                                                                   PropertyServerException,
                                                                                   UserNotAuthorizedException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(entityGUID, entityGUIDParameterName, methodName);
        invalidParameterHandler.validateObject(newStatus, newStatusParameterName, methodName);

        /*
         * This returns the entity for the connect to element and validates it is of the correct type.
         */
        EntityDetail  originalEntity = repositoryHandler.getEntityByGUID(userId,
                                                                         entityGUID,
                                                                         entityGUIDParameterName,
                                                                         entityTypeName,
                                                                         forLineage,
                                                                         forDuplicateProcessing,
                                                                         effectiveTime,
                                                                         methodName);

        if ((originalEntity != null) && (originalEntity.getType() != null))
        {
            EntityDetail anchorEntity = this.validateAnchorEntity(userId,
                                                                  entityGUID,
                                                                  entityTypeName,
                                                                  originalEntity,
                                                                  entityGUIDParameterName,
                                                                  true,
                                                                  forLineage,
                                                                  forDuplicateProcessing,
                                                                  serviceSupportedZones,
                                                                  effectiveTime,
                                                                  methodName);

            /*
             * There is an extra security check if the update is for an asset.
             */
            if (repositoryHelper.isTypeOf(serviceName, originalEntity.getType().getTypeDefName(), OpenMetadataAPIMapper.ASSET_TYPE_NAME))
            {
                this.validateUserForAssetUpdate(userId,
                                                originalEntity,
                                                originalEntity.getProperties(),
                                                newStatus,
                                                methodName);
            }

            repositoryHandler.updateEntityStatus(userId,
                                                 externalSourceGUID,
                                                 externalSourceName,
                                                 originalEntity.getGUID(),
                                                 originalEntity,
                                                 entityTypeGUID,
                                                 entityTypeName,
                                                 newStatus,
                                                 forLineage,
                                                 forDuplicateProcessing,
                                                 effectiveTime,
                                                 methodName);

            /*
             * Update is OK so record that it occurred in the LatestChange classification if there is an anchor entity.
             */
            final String actionDescriptionTemplate = "Updating instance status in %s %s";
            String actionDescription = String.format(actionDescriptionTemplate, entityTypeName, entityGUID);

            if (anchorEntity != null)
            {
                this.addLatestChangeToAnchor(anchorEntity,
                                             OpenMetadataAPIMapper.ATTACHMENT_STATUS_LATEST_CHANGE_TARGET_ORDINAL,
                                             OpenMetadataAPIMapper.UPDATED_LATEST_CHANGE_ACTION_ORDINAL,
                                             null,
                                             entityGUID,
                                             entityTypeName,
                                             null,
                                             userId,
                                             actionDescription,
                                             methodName);
            }
            else if (repositoryHelper.isTypeOf(serviceName, entityTypeName, OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME))
            {
                this.addLatestChangeToAnchor(originalEntity,
                                             OpenMetadataAPIMapper.ENTITY_STATUS_LATEST_CHANGE_TARGET_ORDINAL,
                                             OpenMetadataAPIMapper.UPDATED_LATEST_CHANGE_ACTION_ORDINAL,
                                             null,
                                             null,
                                             null,
                                             null,
                                             userId,
                                             actionDescription,
                                             methodName);
            }
        }
        else
        {
            invalidParameterHandler.throwUnknownElement(userId,
                                                        entityGUID,
                                                        entityTypeName,
                                                        serviceName,
                                                        serverName,
                                                        methodName);
        }
    }


    /**
     * Classify as an Memento any entity if it is anchored to the anchor entity.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param anchorEntity entity anchor to match against
     * @param potentialAnchoredEntity entity to validate
     * @param classificationOriginGUID original entity that the Memento classification  was attached to
     * @param classificationProperties properties for the classification
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     * @throws InvalidParameterException problem with the parameters
     * @throws PropertyServerException problem in the repository services
     * @throws UserNotAuthorizedException calling user is not authorize to issue this request
     */
    private void archiveAnchoredEntity(String             userId,
                                       String             externalSourceGUID,
                                       String             externalSourceName,
                                       EntityDetail       anchorEntity,
                                       EntityProxy        potentialAnchoredEntity,
                                       String             classificationOriginGUID,
                                       InstanceProperties classificationProperties,
                                       boolean            forLineage,
                                       boolean            forDuplicateProcessing,
                                       Date               effectiveTime,
                                       String             methodName) throws InvalidParameterException,
                                                                             PropertyServerException,
                                                                             UserNotAuthorizedException
    {
        /*
         * Only need to progress if anchor entity exists.
         */
        if (anchorEntity != null)
        {
            final String guidParameterName = "potentialAnchoredEntity";

            if ((potentialAnchoredEntity != null) && (potentialAnchoredEntity.getType() != null))
            {
                EntityDetail entity = repositoryHandler.getEntityByGUID(userId,
                                                                        potentialAnchoredEntity.getGUID(),
                                                                        guidParameterName,
                                                                        potentialAnchoredEntity.getType().getTypeDefName(),
                                                                        forLineage,
                                                                        forDuplicateProcessing,
                                                                        effectiveTime,
                                                                        methodName);

                String anchorGUID = this.getAnchorGUIDFromAnchorsClassification(entity, methodName);

                if ((anchorGUID != null) && (anchorGUID.equals(anchorEntity.getGUID())))
                {
                    this.archiveBeanInRepository(userId,
                                                 externalSourceGUID,
                                                 externalSourceName,
                                                 entity.getGUID(),
                                                 guidParameterName,
                                                 potentialAnchoredEntity.getType().getTypeDefName(),
                                                 ClassificationOrigin.PROPAGATED,
                                                 classificationOriginGUID,
                                                 classificationProperties,
                                                 anchorEntity,
                                                 forLineage,
                                                 forDuplicateProcessing,
                                                 effectiveTime,
                                                 methodName);
                }
            }
        }
    }


    /**
     * Classify an entity in the repository to show that its asset/artifact counterpart in the real world has either
     * been deleted or archived.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityGUID unique identifier of object to update
     * @param entityGUIDParameterName name of parameter supplying the GUID
     * @param entityTypeName unique name of the entity's type
     * @param classificationProperties properties for the classification
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem removing the properties from the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public void archiveBeanInRepository(String             userId,
                                        String             externalSourceGUID,
                                        String             externalSourceName,
                                        String             entityGUID,
                                        String             entityGUIDParameterName,
                                        String             entityTypeName,
                                        InstanceProperties classificationProperties,
                                        boolean            forLineage,
                                        boolean            forDuplicateProcessing,
                                        Date               effectiveTime,
                                        String             methodName) throws InvalidParameterException,
                                                                               PropertyServerException,
                                                                               UserNotAuthorizedException
    {
        this.archiveBeanInRepository(userId,
                                     externalSourceGUID,
                                     externalSourceName,
                                     entityGUID,
                                     entityGUIDParameterName,
                                     entityTypeName,
                                     classificationProperties,
                                     forLineage,
                                     forDuplicateProcessing,
                                     supportedZones,
                                     effectiveTime,
                                     methodName);
    }


    /**
     * Classify an entity in the repository to show that its asset/artifact counterpart in the real world has either
     * been deleted or archived. Note, this method is designed to work only on anchor entities or entities with no anchor.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityGUID unique identifier of object to update
     * @param entityGUIDParameterName name of parameter supplying the GUID
     * @param entityTypeName unique name of the entity's type
     * @param classificationProperties properties for the classification
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param serviceSupportedZones supported zones for calling service
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem removing the properties from the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public void archiveBeanInRepository(String             userId,
                                        String             externalSourceGUID,
                                        String             externalSourceName,
                                        String             entityGUID,
                                        String             entityGUIDParameterName,
                                        String             entityTypeName,
                                        InstanceProperties classificationProperties,
                                        boolean            forLineage,
                                        boolean            forDuplicateProcessing,
                                        List       serviceSupportedZones,
                                        Date               effectiveTime,
                                        String             methodName) throws InvalidParameterException,
                                                                              PropertyServerException,
                                                                              UserNotAuthorizedException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(entityGUID, entityGUIDParameterName, methodName);

        EntityDetail anchorEntity = this.validateAnchorEntity(userId,
                                                              entityGUID,
                                                              entityGUIDParameterName,
                                                              entityTypeName,
                                                              true,
                                                              forLineage,
                                                              forDuplicateProcessing,
                                                              serviceSupportedZones,
                                                              effectiveTime,
                                                              methodName);

        /*
         * At this point, archiving is only supported on the anchor entity.  This needs to change (eg to be able to archive schema elements)
         * by adding logic very similar to the templating logic that makes sure the archive processing travels down the hiierarchy and does not
         * cover the whole anchored entity.
         */
        invalidParameterHandler.validateAnchorGUID(entityGUID,
                                                   entityGUIDParameterName,
                                                   anchorEntity,
                                                   entityGUID,
                                                   entityTypeName,
                                                   methodName);

        this.archiveBeanInRepository(userId,
                                     externalSourceGUID,
                                     externalSourceName,
                                     entityGUID,
                                     entityGUIDParameterName,
                                     entityTypeName,
                                     ClassificationOrigin.ASSIGNED,
                                     entityGUID,
                                     classificationProperties,
                                     anchorEntity,
                                     forLineage,
                                     forDuplicateProcessing,
                                     effectiveTime,
                                     methodName);

        /*
         * Update the the LatestChange in the archived entity.
         */
        if (repositoryHelper.isTypeOf(serviceName, entityTypeName, OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME))
        {
            final String actionDescriptionTemplate = "Classifying as Memento %s %s";

            String actionDescription  = String.format(actionDescriptionTemplate, entityTypeName, entityGUID);
            int    latestChangeTarget = OpenMetadataAPIMapper.ENTITY_CLASSIFICATION_LATEST_CHANGE_TARGET_ORDINAL;

            this.addLatestChangeToAnchor(anchorEntity,
                                         latestChangeTarget,
                                         OpenMetadataAPIMapper.CREATED_LATEST_CHANGE_ACTION_ORDINAL,
                                         OpenMetadataAPIMapper.MEMENTO_CLASSIFICATION_TYPE_NAME,
                                         entityGUID,
                                         entityTypeName,
                                         null,
                                         userId,
                                         actionDescription,
                                         methodName);
        }
    }


    /**
     * Classify an entity in the repository to show that its asset/artifact counterpart in the real world has either
     * been deleted or archived.  Note that this classification is propagated to all elements with the same
     * AnchorGUID.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityGUID unique identifier of object to update
     * @param entityGUIDParameterName parameter name supplying entityGUID
     * @param entityTypeName unique name of the entity's type
     * @param classificationOrigin is this classification assigned or propagated?
     * @param classificationOriginGUID which entity did a propagated classification originate from?
     * @param classificationProperties properties for the classification
     * @param anchorEntity anchor entity for the bean (can be null)
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem removing the properties from the repository.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    private void archiveBeanInRepository(String               userId,
                                         String               externalSourceGUID,
                                         String               externalSourceName,
                                         String               entityGUID,
                                         String               entityGUIDParameterName,
                                         String               entityTypeName,
                                         ClassificationOrigin classificationOrigin,
                                         String               classificationOriginGUID,
                                         InstanceProperties   classificationProperties,
                                         EntityDetail         anchorEntity,
                                         boolean              forLineage,
                                         boolean              forDuplicateProcessing,
                                         Date                 effectiveTime,
                                         String               methodName) throws InvalidParameterException,
                                                                                 PropertyServerException,
                                                                                 UserNotAuthorizedException
    {
        /*
         * This is to pick up any errors in the iteration through the anchored elements.
         */
        invalidParameterHandler.validateGUID(entityGUID, entityGUIDParameterName, methodName);

        EntityDetail targetEntity = repositoryHandler.getEntityByGUID(userId,
                                                                      entityGUID,
                                                                      entityGUIDParameterName,
                                                                      entityTypeName,
                                                                      forLineage,
                                                                      forDuplicateProcessing,
                                                                      effectiveTime,
                                                                      methodName);

        if (targetEntity != null)
        {
            /*
             * Retrieve the entities attached to this element.  Any entity that is anchored, directly or indirectly, to the anchor entity is archived.
             */
            RepositoryRelationshipsIterator iterator = new RepositoryRelationshipsIterator(repositoryHandler,
                                                                                           userId,
                                                                                           entityGUID,
                                                                                           entityTypeName,
                                                                                           null,
                                                                                           null,
                                                                                           forDuplicateProcessing,
                                                                                           0,
                                                                                           invalidParameterHandler.getMaxPagingSize(),
                                                                                           effectiveTime,
                                                                                           methodName);

            while (iterator.moreToReceive())
            {
                Relationship relationship = iterator.getNext();

                this.archiveAnchoredEntity(userId,
                                           externalSourceGUID,
                                           externalSourceName,
                                           anchorEntity,
                                           repositoryHandler.getOtherEnd(entityGUID, entityTypeName, relationship, methodName),
                                           classificationOriginGUID,
                                           classificationProperties,
                                           forLineage,
                                           forDuplicateProcessing,
                                           effectiveTime,
                                           methodName);
            }

            repositoryHandler.classifyEntity(userId,
                                             null,
                                             null,
                                             targetEntity.getGUID(),
                                             targetEntity,
                                             entityGUIDParameterName,
                                             entityTypeName,
                                             OpenMetadataAPIMapper.MEMENTO_CLASSIFICATION_TYPE_GUID,
                                             OpenMetadataAPIMapper.MEMENTO_CLASSIFICATION_TYPE_NAME,
                                             classificationOrigin,
                                             classificationOriginGUID,
                                             classificationProperties,
                                             forLineage,
                                             forDuplicateProcessing,
                                             effectiveTime,
                                             methodName);


            /*
             * Update the qualified name in the archived entity.
             */
            if (repositoryHelper.isTypeOf(serviceName, entityTypeName, OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME))
            {
                String qualifiedName = repositoryHelper.getStringProperty(serviceName,
                                                                          OpenMetadataAPIMapper.QUALIFIED_NAME_PROPERTY_NAME,
                                                                          anchorEntity.getProperties(),
                                                                          methodName) + "_archivedOn_" + new Date().toString();

                String entityTypeGUID = invalidParameterHandler.validateTypeName(entityTypeName,
                                                                                 OpenMetadataAPIMapper.OPEN_METADATA_ROOT_TYPE_NAME,
                                                                                 serviceName,
                                                                                 methodName,
                                                                                 repositoryHelper);
                this.updateBeanPropertyInRepository(userId,
                                                    externalSourceGUID,
                                                    externalSourceName,
                                                    entityGUID,
                                                    entityGUIDParameterName,
                                                    entityTypeGUID,
                                                    entityTypeName,
                                                    OpenMetadataAPIMapper.QUALIFIED_NAME_PROPERTY_NAME,
                                                    qualifiedName,
                                                    forLineage,
                                                    forDuplicateProcessing,
                                                    effectiveTime,
                                                    methodName);
            }
        }
    }


    /**
     * Remove an entity if it is anchored to the anchor entity
     *
     * @param anchorEntity entity anchor to match against
     * @param potentialAnchoredEntity entity to validate
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     * @throws InvalidParameterException problem with the parameters
     * @throws PropertyServerException problem in the repository services
     * @throws UserNotAuthorizedException calling user is not authorize to issue this request
     */
    public void deleteAnchoredEntity(EntityDetail anchorEntity,
                                     EntityProxy  potentialAnchoredEntity,
                                     boolean      forLineage,
                                     boolean      forDuplicateProcessing,
                                     Date         effectiveTime,
                                     String       methodName) throws InvalidParameterException,
                                                                     PropertyServerException,
                                                                     UserNotAuthorizedException
    {
        /*
         * Only need to progress if anchor entity exists.
         */
        if (anchorEntity != null)
        {
            final String guidParameterName = "potentialAnchoredEntity";

            if ((potentialAnchoredEntity != null) && (potentialAnchoredEntity.getType() != null))
            {
                String entityGUID = potentialAnchoredEntity.getGUID();
                String entityTypeName = potentialAnchoredEntity.getType().getTypeDefName();

                EntityDetail entity = repositoryHandler.getEntityByGUID(localServerUserId,
                                                                        entityGUID,
                                                                        guidParameterName,
                                                                        entityTypeName,
                                                                        forLineage,
                                                                        forDuplicateProcessing,
                                                                        effectiveTime,
                                                                        methodName);

                String anchorGUID = this.getAnchorGUIDFromAnchorsClassification(entity, methodName);

                if ((anchorGUID != null) && (anchorGUID.equals(anchorEntity.getGUID())))
                {
                    /*
                     * The element is part of the same set of elements for the anchorGUID.
                     * If the element is still connected to the anchor then it should remain
                     * because it is a parent object.   If it now has no anchor then it can be
                     * deleted because it is a child object.
                     */
                    String derivedAnchorGUID = this.deriveAnchorGUID(entityGUID, entityTypeName, forLineage, forDuplicateProcessing, effectiveTime, methodName);

                    if (derivedAnchorGUID == null)
                    {
                        /*
                         * The entity is anchored to the anchor entity so it needs deleting.  This is done
                         */
                        String externalSourceGUID = null;
                        String externalSourceName = null;

                        if (entity.getInstanceProvenanceType() != InstanceProvenanceType.LOCAL_COHORT)
                        {
                            externalSourceGUID = entity.getMetadataCollectionId();
                            externalSourceName = entity.getMetadataCollectionName();
                        }

                        this.deleteBeanInRepository(localServerUserId,
                                                    externalSourceGUID,
                                                    externalSourceName,
                                                    entity.getGUID(),
                                                    guidParameterName,
                                                    potentialAnchoredEntity.getType().getTypeDefGUID(),
                                                    potentialAnchoredEntity.getType().getTypeDefName(),
                                                    null,
                                                    null,
                                                    anchorEntity,
                                                    forLineage,
                                                    forDuplicateProcessing,
                                                    effectiveTime,
                                                    methodName);
                    }
                }
            }
        }
    }


    /**
     * Delete an entity from the repository.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityGUID unique identifier of object to update
     * @param entityGUIDParameterName name of parameter supplying the GUID
     * @param entityTypeGUID unique identifier of the entity's type
     * @param entityTypeName unique name of the entity's type
     * @param validatingPropertyName name of property to verify - of null if no verification is required
     * @param validatingPropertyValue value of property to verify
     * @param methodName calling method
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem removing the properties from the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public void deleteBeanInRepository(String       userId,
                                       String       externalSourceGUID,
                                       String       externalSourceName,
                                       String       entityGUID,
                                       String       entityGUIDParameterName,
                                       String       entityTypeGUID,
                                       String       entityTypeName,
                                       String       validatingPropertyName,
                                       String       validatingPropertyValue,
                                       String       methodName) throws InvalidParameterException,
                                                                       PropertyServerException,
                                                                       UserNotAuthorizedException
    {
        this.deleteBeanInRepository(userId,
                                    externalSourceGUID,
                                    externalSourceName,
                                    entityGUID,
                                    entityGUIDParameterName,
                                    entityTypeGUID,
                                    entityTypeName,
                                    validatingPropertyName,
                                    validatingPropertyValue,
                                    false,
                                    false,
                                    supportedZones,
                                    new Date(),
                                    methodName);
    }


    /**
     * Delete an entity from the repository.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityGUID unique identifier of object to update
     * @param entityGUIDParameterName name of parameter supplying the GUID
     * @param entityTypeGUID unique identifier of the entity's type
     * @param entityTypeName unique name of the entity's type
     * @param validatingPropertyName name of property to verify - or null if no verification is required
     * @param validatingPropertyValue value of property to verify
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem removing the properties from the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public void deleteBeanInRepository(String  userId,
                                       String  externalSourceGUID,
                                       String  externalSourceName,
                                       String  entityGUID,
                                       String  entityGUIDParameterName,
                                       String  entityTypeGUID,
                                       String  entityTypeName,
                                       String  validatingPropertyName,
                                       String  validatingPropertyValue,
                                       boolean forLineage,
                                       boolean forDuplicateProcessing,
                                       Date    effectiveTime,
                                       String  methodName) throws InvalidParameterException,
                                                                  PropertyServerException,
                                                                  UserNotAuthorizedException
    {
        this.deleteBeanInRepository(userId,
                                    externalSourceGUID,
                                    externalSourceName,
                                    entityGUID,
                                    entityGUIDParameterName,
                                    entityTypeGUID,
                                    entityTypeName,
                                    validatingPropertyName,
                                    validatingPropertyValue,
                                    forLineage,
                                    forDuplicateProcessing,
                                    supportedZones,
                                    effectiveTime,
                                    methodName);
    }


    /**
     * Delete an entity from the repository.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityGUID unique identifier of object to update
     * @param entityGUIDParameterName name of parameter supplying the GUID
     * @param entityTypeGUID unique identifier of the entity's type
     * @param entityTypeName unique name of the entity's type
     * @param validatingPropertyName name of property to verify - or null if no verification is required
     * @param validatingPropertyValue value of property to verify
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param serviceSupportedZones supported zones for calling service
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem removing the properties from the repositories.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public void deleteBeanInRepository(String       userId,
                                       String       externalSourceGUID,
                                       String       externalSourceName,
                                       String       entityGUID,
                                       String       entityGUIDParameterName,
                                       String       entityTypeGUID,
                                       String       entityTypeName,
                                       String       validatingPropertyName,
                                       String       validatingPropertyValue,
                                       boolean      forLineage,
                                       boolean      forDuplicateProcessing,
                                       List serviceSupportedZones,
                                       Date         effectiveTime,
                                       String       methodName) throws InvalidParameterException,
                                                                       PropertyServerException,
                                                                       UserNotAuthorizedException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(entityGUID, entityGUIDParameterName, methodName);

        EntityDetail  entity = repositoryHandler.getEntityByGUID(userId,
                                                                 entityGUID,
                                                                 entityGUIDParameterName,
                                                                 entityTypeName,
                                                                 forLineage,
                                                                 forDuplicateProcessing,
                                                                 effectiveTime,
                                                                 methodName);

        EntityDetail anchorEntity = this.validateAnchorEntity(userId,
                                                              entityGUID,
                                                              entityTypeName,
                                                              entity,
                                                              entityGUIDParameterName,
                                                              false,
                                                              forLineage,
                                                              forDuplicateProcessing,
                                                              serviceSupportedZones,
                                                              effectiveTime,
                                                              methodName);

        /*
         * The call above has validated that the entity to delete exists.
         * The anchorEntity is only set up if the deleted entity has an anchor entity.
         * This means it is not an anchor entity itself or without an anchor.
         */
        if (anchorEntity != null)
        {
            this.deleteBeanInRepository(userId,
                                        externalSourceGUID,
                                        externalSourceName,
                                        entityGUID,
                                        entityGUIDParameterName,
                                        entityTypeGUID,
                                        entityTypeName,
                                        validatingPropertyName,
                                        validatingPropertyValue,
                                        anchorEntity,
                                        forLineage,
                                        forDuplicateProcessing,
                                        effectiveTime,
                                        methodName);
        }
        else
        {
            this.deleteBeanInRepository(userId,
                                        externalSourceGUID,
                                        externalSourceName,
                                        entityGUID,
                                        entityGUIDParameterName,
                                        entityTypeGUID,
                                        entityTypeName,
                                        validatingPropertyName,
                                        validatingPropertyValue,
                                        entity,
                                        forLineage,
                                        forDuplicateProcessing,
                                        effectiveTime,
                                        methodName);
        }
    }


    /**
     * Delete an entity from the repository.
     *
     * @param userId calling user
     * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local
     * @param externalSourceName name of the software server capability entity that represented the external source
     * @param entityGUID unique identifier of object to update
     * @param entityGUIDParameterName name of parameter supplying the GUID
     * @param entityTypeGUID unique identifier of the entity's type
     * @param entityTypeName unique name of the entity's type
     * @param validatingPropertyName name of property to verify - null if no verification is required
     * @param validatingPropertyValue value of property to verify
     * @param anchorEntity anchor entity for the bean (can be null)
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem removing the properties from the repository.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public void deleteBeanInRepository(String       userId,
                                       String       externalSourceGUID,
                                       String       externalSourceName,
                                       String       entityGUID,
                                       String       entityGUIDParameterName,
                                       String       entityTypeGUID,
                                       String       entityTypeName,
                                       String       validatingPropertyName,
                                       String       validatingPropertyValue,
                                       EntityDetail anchorEntity,
                                       boolean      forLineage,
                                       boolean      forDuplicateProcessing,
                                       Date         effectiveTime,
                                       String       methodName) throws InvalidParameterException,
                                                                       PropertyServerException,
                                                                       UserNotAuthorizedException
    {
        /*
         * Retrieve the entities attached to this element.  Any entity that is anchored, directly or indirectly, to the anchor entity is deleted.
         * (This is why we explicitly delete the relationship to the parent element before calling this method).
         */
        RepositoryRelationshipsIterator iterator = new RepositoryRelationshipsIterator(repositoryHandler,
                                                                                       userId,
                                                                                       entityGUID,
                                                                                       entityTypeName,
                                                                                       null,
                                                                                       null,
                                                                                       forDuplicateProcessing,
                                                                                       0,
                                                                                       invalidParameterHandler.getMaxPagingSize(),
                                                                                       effectiveTime,
                                                                                       methodName);

        while (iterator.moreToReceive())
        {
            Relationship relationship = iterator.getNext();

            repositoryHandler.removeRelationship(userId,
                                                 externalSourceGUID,
                                                 externalSourceName,
                                                 relationship,
                                                 methodName);

            this.deleteAnchoredEntity(anchorEntity,
                                      repositoryHandler.getOtherEnd(entityGUID,
                                                                    entityTypeName,
                                                                    relationship,
                                                                    methodName),
                                      forLineage,
                                      forDuplicateProcessing,
                                      effectiveTime,
                                      methodName);
        }

        /*
         * This method explicitly removes all relationships attached to the entity before it deleted the entity.  This ensure that repository
         * events are created for all of the relationships.  This is why the code above needs to deal with the nested entities first.
         */
        repositoryHandler.removeEntity(userId,
                                       externalSourceGUID,
                                       externalSourceName,
                                       entityGUID,
                                       entityGUIDParameterName,
                                       entityTypeGUID,
                                       entityTypeName,
                                       validatingPropertyName,
                                       validatingPropertyValue,
                                       forLineage,
                                       forDuplicateProcessing,
                                       effectiveTime,
                                       methodName);

        /*
         * Update the LatestChange in the anchor entity if it is not the instance we have just deleted.
         */
        if ((anchorEntity != null) && (! entityGUID.equals(anchorEntity.getGUID())))
        {
            final String actionDescriptionTemplate = "Deleting %s %s";

            String actionDescription  = String.format(actionDescriptionTemplate, entityTypeName, entityGUID);
            int    latestChangeTarget = OpenMetadataAPIMapper.ATTACHMENT_LATEST_CHANGE_TARGET_ORDINAL;

            this.addLatestChangeToAnchor(anchorEntity,
                                         latestChangeTarget,
                                         OpenMetadataAPIMapper.DELETED_LATEST_CHANGE_ACTION_ORDINAL,
                                         null,
                                         entityGUID,
                                         entityTypeName,
                                         null,
                                         userId,
                                         actionDescription,
                                         methodName);
        }
    }

    /**
     * Is the bean isolated - i.e. has no relationships.
     *
     * @param userId calling user
     * @param entityGUID unique identifier of object to update
     * @param entityTypeName unique name of the entity's type
     * @param methodName calling method
     * @throws InvalidParameterException one of the parameters is null or invalid.
     * @throws PropertyServerException there is a problem removing the properties from the repository.
     * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request.
     */
    public boolean isBeanIsolated(String       userId,
                                  String       entityGUID,
                                  String       entityTypeName,
                                  String       methodName) throws InvalidParameterException,
                                                                  PropertyServerException,
                                                                  UserNotAuthorizedException
    {
        /*
         * Retrieve the first relationship, if there is one then we have relationships.
         */
        RepositoryRelationshipsIterator iterator = new RepositoryRelationshipsIterator(repositoryHandler,
                                                                                       userId,
                                                                                       entityGUID,
                                                                                       entityTypeName,
                                                                                       null,
                                                                                       null,
                                                                                       false,
                                                                                       0,
                                                                                       invalidParameterHandler.getMaxPagingSize(),
                                                                                       null,
                                                                                       methodName);

        return ! (iterator.moreToReceive());
    }


    /**
     * Return the elements of the requested type indirectly attached to an entity identified by the starting GUID via the listed relationship
     * types.
     *
     * @param userId     calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of parameter supplying the GUID
     * @param startingTypeName name of the type of object being attached to
     * @param relationshipPath list of unique identifier of the relationship types to follow to locate the attachment
     * @param relatedEntityTypeName unique name of the attached entity's type
     * @param startingFrom start position for results
     * @param pageSize     maximum number of results
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of unique identifiers for retrieved objects or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    List getRelatedEntityGUIDs(String       userId,
                                       String       startingGUID,
                                       String       startingGUIDParameterName,
                                       String       startingTypeName,
                                       List relationshipPath,
                                       String       relatedEntityTypeName,
                                       int          startingFrom,
                                       int          pageSize,
                                       Date         effectiveTime,
                                       String       methodName) throws InvalidParameterException,
                                                                       PropertyServerException,
                                                                       UserNotAuthorizedException
    {
        return this.getRelatedEntityGUIDs(userId,
                                          startingGUID,
                                          startingGUIDParameterName,
                                          startingTypeName,
                                          relationshipPath,
                                          relatedEntityTypeName,
                                          supportedZones,
                                          startingFrom,
                                          pageSize,
                                          effectiveTime,
                                          methodName);
    }


    /**
     * Return the elements of the requested type indirectly attached to an entity identified by the starting GUID via the listed relationship
     * types.
     *
     * @param userId     calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of parameter supplying the GUID
     * @param startingTypeName name of the type of object being attached to
     * @param relationshipPath list of unique identifier of the relationship types to follow to locate the attachment
     * @param relatedEntityTypeName unique name of the attached entity's type
     * @param serviceSupportedZones supported zones for calling service
     * @param startingFrom start position for results
     * @param pageSize     maximum number of results
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of unique identifiers for retrieved objects or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    List getRelatedEntityGUIDs(String       userId,
                                       String       startingGUID,
                                       String       startingGUIDParameterName,
                                       String       startingTypeName,
                                       List relationshipPath,
                                       String       relatedEntityTypeName,
                                       List serviceSupportedZones,
                                       int          startingFrom,
                                       int          pageSize,
                                       Date         effectiveTime,
                                       String       methodName) throws InvalidParameterException,
                                                                       PropertyServerException,
                                                                       UserNotAuthorizedException
    {
        // todo
        invalidParameterHandler.throwMethodNotSupported(userId, serviceName, serverName, methodName);
        return null;
    }


    /**
     * Return the elements of the requested type attached to an entity identified by the starting GUID.
     *
     * @param userId     calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of parameter supplying the GUID
     * @param startingTypeName name of the type of object being attached to
     * @param attachmentRelationshipTypeGUID unique identifier of the relationship type connect to the attachment
     * @param attachmentRelationshipTypeName unique name of the relationship type connect to the attachment
     * @param attachmentEntityTypeName unique name of the attached entity's type
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param startingFrom start position for results
     * @param pageSize     maximum number of results
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of unique identifiers for retrieved objects or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public List getAttachedElementGUIDs(String       userId,
                                                String       startingGUID,
                                                String       startingGUIDParameterName,
                                                String       startingTypeName,
                                                String       attachmentRelationshipTypeGUID,
                                                String       attachmentRelationshipTypeName,
                                                String       attachmentEntityTypeName,
                                                boolean      forLineage,
                                                boolean      forDuplicateProcessing,
                                                int          startingFrom,
                                                int          pageSize,
                                                Date         effectiveTime,
                                                String       methodName) throws InvalidParameterException,
                                                                                PropertyServerException,
                                                                                UserNotAuthorizedException
    {
        return this.getAttachedElementGUIDs(userId,
                                            startingGUID,
                                            startingGUIDParameterName,
                                            startingTypeName,
                                            attachmentRelationshipTypeGUID,
                                            attachmentRelationshipTypeName,
                                            attachmentEntityTypeName,
                                            forLineage,
                                            forDuplicateProcessing,
                                            supportedZones,
                                            startingFrom,
                                            pageSize,
                                            effectiveTime,
                                            methodName);
    }


    /**
     * Return the elements of the requested type attached to an entity identified by the starting GUID.
     *
     * @param userId     calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of parameter supplying the GUID
     * @param startingTypeName name of the type of object being attached to
     * @param attachmentRelationshipTypeGUID unique identifier of the relationship type connect to the attachment
     * @param attachmentRelationshipTypeName unique name of the relationship type connect to the attachment
     * @param attachmentEntityTypeName unique name of the attached entity's type
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param serviceSupportedZones supported zones for calling service
     * @param startingFrom start position for results
     * @param pageSize     maximum number of results
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of unique identifiers for retrieved objects or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public List getAttachedElementGUIDs(String       userId,
                                                String       startingGUID,
                                                String       startingGUIDParameterName,
                                                String       startingTypeName,
                                                String       attachmentRelationshipTypeGUID,
                                                String       attachmentRelationshipTypeName,
                                                String       attachmentEntityTypeName,
                                                boolean      forLineage,
                                                boolean      forDuplicateProcessing,
                                                List serviceSupportedZones,
                                                int          startingFrom,
                                                int          pageSize,
                                                Date         effectiveTime,
                                                String       methodName) throws InvalidParameterException,
                                                                                PropertyServerException,
                                                                                UserNotAuthorizedException
    {
        final String guidParameterName = "relationship.end.guid";

        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(startingGUID, startingGUIDParameterName, methodName);

        this.validateAnchorEntity(userId,
                                  startingGUID,
                                  startingGUIDParameterName,
                                  startingTypeName,
                                  false,
                                  forLineage,
                                  forDuplicateProcessing,
                                  serviceSupportedZones,
                                  effectiveTime,
                                  methodName);

        /*
         * Validates the parameters and retrieves the links to attached keywords that are visible to this user.
         * Relationships are returned so that the isPublic property from the relationship can be retrieved.
         */
        List  relationships = this.getAttachmentLinks(userId,
                                                                    startingGUID,
                                                                    startingGUIDParameterName,
                                                                    startingTypeName,
                                                                    attachmentRelationshipTypeGUID,
                                                                    attachmentRelationshipTypeName,
                                                                    attachmentEntityTypeName,
                                                                    startingFrom,
                                                                    pageSize,
                                                                    effectiveTime,
                                                                    methodName);

        if ((relationships == null) || (relationships.isEmpty()))
        {
            return null;
        }

        List  results = new ArrayList<>();

        for (Relationship  relationship : relationships)
        {
            if (relationship != null)
            {
                EntityProxy entityProxy = repositoryHandler.getOtherEnd(startingGUID,
                                                                        startingTypeName,
                                                                        relationship,
                                                                        methodName);
                if (entityProxy != null)
                {
                    try
                    {
                        this.validateAnchorEntity(userId,
                                                   entityProxy.getGUID(),
                                                   guidParameterName,
                                                   attachmentEntityTypeName,
                                                   false,
                                                   forLineage,
                                                   forDuplicateProcessing,
                                                   serviceSupportedZones,
                                                   effectiveTime,
                                                   methodName);

                        results.add(entityProxy.getGUID());
                    }
                    catch (InvalidParameterException | UserNotAuthorizedException | PropertyServerException nonAccessibleEntity)
                    {
                        // skip entities that are not visible to this user
                        if (log.isDebugEnabled())
                        {
                            log.debug("Skipping entity", nonAccessibleEntity);
                        }
                    }
                }
            }
        }

        if (results.isEmpty())
        {
            return null;
        }
        else
        {
            return results;
        }
    }


    /**
     * Return the elements of the requested type attached to an entity identified by the starting GUID.
     *
     * @param userId     calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of parameter supplying the GUID
     * @param startingTypeName name of the type of object being attached to
     * @param attachmentRelationshipTypeGUID unique identifier of the relationship type connect to the attachment
     * @param attachmentRelationshipTypeName unique name of the relationship type connect to the attachment
     * @param attachmentEntityTypeName unique name of the attached entity's type
     * @param selectionEnd 0 means either end, 1 means only take from end 1, 2 means only take from end 2
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of unique identifiers for retrieved objects or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public String getAttachedElementGUID(String  userId,
                                         String  startingGUID,
                                         String  startingGUIDParameterName,
                                         String  startingTypeName,
                                         String  attachmentRelationshipTypeGUID,
                                         String  attachmentRelationshipTypeName,
                                         String  attachmentEntityTypeName,
                                         int     selectionEnd,
                                         boolean forLineage,
                                         boolean forDuplicateProcessing,
                                         Date    effectiveTime,
                                         String  methodName) throws InvalidParameterException,
                                                                    PropertyServerException,
                                                                    UserNotAuthorizedException
    {
        return getAttachedElementGUID(userId,
                                      startingGUID,
                                      startingGUIDParameterName,
                                      startingTypeName,
                                      attachmentRelationshipTypeGUID,
                                      attachmentRelationshipTypeName,
                                      attachmentEntityTypeName,
                                      selectionEnd,
                                      forLineage,
                                      forDuplicateProcessing,
                                      supportedZones,
                                      effectiveTime,
                                      methodName);
    }


    /**
     * Return the elements of the requested type attached to an entity identified by the starting GUID.
     *
     * @param userId     calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of parameter supplying the GUID
     * @param startingTypeName name of the type of object being attached to
     * @param attachmentRelationshipTypeGUID unique identifier of the relationship type connect to the attachment
     * @param attachmentRelationshipTypeName unique name of the relationship type connect to the attachment
     * @param attachmentEntityTypeName unique name of the attached entity's type
     * @param selectionEnd 0 means either end, 1 means only take from end 1, 2 means only take from end 2
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param serviceSupportedZones supported zones for calling service
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of unique identifiers for retrieved objects or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public String getAttachedElementGUID(String       userId,
                                         String       startingGUID,
                                         String       startingGUIDParameterName,
                                         String       startingTypeName,
                                         String       attachmentRelationshipTypeGUID,
                                         String       attachmentRelationshipTypeName,
                                         String       attachmentEntityTypeName,
                                         int          selectionEnd,
                                         boolean      forLineage,
                                         boolean      forDuplicateProcessing,
                                         List serviceSupportedZones,
                                         Date         effectiveTime,
                                         String       methodName) throws InvalidParameterException,
                                                                         PropertyServerException,
                                                                         UserNotAuthorizedException
    {
        final String guidParameterName = "relationship.end.guid";

        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(startingGUID, startingGUIDParameterName, methodName);

        this.validateAnchorEntity(userId,
                                  startingGUID,
                                  startingGUIDParameterName,
                                  startingTypeName,
                                  false,
                                  forLineage,
                                  forDuplicateProcessing,
                                  serviceSupportedZones,
                                  effectiveTime,
                                  methodName);

        /*
         * Validates the parameters and retrieves the links to attached keywords that are visible to this user.
         * Relationships are returned so that the isPublic property from the relationship can be retrieved.
         */
        List  relationships = this.getAttachmentLinks(userId,
                                                                    startingGUID,
                                                                    startingGUIDParameterName,
                                                                    startingTypeName,
                                                                    attachmentRelationshipTypeGUID,
                                                                    attachmentRelationshipTypeName,
                                                                    attachmentEntityTypeName,
                                                                    0,
                                                                    invalidParameterHandler.getMaxPagingSize(),
                                                                    effectiveTime,
                                                                    methodName);

        if ((relationships == null) || (relationships.isEmpty()))
        {
            return null;
        }

        String  result = null;

        for (Relationship  relationship : relationships)
        {
            if (relationship != null)
            {
                EntityProxy entityProxy = null;

                if (selectionEnd == 0)
                {
                    entityProxy = repositoryHandler.getOtherEnd(startingGUID,
                                                                startingTypeName,
                                                                relationship,
                                                                methodName);
                }
                else if (selectionEnd == 1)
                {
                    entityProxy = relationship.getEntityOneProxy();
                }
                else if (selectionEnd == 2)
                {
                    entityProxy = relationship.getEntityTwoProxy();
                }

                if (entityProxy != null)
                {
                    try
                    {
                        this.validateAnchorEntity(userId,
                                                  entityProxy.getGUID(),
                                                  guidParameterName,
                                                  attachmentEntityTypeName,
                                                  false,
                                                  forLineage,
                                                  forDuplicateProcessing,
                                                  serviceSupportedZones,
                                                  effectiveTime,
                                                  methodName);

                        if (result == null)
                        {
                            result = entityProxy.getGUID();
                        }
                        else
                        {
                            errorHandler.handleAmbiguousRelationships(startingGUID,
                                                                      startingTypeName,
                                                                      attachmentRelationshipTypeName,
                                                                      relationships,
                                                                      methodName);
                        }
                    }
                    catch (InvalidParameterException | UserNotAuthorizedException | PropertyServerException nonAccessibleEntity)
                    {
                        // skip entities that are not visible to this user
                        if (log.isDebugEnabled())
                        {
                            log.debug("Skipping entity", nonAccessibleEntity);
                        }
                    }
                }
            }
        }

        return result;
    }


    /**
     * Return the Bean for the required relationship attached to a specific element.  This method assumes the starting element has
     * a validated anchor.
     *
     * @param userId     calling user
     * @param startingElementGUID identifier for the entity that the identifier is attached to
     * @param startingElementGUIDParameterName name of the parameter used to pass the guid
     * @param startingElementTypeName type name for anchor
     * @param relationshipTypeGUID unique identifier of the attachment's relationship type
     * @param relationshipTypeName unique name of the attachment's relationship type
     * @param resultingElementTypeName unique name of the attached entity's type
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param serviceSupportedZones supported zones for calling service
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of retrieved objects or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public B getAttachedElement(String       userId,
                                String       startingElementGUID,
                                String       startingElementGUIDParameterName,
                                String       startingElementTypeName,
                                String       relationshipTypeGUID,
                                String       relationshipTypeName,
                                String       resultingElementTypeName,
                                boolean      forLineage,
                                boolean      forDuplicateProcessing,
                                List serviceSupportedZones,
                                Date         effectiveTime,
                                String       methodName) throws InvalidParameterException,
                                                                PropertyServerException,
                                                                UserNotAuthorizedException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(startingElementGUID, startingElementGUIDParameterName, methodName);

        EntityDetail startingEntity = this.getEntityFromRepository(userId,
                                                                   startingElementGUID,
                                                                   startingElementGUIDParameterName,
                                                                   startingElementTypeName,
                                                                   null,
                                                                   null,
                                                                   forLineage,
                                                                   forDuplicateProcessing,
                                                                   serviceSupportedZones,
                                                                   effectiveTime,
                                                                   methodName);

        EntityDetail entity = repositoryHandler.getEntityForRelationshipType(userId,
                                                                             startingEntity,
                                                                             startingElementTypeName,
                                                                             relationshipTypeGUID,
                                                                             relationshipTypeName,
                                                                             null,
                                                                             0,
                                                                             resultingElementTypeName,
                                                                             forLineage,
                                                                             forDuplicateProcessing,
                                                                             effectiveTime,
                                                                             methodName);

        if (entity != null)
        {
            validateAnchorEntity(userId,
                                 startingElementGUID,
                                 startingElementTypeName,
                                 entity,
                                 startingElementGUIDParameterName,
                                 false,
                                 forLineage,
                                 forDuplicateProcessing,
                                 serviceSupportedZones,
                                 effectiveTime,
                                 methodName);

            return converter.getNewBean(beanClass, entity, methodName);
        }

        return null;
    }


    /**
     * Return the elements of the requested type attached to an entity identified by the starting GUID.
     *
     * @param userId     calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of parameter supplying the GUID
     * @param startingTypeName name of the type of object being attached to
     * @param attachmentRelationshipTypeGUID unique identifier of the relationship type connect to the attachment
     * @param attachmentRelationshipTypeName unique name of the relationship type connect to the attachment
     * @param attachmentEntityTypeName unique name of the attached entity's type
     * @param startingFrom start position for results
     * @param pageSize     maximum number of results
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of retrieved objects or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public   List getAttachedElements(String userId,
                                         String startingGUID,
                                         String startingGUIDParameterName,
                                         String startingTypeName,
                                         String attachmentRelationshipTypeGUID,
                                         String attachmentRelationshipTypeName,
                                         String attachmentEntityTypeName,
                                         int    startingFrom,
                                         int    pageSize,
                                         Date   effectiveTime,
                                         String methodName) throws InvalidParameterException,
                                                                   PropertyServerException,
                                                                   UserNotAuthorizedException
    {
        return this.getAttachedElements(userId,
                                        null,
                                        null,
                                        startingGUID,
                                        startingGUIDParameterName,
                                        startingTypeName,
                                        attachmentRelationshipTypeGUID,
                                        attachmentRelationshipTypeName,
                                        attachmentEntityTypeName,
                                        null,
                                        null,
                                        0,
                                        false,
                                        false,
                                        supportedZones,
                                        startingFrom,
                                        pageSize,
                                        effectiveTime,
                                        methodName);
    }


    /**
     * Return the elements of the requested type attached to an entity identified by the starting GUID.
     *
     * @param userId     calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of parameter supplying the GUID
     * @param startingTypeName name of the type of object being attached to
     * @param attachmentRelationshipTypeGUID unique identifier of the relationship type connect to the attachment
     * @param attachmentRelationshipTypeName unique name of the relationship type connect to the attachment
     * @param attachmentEntityTypeName unique name of the attached entity's type
     * @param requiredClassificationName  String the name of the classification that must be on the attached entity.
     * @param omittedClassificationName   String the name of a classification that must not be on the attached entity.
     * @param selectionEnd 0 means either end, 1 means only take from end 1, 2 means only take from end 2
     * @param startingFrom start position for results
     * @param pageSize     maximum number of results
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of retrieved objects or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public   List getAttachedElements(String  userId,
                                         String  startingGUID,
                                         String  startingGUIDParameterName,
                                         String  startingTypeName,
                                         String  attachmentRelationshipTypeGUID,
                                         String  attachmentRelationshipTypeName,
                                         String  attachmentEntityTypeName,
                                         String  requiredClassificationName,
                                         String  omittedClassificationName,
                                         int     selectionEnd,
                                         boolean forLineage,
                                         boolean forDuplicateProcessing,
                                         int     startingFrom,
                                         int     pageSize,
                                         Date    effectiveTime,
                                         String  methodName) throws InvalidParameterException,
                                                                    PropertyServerException,
                                                                    UserNotAuthorizedException
    {
        return this.getAttachedElements(userId,
                                        null,
                                        null,
                                        startingGUID,
                                        startingGUIDParameterName,
                                        startingTypeName,
                                        attachmentRelationshipTypeGUID,
                                        attachmentRelationshipTypeName,
                                        attachmentEntityTypeName,
                                        requiredClassificationName,
                                        omittedClassificationName,
                                        selectionEnd,
                                        forLineage,
                                        forDuplicateProcessing,
                                        supportedZones,
                                        startingFrom,
                                        pageSize,
                                        effectiveTime,
                                        methodName);
    }


    /**
     * Return the elements of the requested type attached to an entity identified by the starting GUID.
     *
     * @param userId     calling user
     * @param anchorGUID expected anchorGUID for this element
     * @param anchorGUIDParameterName parameter supplying anchorGUID
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of parameter supplying the GUID
     * @param startingTypeName name of the type of object being attached to
     * @param attachmentRelationshipTypeGUID unique identifier of the relationship type connect to the attachment
     * @param attachmentRelationshipTypeName unique name of the relationship type connect to the attachment
     * @param attachmentEntityTypeName unique name of the attached entity's type
     * @param requiredClassificationName  String the name of the classification that must be on the attached entity.
     * @param omittedClassificationName   String the name of a classification that must not be on the attached entity.
     * @param selectionEnd 0 means either end, 1 means only take from end 1, 2 means only take from end 2
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param serviceSupportedZones supported zones for calling service
     * @param startingFrom start position for results
     * @param pageSize     maximum number of results
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName calling method
     *
     * @return list of retrieved objects or null if none found
     *
     * @throws InvalidParameterException  the input properties are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    public   List getAttachedElements(String       userId,
                                         String       anchorGUID,
                                         String       anchorGUIDParameterName,
                                         String       startingGUID,
                                         String       startingGUIDParameterName,
                                         String       startingTypeName,
                                         String       attachmentRelationshipTypeGUID,
                                         String       attachmentRelationshipTypeName,
                                         String       attachmentEntityTypeName,
                                         String       requiredClassificationName,
                                         String       omittedClassificationName,
                                         int          selectionEnd,
                                         boolean      forLineage,
                                         boolean      forDuplicateProcessing,
                                         List serviceSupportedZones,
                                         int          startingFrom,
                                         int          pageSize,
                                         Date         effectiveTime,
                                         String       methodName) throws InvalidParameterException,
                                                                         PropertyServerException,
                                                                         UserNotAuthorizedException
    {
        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(startingGUID, startingGUIDParameterName, methodName);

        EntityDetail anchorEntity = this.validateAnchorEntity(userId,
                                                              startingGUID,
                                                              startingGUIDParameterName,
                                                              startingTypeName,
                                                              false,
                                                              forLineage,
                                                              forDuplicateProcessing,
                                                              serviceSupportedZones,
                                                              effectiveTime,
                                                              methodName);

        invalidParameterHandler.validateAnchorGUID(anchorGUID, anchorGUIDParameterName, anchorEntity, startingGUID, startingTypeName, methodName);

        /*
         * Validates the parameters and retrieves the links to attached keywords that are visible to this user.
         * Relationships are returned so that the isPublic property from the relationship can be retrieved.
         */
        List  relationships = this.getAttachmentLinks(userId,
                                                                    startingGUID,
                                                                    startingGUIDParameterName,
                                                                    startingTypeName,
                                                                    attachmentRelationshipTypeGUID,
                                                                    attachmentRelationshipTypeName,
                                                                    null,
                                                                    attachmentEntityTypeName,
                                                                    selectionEnd,
                                                                    forDuplicateProcessing,
                                                                    startingFrom,
                                                                    pageSize,
                                                                    effectiveTime,
                                                                    methodName);

        if ((relationships == null) || (relationships.isEmpty()))
        {
            return null;
        }

        List  results = new ArrayList<>();

        for (Relationship  relationship : relationships)
        {
            if (relationship != null)
            {
                try
                {
                    B bean = this.getAttachedElement(userId,
                                                     startingGUID,
                                                     startingGUIDParameterName,
                                                     startingTypeName,
                                                     relationship,
                                                     attachmentEntityTypeName,
                                                     requiredClassificationName,
                                                     omittedClassificationName,
                                                     selectionEnd,
                                                     forLineage,
                                                     forDuplicateProcessing,
                                                     serviceSupportedZones,
                                                     effectiveTime,
                                                     methodName);
                    if (bean != null)
                    {
                        results.add(bean);
                    }
                }
                catch (InvalidParameterException | UserNotAuthorizedException | PropertyServerException inaccessibleEntity)
                {
                    // skip entities that are not visible to this user
                    if (log.isDebugEnabled())
                    {
                        log.debug("Skipping inaccessible entity", inaccessibleEntity);
                    }
                }
            }
        }

        if (results.isEmpty())
        {
            return null;
        }
        else
        {
            return results;
        }
    }


    /**
     * Retrieve the requested element from the supplied relationship.
     *
     * @param userId       calling user
     * @param startingGUID identifier for the entity that the identifier is attached to
     * @param startingGUIDParameterName name of parameter supplying the GUID
     * @param startingTypeName name of the type of object being attached to
     * @param relationship relationship between the requested element and the related keyword
     * @param attachmentEntityTypeName unique name of the attached entity's type
     * @param requiredClassificationName  String the name of the classification that must be on the attached entity
     * @param omittedClassificationName   String the name of a classification that must not be on the attached entity
     * @param selectionEnd 0 means either end, 1 means only take from end 1, 2 means only take from end 2
     * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned
     * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate
     * @param serviceSupportedZones supported zones for calling service
     * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now)
     * @param methodName   calling method
     * @return new bean
     * @throws InvalidParameterException  the parameters are invalid
     * @throws UserNotAuthorizedException user not authorized to issue this request
     * @throws PropertyServerException    problem accessing the repositories
     */
    private B getAttachedElement(String        userId,
                                 String        startingGUID,
                                 String        startingGUIDParameterName,
                                 String        startingTypeName,
                                 Relationship  relationship,
                                 String        attachmentEntityTypeName,
                                 String        requiredClassificationName,
                                 String        omittedClassificationName,
                                 int           selectionEnd,
                                 boolean       forLineage,
                                 boolean       forDuplicateProcessing,
                                 List  serviceSupportedZones,
                                 Date          effectiveTime,
                                 String        methodName) throws InvalidParameterException,
                                                                  PropertyServerException,
                                                                  UserNotAuthorizedException
    {
        final String guidParameterName = "relationship.end.guid";

        invalidParameterHandler.validateUserId(userId, methodName);
        invalidParameterHandler.validateGUID(startingGUID, startingGUIDParameterName, methodName);

        if (relationship != null)
        {
            EntityProxy entityProxy = null;

            if (selectionEnd == 0)
            {
                entityProxy = repositoryHandler.getOtherEnd(startingGUID,
                                                            startingTypeName,
                                                            relationship,
                                                            methodName);
            }
            else if (selectionEnd == 1)
            {
                entityProxy = relationship.getEntityOneProxy();
            }
            else if (selectionEnd == 2)
            {
                entityProxy = relationship.getEntityTwoProxy();
            }

            if (entityProxy != null)
            { 
                EntityDetail entity = repositoryHandler.getEntityByGUID(userId,
                                                                        entityProxy.getGUID(),
                                                                        guidParameterName,
                                                                        attachmentEntityTypeName,
                                                                        forLineage,
                                                                        forDuplicateProcessing,
                                                                        effectiveTime,
                                                                        methodName);

                this.validateAnchorEntity(userId,
                                          entityProxy.getGUID(),
                                          attachmentEntityTypeName,
                                          entity,
                                          guidParameterName,
                                          false,
                                          forLineage,
                                          forDuplicateProcessing,
                                          serviceSupportedZones,
                                          effectiveTime,
                                          methodName);

                boolean beanValid = true;

                if (requiredClassificationName != null)
                {
                    try
                    {
                        if (repositoryHelper.getClassificationFromEntity(serviceName, entity, requiredClassificationName, methodName) == null)
                        {
                            beanValid = false;
                        }
                    }
                    catch (ClassificationErrorException error)
                    {
                        /*
                         * Since this classification is not supported, it can not be attached to the entity
                         */
                        beanValid = false;
                    }
                }

                if (omittedClassificationName != null)
                {
                    try
                    {
                        if (repositoryHelper.getClassificationFromEntity(serviceName, entity, omittedClassificationName, methodName) != null)
                        {
                            beanValid = false;
                        }
                    }
                    catch (ClassificationErrorException error)
                    {
                        /*
                         * Since this classification is not supported, it can not be attached to the entity
                         */
                    }
                }

                if (beanValid)
                {
                    /*
                     * Valid entity to return since no exception occurred.
                     */
                    return converter.getNewBean(beanClass, entity, relationship, methodName);
                }
            }
        }

        return null;
    }


    /**
     * Retrieve the entities that are attached to the entity with startingGUID. The entities are only returned if they match the supplied filtering.
     *
     * To be returned the attached entity needs to be directly attached to the entity with startingGUID:
     * 
    *
  • with the relationship relationshipTypeName
  • *
  • the relationship relationshipTypeGUID
  • *
  • be at this end of the relationship.
  • *
  • it is visible to the calling user
  • *
*

* Optionally if specified, the attached entity needs to *

    *
  • match the searchCriteria taking into account the ignoreCase and startsWith flags against the text property fields named in specificMatchPropertyNames.
  • *
* Optionally if specified, the attached entity needs to *
    *
  • be in the requested page as specified by startFrom and queryPageSize
  • *
* * @param userId calling user * @param startingGUID identifier for the entity that the identifier is attached to * @param startingGUIDParameterName name of parameter supplying the GUID * @param startingTypeName name of the type of object being attached to * @param relationshipTypeName name of the type of relationship attaching the attached entity * @param relationshipTypeGUID guid of the type of relationship attaching the attached entity * @param selectionEnd 0 means either end, 1 means only take from end 1, 2 means only take from end 2 * @param specificMatchPropertyNames list of property names to * @param searchCriteria text to search on * @param startFrom index of the list to start from (0 for start) * @param startsWith if flag set search looking for matches starting with the supplied searchCriteria, otherwise an exact match * @param ignoreCase if set ignore case on the match, if not set then case must match * @param queryPageSize requested page size * @param methodName calling method * @return List of attached entities * @throws InvalidParameterException the parameters are invalid * @throws UserNotAuthorizedException user not authorized to issue this request * @throws PropertyServerException problem accessing the repositories */ public List getAttachedFilteredEntities(String userId, String startingGUID, String startingGUIDParameterName, String startingTypeName, String relationshipTypeName, String relationshipTypeGUID, int selectionEnd, Set specificMatchPropertyNames, String searchCriteria, int startFrom, boolean startsWith, boolean ignoreCase, int queryPageSize, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return getAttachedFilteredEntities(userId, startingGUID, startingGUIDParameterName, startingTypeName, relationshipTypeName, relationshipTypeGUID, selectionEnd, null, null, true, specificMatchPropertyNames, searchCriteria, startFrom, startsWith, ignoreCase, queryPageSize, false, null, methodName); } /** * Retrieve the entities that are attached to the entity with startingGUID. The entities are only returned if they match the supplied filtering. * * To be returned the attached entity needs to be directly attached to the entity with startingGUID: *
    *
  • with the relationship relationshipTypeName
  • *
  • the relationship relationshipTypeGUID
  • *
  • be at this end of the relationship.
  • *
  • it is visible to the calling user
  • *
*

* Optionally if specified, the attached entity needs to *

    *
  • match the searchCriteria taking into account the ignoreCase and startsWith flags against the text property fields named in specificMatchPropertyNames.
  • *
* Optionally if specified, the attached entity needs to *
    *
  • be in the requested page as specified by startFrom and queryPageSize
  • *
* * @param userId calling user * @param startingGUID identifier for the entity that the identifier is attached to * @param startingGUIDParameterName name of parameter supplying the GUID * @param startingTypeName name of the type of object being attached to * @param relationshipTypeName name of the type of relationship attaching the attached entity * @param relationshipTypeGUID guid of the type of relationship attaching the attached entity * @param selectionEnd 0 means either end, 1 means only take from end 1, 2 means only take from end 2 * @param specificMatchPropertyNames list of property names to * @param searchCriteria text to search on * @param startFrom index of the list to start from (0 for start) * @param startsWith if flag set search looking for matches starting with the supplied searchCriteria, otherwise an exact match * @param ignoreCase if set ignore case on the match, if not set then case must match * @param queryPageSize requested page size * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * @return List of attached entities * @throws InvalidParameterException the parameters are invalid * @throws UserNotAuthorizedException user not authorized to issue this request * @throws PropertyServerException problem accessing the repositories */ public List getAttachedFilteredEntities(String userId, String startingGUID, String startingGUIDParameterName, String startingTypeName, String relationshipTypeName, String relationshipTypeGUID, int selectionEnd, Set specificMatchPropertyNames, String searchCriteria, int startFrom, boolean startsWith, boolean ignoreCase, int queryPageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return getAttachedFilteredEntities(userId, startingGUID, startingGUIDParameterName, startingTypeName, relationshipTypeName, relationshipTypeGUID, selectionEnd, null, null, true, specificMatchPropertyNames, searchCriteria, startFrom, startsWith, ignoreCase, queryPageSize, false, effectiveTime, methodName); } /** * Retrieve the entities that are attached to the entity with startingGUID. The entities are only returned if they match the supplied filtering. * * To be returned the attached entity needs to be directly attached to the entity with startingGUID: *
    *
  • with the relationship relationshipTypeName
  • *
  • the relationship relationshipTypeGUID
  • *
  • be at this end of the relationship.
  • *
  • it is visible to the calling user
  • *
*

* Optionally if specified, the attached entity needs to *

    *
  • match the searchCriteria taking into account the ignoreCase and startsWith flags against the text property fields named in specificMatchPropertyNames.
  • *
* Optionally if specified, the attached entity needs to *
    *
  • be in the requested page as specified by startFrom and queryPageSize
  • *
* Optionally if specified, the attached entity needs to *
    *
  • not have a relationship to a unique parent entity via the attachedEntityFilterRelationshipTypeName
  • *
  • not have a relationship to a unique parent entity via the attachedEntityFilterRelationshipTypeGUID
  • *
  • not have a relationship to a unique parent entity where the parent is at the other end, the parent end is identified using attachedEntityParentAtEnd1
  • *
* * @param userId calling user * @param startingGUID identifier for the entity that the identifier is attached to * @param startingGUIDParameterName name of parameter supplying the GUID * @param startingTypeName name of the type of object being attached to * @param relationshipTypeName name of the type of relationship attaching the attached entity * @param relationshipTypeGUID guid of the type of relationship attaching the attached entity * @param selectionEnd 0 means either end, 1 means only take from end 1, 2 means only take from end 2 * @param attachedEntityFilterRelationshipTypeName do not return attached entities that have this parent relationship at attachedEntityParentAtEnd1. If null this has not effect on the match. * @param attachedEntityFilterRelationshipTypeGUID do not return attached entities that have this parent relationship at attachedEntityParentAtEnd1. If null this has not effect on the match. * @param attachedEntityParentAtEnd1 if the attached entity has a parent the entity will not be returned. * @param specificMatchPropertyNames list of property names to * @param searchCriteria text to search on * @param startFrom index of the list to start from (0 for start) * @param startsWith if flag set search looking for matches starting with the supplied searchCriteria, otherwise an exact match * @param ignoreCase if set ignore case on the match, if not set then case must match * @param queryPageSize requested page size * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * @return List of attached entities * @throws InvalidParameterException the parameters are invalid * @throws UserNotAuthorizedException user not authorized to issue this request * @throws PropertyServerException problem accessing the repositories */ public List getAttachedFilteredEntities(String userId, String startingGUID, String startingGUIDParameterName, String startingTypeName, String relationshipTypeName, String relationshipTypeGUID, int selectionEnd, String attachedEntityFilterRelationshipTypeName, String attachedEntityFilterRelationshipTypeGUID, boolean attachedEntityParentAtEnd1, Set specificMatchPropertyNames, String searchCriteria, int startFrom, boolean startsWith, boolean ignoreCase, int queryPageSize, boolean forDuplicateProcessing, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateGUID(startingGUID, startingGUIDParameterName, methodName); int localStartFrom = 0; RepositoryRelatedEntitiesIterator relatedEntityIterator = new RepositoryRelatedEntitiesIterator(repositoryHandler, userId, startingGUID, startingTypeName, relationshipTypeGUID, relationshipTypeName, null, false, false, localStartFrom, queryPageSize, selectionEnd, effectiveTime, methodName); // resultsToReturn is the subset of the filtered results to meets the requested startFrom List resultsToReturn = new ArrayList<>(); // accumulate the total filtered results from 0 - so we can then honour the requested startFrom which is the index into the filtered results. List totalFilteredResults = new ArrayList<>(); // iterate through the related entities applying the filter. while ((relatedEntityIterator.moreToReceive()) && ((queryPageSize == 0) || resultsToReturn.size() < queryPageSize)) { EntityDetail relatedEntity = relatedEntityIterator.getNext(); if (relatedEntity != null) { if (log.isDebugEnabled()) { String displayName = ""; String qualifiedName = ""; if (relatedEntity.getProperties() !=null && relatedEntity.getProperties().getInstanceProperties() != null ) { if (relatedEntity.getProperties().getInstanceProperties().get(OpenMetadataAPIMapper.DISPLAY_NAME_PROPERTY_NAME) != null) { displayName = relatedEntity.getProperties().getInstanceProperties().get(OpenMetadataAPIMapper.DISPLAY_NAME_PROPERTY_NAME).toString(); } else if (relatedEntity.getProperties().getInstanceProperties().get(OpenMetadataAPIMapper.NAME_PROPERTY_NAME) != null) { displayName = relatedEntity.getProperties().getInstanceProperties().get(OpenMetadataAPIMapper.NAME_PROPERTY_NAME).toString(); } if ( relatedEntity.getProperties().getInstanceProperties().get(OpenMetadataAPIMapper.QUALIFIED_NAME_PROPERTY_NAME)!=null) { qualifiedName = relatedEntity.getProperties().getInstanceProperties().get(OpenMetadataAPIMapper.QUALIFIED_NAME_PROPERTY_NAME).toString(); } } log.debug("getAttachedFilteredEntities - while relatedEntity guid="+relatedEntity.getGUID() + ",displayName=" + displayName + ",qualifiedName="+ qualifiedName); } Relationship parentRelationship = null; // apply the filter if there is one if ((attachedEntityFilterRelationshipTypeGUID != null) && (attachedEntityFilterRelationshipTypeName != null)) { parentRelationship = repositoryHandler.getUniqueParentRelationshipByType(userId, relatedEntity.getGUID(), relatedEntity.getType().getTypeDefName(), attachedEntityFilterRelationshipTypeGUID, attachedEntityFilterRelationshipTypeName, attachedEntityParentAtEnd1, forDuplicateProcessing, effectiveTime, methodName); if (log.isDebugEnabled()) { if (parentRelationship != null) { log.debug("getAttachedFilteredEntities - while found parent relationship parentRelationship" + parentRelationship.getGUID()); } } } // if there is a parentRelationship - this should not be included. if ((parentRelationship == null) && entityMatchSearchCriteria(relatedEntity, specificMatchPropertyNames, searchCriteria, !startsWith, ignoreCase)) { totalFilteredResults.add(relatedEntity); if (totalFilteredResults.size() > startFrom) { resultsToReturn.add(relatedEntity); } } } } if (resultsToReturn.isEmpty()) { return null; } else { return resultsToReturn; } } /** * Check whether the attribute values, associated with the supplied attribute names, in the supplied entity match the search criteria. This text match is influenced by the * exactValue and ignoreCase flags. * * @param entity entity to check * @param attributeNames attribute names to check the value of - these are expected to be attributes that hold text values; if they will be ignored * @param searchCriteria literal text search criteria * @param exactValue when set match exactly otherwise look for matches starting with this text * @param ignoreCase when set ignore the case, otherwise do a case sensitive match. * @return true for match otherwise false */ protected boolean entityMatchSearchCriteria(EntityDetail entity, Set attributeNames, String searchCriteria, boolean exactValue, boolean ignoreCase) { if (attributeNames == null) { return true; // TODO maybe this should be an error } if (searchCriteria == null) { return true; // nothing specific to match so it all matches } String regExedSearchCriteria = regexSearchCriteria(searchCriteria, exactValue, ignoreCase); boolean isMatch = false; InstanceProperties matchProperties = entity.getProperties(); Iterator propertyNames = matchProperties.getPropertyNames(); if (propertyNames != null) { while (propertyNames.hasNext()) { String propertyName = propertyNames.next(); if (attributeNames.contains(propertyName)) { InstancePropertyValue instancePropertyValue = matchProperties.getPropertyValue(propertyName); InstancePropertyCategory ipCat = instancePropertyValue.getInstancePropertyCategory(); if (ipCat == InstancePropertyCategory.PRIMITIVE) { PrimitivePropertyValue ppv = (PrimitivePropertyValue) instancePropertyValue; PrimitiveDefCategory pdCat = ppv.getPrimitiveDefCategory(); if (pdCat == PrimitiveDefCategory.OM_PRIMITIVE_TYPE_STRING) { String currentValue = (String) ppv.getPrimitiveValue(); if (currentValue != null && currentValue.matches(regExedSearchCriteria)) { isMatch = true; } } // TODO if there are no valid string attributes throw an error? } } } } return isMatch; } /** * Take a literal string supplied in searchCriteria and augment it with extra content for the regex engine to process. * 2 flags exactValue and ignoreCase are supplied that determine the nature of the regex expression that is created. * * @param searchCriteria text literal use as the basis of the match, if this empty then match everything ignoring the flags. * @param exactValue the exactValue flag when set means to exactly match the streing, otehrwise it looks for srings starting with the searchCriteria. * @param ignoreCase if set ignore the case on the match, if not set then the case must match. * @return a regex expression created to match implement the supplied searchCriteria and flags. */ protected String regexSearchCriteria(String searchCriteria, boolean exactValue, boolean ignoreCase) { if (searchCriteria == null || searchCriteria.trim().length() == 0) { // ignore the flags for an empty search criteria string - assume we want everything searchCriteria = ".*"; } else { // lose any leading and trailing blanks searchCriteria = searchCriteria.trim(); if (exactValue) { searchCriteria = repositoryHelper.getExactMatchRegex(searchCriteria, ignoreCase); } else { searchCriteria = repositoryHelper.getStartsWithRegex(searchCriteria, ignoreCase); } } return searchCriteria; } /** * Return the keyword for the supplied unique identifier (guid). The keyword is only returned if * * @param userId userId of the user making the request * @param requestedEntityGUID unique identifier of the entity to retrieve from the repository * @param requestedEntityGUIDParameterName name of the parameter supplying the GUID * @param requestedEntityTypeName name of type of entity to retrieve * @param requiredClassificationName String the name of the classification that must be on the attached entity. * @param omittedClassificationName String the name of a classification that must not be on the attached entity. * @param forLineage the query is to support lineage retrieval * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return retrieved entity * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public EntityDetail getEntityFromRepository(String userId, String requestedEntityGUID, String requestedEntityGUIDParameterName, String requestedEntityTypeName, String requiredClassificationName, String omittedClassificationName, boolean forLineage, boolean forDuplicateProcessing, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getEntityFromRepository(userId, requestedEntityGUID, requestedEntityGUIDParameterName, requestedEntityTypeName, requiredClassificationName, omittedClassificationName, forLineage, forDuplicateProcessing, supportedZones, effectiveTime, methodName); } /** * Return the entity for the supplied unique identifier (guid). An exception is thrown if the entity does not exist. * * @param userId userId of the user making the request * @param requestedEntityGUID unique identifier of the entity to retrieve from the repository * @param requestedEntityGUIDParameterName name of the parameter supplying the GUID * @param requestedEntityTypeName name of type of entity to retrieve * @param requiredClassificationName String the name of the classification that must be on the attached entity * @param omittedClassificationName String the name of a classification that must not be on the attached entity * @param forLineage the query is to support lineage retrieval * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate * @param serviceSupportedZones supported zones for calling service * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return retrieved entity * @throws InvalidParameterException the userId is null or invalid, the entity does not exist. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public EntityDetail getEntityFromRepository(String userId, String requestedEntityGUID, String requestedEntityGUIDParameterName, String requestedEntityTypeName, String requiredClassificationName, String omittedClassificationName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateGUID(requestedEntityGUID, requestedEntityGUIDParameterName, methodName); EntityDetail retrievedEntity = repositoryHandler.getEntityByGUID(userId, requestedEntityGUID, requestedEntityGUIDParameterName, requestedEntityTypeName, forLineage, forDuplicateProcessing, effectiveTime, methodName); /* * This method validates that the entity is visible to the calling user. */ this.validateAnchorEntity(userId, requestedEntityGUID, requestedEntityTypeName, retrievedEntity, requestedEntityGUIDParameterName, false, forLineage, forDuplicateProcessing, serviceSupportedZones, effectiveTime, methodName); boolean beanValid = true; if (requiredClassificationName != null) { try { if (repositoryHelper.getClassificationFromEntity(serviceName, retrievedEntity, requiredClassificationName, methodName) == null) { beanValid = false; } } catch (ClassificationErrorException error) { /* * Since this classification is not supported, it can not be attached to the entity */ beanValid = false; } } if (omittedClassificationName != null) { try { if (repositoryHelper.getClassificationFromEntity(serviceName, retrievedEntity, omittedClassificationName, methodName) != null) { beanValid = false; } } catch (ClassificationErrorException error) { /* * Since this classification is not supported, it can not be attached to the entity */ } } if (! beanValid) { return null; } return retrievedEntity; } /** * Use the supplied unique identifier (guid) of an entity in the repository to retrieve its contents as a bean. * The entity is checked to ensure it is of the required return type. * * @param userId calling user * @param guid unique identifier of the entity to retrieve * @param guidParameterName parameter supplying the unique identifier * @param resultTypeName type of the retrieve entity * @param methodName calling method * @return B bean * @throws InvalidParameterException one of the properties (probably the GUID) is invalid * @throws PropertyServerException the repository services hit an unexpected problem * @throws UserNotAuthorizedException the user is not permitted to access this entity */ public B getBeanFromRepository(String userId, String guid, String guidParameterName, String resultTypeName, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getBeanFromRepository(userId, guid, guidParameterName, resultTypeName, false, false, supportedZones, new Date(), methodName); } /** * Return the bean for the supplied unique identifier (guid). An exception occurs if the bean GUID is not known. * * @param userId userId of the user making the request * @param guid unique identifier of the entity to retrieve from the repository * @param guidParameterName name of the parameter supplying the GUID * @param entityTypeName name of type of entity to retrieve * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return new bean * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public B getBeanFromRepository(String userId, String guid, String guidParameterName, String entityTypeName, boolean forLineage, boolean forDuplicateProcessing, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getBeanFromRepository(userId, guid, guidParameterName, entityTypeName, forLineage, forDuplicateProcessing, supportedZones, effectiveTime, methodName); } /** * Return the bean for the supplied unique identifier (guid). An exception occurs if the bean GUID is not known. * * @param userId userId of the user making the request * @param guid unique identifier of the entity to retrieve from the repository * @param guidParameterName name of the parameter supplying the GUID * @param entityTypeName name of type of entity to retrieve * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param serviceSupportedZones supported zones for calling service * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return new bean * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public B getBeanFromRepository(String userId, String guid, String guidParameterName, String entityTypeName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateGUID(guid, guidParameterName, methodName); EntityDetail entity = this.getEntityFromRepository(userId, guid, guidParameterName, entityTypeName, null, null, forLineage, forDuplicateProcessing, serviceSupportedZones, effectiveTime, methodName); if (entity != null) { return converter.getNewBean(beanClass, entity, methodName); } return null; } /** * Return the bean for the supplied unique identifier (guid). An exception occurs if the bean GUID is not known. * * @param userId userId of the user making the request * @param entity entity retrieved from the repository * @param entityParameterName name of the parameter supplying the entity * @param methodName calling method * * @return new bean * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public B getBeanFromEntity(String userId, EntityDetail entity, String entityParameterName, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateObject(entity, entityParameterName, methodName); if (entity != null) { return converter.getNewBean(beanClass, entity, methodName); } return null; } /** * Return the requested string property for the supplied entity guid. * * @param userId calling user * @param entityGUID unique identifier of entity to retrieve * @param entityGUIDParameterName name of parameter providing the entityGUID * @param entityTypeName expected type of entity * @param propertyName name of property to extract * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @throws InvalidParameterException one of the parameters is null or invalid. * @throws PropertyServerException there is a problem retrieving the properties from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ String getBeanStringPropertyFromRepository(String userId, String entityGUID, String entityGUIDParameterName, String entityTypeName, String propertyName, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getBeanStringPropertyFromRepository(userId, entityGUID, entityGUIDParameterName, entityTypeName, propertyName, false, false, supportedZones, effectiveTime, methodName); } /** * Return the requested string property for the supplied entity guid. * * @param userId calling user * @param entityGUID unique identifier of entity to retrieve * @param entityGUIDParameterName name of parameter providing the entityGUID * @param entityTypeName expected type of entity * @param propertyName name of property to extract * @param serviceSupportedZones supported zones for calling service * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @throws InvalidParameterException one of the parameters is null or invalid. * @throws PropertyServerException there is a problem retrieving the properties from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ String getBeanStringPropertyFromRepository(String userId, String entityGUID, String entityGUIDParameterName, String entityTypeName, String propertyName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { final String propertyNameParameter = "propertyName"; invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateGUID(entityGUID, entityGUIDParameterName, methodName); invalidParameterHandler.validateName(propertyName, propertyNameParameter, methodName); String property = null; EntityDetail entityDetail = this.getEntityFromRepository(userId, entityGUID, entityGUIDParameterName, entityTypeName, null, null, forLineage, forDuplicateProcessing, serviceSupportedZones, effectiveTime, methodName); if (entityDetail != null) { property = repositoryHelper.getStringProperty(serviceName, propertyName, entityDetail.getProperties(), methodName); } return property; } /** * This method fills an instance properties object with the list of names properties, each with the supplied search value. * It is used when searching for a specific value that may be located in multiple properties. * * @param searchValue property value to store in each named property * @param specificMatchPropertyNames list of property names to * @param methodName calling method * @return instance properties object */ private InstanceProperties getSearchInstanceProperties(String searchValue, List specificMatchPropertyNames, String methodName) { InstanceProperties properties = new InstanceProperties(); for (String propertyName : specificMatchPropertyNames) { if (propertyName != null) { properties = repositoryHelper.addStringPropertyToInstance(serviceName, properties, propertyName, searchValue, methodName); } } return properties; } /** * Base on the parameters, load an appropriate repository helper iterator. * * @param userId calling user * @param searchString search string (or null to just return entities of a specific type) * @param resultTypeGUID type identifier of entities to return * @param resultTypeName type name of entities to return * @param specificMatchPropertyNames list of property names to look in (or null to search any string property) * @param exactValueMatch should the value be treated as a literal or a RegEx? * @param sequencingPropertyName should the results be sequenced? * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param startFrom index of the list to start from (0 for start) * @param queryPageSize maximum number of values to return * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * @return configured iterator */ RepositoryIteratorForEntities getEntitySearchIterator(String userId, String searchString, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean exactValueMatch, String sequencingPropertyName, boolean forLineage, boolean forDuplicateProcessing, int startFrom, int queryPageSize, Date effectiveTime, String methodName) { RepositoryIteratorForEntities iterator; if (searchString != null) { String searchValue = searchString; if (exactValueMatch) { searchValue = repositoryHelper.getExactMatchRegex(searchString); } if ((specificMatchPropertyNames == null) || (specificMatchPropertyNames.isEmpty())) { /* * Search for value in any string property */ iterator = new RepositorySelectedEntitiesIterator(repositoryHandler, userId, resultTypeGUID, searchValue, sequencingPropertyName, forLineage, forDuplicateProcessing, startFrom, queryPageSize, effectiveTime, methodName); } else { /* * Search for value in specific properties */ iterator = new RepositorySelectedEntitiesIterator(repositoryHandler, userId, resultTypeGUID, this.getSearchInstanceProperties(searchValue, specificMatchPropertyNames, methodName), MatchCriteria.ANY, sequencingPropertyName, forLineage, forDuplicateProcessing, startFrom, queryPageSize, effectiveTime, methodName); } } else { /* * Get all values of a specific type */ iterator = new RepositoryEntitiesIterator(repositoryHandler, userId, resultTypeGUID, resultTypeName, sequencingPropertyName, forLineage, forDuplicateProcessing, startFrom, queryPageSize, effectiveTime, methodName); } return iterator; } /** * Return the unique identifier of the entity that has the supplied unique name. An exception is thrown if * multiple entities are found with this name. * * @param userId the calling user * @param name value to search * @param nameParameterName parameter providing value * @param namePropertyName open metadata property name to match on * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return unique identifier of the requested entity/bean * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public String getBeanGUIDByUniqueName(String userId, String name, String nameParameterName, String namePropertyName, String resultTypeGUID, String resultTypeName, boolean forLineage, boolean forDuplicateProcessing, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getBeanGUIDByUniqueName(userId, name, nameParameterName, namePropertyName, resultTypeGUID, resultTypeName, forLineage, forDuplicateProcessing, supportedZones, effectiveTime, methodName); } /** * Return the unique identifier of the entity that has the supplied unique name. An exception is thrown if * multiple entities are found with this name. * * @param userId the calling user * @param name value to search * @param nameParameterName parameter providing value * @param namePropertyName open metadata property name to match on * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param serviceSupportedZones list of supported zones for this service * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return unique identifier of the requested entity/bean * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public String getBeanGUIDByUniqueName(String userId, String name, String nameParameterName, String namePropertyName, String resultTypeGUID, String resultTypeName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateName(name, nameParameterName, methodName); List propertyNames = new ArrayList<>(); propertyNames.add(namePropertyName); int queryPageSize = invalidParameterHandler.getMaxPagingSize(); RepositoryIteratorForEntities iterator = getEntitySearchIterator(userId, name, resultTypeGUID, resultTypeName, propertyNames, true, null, forLineage, forDuplicateProcessing, 0, queryPageSize, effectiveTime, methodName); /* * The loop is necessary because some of the entities returned may not be visible to the calling user. * Once they are filtered out, more entities need to be retrieved to fill the gaps. */ String guid = null; List duplicateEntities = new ArrayList<>(); String entityParameterName = "Entity from search of value " + name; while (iterator.moreToReceive() && ((queryPageSize == 0) || (duplicateEntities.size() < queryPageSize))) { EntityDetail entity = iterator.getNext(); if (entity != null) { duplicateEntities.add(entity.getGUID()); try { validateAnchorEntity(userId, entity.getGUID(), resultTypeName, entity, entityParameterName, false, forLineage, forDuplicateProcessing, serviceSupportedZones, effectiveTime, methodName); /* * Valid entity to return since no exception occurred. */ if (guid == null) { guid = entity.getGUID(); } } catch (InvalidParameterException | PropertyServerException | UserNotAuthorizedException invisibleEntity) { /* * Skipping entity */ } } } if (guid == null) { return null; } else if (duplicateEntities.size() == 1) { return guid; } throw new PropertyServerException(GenericHandlersErrorCode.MULTIPLE_ENTITIES_FOUND.getMessageDefinition(resultTypeName, name, duplicateEntities.toString(), methodName, nameParameterName, serverName), this.getClass().getName(), methodName); } /** * Return the list of beans of the requested type that match the supplied value. * * @param userId the calling user * @param name value to search * @param nameParameterName parameter providing value * @param namePropertyName open metadata property name to match on * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public B getBeanByUniqueName(String userId, String name, String nameParameterName, String namePropertyName, String resultTypeGUID, String resultTypeName, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return getBeanByUniqueName(userId, name, nameParameterName, namePropertyName, resultTypeGUID, resultTypeName, false, false, supportedZones, effectiveTime, methodName); } /** * Return the list of beans of the requested type that match the supplied value. * * @param userId the calling user * @param name value to search * @param nameParameterName parameter providing value * @param namePropertyName open metadata property name to match on * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param serviceSupportedZones list of supported zones for this service * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public B getBeanByUniqueName(String userId, String name, String nameParameterName, String namePropertyName, String resultTypeGUID, String resultTypeName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateName(name, nameParameterName, methodName); List propertyNames = new ArrayList<>(); propertyNames.add(namePropertyName); int queryPageSize = invalidParameterHandler.getMaxPagingSize(); RepositoryIteratorForEntities iterator = getEntitySearchIterator(userId, name, resultTypeGUID, resultTypeName, propertyNames, true, null, forLineage, forDuplicateProcessing, 0, queryPageSize, effectiveTime, methodName); /* * The loop is necessary because some of the entities returned may not be visible to the calling user. * Once they are filtered out, more entities need to be retrieved to fill the gaps. */ B bean = null; List duplicateEntities = new ArrayList<>(); String entityParameterName = "Entity from search of value " + name; while (iterator.moreToReceive() && ((queryPageSize == 0) || (duplicateEntities.size() < queryPageSize))) { EntityDetail entity = iterator.getNext(); if (entity != null) { duplicateEntities.add(entity.getGUID()); try { validateAnchorEntity(userId, entity.getGUID(), resultTypeName, entity, entityParameterName, false, forLineage, forDuplicateProcessing, serviceSupportedZones, effectiveTime, methodName); /* * Valid entity to return since no exception occurred. */ if (bean == null) { bean = converter.getNewBean(beanClass, entity, methodName); } } catch (InvalidParameterException | PropertyServerException | UserNotAuthorizedException invisibleEntity) { /* * Skipping entity */ } } } if (bean == null) { return null; } else if (duplicateEntities.size() == 1) { return bean; } throw new PropertyServerException(GenericHandlersErrorCode.MULTIPLE_ENTITIES_FOUND.getMessageDefinition(resultTypeName, name, duplicateEntities.toString(), methodName, nameParameterName, serverName), this.getClass().getName(), methodName); } /** * Return the bean that matches the requested value. * * @param userId identifier of calling user * @param value value to search * @param valueParameterName parameter providing value * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param specificMatchPropertyNames list of property value to look in - if null or empty list then all string properties are checked. * @param forLineage the query is to support lineage retrieval * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * @return matching bean. * * @throws InvalidParameterException one of the parameters is null or invalid. * @throws UserNotAuthorizedException user not authorized to issue this request. * @throws PropertyServerException problem retrieving the bean definition. */ public B getBeanByValue(String userId, String value, String valueParameterName, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean forLineage, boolean forDuplicateProcessing, Date effectiveTime, String methodName) throws InvalidParameterException, UserNotAuthorizedException, PropertyServerException { List results = this.getEntitiesByValue(userId, value, valueParameterName, resultTypeGUID, resultTypeName, specificMatchPropertyNames, true, null, null, forLineage, forDuplicateProcessing, 0, invalidParameterHandler.getMaxPagingSize(), effectiveTime, methodName); if ((results == null) || (results.isEmpty())) { return null; } else if (results.size() == 1) { return converter.getNewBean(beanClass, results.get(0), methodName); } else { errorHandler.handleAmbiguousEntityName(value, valueParameterName, resultTypeName, results, methodName); } return null; } /** * Return the list of beans of the requested type that match the supplied value. * * @param userId the calling user * @param value value to search * @param valueParameterName parameter providing value * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param specificMatchPropertyNames list of property value to look in - if null or empty list then all string properties are checked. * @param exactValueMatch indicates whether the value must match the whole property value in a matching result, or whether it is a * RegEx partial match * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getBeansByValue(String userId, String value, String valueParameterName, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean exactValueMatch, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getBeansByValue(userId, value, valueParameterName, resultTypeGUID, resultTypeName, specificMatchPropertyNames, exactValueMatch, null, null, false, false, supportedZones, sequencingPropertyName, startFrom, pageSize, effectiveTime, methodName); } /** * Return the list of beans matching the supplied searchString. * * @param userId the searchString of the calling user. * @param searchString searchString of endpoint. This may include wild card characters. * @param searchStringParameterName name of parameter providing search string * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return. * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List findBeans(String userId, String searchString, String searchStringParameterName, String resultTypeGUID, String resultTypeName, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getBeansByValue(userId, searchString, searchStringParameterName, resultTypeGUID, resultTypeName, null, false, null, null, false, false, supportedZones, sequencingPropertyName, startFrom, pageSize, effectiveTime, methodName); } /** * Return the list of beans matching the supplied searchString. * * @param userId the searchString of the calling user. * @param searchString searchString of endpoint. This may include wild card characters. * @param searchStringParameterName name of parameter providing search string * @param serviceSupportedZones list of supported zones for this service. * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return. * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List findBeans(String userId, String searchString, String searchStringParameterName, String resultTypeGUID, String resultTypeName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getBeansByValue(userId, searchString, searchStringParameterName, resultTypeGUID, resultTypeName, null, false, null, null, forLineage, forDuplicateProcessing, serviceSupportedZones, sequencingPropertyName, startFrom, pageSize, effectiveTime, methodName); } /** * Return a list of metadata elements that match the supplied criteria. The results can be returned over many pages. * * @param userId caller's userId * @param metadataElementTypeName type of interest (null means any element type) * @param metadataElementSubtypeName optional list of the subtypes of the metadataElementTypeName to * include in the search results. Null means all subtypes. * @param searchProperties Optional list of entity property conditions to match. * @param limitResultsByStatus By default, entities in all statuses (other than DELETE) are returned. However, it is possible * to specify a list of statuses (eg ACTIVE) to restrict the results to. Null means all status values. * @param searchClassifications Optional list of classifications to match. * @param asOfTime Requests a historical query of the entity. Null means return the present values. * @param sequencingProperty String name of the property that is to be used to sequence the results. * Null means do not sequence on a property name (see SequencingOrder). * @param sequencingOrder Enum defining how the results should be ordered. * @param forLineage the retrieved element is for lineage processing so include archived elements * @param forDuplicateProcessing the retrieved element is for duplicate processing so do not combine results from known duplicates. * @param startingFrom paging start point * @param pageSize maximum results that can be returned * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return a list of elements matching the supplied criteria; null means no matching elements in the metadata store. * @throws InvalidParameterException one of the search parameters is invalid * @throws UserNotAuthorizedException the governance action service is not able to access the elements * @throws PropertyServerException there is a problem accessing the metadata store */ public List findBeans(String userId, String metadataElementTypeName, List metadataElementSubtypeName, SearchProperties searchProperties, List limitResultsByStatus, SearchClassifications searchClassifications, Date asOfTime, String sequencingProperty, SequencingOrder sequencingOrder, boolean forLineage, boolean forDuplicateProcessing, int startingFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, UserNotAuthorizedException, PropertyServerException { invalidParameterHandler.validateUserId(userId, methodName); int queryPageSize = invalidParameterHandler.validatePaging(startingFrom, pageSize, methodName); /* * Now need to ensure that the anchor's classification is pushed down to the dependent elements. This is done by retrieving the * relationships. */ RepositoryFindEntitiesIterator iterator = new RepositoryFindEntitiesIterator(repositoryHandler, userId, metadataElementTypeName, metadataElementSubtypeName, searchProperties, limitResultsByStatus, searchClassifications, asOfTime, sequencingProperty, sequencingOrder, forLineage, forDuplicateProcessing, startingFrom, queryPageSize, effectiveTime, methodName); List results = new ArrayList<>(); while ((iterator.moreToReceive()) && ((queryPageSize == 0) || (results.size() < queryPageSize))) { EntityDetail entity = iterator.getNext(); if (entity != null) { results.add(converter.getNewBean(beanClass, entity, methodName)); } } if (! results.isEmpty()) { return results; } return null; } /** * Return the list of beans of the requested type that match the supplied value. * * @param userId the calling user * @param value value to search * @param valueParameterName parameter providing value * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param specificMatchPropertyNames list of property value to look in - if null or empty list then all string properties are checked. * @param exactValueMatch indicates whether the value must match the whole property value in a matching result, or whether it is a * RegEx partial match * @param serviceSupportedZones list of supported zones for this service * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getBeansByValue(String userId, String value, String valueParameterName, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean exactValueMatch, List serviceSupportedZones, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getBeansByValue(userId, value, valueParameterName, resultTypeGUID, resultTypeName, specificMatchPropertyNames, exactValueMatch, null, null, false, false, serviceSupportedZones, sequencingPropertyName, startFrom, pageSize, effectiveTime, methodName); } /** * Return the list of beans of the requested type that match the supplied value. * * @param userId the calling user * @param value value to search * @param valueParameterName parameter providing value * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param specificMatchPropertyNames list of property value to look in - if null or empty list then all string properties are checked. * @param exactValueMatch indicates whether the value must match the whole property value in a matching result, or whether it is a * RegEx partial match * @param requiredClassificationName String the name of the classification that must be on the entity. * @param omittedClassificationName String the name of a classification that must not be on the entity. * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getBeansByValue(String userId, String value, String valueParameterName, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean exactValueMatch, String requiredClassificationName, String omittedClassificationName, boolean forLineage, boolean forDuplicateProcessing, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getBeansByValue(userId, value, valueParameterName, resultTypeGUID, resultTypeName, specificMatchPropertyNames, exactValueMatch, requiredClassificationName, omittedClassificationName, forLineage, forDuplicateProcessing, supportedZones, sequencingPropertyName, startFrom, pageSize, effectiveTime, methodName); } /** * Return the list of beans of the requested type that match the supplied integer value. * * @param userId the calling user * @param value value to search * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param propertyName property to look in - if null or empty list then all string properties are checked. * @param requiredClassificationName String the name of the classification that must be on the entity. * @param omittedClassificationName String the name of a classification that must not be on the entity. * @param forLineage the query is to support lineage retrieval * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate * @param serviceSupportedZones list of supported zones for this service * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getBeansByIntValue(String userId, int value, String resultTypeGUID, String resultTypeName, String propertyName, String requiredClassificationName, String omittedClassificationName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { List entities = getEntitiesByIntValue(userId, value, resultTypeGUID, resultTypeName, propertyName, requiredClassificationName, omittedClassificationName, forLineage, forDuplicateProcessing, serviceSupportedZones, sequencingPropertyName, startFrom, pageSize, effectiveTime, methodName); if (entities != null) { List results = new ArrayList<>(); for (EntityDetail entity : entities) { if (entity != null) { B bean = converter.getNewBean(beanClass, entity, methodName); results.add(bean); } } if (! results.isEmpty()) { return results; } } return null; } /** * Return the list of beans of the requested type that match the supplied value. * * @param userId the calling user * @param value value to search * @param valueParameterName parameter providing value * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param specificMatchPropertyNames list of property value to look in - if null or empty list then all string properties are checked. * @param exactValueMatch indicates whether the value must match the whole property value in a matching result, or whether it is a * RegEx partial match * @param requiredClassificationName String the name of the classification that must be on the entity. * @param omittedClassificationName String the name of a classification that must not be on the entity. * @param forLineage the query is to support lineage retrieval * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate * @param serviceSupportedZones list of supported zones for this service * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getBeansByValue(String userId, String value, String valueParameterName, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean exactValueMatch, String requiredClassificationName, String omittedClassificationName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { List entities = getEntitiesByValue(userId, value, valueParameterName, resultTypeGUID, resultTypeName, specificMatchPropertyNames, exactValueMatch, requiredClassificationName, omittedClassificationName, forLineage, forDuplicateProcessing, serviceSupportedZones, sequencingPropertyName, startFrom, pageSize, effectiveTime, methodName); if (entities != null) { List results = new ArrayList<>(); for (EntityDetail entity : entities) { if (entity != null) { B bean = converter.getNewBean(beanClass, entity, methodName); results.add(bean); } } if (! results.isEmpty()) { return results; } } return null; } /** * Return the list of entities of the requested type. * * @param userId the name of the calling user * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique name of the type that the results should match with * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param forLineage the query is to support lineage retrieval * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of entities * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getEntitiesByType(String userId, String resultTypeGUID, String resultTypeName, String sequencingPropertyName, boolean forLineage, boolean forDuplicateProcessing, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return getEntitiesByType(userId, resultTypeGUID, resultTypeName, sequencingPropertyName, forLineage, forDuplicateProcessing, supportedZones, startFrom, pageSize, effectiveTime, methodName); } /** * Return the list of entities of the requested type. * * @param userId the name of the calling user * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique name of the type that the results should match with * @param serviceSupportedZones list of supported zones for this service * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param forLineage the query is to support lineage retrieval * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of entities * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getEntitiesByType(String userId, String resultTypeGUID, String resultTypeName, String sequencingPropertyName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); int queryPageSize = invalidParameterHandler.validatePaging(startFrom, pageSize, methodName); RepositoryIteratorForEntities iterator = getEntitySearchIterator(userId, null, resultTypeGUID, resultTypeName, null, false, sequencingPropertyName, forLineage, forDuplicateProcessing, 0, queryPageSize, effectiveTime, methodName); /* * The loop is necessary because some of the entities returned may not be visible to the calling user. * Once they are filtered out, more entities need to be retrieved to fill the gaps. */ List results = new ArrayList<>(); String entityParameterName = "Entity of type" + resultTypeName; int skippedValues = 0; while (iterator.moreToReceive() && ((queryPageSize == 0) || (results.size() < queryPageSize))) { EntityDetail entity = iterator.getNext(); if (entity != null) { try { validateAnchorEntity(userId, entity.getGUID(), resultTypeName, entity, entityParameterName, false, forLineage, forDuplicateProcessing, serviceSupportedZones, effectiveTime, methodName); boolean beanArchived = false; try { /* * The Memento classification means the entity is archived and should only be returned for lineage requests. * This method is not to be used for lineage requests. */ if (repositoryHelper.getClassificationFromEntity(serviceName, entity, OpenMetadataAPIMapper.MEMENTO_CLASSIFICATION_TYPE_NAME, methodName) != null) { beanArchived = true; } } catch (ClassificationErrorException error) { /* * Since this classification is not supported, it can not be attached to the entity. */ } if (! beanArchived) { /* * Valid entity to return since no exception occurred and it is not archived. */ if (skippedValues < startFrom) { skippedValues ++; } else { results.add(entity); } } } catch (InvalidParameterException | PropertyServerException | UserNotAuthorizedException invisibleEntity) { /* * Skipping entity */ } } } if (results.isEmpty()) { return null; } else { return results; } } /** * Return the list of entities matching the supplied searchString. * * @param userId the searchString of the calling user. * @param searchString searchString of endpoint. This may include wild card characters. * @param searchStringParameterName name of parameter providing search string * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param requiredClassificationName String the name of the classification that must be on the entity. * @param omittedClassificationName String the name of a classification that must not be on the entity. * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return. * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of entities * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List findEntities(String userId, String searchString, String searchStringParameterName, String resultTypeGUID, String resultTypeName, String requiredClassificationName, String omittedClassificationName, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateSearchString(searchString, searchStringParameterName, methodName); return this.getEntitiesByValue(userId, searchString, searchStringParameterName, resultTypeGUID, resultTypeName, null, false, requiredClassificationName, omittedClassificationName, false, false, supportedZones, sequencingPropertyName, startFrom, pageSize, effectiveTime, methodName); } /** * Return the list of entities of the requested type that match the supplied value. * * @param userId the calling user * @param value value to search * @param valueParameterName parameter providing value * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param specificMatchPropertyNames list of property value to look in - if null or empty list then all string properties are checked. * @param exactValueMatch indicates whether the value must match the whole property value in a matching result, or whether it is a * RegEx partial match * @param requiredClassificationName String the name of the classification that must be on the attached entity. * @param omittedClassificationName String the name of a classification that must not be on the attached entity. * @param forLineage the query is to support lineage retrieval * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getEntitiesByValue(String userId, String value, String valueParameterName, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean exactValueMatch, String requiredClassificationName, String omittedClassificationName, boolean forLineage, boolean forDuplicateProcessing, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getEntitiesByValue(userId, value, valueParameterName, resultTypeGUID, resultTypeName, specificMatchPropertyNames, exactValueMatch, requiredClassificationName, omittedClassificationName, forLineage, forDuplicateProcessing, supportedZones, null, startFrom, pageSize, effectiveTime, methodName); } /** * Return the list of entities of the requested type that match the supplied value. * * @param userId the calling user * @param value value to search * @param valueParameterName parameter providing value * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param specificMatchPropertyNames list of property value to look in - if null or empty list then all string properties are checked. * @param exactValueMatch indicates whether the value must match the whole property value in a matching result, or whether it is a * RegEx partial match * @param requiredClassificationName String the name of the classification that must be on the attached entity. * @param omittedClassificationName String the name of a classification that must not be on the attached entity. * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getEntitiesByValue(String userId, String value, String valueParameterName, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean exactValueMatch, String requiredClassificationName, String omittedClassificationName, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getEntitiesByValue(userId, value, valueParameterName, resultTypeGUID, resultTypeName, specificMatchPropertyNames, exactValueMatch, requiredClassificationName, omittedClassificationName, false, false, supportedZones, sequencingPropertyName, startFrom, pageSize, effectiveTime, methodName); } /** * Return the list of entities of the requested type that match the supplied value. * * @param userId the calling user * @param value value to search * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param propertyName property name to look in. * @param requiredClassificationName String the name of the classification that must be on the attached entity * @param omittedClassificationName String the name of a classification that must not be on the attached entity * @param forLineage boolean indicating whether the entity is being retrieved for a lineage request or not * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate * @param serviceSupportedZones list of supported zones for this service * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getEntitiesByIntValue(String userId, int value, String resultTypeGUID, String resultTypeName, String propertyName, String requiredClassificationName, String omittedClassificationName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { final String propertyParameterName = "propertyName"; String entityParameterName = "Entity from search of value " + value; invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateName(propertyName, propertyParameterName, methodName); int queryPageSize = invalidParameterHandler.validatePaging(startFrom, pageSize, methodName); RepositoryIteratorForEntities iterator = new RepositorySelectedEntitiesIterator(repositoryHandler, userId, resultTypeGUID, repositoryHelper.addIntPropertyToInstance(serviceName, null, propertyName, value, methodName), MatchCriteria.ANY, sequencingPropertyName, forLineage, forDuplicateProcessing, 0, queryPageSize, effectiveTime, methodName); return getEntitiesByValue(userId, iterator, entityParameterName, resultTypeName, requiredClassificationName, omittedClassificationName, forLineage, forDuplicateProcessing, serviceSupportedZones, startFrom, queryPageSize, effectiveTime, methodName); } /** * Return the list of entities of the requested type that match the supplied value. * * @param userId the calling user * @param value value to search * @param valueParameterName parameter providing value * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param specificMatchPropertyNames list of property value to look in - if null or empty list then all string properties are checked. * @param exactValueMatch indicates whether the value must match the whole property value in a matching result, or whether it is a * RegEx partial match * @param requiredClassificationName String the name of the classification that must be on the attached entity * @param omittedClassificationName String the name of a classification that must not be on the attached entity * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getEntitiesByValue(String userId, String value, String valueParameterName, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean exactValueMatch, String requiredClassificationName, String omittedClassificationName, boolean forLineage, boolean forDuplicateProcessing, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getEntitiesByValue(userId, value, valueParameterName, resultTypeGUID, resultTypeName, specificMatchPropertyNames, exactValueMatch, requiredClassificationName, omittedClassificationName, forLineage, forDuplicateProcessing, supportedZones, sequencingPropertyName, startFrom, pageSize, effectiveTime, methodName); } /** * Return the list of entities of the requested type that match the supplied value. * * @param userId the calling user * @param value value to search * @param valueParameterName parameter providing value * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param specificMatchPropertyNames list of property value to look in - if null or empty list then all string properties are checked. * @param exactValueMatch indicates whether the value must match the whole property value in a matching result, or whether it is a * RegEx partial match * @param requiredClassificationName String the name of the classification that must be on the attached entity * @param omittedClassificationName String the name of a classification that must not be on the attached entity * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param serviceSupportedZones list of supported zones for this service * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getEntitiesByValue(String userId, String value, String valueParameterName, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean exactValueMatch, String requiredClassificationName, String omittedClassificationName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateName(value, valueParameterName, methodName); int queryPageSize = invalidParameterHandler.validatePaging(startFrom, pageSize, methodName); /* * Notice that the startFrom is 0 - is allows the filtering process to skip over the right number of * elements. */ RepositoryIteratorForEntities iterator = getEntitySearchIterator(userId, value, resultTypeGUID, resultTypeName, specificMatchPropertyNames, exactValueMatch, sequencingPropertyName, forLineage, forDuplicateProcessing, 0, queryPageSize, effectiveTime, methodName); String entityParameterName = "Entity from search of value " + value; return getEntitiesByValue(userId, iterator, entityParameterName, resultTypeName, requiredClassificationName, omittedClassificationName, forLineage, forDuplicateProcessing, serviceSupportedZones, startFrom, queryPageSize, effectiveTime, methodName); } /** * Return the list of entities of the requested type that match the supplied value. * * @param userId the calling user * @param iterator mechanism for search * @param entityParameterName parameter description * @param resultTypeName unique value of the type that the results should match with * @param requiredClassificationName String the name of the classification that must be on the attached entity * @param omittedClassificationName String the name of a classification that must not be on the attached entity * @param forLineage boolean indicating whether the entity is being retrieved for a lineage request or not * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate * @param serviceSupportedZones list of supported zones for this service * @param startFrom index of the list to start from (0 for start) * @param queryPageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getEntitiesByValue(String userId, RepositoryIteratorForEntities iterator, String entityParameterName, String resultTypeName, String requiredClassificationName, String omittedClassificationName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, int startFrom, int queryPageSize, Date effectiveTime, String methodName) throws PropertyServerException, UserNotAuthorizedException { /* * The loop is necessary because some of the entities returned may not be visible to the calling user. * Once they are filtered out, more entities need to be retrieved to fill the gaps. */ List results = new ArrayList<>(); int skippedValues = 0; while (iterator.moreToReceive() && ((queryPageSize == 0) || (results.size() < queryPageSize))) { EntityDetail entity = iterator.getNext(); if (entity != null) { boolean beanValid = true; try { validateAnchorEntity(userId, entity.getGUID(), resultTypeName, entity, entityParameterName, false, forLineage, forDuplicateProcessing, serviceSupportedZones, effectiveTime, methodName); if (requiredClassificationName != null) { try { if (repositoryHelper.getClassificationFromEntity(serviceName, entity, requiredClassificationName, methodName) == null) { beanValid = false; } } catch (ClassificationErrorException error) { /* * Since this classification is not supported, it can not be attached to the entity */ beanValid = false; } } } catch (InvalidParameterException | PropertyServerException | UserNotAuthorizedException invisibleEntity) { /* * Skipping entity */ beanValid = false; } if (beanValid) { /* * Ignore entities until it reaches the start point */ if (skippedValues < startFrom) { skippedValues ++; } else { results.add(entity); } } } } if (results.isEmpty()) { return null; } else { return results; } } /** * Return the list of entities of the requested type that match the supplied value. * * @param userId the calling user * @param value value to search * @param valueParameterName parameter providing value * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param specificMatchPropertyNames list of property value to look in - if null or empty list then all string properties are checked. * @param exactValueMatch indicates whether the value must match the whole property value in a matching result, or whether it is a * RegEx partial match * @param requiredClassificationName String the name of the classification that must be on the attached entity. * @param omittedClassificationName String the name of a classification that must not be on the attached entity. * @param forLineage boolean indicating whether the entity is being retrieved for a lineage request or not. * @param serviceSupportedZones list of supported zones for this service * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getEntityGUIDsByValue(String userId, String value, String valueParameterName, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean exactValueMatch, String requiredClassificationName, String omittedClassificationName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { List entities = this.getEntitiesByValue(userId, value, valueParameterName, resultTypeGUID, resultTypeName, specificMatchPropertyNames, exactValueMatch, requiredClassificationName, omittedClassificationName, forLineage, forDuplicateProcessing, serviceSupportedZones, sequencingPropertyName, startFrom, pageSize, effectiveTime, methodName); if (entities != null) { List results = new ArrayList<>(); for (EntityDetail entity : entities) { if (entity != null) { results.add(entity.getGUID()); } } if (! results.isEmpty()) { return results; } } return null; } /** * Return the entity that matches the requested value. * * @param userId identifier of calling user * @param value value to search * @param valueParameterName parameter providing value * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param specificMatchPropertyNames list of property value to look in - if null or empty list then all string properties are checked. * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the query is for duplicate processing and so must not deduplicate * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * @return requested entity * * @throws InvalidParameterException one of the parameters is null or invalid. * @throws UserNotAuthorizedException user not authorized to issue this request. * @throws PropertyServerException problem retrieving the entity. */ public EntityDetail getEntityByValue(String userId, String value, String valueParameterName, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean forLineage, boolean forDuplicateProcessing, Date effectiveTime, String methodName) throws InvalidParameterException, UserNotAuthorizedException, PropertyServerException { List results = this.getEntitiesByValue(userId, value, valueParameterName, resultTypeGUID, resultTypeName, specificMatchPropertyNames, true, null, null, forLineage, forDuplicateProcessing, supportedZones, null, 0, invalidParameterHandler.getMaxPagingSize(), effectiveTime, methodName); if (results != null) { if (results.size() == 1) { return results.get(0); } else if (results.size() > 1) { errorHandler.handleAmbiguousEntityName(value, valueParameterName, resultTypeName, results, methodName); } } return null; } /** * Return the unique identifier of the entity matching the value. * * @param userId identifier of calling user * @param value value to search * @param valueParameterName parameter providing value * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param specificMatchPropertyNames list of property value to look in - if null or empty list then all string properties are checked. * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * @return unique identifier of the requested entity. * * @throws InvalidParameterException one of the parameters is null or invalid. * @throws UserNotAuthorizedException user not authorized to issue this request. * @throws PropertyServerException problem retrieving the entity. */ public String getEntityGUIDByValue(String userId, String value, String valueParameterName, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean forLineage, boolean forDuplicateProcessing, Date effectiveTime, String methodName) throws InvalidParameterException, UserNotAuthorizedException, PropertyServerException { EntityDetail result = this.getEntityByValue(userId, value, valueParameterName, resultTypeGUID, resultTypeName, specificMatchPropertyNames, forLineage, forDuplicateProcessing, effectiveTime, methodName); if (result != null) { return result.getGUID(); } return null; } /** * Return the list of beans of the requested type that match the supplied value. * * @param userId the searchString of the calling user. * @param searchString searchString of endpoint. This may include wild card characters. * @param searchStringParameterName name of parameter providing search string * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return. * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List findBeanGUIDs(String userId, String searchString, String searchStringParameterName, String resultTypeGUID, String resultTypeName, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getEntityGUIDsByValue(userId, searchString, searchStringParameterName, resultTypeGUID, resultTypeName, null, false, null, null, false, false, supportedZones, sequencingPropertyName, startFrom, pageSize, effectiveTime, methodName); } /** * Return the list of beans of the requested type that match the supplied value. * * @param userId the calling user * @param value value to search * @param valueParameterName parameter providing value * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique value of the type that the results should match with * @param specificMatchPropertyNames list of property value to look in - if null or empty list then all string properties are checked. * @param exactValueMatch indicates whether the value must match the whole property value in a matching result, or whether it is a * RegEx partial match * @param serviceSupportedZones list of supported zones for this service * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getBeanGUIDsByValue(String userId, String value, String valueParameterName, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean exactValueMatch, List serviceSupportedZones, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getEntityGUIDsByValue(userId, value, valueParameterName, resultTypeGUID, resultTypeName, specificMatchPropertyNames, exactValueMatch, null, null, false, false, serviceSupportedZones, sequencingPropertyName, startFrom, pageSize, effectiveTime, methodName); } /** * Return the list of beans of the requested type that were created by the requesting user. * * @param userId the name of the calling user * @param searchString value that describes what to search for * @param searchStringParameterName parameter providing search string * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique name of the type that the results should match with * @param specificMatchPropertyNames name of properties to visit * @param exactValueMatch does the value need to match exactly? * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param serviceSupportedZones list of supported zones for this service * @param sequencingPropertyName should the results be sequenced? * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getBeansByCreator(String userId, String searchString, String searchStringParameterName, String resultTypeGUID, String resultTypeName, List specificMatchPropertyNames, boolean exactValueMatch, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateName(searchString, searchStringParameterName, methodName); int queryPageSize = invalidParameterHandler.validatePaging(startFrom, pageSize, methodName); RepositoryIteratorForEntities iterator = getEntitySearchIterator(userId, searchString, resultTypeGUID, resultTypeName, specificMatchPropertyNames, exactValueMatch, sequencingPropertyName, forLineage, forDuplicateProcessing, 0, queryPageSize, effectiveTime, methodName); /* * The loop is necessary because some of the entities returned may not be visible to the calling user. * Once they are filtered out, more entities need to be retrieved to fill the gaps. */ List results = new ArrayList<>(); String entityParameterName = "Entity from createdBy search of " + searchString; int skippedValues = 0; while (iterator.moreToReceive() && ((queryPageSize == 0) || (results.size() < queryPageSize))) { EntityDetail entity = iterator.getNext(); if (entity != null) { /* * Eliminate anything that was not created by this user */ if (userId.equals(entity.getCreatedBy())) { try { validateAnchorEntity(userId, entity.getGUID(), resultTypeName, entity, entityParameterName, false, forLineage, forDuplicateProcessing, serviceSupportedZones, effectiveTime, methodName); boolean beanArchived = false; try { /* * The Memento classification means the entity is archived and should only be returned for lineage requests. * This method is not to be used for lineage requests. */ if (repositoryHelper.getClassificationFromEntity(serviceName, entity, OpenMetadataAPIMapper.MEMENTO_CLASSIFICATION_TYPE_NAME, methodName) != null) { beanArchived = true; } } catch (ClassificationErrorException error) { /* * Since this classification is not supported, it can not be attached to the entity. */ } /* * Valid entity to return since no exception occurred. */ if (! beanArchived) { B bean = converter.getNewBean(beanClass, entity, methodName); if (bean != null) { if (skippedValues < startFrom) { skippedValues ++; } else { results.add(bean); } } } } catch (InvalidParameterException | PropertyServerException | UserNotAuthorizedException invisibleEntity) { /* * Skipping entity */ } } } } if (results.isEmpty()) { return null; } else { return results; } } /** * Return the list of beans of the requested type. * * @param userId the name of the calling user * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique name of the type that the results should match with * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getBeansByType(String userId, String resultTypeGUID, String resultTypeName, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return getBeansByType(userId, resultTypeGUID, resultTypeName, sequencingPropertyName,false, false, supportedZones, startFrom, pageSize, effectiveTime, methodName); } /** * Return the list of beans of the requested type that match the supplied name. * * @param userId the name of the calling user * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique name of the type that the results should match with * @param serviceSupportedZones list of supported zones for this service * @param sequencingPropertyName name of property used to sequence the results - null means no sequencing * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getBeansByType(String userId, String resultTypeGUID, String resultTypeName, String sequencingPropertyName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { List entities = this.getEntitiesByType(userId, resultTypeGUID, resultTypeName, sequencingPropertyName, forLineage, forDuplicateProcessing, serviceSupportedZones, startFrom, pageSize, effectiveTime, methodName); if ((entities != null) && (! entities.isEmpty())) { List results = new ArrayList<>(); for (EntityDetail entity : entities) { if (entity != null) { B bean = converter.getNewBean(beanClass, entity, methodName); if (bean != null) { results.add(bean); } } } if (! results.isEmpty()) { return results; } } return null; } /** * Return the list of beans of the requested type. * * @param userId the name of the calling user * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique name of the type that the results should match with * @param sequencingPropertyName should the results be sequenced? * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of unique identifiers for matching beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getBeanGUIDsByType(String userId, String resultTypeGUID, String resultTypeName, String sequencingPropertyName, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.getBeanGUIDsByType(userId, resultTypeGUID, resultTypeName, sequencingPropertyName, false, false, supportedZones, startFrom, pageSize, effectiveTime, methodName); } /** * Return the list of beans of the requested type. * * @param userId the name of the calling user * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultTypeName unique name of the type that the results should match with * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param serviceSupportedZones list of supported zones for this service * @param sequencingPropertyName should the results be sequenced? * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of unique identifiers for matching beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getBeanGUIDsByType(String userId, String resultTypeGUID, String resultTypeName, String sequencingPropertyName, boolean forLineage, boolean forDuplicateProcessing, List serviceSupportedZones, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); int queryPageSize = invalidParameterHandler.validatePaging(startFrom, pageSize, methodName); RepositoryIteratorForEntities iterator = getEntitySearchIterator(userId, null, resultTypeGUID, resultTypeName, null, false, sequencingPropertyName, forLineage, forDuplicateProcessing, 0, queryPageSize, effectiveTime, methodName); /* * The loop is necessary because some of the entities returned may not be visible to the calling user. * Once they are filtered out, more entities need to be retrieved to fill the gaps. */ List results = new ArrayList<>(); String entityParameterName = "Entity of type" + resultTypeName; int skippedValues = 0; while (iterator.moreToReceive() && ((queryPageSize == 0) || (results.size() < queryPageSize))) { EntityDetail entity = iterator.getNext(); if (entity != null) { try { validateAnchorEntity(userId, entity.getGUID(), resultTypeName, entity, entityParameterName, false, forLineage, forDuplicateProcessing, serviceSupportedZones, effectiveTime, methodName); /* * Valid entity to return since no exception occurred. */ if (skippedValues < startFrom) { skippedValues ++; } else { results.add(entity.getGUID()); } } catch (InvalidParameterException | PropertyServerException | UserNotAuthorizedException invisibleEntity) { /* * Skipping entity */ } } } if (results.isEmpty()) { return null; } else { return results; } } /** * Return the list of beans of the requested type that match the supplied classification. * * @param userId the name of the calling user * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultClassificationName unique name of the classification that the results should match with * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for * @param methodName calling method * * @return list of beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getBeansByClassification(String userId, String resultTypeGUID, String resultClassificationName, boolean forLineage, boolean forDuplicateProcessing, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); int queryPageSize = invalidParameterHandler.validatePaging(startFrom, pageSize, methodName); List entities = repositoryHandler.getEntitiesForClassificationType(userId, resultTypeGUID, resultClassificationName, forLineage, forDuplicateProcessing, startFrom, queryPageSize, effectiveTime, methodName); if (entities != null) { List softwareServerCapabilities = new ArrayList<>(); for (EntityDetail entity : entities) { if (entity != null) { softwareServerCapabilities.add(converter.getNewBean(beanClass, entity, methodName)); } } if (! softwareServerCapabilities.isEmpty()) { return softwareServerCapabilities; } } return null; } /** * Return the list of beans of the requested type that match the supplied classification. * * @param userId the name of the calling user * @param resultTypeGUID unique identifier of the type that the results should match with * @param resultClassificationName unique name of the classification that the results should match with * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param startFrom index of the list to start from (0 for start) * @param pageSize maximum number of elements to return * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return list of guids representing beans * @throws InvalidParameterException the userId is null or invalid. * @throws PropertyServerException there is a problem retrieving information from the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public List getBeanGUIDsByClassification(String userId, String resultTypeGUID, String resultClassificationName, boolean forLineage, boolean forDuplicateProcessing, int startFrom, int pageSize, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); int queryPageSize = invalidParameterHandler.validatePaging(startFrom, pageSize, methodName); List entities = repositoryHandler.getEntitiesForClassificationType(userId, resultTypeGUID, resultClassificationName, forLineage, forDuplicateProcessing, startFrom, queryPageSize, effectiveTime, methodName); if (entities != null) { List softwareServerCapabilityGUIDs = new ArrayList<>(); for (EntityDetail entity : entities) { if (entity != null) { softwareServerCapabilityGUIDs.add(entity.getGUID()); } } if (! softwareServerCapabilityGUIDs.isEmpty()) { return softwareServerCapabilityGUIDs; } } return null; } /** * Creates a relationship between two elements and updates the LatestChange in each one's anchor entity (if they have one). * Both elements must be visible to the user to allow the link. * * @param userId userId of user making request * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local * @param externalSourceName name of the software server capability entity that represented the external source * @param startingGUID unique id for the starting element's entity * @param startingGUIDParameterName name of the parameter supplying the startingGUID * @param startingElementTypeName type name of the starting element's entity * @param attachingGUID unique id of the entity for the element that is being attached * @param attachingGUIDParameterName name of the parameter supplying the attachingGUID * @param attachingElementTypeName type name of the attaching element's entity * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param relationshipTypeGUID unique identifier of type of the relationship to create * @param relationshipTypeName unique name of type of the relationship to create * @param relationshipProperties properties to add to the relationship or null if no properties to add * @param methodName calling method * * @return unique identifier of the new relationship * @throws InvalidParameterException one of the parameters is null or invalid. * @throws PropertyServerException there is a problem updating the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public String linkElementToElement(String userId, String externalSourceGUID, String externalSourceName, String startingGUID, String startingGUIDParameterName, String startingElementTypeName, String attachingGUID, String attachingGUIDParameterName, String attachingElementTypeName, boolean forLineage, boolean forDuplicateProcessing, String relationshipTypeGUID, String relationshipTypeName, InstanceProperties relationshipProperties, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { return this.linkElementToElement(userId, externalSourceGUID, externalSourceName, startingGUID, startingGUIDParameterName, startingElementTypeName, attachingGUID, attachingGUIDParameterName, attachingElementTypeName, forLineage, forDuplicateProcessing, supportedZones, relationshipTypeGUID, relationshipTypeName, relationshipProperties, methodName); } /** * Creates a relationship between two elements and updates the LatestChange in each one's anchor entity (if they have one). * Both elements must be visible to the user to allow the link. If the relationship already exists with matching effectivity dates, * the properties are updated. * * @param userId userId of user making request * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local * @param externalSourceName name of the software server capability entity that represented the external source * @param startingGUID unique id for the starting element's entity * @param startingGUIDParameterName name of the parameter supplying the startingGUID * @param startingElementTypeName type name of the starting element's entity * @param attachingGUID unique id of the entity for the element that is being attached * @param attachingGUIDParameterName name of the parameter supplying the attachingGUID * @param attachingElementTypeName type name of the attaching element's entity * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param suppliedSupportedZones list of zones that any asset must be a member of at least one to be visible * @param attachmentTypeGUID unique identifier of type of the relationship to create * @param attachmentTypeName unique name of type of the relationship to create * @param relationshipProperties properties to add to the relationship or null if no properties to add * @param methodName calling method * * @return unique identifier of the new relationship * @throws InvalidParameterException one of the parameters is null or invalid. * @throws PropertyServerException there is a problem adding the relationship to the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public String linkElementToElement(String userId, String externalSourceGUID, String externalSourceName, String startingGUID, String startingGUIDParameterName, String startingElementTypeName, String attachingGUID, String attachingGUIDParameterName, String attachingElementTypeName, boolean forLineage, boolean forDuplicateProcessing, List suppliedSupportedZones, String attachmentTypeGUID, String attachmentTypeName, InstanceProperties relationshipProperties, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { Date effectiveFrom = null; Date effectiveTo = null; if (relationshipProperties != null) { effectiveFrom = relationshipProperties.getEffectiveFromTime(); effectiveTo = relationshipProperties.getEffectiveToTime(); } Date effectiveTime = getEffectiveTime(effectiveFrom, effectiveTo); return linkElementToElement(userId, externalSourceGUID, externalSourceName, startingGUID, startingGUIDParameterName, startingElementTypeName, attachingGUID, attachingGUIDParameterName, attachingElementTypeName, forLineage, forDuplicateProcessing, suppliedSupportedZones, attachmentTypeGUID, attachmentTypeName, relationshipProperties, effectiveFrom, effectiveTo, effectiveTime, methodName); } /** * Creates a relationship between two elements and updates the LatestChange in each one's anchor entity (if they have one). * Both elements must be visible to the user to allow the link. If the relationship already exists with matching effectivity dates, * the properties are updated. * * @param userId userId of user making request * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local * @param externalSourceName name of the software server capability entity that represented the external source * @param startingElementGUID unique id for the starting element's entity * @param startingGUIDParameterName name of the parameter supplying the startingGUID * @param startingElementTypeName type name of the starting element's entity * @param attachingElementGUID unique id of the entity for the element that is being attached * @param attachingGUIDParameterName name of the parameter supplying the attachingGUID * @param attachingElementTypeName type name of the attaching element's entity * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param suppliedSupportedZones list of zones that any asset must be a member of at least one to be visible * @param attachmentTypeGUID unique identifier of type of the relationship to create * @param attachmentTypeName unique name of type of the relationship to create * @param relationshipProperties properties to add to the relationship or null if no properties to add * @param effectiveFrom the date when this element is active - null for active now * @param effectiveTo the date when this element becomes inactive - null for active until deleted * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return unique identifier of the new relationship * @throws InvalidParameterException one of the parameters is null or invalid. * @throws PropertyServerException there is a problem adding the relationship to the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public String linkElementToElement(String userId, String externalSourceGUID, String externalSourceName, String startingElementGUID, String startingGUIDParameterName, String startingElementTypeName, String attachingElementGUID, String attachingGUIDParameterName, String attachingElementTypeName, boolean forLineage, boolean forDuplicateProcessing, List suppliedSupportedZones, String attachmentTypeGUID, String attachmentTypeName, InstanceProperties relationshipProperties, Date effectiveFrom, Date effectiveTo, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateGUID(startingElementGUID, startingGUIDParameterName, methodName); invalidParameterHandler.validateGUID(attachingElementGUID, attachingGUIDParameterName, methodName); String startingGUID = startingElementGUID; String attachingGUID = attachingElementGUID; EntityDetail startingElementEntity = repositoryHandler.getEntityByGUID(userId, startingElementGUID, startingGUIDParameterName, startingElementTypeName, forLineage, forDuplicateProcessing, effectiveTime, methodName); if (startingElementEntity != null) { /* * In case a consolidated entity is returned */ startingGUID = startingElementEntity.getGUID(); } EntityDetail startingElementAnchorEntity = this.validateAnchorEntity(userId, startingGUID, startingElementTypeName, startingElementEntity, startingGUIDParameterName, false, forLineage, forDuplicateProcessing, suppliedSupportedZones, effectiveTime, methodName); EntityDetail attachingElementEntity = repositoryHandler.getEntityByGUID(userId, attachingGUID, attachingGUIDParameterName, attachingElementTypeName, forLineage, forDuplicateProcessing, effectiveTime, methodName); if (attachingElementEntity != null) { /* * In case a consolidated entity is returned */ attachingGUID = attachingElementEntity.getGUID(); } EntityDetail attachingElementAnchorEntity = this.validateAnchorEntity(userId, attachingGUID, attachingElementTypeName, attachingElementEntity, attachingGUIDParameterName, false, forLineage, forDuplicateProcessing, suppliedSupportedZones, effectiveTime, methodName); /* * The calls above validate the existence of the two entities and that they are visible to the user. * An exception is thrown if there are any problems. * The anchor entities are returned if there are anchor entities associated with a specific end. * * The next test ensures that the effectivity dates in the new relationship's properties are compatible with the * existing relationships. */ List existingRelationships = repositoryHandler.getRelationshipsBetweenEntities(userId, startingGUID, startingElementTypeName, attachingGUID, attachmentTypeGUID, attachmentTypeName, forDuplicateProcessing, effectiveFrom, effectiveTo, true, methodName); Relationship newRelationship; String actionDescriptionTemplate; int actionOrdinal; if (existingRelationships != null) { if (existingRelationships.size() == 1) { actionDescriptionTemplate = "Updating link from %s %s to %s %s"; actionOrdinal = OpenMetadataAPIMapper.UPDATED_LATEST_CHANGE_ACTION_ORDINAL; newRelationship = repositoryHandler.updateRelationshipProperties(userId, externalSourceGUID, externalSourceName, existingRelationships.get(0), relationshipProperties, methodName); } else { String guids = null; for (Relationship relationship : existingRelationships) { if (relationship != null) { if (guids == null) { guids = "["; } else { guids = ", "; } guids = guids + relationship.getGUID() + " "; } } if (guids == null) { guids = "[]"; } else { guids = guids + "]"; } throw new InvalidParameterException(GenericHandlersErrorCode.MULTIPLE_RELATIONSHIPS_FOUND.getMessageDefinition(attachmentTypeName, startingElementTypeName, startingGUID, attachingElementTypeName, attachingGUID, guids, methodName, serverName), this.getClass().getName(), methodName, attachmentTypeName); } } else { actionDescriptionTemplate = "Linking %s %s to %s %s"; actionOrdinal = OpenMetadataAPIMapper.CREATED_LATEST_CHANGE_ACTION_ORDINAL; newRelationship = repositoryHandler.createRelationship(userId, attachmentTypeGUID, externalSourceGUID, externalSourceName, startingGUID, attachingGUID, relationshipProperties, methodName); } /* * Final stage is to add the latest change classification to the anchor(s). * The act of creating the relationship may set up the anchor GUID in either element. */ String startingElementAnchorGUID; if (startingElementAnchorEntity == null) { startingElementAnchorGUID = this.reEvaluateAnchorGUID(startingGUID, startingGUIDParameterName, startingElementTypeName, startingElementEntity, null, forLineage, forDuplicateProcessing, effectiveTime, methodName); if (startingElementAnchorGUID != null) { if ((attachingElementAnchorEntity != null) && (attachingElementAnchorEntity.getGUID().equals(startingElementAnchorGUID))) { startingElementAnchorEntity = attachingElementAnchorEntity; } else { final String anchorGUIDParameterName = "startingElementAnchorGUID"; startingElementAnchorEntity = repositoryHandler.getEntityByGUID(userId, startingElementAnchorGUID, anchorGUIDParameterName, OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME, forLineage, forDuplicateProcessing, effectiveTime, methodName); } } else { startingElementAnchorGUID = startingGUID; } } else { startingElementAnchorGUID = startingElementAnchorEntity.getGUID(); } if (attachingElementAnchorEntity == null) { String attachingElementAnchorGUID = this.reEvaluateAnchorGUID(attachingGUID, attachingGUIDParameterName, attachingElementTypeName, attachingElementEntity, null, forLineage, forDuplicateProcessing, effectiveTime, methodName); if (attachingElementAnchorGUID != null) { if (attachingElementAnchorGUID.equals(startingElementAnchorGUID)) { attachingElementAnchorEntity = startingElementAnchorEntity; } else { final String anchorGUIDParameterName = "attachingElementAnchorGUID"; attachingElementAnchorEntity = repositoryHandler.getEntityByGUID(userId, attachingElementAnchorGUID, anchorGUIDParameterName, OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME, forLineage, forDuplicateProcessing, effectiveTime, methodName); } } } /* * Set up LatestChange classification if there are any anchor entities returned from the initial validation. */ String actionDescription = String.format(actionDescriptionTemplate, startingElementTypeName, startingGUID, attachingElementTypeName, attachingGUID); if (startingElementAnchorEntity != null) { this.addLatestChangeToAnchor(startingElementAnchorEntity, OpenMetadataAPIMapper.ATTACHMENT_RELATIONSHIP_LATEST_CHANGE_TARGET_ORDINAL, actionOrdinal, null, attachingGUID, attachingElementTypeName, attachmentTypeName, userId, actionDescription, methodName); } else { if (repositoryHelper.isTypeOf(serviceName, startingElementEntity.getType().getTypeDefName(), OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME)) { this.addLatestChangeToAnchor(startingElementEntity, OpenMetadataAPIMapper.ENTITY_RELATIONSHIP_LATEST_CHANGE_TARGET_ORDINAL, actionOrdinal, null, attachingGUID, attachingElementTypeName, attachmentTypeName, userId, actionDescription, methodName); } } if (attachingElementAnchorEntity != null) { /* * Only need to add latestChange if the anchor of the attached element is different from the starting element */ if (! attachingElementAnchorEntity.getGUID().equals(startingElementAnchorGUID)) { this.addLatestChangeToAnchor(attachingElementAnchorEntity, OpenMetadataAPIMapper.ATTACHMENT_RELATIONSHIP_LATEST_CHANGE_TARGET_ORDINAL, actionOrdinal, null, startingGUID, startingElementTypeName, attachmentTypeName, userId, actionDescription, methodName); } } else if (! attachingGUID.equals(startingElementAnchorGUID)) { /* * The attaching element does not have an anchor and is different from the starting element's anchor */ if (repositoryHelper.isTypeOf(serviceName, attachingElementEntity.getType().getTypeDefName(), OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME)) { /* * The attaching element is an anchor in its own right. */ this.addLatestChangeToAnchor(attachingElementEntity, OpenMetadataAPIMapper.ENTITY_RELATIONSHIP_LATEST_CHANGE_TARGET_ORDINAL, actionOrdinal, null, startingGUID, startingElementTypeName, attachmentTypeName, userId, actionDescription, methodName); } } if (newRelationship != null) { return newRelationship.getGUID(); } return null; } /** * Updates a relationship between two elements and updates the LatestChange in each one's anchor entity (if they have one). * Both elements must be visible to the user to allow the update. * * @param userId userId of user making request * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local * @param externalSourceName name of the software server capability entity that represented the external source * @param startingGUID unique id for the starting element's entity * @param startingGUIDParameterName name of the parameter supplying the startingGUID * @param startingElementTypeName type name of the starting element's entity * @param attachingGUID unique id of the entity for the element that is being attached * @param attachingGUIDParameterName name of the parameter supplying the attachingGUID * @param attachingElementTypeName type name of the attaching element's entity * @param attachmentTypeGUID unique identifier of type of the relationship to create * @param attachmentTypeName unique name of type of the relationship to create * @param relationshipProperties properties to add to the relationship or null if no properties to add * @param methodName calling method * * @throws InvalidParameterException one of the parameters is null or invalid. * @throws PropertyServerException there is a problem adding the relationship to the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public void updateElementToElementLink(String userId, String externalSourceGUID, String externalSourceName, String startingGUID, String startingGUIDParameterName, String startingElementTypeName, String attachingGUID, String attachingGUIDParameterName, String attachingElementTypeName, String attachmentTypeGUID, String attachmentTypeName, InstanceProperties relationshipProperties, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { updateElementToElementLink(userId, externalSourceGUID, externalSourceName, startingGUID, startingGUIDParameterName, startingElementTypeName, attachingGUID, attachingGUIDParameterName, attachingElementTypeName, false, false, supportedZones, attachmentTypeGUID, attachmentTypeName, false, relationshipProperties, methodName); } /** * Updates a relationship between two elements and updates the LatestChange in each one's anchor entity (if they have one). * Both elements must be visible to the user to allow the update. * * @param userId userId of user making request * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local * @param externalSourceName name of the software server capability entity that represented the external source * @param startingGUID unique id for the starting element's entity * @param startingGUIDParameterName name of the parameter supplying the startingGUID * @param startingElementTypeName type name of the starting element's entity * @param attachingGUID unique id of the entity for the element that is being attached * @param attachingGUIDParameterName name of the parameter supplying the attachingGUID * @param attachingElementTypeName type name of the attaching element's entity * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param suppliedSupportedZones list of zones that any asset must be a member of at least one to be visible * @param attachmentTypeGUID unique identifier of type of the relationship to create * @param attachmentTypeName unique name of type of the relationship to create * @param isMergeUpdate should the supplied properties be merged with existing properties (true) by replacing the just the properties with * matching names, or should the entire properties of the instance be replaced? * @param relationshipProperties properties to add to the relationship or null if no properties to add * @param methodName calling method * * @throws InvalidParameterException one of the parameters is null or invalid. * @throws PropertyServerException there is a problem adding the relationship to the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public void updateElementToElementLink(String userId, String externalSourceGUID, String externalSourceName, String startingGUID, String startingGUIDParameterName, String startingElementTypeName, String attachingGUID, String attachingGUIDParameterName, String attachingElementTypeName, boolean forLineage, boolean forDuplicateProcessing, List suppliedSupportedZones, String attachmentTypeGUID, String attachmentTypeName, boolean isMergeUpdate, InstanceProperties relationshipProperties, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateGUID(startingGUID, startingGUIDParameterName, methodName); invalidParameterHandler.validateGUID(attachingGUID, attachingGUIDParameterName, methodName); Date effectiveTime = getEffectiveTime(relationshipProperties); EntityDetail startingElementEntity = repositoryHandler.getEntityByGUID(userId, startingGUID, startingGUIDParameterName, startingElementTypeName, forLineage, forDuplicateProcessing, effectiveTime, methodName); EntityDetail startingElementAnchorEntity = this.validateAnchorEntity(userId, startingGUID, startingElementTypeName, startingElementEntity, startingGUIDParameterName, false, forLineage, forDuplicateProcessing, suppliedSupportedZones, effectiveTime, methodName); String startingElementAnchorGUID = startingGUID; if (startingElementAnchorEntity != null) { startingElementAnchorGUID = startingElementAnchorEntity.getGUID(); } EntityDetail attachingElementEntity = repositoryHandler.getEntityByGUID(userId, attachingGUID, attachingGUIDParameterName, attachingElementTypeName, forLineage, forDuplicateProcessing, effectiveTime, methodName); EntityDetail attachingElementAnchorEntity = this.validateAnchorEntity(userId, attachingGUID, attachingElementTypeName, attachingElementEntity, attachingGUIDParameterName, false, forLineage, forDuplicateProcessing, suppliedSupportedZones, effectiveTime, methodName); /* * The calls above validate the existence of the two entities and that they are visible to the user. * An exception is thrown if there are any problems. * The anchor entities are returned if there are anchor entities associated with a specific end. * Next step is to find the relationship to update */ Relationship relationship = repositoryHandler.getRelationshipBetweenEntities(userId, startingGUID, startingElementTypeName, attachingGUID, attachmentTypeGUID, attachmentTypeName, forDuplicateProcessing, effectiveTime, methodName); if (relationship != null) { InstanceProperties newProperties = setUpNewProperties(isMergeUpdate, relationshipProperties, relationship.getProperties()); repositoryHandler.updateRelationshipProperties(userId, externalSourceGUID, externalSourceName, relationship, newProperties, methodName); /* * Set up LatestChange classification if there are any anchor entities returned from the initial validation. */ final String actionDescriptionTemplate = "Updating link from %s %s to %s %s"; String actionDescription = String.format(actionDescriptionTemplate, startingElementTypeName, startingGUID, attachingElementTypeName, attachingGUID); if (startingElementAnchorEntity != null) { this.addLatestChangeToAnchor(startingElementAnchorEntity, OpenMetadataAPIMapper.ATTACHMENT_RELATIONSHIP_LATEST_CHANGE_TARGET_ORDINAL, OpenMetadataAPIMapper.UPDATED_LATEST_CHANGE_ACTION_ORDINAL, null, attachingGUID, attachingElementTypeName, attachmentTypeName, userId, actionDescription, methodName); } else { if (repositoryHelper.isTypeOf(serviceName, startingElementEntity.getType().getTypeDefName(), OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME)) { this.addLatestChangeToAnchor(startingElementEntity, OpenMetadataAPIMapper.ENTITY_RELATIONSHIP_LATEST_CHANGE_TARGET_ORDINAL, OpenMetadataAPIMapper.UPDATED_LATEST_CHANGE_ACTION_ORDINAL, null, attachingGUID, attachingElementTypeName, attachmentTypeName, userId, actionDescription, methodName); } /* * Now that this relationship is in place, the anchorGUID might be set up */ this.reEvaluateAnchorGUID(startingGUID, startingGUIDParameterName, startingElementTypeName, startingElementEntity, null, forLineage, forDuplicateProcessing, effectiveTime, methodName); } if (attachingElementAnchorEntity != null) { /* * Only need to add latestChange if the anchor of the attached element is different */ if (! attachingElementAnchorEntity.getGUID().equals(startingElementAnchorGUID)) { this.addLatestChangeToAnchor(attachingElementAnchorEntity, OpenMetadataAPIMapper.ATTACHMENT_RELATIONSHIP_LATEST_CHANGE_TARGET_ORDINAL, OpenMetadataAPIMapper.UPDATED_LATEST_CHANGE_ACTION_ORDINAL, null, startingGUID, startingElementTypeName, attachmentTypeName, userId, actionDescription, methodName); } } else { if (repositoryHelper.isTypeOf(serviceName, startingElementEntity.getType().getTypeDefName(), OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME)) { this.addLatestChangeToAnchor(attachingElementEntity, OpenMetadataAPIMapper.ENTITY_RELATIONSHIP_LATEST_CHANGE_TARGET_ORDINAL, OpenMetadataAPIMapper.UPDATED_LATEST_CHANGE_ACTION_ORDINAL, null, startingGUID, startingElementTypeName, attachmentTypeName, userId, actionDescription, methodName); } /* * Now that this relationship is in place, the anchorGUID may now be set up */ this.reEvaluateAnchorGUID(attachingGUID, attachingGUIDParameterName, attachingElementTypeName, attachingElementEntity, null, forLineage, forDuplicateProcessing, effectiveTime, methodName); } } } /** * Delete the existing relationship between the starting element and another element then create a new relationship * between the starting element element and the new attaching element. * * If successful this updates the LatestChange in each one's anchor entity (if they have one). * Both elements must be visible to the user to allow the relinking. * * @param userId userId of user making request * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local * @param externalSourceName name of the software server capability entity that represented the external source * @param startingGUID unique id for the starting element's entity * @param startingGUIDParameterName name of the parameter supplying the startingGUID * @param startingElementTypeName type name of the starting element's entity * @param startAtEnd1 is the starting element at end 1 of the relationship * @param newAttachingGUID unique id of the entity for the element that is being attached * @param newAttachingGUIDParameterName name of the parameter supplying the newAttachingGUID * @param newAttachingElementTypeName type name of the attaching element's entity * @param attachmentTypeGUID unique identifier of type of the relationship to create * @param attachmentTypeName unique name of type of the relationship to create * @param relationshipProperties properties to add to the relationship or null if no properties to add * @param methodName calling method * * @throws InvalidParameterException one of the parameters is null or invalid; or the relationship to change is not obvious * @throws PropertyServerException there is a problem adding the relationship to the repositories * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public void relinkElementToNewElement(String userId, String externalSourceGUID, String externalSourceName, String startingGUID, String startingGUIDParameterName, String startingElementTypeName, boolean startAtEnd1, String newAttachingGUID, String newAttachingGUIDParameterName, String newAttachingElementTypeName, String attachmentTypeGUID, String attachmentTypeName, InstanceProperties relationshipProperties, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { this.relinkElementToNewElement(userId, externalSourceGUID, externalSourceName, startingGUID, startingGUIDParameterName, startingElementTypeName, startAtEnd1, newAttachingGUID, newAttachingGUIDParameterName, newAttachingElementTypeName, false, false, supportedZones, attachmentTypeGUID, attachmentTypeName, relationshipProperties, methodName); } /** * Deleted the existing relationship between the starting element and another element then create a new relationship * between the starting element and the new attaching element. * * If successful this updates the LatestChange in each one's anchor entity (if they have one). * Both elements must be visible to the user to allow the relinking. * * @param userId userId of user making request * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local * @param externalSourceName name of the software server capability entity that represented the external source * @param startingGUID unique id for the starting element's entity * @param startingGUIDParameterName name of the parameter supplying the startingGUID * @param startingElementTypeName type name of the starting element's entity * @param startAtEnd1 is the starting element at end 1 of the relationship * @param newAttachingGUID unique id of the entity for the element that is being attached * @param newAttachingGUIDParameterName name of the parameter supplying the newAttachingGUID * @param newAttachingElementTypeName type name of the attaching element's entity * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param suppliedSupportedZones list of zones that any asset must be a member of at least one to be visible * @param attachmentTypeGUID unique identifier of type of the relationship to create * @param attachmentTypeName unique name of type of the relationship to create * @param relationshipProperties properties to add to the relationship or null if no properties to add * @param methodName calling method * * @throws InvalidParameterException one of the parameters is null or invalid; or the relationship to change is not obvious * @throws PropertyServerException there is a problem adding the relationship to the repositories * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public void relinkElementToNewElement(String userId, String externalSourceGUID, String externalSourceName, String startingGUID, String startingGUIDParameterName, String startingElementTypeName, boolean startAtEnd1, String newAttachingGUID, String newAttachingGUIDParameterName, String newAttachingElementTypeName, boolean forLineage, boolean forDuplicateProcessing, List suppliedSupportedZones, String attachmentTypeGUID, String attachmentTypeName, InstanceProperties relationshipProperties, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateGUID(startingGUID, startingGUIDParameterName, methodName); invalidParameterHandler.validateGUID(newAttachingGUID, newAttachingGUIDParameterName, methodName); Date effectiveTime = getEffectiveTime(relationshipProperties); EntityDetail startingElementEntity = repositoryHandler.getEntityByGUID(userId, startingGUID, startingGUIDParameterName, startingElementTypeName, forLineage, forDuplicateProcessing, effectiveTime, methodName); EntityDetail startingElementAnchorEntity = this.validateAnchorEntity(userId, startingGUID, startingElementTypeName, startingElementEntity, startingGUIDParameterName, false, forLineage, forDuplicateProcessing, suppliedSupportedZones, effectiveTime, methodName); String startingElementAnchorGUID = startingGUID; if (startingElementAnchorEntity != null) { startingElementAnchorGUID = startingElementAnchorEntity.getGUID(); } EntityDetail newAttachingElementEntity = repositoryHandler.getEntityByGUID(userId, newAttachingGUID, newAttachingGUIDParameterName, newAttachingElementTypeName, forLineage, forDuplicateProcessing, effectiveTime, methodName); EntityDetail newAttachingElementAnchorEntity = this.validateAnchorEntity(userId, newAttachingGUID, newAttachingElementTypeName, newAttachingElementEntity, newAttachingGUIDParameterName, false, forLineage, forDuplicateProcessing, suppliedSupportedZones, effectiveTime, methodName); /* * The calls above validate the existence of the two entities and that they are visible to the user. * An exception is thrown if there are any problems. * The anchor entities are returned if there are anchor entities associated with a specific end. * * The next step is to remove the relationship if it exists. */ Relationship relationship = repositoryHandler.getUniqueRelationshipByType(userId, startingGUID, startingElementTypeName, startAtEnd1, attachmentTypeGUID, attachmentTypeName, forDuplicateProcessing, effectiveTime, methodName); String oldAttachingGUID = ""; String oldAttachingElementTypeName = " suppliedSupportedZones, String attachmentTypeGUID, String attachmentTypeName, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateGUID(startingGUID, startingGUIDParameterName, methodName); invalidParameterHandler.validateGUID(attachedGUID, attachedGUIDParameterName, methodName); Relationship relationship = repositoryHandler.getRelationshipBetweenEntities(userId, startingGUID, startingElementTypeName, attachedGUID, attachmentTypeGUID, attachmentTypeName, forDuplicateProcessing, effectiveTime, methodName); this.unlinkElementFromElement(userId, onlyCreatorPermitted, externalSourceGUID, externalSourceName, startingGUID, startingGUIDParameterName, startingElementTypeName, attachedGUID, attachedGUIDParameterName, attachedElementTypeGUID, attachedElementTypeName, forLineage, forDuplicateProcessing, suppliedSupportedZones, attachmentTypeName, relationship, methodName); } /** * Removes a relationship between two specified elements. If after the relationship is deleted, one of the ends has now * lost it's anchor, then that entity is deleted. Anchored entities should not be left unanchored. This can cause a cascading effect * if the anchored elements are organized in a hierarchy, such as a schema or a comment conversation. * * @param userId userId of user making request * @param onlyCreatorPermitted operation only permitted if the userId was the same one that created the relationship * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local * @param externalSourceName name of the software server capability entity that represented the external source * @param startingGUID unique id for the starting element's entity * @param startingGUIDParameterName name of the parameter supplying the startingGUID * @param startingElementTypeName type name of the starting element's entity * @param attachedGUID unique id of the entity for the element that is being detached * @param attachedGUIDParameterName name of the parameter supplying the attachedGUID * @param attachedElementTypeGUID type GUID of the attaching element's entity * @param attachedElementTypeName type name of the attaching element's entity * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param attachmentTypeName unique name of type of the relationship to remove * @param relationship specific relationship to remove * @param methodName calling method * * @throws InvalidParameterException one of the parameters is null or invalid. * @throws PropertyServerException there is a problem updating relationship in the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public void unlinkElementFromElement(String userId, boolean onlyCreatorPermitted, String externalSourceGUID, String externalSourceName, String startingGUID, String startingGUIDParameterName, String startingElementTypeName, String attachedGUID, String attachedGUIDParameterName, String attachedElementTypeGUID, String attachedElementTypeName, boolean forLineage, boolean forDuplicateProcessing, String attachmentTypeName, Relationship relationship, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { this.unlinkElementFromElement(userId, onlyCreatorPermitted, externalSourceGUID, externalSourceName, startingGUID, startingGUIDParameterName, startingElementTypeName, attachedGUID, attachedGUIDParameterName, attachedElementTypeGUID, attachedElementTypeName, forLineage, forDuplicateProcessing, supportedZones, attachmentTypeName, relationship, methodName); } /** * Removes a relationship between two specified elements. If after the relationship is deleted, one of the ends has now * lost it's anchor, then that entity is deleted. Anchored entities should not be left unanchored. This can cause a cascading effect * if the anchored elements are organized in a hierarchy, such as a schema or a comment conversation. * * @param userId userId of user making request * @param onlyCreatorPermitted operation only permitted if the userId was the same one that created the relationship * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local * @param externalSourceName name of the software server capability entity that represented the external source * @param startingGUID unique id for the starting element's entity * @param startingGUIDParameterName name of the parameter supplying the startingGUID * @param startingElementTypeName type name of the starting element's entity * @param attachedGUID unique id of the entity for the element that is being detached * @param attachedGUIDParameterName name of the parameter supplying the attachedGUID * @param attachedElementTypeGUID type GUID of the attaching element's entity * @param attachedElementTypeName type name of the attaching element's entity * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param suppliedSupportedZones list of zones that any asset must be a member of at least one to be visible * @param attachmentTypeName unique name of type of the relationship to remove * @param relationship specific relationship to remove * @param methodName calling method * * @throws InvalidParameterException one of the parameters is null or invalid. * @throws PropertyServerException there is a problem updating relationship in the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public void unlinkElementFromElement(String userId, boolean onlyCreatorPermitted, String externalSourceGUID, String externalSourceName, String startingGUID, String startingGUIDParameterName, String startingElementTypeName, String attachedGUID, String attachedGUIDParameterName, String attachedElementTypeGUID, String attachedElementTypeName, boolean forLineage, boolean forDuplicateProcessing, List suppliedSupportedZones, String attachmentTypeName, Relationship relationship, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { final String relationshipParameterName = "relationship"; invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateGUID(startingGUID, startingGUIDParameterName, methodName); invalidParameterHandler.validateGUID(attachedGUID, attachedGUIDParameterName, methodName); invalidParameterHandler.validateObject(relationship, relationshipParameterName, methodName); Date effectiveTime = getEffectiveTime(relationship.getProperties()); EntityDetail startingElementEntity = repositoryHandler.getEntityByGUID(userId, startingGUID, startingGUIDParameterName, startingElementTypeName, forLineage, forDuplicateProcessing, effectiveTime, methodName); EntityDetail startingElementAnchorEntity = this.validateAnchorEntity(userId, startingGUID, startingElementTypeName, startingElementEntity, startingGUIDParameterName, false, forLineage, forDuplicateProcessing, suppliedSupportedZones, effectiveTime, methodName); String startingElementAnchorGUID = startingGUID; String newStartingAnchorGUID = null; String newAttachedAnchorGUID = null; if (startingElementAnchorEntity != null) { startingElementAnchorGUID = startingElementAnchorEntity.getGUID(); } EntityDetail attachedElementEntity = repositoryHandler.getEntityByGUID(userId, attachedGUID, attachedGUIDParameterName, attachedElementTypeName, forLineage, forDuplicateProcessing, effectiveTime, methodName); EntityDetail attachedElementAnchorEntity = this.validateAnchorEntity(userId, attachedGUID, attachedElementTypeName, attachedElementEntity, attachedGUIDParameterName, false, forLineage, forDuplicateProcessing, suppliedSupportedZones, effectiveTime, methodName); if ((!onlyCreatorPermitted) || (userId.equals(relationship.getCreatedBy()))) { if (this.visibleToUserThroughRelationship(userId, relationship, methodName)) { repositoryHandler.removeRelationship(userId, externalSourceGUID, externalSourceName, relationship, methodName); } } else { throw new UserNotAuthorizedException(GenericHandlersErrorCode.ONLY_CREATOR_CAN_DELETE.getMessageDefinition(methodName, startingElementTypeName, startingGUID, attachedElementTypeName, attachedGUID, userId), this.getClass().getName(), methodName, userId); } /* * Set up LatestChange classification if there are any anchor entities returned from the initial validation. */ final String actionDescriptionTemplate = "Unlinking %s %s from %s %s"; String actionDescription = String.format(actionDescriptionTemplate, startingElementTypeName, startingGUID, attachedElementTypeName, attachedGUID); if (startingElementAnchorEntity != null) { this.addLatestChangeToAnchor(startingElementAnchorEntity, OpenMetadataAPIMapper.ATTACHMENT_RELATIONSHIP_LATEST_CHANGE_TARGET_ORDINAL, OpenMetadataAPIMapper.DELETED_LATEST_CHANGE_ACTION_ORDINAL, null, attachedGUID, attachedElementTypeName, attachmentTypeName, userId, actionDescription, methodName); /* * Now that this relationship is gone, the anchorGUID may now be wrong */ newStartingAnchorGUID = this.reEvaluateAnchorGUID(startingGUID, startingGUIDParameterName, startingElementTypeName, startingElementAnchorEntity.getGUID(), forLineage, forDuplicateProcessing, effectiveTime, methodName); } else { if (repositoryHelper.isTypeOf(serviceName, attachedElementEntity.getType().getTypeDefName(), OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME)) { this.addLatestChangeToAnchor(startingElementEntity, OpenMetadataAPIMapper.ENTITY_RELATIONSHIP_LATEST_CHANGE_TARGET_ORDINAL, OpenMetadataAPIMapper.DELETED_LATEST_CHANGE_ACTION_ORDINAL, null, attachedGUID, attachedElementTypeName, attachmentTypeName, userId, actionDescription, methodName); } } if (attachedElementAnchorEntity != null) { /* * Only need to add latestChange if the anchor of the attached element is different */ if (! attachedElementAnchorEntity.getGUID().equals(startingElementAnchorGUID)) { this.addLatestChangeToAnchor(attachedElementAnchorEntity, OpenMetadataAPIMapper.ATTACHMENT_RELATIONSHIP_LATEST_CHANGE_TARGET_ORDINAL, OpenMetadataAPIMapper.DELETED_LATEST_CHANGE_ACTION_ORDINAL, null, startingGUID, startingElementTypeName, attachmentTypeName, userId, actionDescription, methodName); } /* * Now that this relationship is gone, the anchorGUID may now be wrong */ newAttachedAnchorGUID = this.reEvaluateAnchorGUID(attachedGUID, attachedGUIDParameterName, attachedElementTypeName, attachedElementAnchorEntity.getGUID(), forLineage, forDuplicateProcessing, effectiveTime, methodName); } else { if (repositoryHelper.isTypeOf(serviceName, attachedElementEntity.getType().getTypeDefName(), OpenMetadataAPIMapper.REFERENCEABLE_TYPE_NAME)) { this.addLatestChangeToAnchor(startingElementEntity, OpenMetadataAPIMapper.ENTITY_RELATIONSHIP_LATEST_CHANGE_TARGET_ORDINAL, OpenMetadataAPIMapper.DELETED_LATEST_CHANGE_ACTION_ORDINAL, null, startingGUID, startingElementTypeName, attachmentTypeName, userId, actionDescription, methodName); } } /* * If the attached element had an anchor before the relationship deletion, but now is without an anchor, then delete the bean. */ if (attachedElementAnchorEntity != null && newAttachedAnchorGUID == null) { try { this.deleteBeanInRepository(userId, externalSourceGUID, externalSourceName, attachedGUID, attachedGUIDParameterName, attachedElementTypeGUID, attachedElementTypeName, null, null, attachedElementAnchorEntity, forLineage, forDuplicateProcessing, effectiveTime, methodName); } catch (InvalidParameterException | PropertyServerException| UserNotAuthorizedException error) { // This method should succeed, because the relationship has been deleted. Issue an audit log indicating that the bean delete failed auditLog.logException(methodName, GenericHandlersAuditCode.UNABLE_TO_DELETE_UNANCHORED_BEAN.getMessageDefinition(serviceName, attachedGUID, attachedElementTypeName, attachedElementTypeGUID, methodName, error.getClass().getName(), error.getMessage()), error); } } /* * If the starting element had an anchor before the relationship deletion, but now is without an anchor, then delete the bean. */ if (startingElementAnchorEntity != null && newStartingAnchorGUID == null) { final String startingElementTypeGUID = repositoryHelper.getTypeDefByName(methodName, startingElementTypeName).getGUID(); try { this.deleteBeanInRepository(userId, externalSourceGUID, externalSourceName, startingGUID, startingGUIDParameterName, startingElementTypeGUID, startingElementTypeName, null, null, startingElementAnchorEntity, forLineage, forDuplicateProcessing, effectiveTime, methodName); } catch (InvalidParameterException | PropertyServerException| UserNotAuthorizedException error) { // This method should succeed, because the relationship has been deleted. Issue an audit log indicating that the bean delete failed auditLog.logException(methodName, GenericHandlersAuditCode.UNABLE_TO_DELETE_UNANCHORED_BEAN.getMessageDefinition(serviceName, startingGUID, startingElementTypeName, startingElementTypeGUID, methodName, error.getClass().getName(), error.getMessage()), error); } } } /** * Calls unlinkElementFromElement for all relationships of a certain type emanating from the requested element. * * @param userId userId of user making request * @param onlyCreatorPermitted operation only permitted if the userId was the same one that created the relationship * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local * @param externalSourceName name of the software server capability entity that represented the external source * @param startingGUID unique id for the starting element's entity * @param startingGUIDParameterName name of the parameter supplying the startingGUID * @param startingElementTypeName type name of the starting element's entity * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param suppliedSupportedZones list of zones that any asset must be a member of at least one to be visible * @param attachmentTypeGUID unique identifier of type of the relationship to create * @param attachmentTypeName unique name of type of the relationship to create * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @throws InvalidParameterException one of the parameters is null or invalid. * @throws PropertyServerException there is a problem updating the relationships in the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public void unlinkAllElements(String userId, boolean onlyCreatorPermitted, String externalSourceGUID, String externalSourceName, String startingGUID, String startingGUIDParameterName, String startingElementTypeName, boolean forLineage, boolean forDuplicateProcessing, List suppliedSupportedZones, String attachmentTypeGUID, String attachmentTypeName, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { final String entityProxyParameterName = "entityProxy.getGUID()"; invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateGUID(startingGUID, startingGUIDParameterName, methodName); this.validateAnchorEntity(userId, startingGUID, startingGUIDParameterName, startingElementTypeName, true, forLineage, forDuplicateProcessing, suppliedSupportedZones, effectiveTime, methodName); RepositoryRelationshipsIterator iterator = new RepositoryRelationshipsIterator(repositoryHandler, userId, startingGUID, startingElementTypeName, attachmentTypeGUID, attachmentTypeName, forDuplicateProcessing, 0, invalidParameterHandler.getMaxPagingSize(), effectiveTime, methodName); while (iterator.moreToReceive()) { Relationship relationship = iterator.getNext(); EntityProxy entityProxy = repositoryHandler.getOtherEnd(startingGUID, startingElementTypeName, relationship, methodName); if ((entityProxy != null) && (entityProxy.getType() != null)) { this.unlinkElementFromElement(userId, onlyCreatorPermitted, externalSourceGUID, externalSourceName, startingGUID, startingGUIDParameterName, startingElementTypeName, entityProxy.getGUID(), entityProxyParameterName, entityProxy.getType().getTypeDefGUID(), entityProxy.getType().getTypeDefName(), forLineage, forDuplicateProcessing, attachmentTypeGUID, attachmentTypeName, effectiveTime, methodName); } } } /** * Removes the relationship of a specific type attached to an entity. If the connected entity is anchored to the starting entity * it is deleted (and linked dependent elements). There should be only one relationship. If there are more, an error is thrown. * * @param userId userId of user making request * @param onlyCreatorPermitted operation only permitted if the userId was the same one that created the relationship * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local * @param externalSourceName name of the software server capability entity that represented the external source * @param startingGUID unique id for the starting element's entity * @param startingGUIDParameterName name of the parameter supplying the startingGUID * @param startingElementTypeName type name of the starting element's entity * @param forLineage the request is to support lineage retrieval this means entities with the Memento classification can be returned * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param suppliedSupportedZones list of zones that any asset must be a member of at least one to be visible * @param attachmentTypeGUID unique identifier of type of the relationship to create * @param attachmentTypeName unique name of type of the relationship to create * @param detachedElementTypeName name of type of element that will be detached * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * * @return unique identifier of the entity that has been detached * * @throws InvalidParameterException one of the parameters is null or invalid. * @throws PropertyServerException there is a problem updating the relationship in the repositories. * @throws UserNotAuthorizedException the requesting user is not authorized to issue this request. */ public String unlinkConnectedElement(String userId, boolean onlyCreatorPermitted, String externalSourceGUID, String externalSourceName, String startingGUID, String startingGUIDParameterName, String startingElementTypeName, boolean forLineage, boolean forDuplicateProcessing, List suppliedSupportedZones, String attachmentTypeGUID, String attachmentTypeName, String detachedElementTypeName, Date effectiveTime, String methodName) throws InvalidParameterException, PropertyServerException, UserNotAuthorizedException { final String entityProxyParameterName = "entityProxy.getGUID()"; invalidParameterHandler.validateUserId(userId, methodName); invalidParameterHandler.validateGUID(startingGUID, startingGUIDParameterName, methodName); this.validateAnchorEntity(userId, startingGUID, startingGUIDParameterName, startingElementTypeName, true, forLineage, forDuplicateProcessing, suppliedSupportedZones, effectiveTime, methodName); List links = this.getAttachmentLinks(userId, startingGUID, startingGUIDParameterName, startingElementTypeName, attachmentTypeGUID, attachmentTypeName, detachedElementTypeName, 0, invalidParameterHandler.getMaxPagingSize(), effectiveTime, methodName); if (links == null) { return null; } if (links.size() > 1) { errorHandler.handleAmbiguousRelationships(startingGUID, startingElementTypeName, attachmentTypeName, links, methodName); } EntityProxy entityProxy = repositoryHandler.getOtherEnd(startingGUID, startingElementTypeName, links.get(0), methodName); String detachedElementGUID = null; if ((entityProxy != null) && (entityProxy.getType() != null)) { detachedElementGUID = entityProxy.getGUID(); this.unlinkElementFromElement(userId, onlyCreatorPermitted, externalSourceGUID, externalSourceName, startingGUID, startingGUIDParameterName, startingElementTypeName, entityProxy.getGUID(), entityProxyParameterName, entityProxy.getType().getTypeDefGUID(), entityProxy.getType().getTypeDefName(), forLineage, forDuplicateProcessing, suppliedSupportedZones, attachmentTypeGUID, attachmentTypeName, effectiveTime, methodName); } return detachedElementGUID; } /** * Set up the effectivity times for an element not using a builder - typically a relationship. * * @param currentProperties other property values for the element * @param effectiveFrom supplied starting time for this element (null for all time) * @param effectiveTo supplied ending time for this element * @return correctly filled out properties */ InstanceProperties setUpEffectiveDates(InstanceProperties currentProperties, Date effectiveFrom, Date effectiveTo) { InstanceProperties properties = currentProperties; if ((effectiveFrom != null) || (effectiveTo != null)) { if (properties == null) { properties = new InstanceProperties(); } properties.setEffectiveFromTime(effectiveFrom); properties.setEffectiveToTime(effectiveTo); } return properties; } /** * Derive the effective time value to use when retrieving relationships. * This is based on the effectivity values from an element's properties. * If there are no properties then effectively there is no effective time. * * @param properties properties from element * @return single date to use on retrieve commands - null means all time */ public Date getEffectiveTime(InstanceProperties properties) { if (properties != null) { return getEffectiveTime(properties.getEffectiveFromTime(), properties.getEffectiveToTime()); } return null; } /** * Derive the effective time value to use when retrieving relationships. * This is based on the effectivity values from an element's properties. * * @param effectiveFromTime start date/time for the element * @param effectiveToTime end date/time for the * @return single date to use on retrieve commands - null means all time */ public Date getEffectiveTime(Date effectiveFromTime, Date effectiveToTime) { if (effectiveFromTime != null) { return effectiveFromTime; } else if (effectiveToTime != null) { return effectiveToTime; } return null; } /** * Verify that the integrator identities are either null or refer to a valid software server capability. * These values will be used to set up the * * @param userId calling user * @param externalSourceGUID guid of the software server capability entity that represented the external source - null for local * @param externalSourceName name of the software server capability entity that represented the external source * @param forLineage is this request part of a lineage service * @param forDuplicateProcessing the request is for duplicate processing and so must not deduplicate * @param effectiveTime the time that the retrieved elements must be effective for (null for any time, new Date() for now) * @param methodName calling method * @throws InvalidParameterException the integrator GUID or name does not match what is in the metadata repository * @throws PropertyServerException problem accessing repositories * @throws UserNotAuthorizedException security access problem */ public void verifyExternalSourceIdentity(String userId, String externalSourceGUID, String externalSourceName, boolean forLineage, boolean forDuplicateProcessing, Date effectiveTime, String methodName) throws InvalidParameterException, UserNotAuthorizedException, PropertyServerException { final String guidParameterName = "externalSourceGUID"; if ((externalSourceGUID == null) && (externalSourceName == null)) { return; } try { EntityDetail integrator = this.getEntityFromRepository(userId, externalSourceGUID, guidParameterName, OpenMetadataAPIMapper.SOFTWARE_SERVER_CAPABILITY_TYPE_NAME, null, null, forLineage, forDuplicateProcessing, supportedZones, effectiveTime, methodName); if (integrator == null) { throw new InvalidParameterException(GenericHandlersErrorCode.INTEGRATOR_NOT_RETURNED.getMessageDefinition(serviceName, methodName, externalSourceGUID, externalSourceName, null), this.getClass().getName(), methodName, externalSourceGUID); } else { String qualifiedName = repositoryHelper.getStringProperty(serviceName, OpenMetadataAPIMapper.QUALIFIED_NAME_PROPERTY_NAME, integrator.getProperties(), methodName); if (! externalSourceName.equals(qualifiedName)) { throw new InvalidParameterException(GenericHandlersErrorCode.BAD_INTEGRATOR_NAME.getMessageDefinition(serviceName, methodName, externalSourceName, qualifiedName, externalSourceGUID), this.getClass().getName(), methodName, externalSourceGUID); } } } catch (InvalidParameterException error) { throw new InvalidParameterException(GenericHandlersErrorCode.INTEGRATOR_NOT_RETURNED.getMessageDefinition(serviceName, methodName, externalSourceGUID, externalSourceName, error.getMessage()), this.getClass().getName(), methodName, error, externalSourceGUID); } } }