docs: @FFDCIgnore({ ConfigEvaluatorException.class, ConfigRetrieverException.class })
      Object evaluateMetaTypeAttribute(final String attributeName, final EvaluationContext context,
                                       final ExtendedAttributeDefinition attributeDef, final String flatPrefix,
                                       final boolean ignoreWarnings) throws ConfigEvaluatorException {
          final ConfigElement config = context.getConfigElement();
          context.setAttributeName(attributeName);
          context.addProcessed(attributeName);
  
          final String prefixedAttributeName = flatPrefix.length() == 0 ? attributeName : flatPrefix + attributeName;
  
          Object rawValue = null;
          if (attributeDef.getCopyOf() != null) {
              AttributeValueCopy copy = new AttributeValueCopy(prefixedAttributeName, attributeDef.getCopyOf());
              context.addAttributeValueCopy(copy);
          }
  
          if (attributeDef.isFlat()) {
  
              //TODO check for not final, not PID??
              RegistryEntry nestedRegistryEntry = getRegistryEntry(attributeDef.getReferencePid());
              if (nestedRegistryEntry == null) {
                  return null;
              }
              //TODO rename + flat wont' work yet.
              final Set<String> processedNames = new HashSet<String>();
              final AtomicInteger i = new AtomicInteger();
              String[] referenceAttributes = getReferenceAttributes(attributeName);
              evaluateFlatReference(referenceAttributes, context, nestedRegistryEntry, flatPrefix, config, i, processedNames, ignoreWarnings, attributeDef);
              ConfigEvaluatorException e = nestedRegistryEntry.traverseHierarchy(new EntryAction<ConfigEvaluatorException>() {
  
                  private ConfigEvaluatorException result;
  
                  @Override
                  public boolean entry(RegistryEntry registryEntry) {
                      String elementName = registryEntry.getEffectiveAD(attributeName);
                      if (elementName != null) {
                          context.addProcessed(elementName);
                          try {
                              evaluateFlatAttribute(attributeName, elementName, context, registryEntry, flatPrefix, config, i, processedNames, ignoreWarnings);
                          } catch (ConfigEvaluatorException e) {
                              result = e;
                              return false;
                          }
                      }
                      return true;
                  }
  
                  @Override
                  public ConfigEvaluatorException getResult() {
                      return result;
                  }
              });
              if (e != null) {
                  throw e;
              }
              evaluateFlatAttribute(attributeName, attributeName, context, nestedRegistryEntry, flatPrefix, config, i, processedNames, ignoreWarnings);
  //            if (hierarchy != null) {
  //                for (RegistryEntry entry : hierarchy) {
  //                    //TODO both pid and alias?  or just alias?
  //                    i = evaluateFlatAttribute(attributeName, entry.getPid(), context, entry, flatPrefix, config, i, processedNames, ignoreWarnings);
  //                    if (entry.getAlias() != null) {
  //                        i = evaluateFlatAttribute(attributeName, entry.getAlias(), context, entry, flatPrefix, config, i, processedNames, ignoreWarnings);
  //                    }
  //                    //TODO extends alias, not childAlias
  //                    if (entry.getChildAlias() != null) {
  //                        i = evaluateFlatAttribute(attributeName, entry.getChildAlias(), context, entry, flatPrefix, config, i, processedNames, ignoreWarnings);
  //                    }
  //                }
  //            }
  //            i = evaluateFlatAttribute(attributeName, attributeName, context, nestedRegistryEntry, flatPrefix, config, i, processedNames, ignoreWarnings);
  
              //TODO no merging of flat stuff for cardinality 0.
              int cardinality = attributeDef.getCardinality();
              cardinality = cardinality == Integer.MIN_VALUE ? Integer.MAX_VALUE : cardinality < 0 ? -cardinality : cardinality == 0 ? 1 : cardinality;
              if (i.get() > cardinality) {
  
                  throw new ConfigEvaluatorException("Attribute " + attributeDef.getID() + " exceeded maximum allowed size " + cardinality);
              }
              return i.get() == 0 ? null : i;
          }
  
          //Check the properties to pick up already evaluated attributes
          Object actualValue = context.getProperties().get(attributeDef.getID());
          if (actualValue != null) {
              return actualValue;
          }
  
          if (!attributeDef.isFinal()) {
              if (attributeDef.getType() == MetaTypeFactory.PID_TYPE) {
                  if (attributeDef.getReferencePid() != null) {
                      RegistryEntry registryEntry = getRegistryEntry(attributeDef.getReferencePid());
                      if (registryEntry != null) {
                          rawValue = registryEntry.traverseHierarchy(new EntryAction<Object>() {
  
                              private Object rawValue;
  
                              @Override
                              public boolean entry(RegistryEntry registryEntry) {
                                  if (registryEntry.getEffectiveAD(attributeName) != null) {
                                      rawValue = mergeReferenceAttributes(registryEntry.getEffectiveAD(attributeName), context, attributeDef, config, rawValue);
                                  }
                                  return true;
                              }
  
                              @Override
                              public Object getResult() {
                                  return rawValue;
                              }
                          });
                      }
                  }
                  rawValue = mergeReferenceAttributes(attributeName, context, attributeDef, config, rawValue);
              } else {
                  rawValue = config.getAttribute(attributeName);
              }
          } else if (isWildcardReference(attributeDef)) {
              String factoryPid = attributeDef.getReferencePid();
              WildcardReference ref = new WildcardReference(factoryPid, attributeDef, context.getConfigElement().getConfigID());
              context.addUnresolvedReference(ref);
              try {
                  ExtendedConfiguration[] configs = configRetriever.findAllConfigurationsByPid(factoryPid);
                  configs = configs == null ? new ExtendedConfiguration[0] : configs;
                  ArrayList<String> pids = new ArrayList<String>(configs.length);
                  for (ExtendedConfiguration extConfig : configs) {
                      pids.add(extConfig.getPid());
                  }
                  if (attributeDef.getCardinality() > 0) {
                      actualValue = pids.toArray(new String[0]);
                  } else {
                      actualValue = pids;
                  }
                  if (actualValue != null) {
                      context.setProperty(prefixedAttributeName, actualValue);
                  }
                  evaluateFinish(context);
                  return actualValue;
              } catch (ConfigRetrieverException e) {
                  throw new ConfigEvaluatorException("problem looking up configurations with factoryPid " + factoryPid);
              }
          } else if (isWildcardService(attributeDef)) {
              String service = attributeDef.getService();
              WildcardService ref = new WildcardService(service, attributeDef, context.getConfigElement().getConfigID());
              context.addUnresolvedReference(ref);
              try {
                  ArrayList<String> pids = new ArrayList<String>();
                  List<RegistryEntry> entries = metatypeRegistry.getEntriesExposingService(service);
                  if (entries != null) {
                      for (RegistryEntry entry : entries) {
                          ExtendedConfiguration[] configs = configRetriever.findAllConfigurationsByPid(entry.getPid());
                          configs = configs == null ? new ExtendedConfiguration[0] : configs;
                          for (ExtendedConfiguration extConfig : configs) {
                              pids.add(extConfig.getPid());
                          }
                      }
                  }
                  if (attributeDef.getCardinality() > 0) {
                      actualValue = pids.toArray(new String[0]);
                  } else {
                      actualValue = pids;
                  }
                  if (actualValue != null) {
                      context.setProperty(prefixedAttributeName, actualValue);
                  }
                  evaluateFinish(context);
                  return actualValue;
              } catch (ConfigRetrieverException e) {
                  throw new ConfigEvaluatorException("problem looking up configurations with service " + service);
              }
          }
  
          if (rawValue == null) {
              rawValue = getUnconfiguredAttributeValue(context, attributeDef);
          }
  
          // Process any list variables of the form ${list(variableName)}. We do this here separately from the other variable expression
          // processing so that the values are available prior to cardinality checks.
          if (rawValue != null) {
              rawValue = variableEvaluator.processVariableLists(rawValue, attributeDef, context, ignoreWarnings);
          }
          if (rawValue != null) {
              try {
                  actualValue = evaluateMetaType(rawValue, attributeDef, context, ignoreWarnings);
              } catch (ConfigEvaluatorException iae) {
                  // try to fall-back to default value if validation of an option fails.
                  String validOptions[] = attributeDef.getOptionValues();
                  if (validOptions == null) {
                      // Fall back to variable registry or default value
                      Object badValue = rawValue;
                      rawValue = getUnconfiguredAttributeValue(context, attributeDef);
                      if (rawValue != null && !ignoreWarnings) {
                          Tr.warning(tc, "warn.config.validate.failed", iae.getMessage());
                          Tr.warning(tc, "warn.config.invalid.using.default.value", attributeDef.getID(), badValue, rawValue);
                          actualValue = evaluateMetaType(rawValue, attributeDef, context, ignoreWarnings);
                      } else {
                          throw iae;
                      }
  
                  } else {
                      if (tc.isWarningEnabled() && !ignoreWarnings) {
                          StringBuffer strBuffer = new StringBuffer();
                          // This formatter is consistent with the message in nlsprops
                          for (int i = 0; i < validOptions.length; i++) {
                              strBuffer.append("[");
                              strBuffer.append(validOptions[i]);
                              strBuffer.append("]");
                          }
                          Tr.warning(tc, "warn.config.invalid.value", attributeDef.getID(), rawValue, strBuffer.toString());
                      }
  
                      // Fall back to variable registry or default value
                      rawValue = getUnconfiguredAttributeValue(context, attributeDef);
                      if (rawValue != null) {
                          actualValue = evaluateMetaType(rawValue, attributeDef, context, ignoreWarnings);
                      }
                  }
              }
  
              if (actualValue != null) {
                  context.setProperty(prefixedAttributeName, actualValue);
              }
              evaluateFinish(context);
          }
  
          return actualValue;
      }