/**
  * Sets relationships.
  *
  * <p>Looks at merge actions and whether the relationship is empty when adding, so not all
  * relationships are added.
  *
  * @param relationships list of data object relationships or null
  */
 public void setRelationships(List<DataObjectRelationship> relationships) {
   if (relationships == null) {
     this.relationships = null;
     relationshipMap = null;
     lastAttributeToRelationshipMap = null;
     attributeToRelationshipMap = null;
     return;
   }
   this.relationships = Collections.unmodifiableList(relationships);
   relationshipMap = new HashMap<String, DataObjectRelationship>(relationships.size());
   attributeToRelationshipMap = new HashMap<String, List<DataObjectRelationship>>();
   lastAttributeToRelationshipMap =
       new HashMap<String, DataObjectRelationship>(relationships.size());
   removedRelationshipNames = new ArrayList<String>();
   // Builds maps to link attribute names to their relationships
   for (DataObjectRelationship rel : relationships) {
     // This is not quite correct - we really only want to not add the NO_OVERRIDE items if they
     // are
     // overriding something. However, at the point this is running, we don't know whether we will
     // be embedding
     // anything...
     if (rel.getMergeAction() != MetadataMergeAction.REMOVE
         && rel.getMergeAction() != MetadataMergeAction.NO_OVERRIDE) {
       // related object attribute name
       relationshipMap.put(rel.getName(), rel);
       // last attribute in list linking the objects
       if (!rel.getAttributeRelationships().isEmpty()) {
         DataObjectAttributeRelationship relAttr =
             rel.getAttributeRelationships().get(rel.getAttributeRelationships().size() - 1);
         lastAttributeToRelationshipMap.put(relAttr.getParentAttributeName(), rel);
       }
       // all relationships relating to an attribute
       for (DataObjectAttributeRelationship relAttr : rel.getAttributeRelationships()) {
         List<DataObjectRelationship> rels =
             attributeToRelationshipMap.get(relAttr.getParentAttributeName());
         if (rels == null) {
           rels = new ArrayList<DataObjectRelationship>();
           attributeToRelationshipMap.put(relAttr.getParentAttributeName(), rels);
         }
         rels.add(rel);
       }
     }
     // since the attribute will still exist in the embedded metadata, we need to put a block in on
     // the standard
     // cascade
     if (rel.getMergeAction() == MetadataMergeAction.REMOVE) {
       removedRelationshipNames.add(rel.getName());
     }
   }
   relationshipMap = Collections.unmodifiableMap(relationshipMap);
   lastAttributeToRelationshipMap = Collections.unmodifiableMap(lastAttributeToRelationshipMap);
   attributeToRelationshipMap = Collections.unmodifiableMap(attributeToRelationshipMap);
 }
 /** {@inheritDoc} */
 @Override
 public DataObjectRelationship getRelationship(String relationshipName) {
   if (relationshipName == null) {
     return null;
   }
   DataObjectRelationship relationship = null;
   // attempt to get it from the local attribute map (if any attributed defined locally)
   if (relationships != null) {
     relationship = relationshipMap.get(relationshipName);
   }
   // if we don't find one, but we have an embedded metadata object, check it
   if (relationship == null && embedded != null) {
     relationship = embedded.getRelationship(relationshipName);
     // but, ensure it's not on the removed attribute list
     if (relationship != null
         && removedRelationshipNames != null
         && removedRelationshipNames.contains(relationship.getName())) {
       relationship = null;
     }
   }
   return relationship;
 }
 /** {@inheritDoc} */
 @Override
 public List<DataObjectRelationship> getRelationshipsInvolvingAttribute(String attributeName) {
   // somewhat complex, since it returns a list of all possible relationships
   //
   if (StringUtils.isBlank(attributeName)) {
     return null;
   }
   Map<Object, DataObjectRelationship> relationships =
       new HashMap<Object, DataObjectRelationship>();
   // Look locally
   if (attributeToRelationshipMap != null
       && attributeToRelationshipMap.containsKey(attributeName)) {
     for (DataObjectRelationship rel : attributeToRelationshipMap.get(attributeName)) {
       Object mergeKey = rel.getName();
       if (rel instanceof MetadataCommonInternal) {
         mergeKey = ((MetadataCommonInternal) rel).getUniqueKeyForMerging();
       }
       relationships.put(mergeKey, rel);
     }
   }
   // now, if we have an embedded object, look for matching ones, but exclude if the relationship
   // is the same
   // as that means it was overridden by this bean
   if (embedded != null) {
     for (DataObjectRelationship rel :
         embedded.getRelationshipsInvolvingAttribute(attributeName)) {
       Object mergeKey = rel.getName();
       if (rel instanceof MetadataCommonInternal) {
         mergeKey = ((MetadataCommonInternal) rel).getUniqueKeyForMerging();
       }
       if (!relationships.containsKey(mergeKey)) {
         relationships.put(mergeKey, rel);
       }
     }
   }
   return new ArrayList<DataObjectRelationship>(relationships.values());
 }
 protected Control getControlInstance(
     AttributeDefinition attrDef, DataObjectAttribute dataObjectAttribute) {
   Control c = null;
   // Check for the hidden hint - if present - then use that control type
   if (dataObjectAttribute != null
       && hasHintOfType(dataObjectAttribute, UifDisplayHintType.HIDDEN)) {
     c = ComponentFactory.getHiddenControl();
   } else if (attrDef.getOptionsFinder() != null) {
     // if a values finder has been established, use a radio button group or drop-down list
     if (dataObjectAttribute != null
         && hasHintOfType(dataObjectAttribute, UifDisplayHintType.RADIO)) {
       c = ComponentFactory.getRadioGroupControl();
     } else {
       c = ComponentFactory.getSelectControl();
     }
   } else if (attrDef.getName().endsWith(".principalName") && dataObjectAttribute != null) {
     // FIXME: JHK: Yes, I know this is a *HORRIBLE* hack - but the alternative
     // would look even more "hacky" and error-prone
     c = ComponentFactory.getUserControl();
     // Need to find the relationship information
     // get the relationship ID by removing .principalName from the attribute name
     String relationshipName = StringUtils.removeEnd(attrDef.getName(), ".principalName");
     DataObjectMetadata metadata =
         dataObjectService
             .getMetadataRepository()
             .getMetadata(dataObjectAttribute.getOwningType());
     if (metadata != null) {
       DataObjectRelationship relationship = metadata.getRelationship(relationshipName);
       if (relationship != null
           && CollectionUtils.isNotEmpty(relationship.getAttributeRelationships())) {
         ((UserControl) c)
             .setPrincipalIdPropertyName(
                 relationship.getAttributeRelationships().get(0).getParentAttributeName());
         ((UserControl) c)
             .setPersonNamePropertyName(
                 relationshipName + "." + KimConstants.AttributeConstants.NAME);
         ((UserControl) c).setPersonObjectPropertyName(relationshipName);
       }
     } else {
       LOG.warn(
           "Attempt to pull relationship name: "
               + relationshipName
               + " resulted in missing metadata when looking for: "
               + dataObjectAttribute.getOwningType());
     }
   } else {
     switch (attrDef.getDataType()) {
       case STRING:
         // TODO: Determine better way to store the "200" metric below
         if (attrDef.getMaxLength() != null && attrDef.getMaxLength().intValue() > 200) {
           c = ComponentFactory.getTextAreaControl();
         } else {
           c = ComponentFactory.getTextControl();
         }
         break;
       case BOOLEAN:
         c = ComponentFactory.getCheckboxControl();
         break;
       case DATE:
       case DATETIME:
       case TRUNCATED_DATE:
         c = ComponentFactory.getDateControl();
         break;
       case CURRENCY:
       case DOUBLE:
       case FLOAT:
       case INTEGER:
       case LARGE_INTEGER:
       case LONG:
       case PRECISE_DECIMAL:
         c = ComponentFactory.getTextControl();
         break;
       case MARKUP:
         c = ComponentFactory.getTextAreaControl();
         break;
       default:
         c = ComponentFactory.getTextControl();
         break;
     }
   }
   return c;
 }