/**
   * Enact all of the value definition changes, then call the group listener (if there is one).
   *
   * @param groupUpdateAction the group action to enact value changes on.
   * @param configurationSource will be passed to the {@link
   *     GroupChangeListener#onChange(ConfigurationSource)} method giving it a chance to lookup
   *     other values at runtime.
   * @throws GroupConfigurationException if any problem occurs with the value updates or listener
   *     invocation.
   */
  public void enactGroupChange(
      GroupChangeAction groupUpdateAction, ConfigurationSource configurationSource) {
    ValueDefinitionGroup valueDefinitionGroup = groupUpdateAction.getGroup();
    List<ValueChangeAction> actionList = groupUpdateAction.getActionList();
    List<ConfigurationException> valueUpdateErrors = new ArrayList<ConfigurationException>();

    Object semaphore = valueDefinitionGroup.getSemaphore();
    if (semaphore == null) {
      // Semaphore must be set to something
      semaphore = new Object();
    }

    /*
     * Lock on the semaphore, providing an opportunity to atomically update a group of variables.
     */
    synchronized (semaphore) {

      /*
       * Make the value updates by calling the listeners. Make an attempt to update all values, in case
       */
      for (ValueChangeAction valueUpdateAction : actionList) {
        try {
          enactValueChange(valueUpdateAction);
        } catch (ConfigurationException e) {
          valueUpdateErrors.add(e);
        }
      }

      if (!valueUpdateErrors.isEmpty()) {
        throw new GroupConfigurationException(
            valueDefinitionGroup.getName(), Phase.VALUE_ASSIGNMENT, valueUpdateErrors);
      }

      /*
       * We are clear to notify of the change
       */
      GroupChangeListener changeListener = valueDefinitionGroup.getChangeListener();
      if (changeListener != null) {
        try {
          changeListener.onChange(configurationSource);
        } catch (RuntimeException e) {
          throw new GroupConfigurationException(
              valueDefinitionGroup.getName(), Phase.LISTENER_INVOCATION, e);
        }
      }
    }
  }
  /**
   * Prepares a {@link ValueChangeAction} for each {@link ValueDefinition} in the specified group.
   *
   * @param valueDefinitionGroup the group to prepare value changes for
   * @param configurationSource the source from which to resolve the values of this group
   * @return the group change action
   * @throws GroupConfigurationException if there is a problem retrieving values for one or more of
   *     the {@link ValueDefinition}s in the group
   */
  public GroupChangeAction prepareGroupChange(
      ValueDefinitionGroup valueDefinitionGroup, ConfigurationSource configurationSource) {
    Collection<ValueDefinition<?, ?>> valueDefinitionList = valueDefinitionGroup.getValues();
    List<ValueChangeAction> updateActions =
        new ArrayList<ValueChangeAction>(valueDefinitionList.size());
    List<ConfigurationException> valueResolveErrors = new ArrayList<ConfigurationException>();
    try {
      for (ValueDefinition<?, ?> valueDefinition : valueDefinitionList) {
        ValueChangeAction valueChangeAction =
            prepareValueChange(valueDefinition, configurationSource);
        updateActions.add(valueChangeAction);
      }
    } catch (ConfigurationException e) {
      valueResolveErrors.add(e);
    }

    if (!valueResolveErrors.isEmpty()) {
      throw new GroupConfigurationException(
          valueDefinitionGroup.getName(), Phase.VALUE_DISCOVERY, valueResolveErrors);
    }
    return new GroupChangeAction(valueDefinitionGroup, updateActions);
  }