content: package org.aion.zero.impl.config.dynamic;
  
  import com.google.common.annotations.VisibleForTesting;
  import java.util.ArrayDeque;
  import java.util.Deque;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Objects;
  import java.util.Optional;
  import java.util.function.Function;
  import org.aion.log.AionLoggerFactory;
  import org.aion.log.LogEnum;
  import org.aion.zero.impl.config.CfgAion;
  import org.slf4j.Logger;
  
  /**
   * Responsible for receiving new Aion kernel configurations and applying them to the kernel while it
   * is running ("in-flight").
   *
   * <p>New configurations can be submitted through the {@link #propose(String)} method. This is
   * exposed through the JMX interface if it needs to be called from a separate process.
   *
   * <p>When new configurations are proposed, they can be accepted or rejected. If any error occurs
   * while applying a new configuration, it is rejected. When it is accepted, the new configuration is
   * used in its entirety; similarly, if it is rejected, no changes at all should be made to kernel
   * (any changes applied up until the error must be reverted).
   *
   * <p>Not all properties in <tt>config.xml</tt> are modifiable in-flight. If a change to a such a
   * property is present in the new configuration, it will be rejected.
   */
  public class InFlightConfigReceiver implements InFlightConfigReceiverMBean {
      /** Default JMX port */
      public static final int DEFAULT_JMX_PORT = 11234; /* TODO put in config.xml */
      /** Default JMX object name */
      public static final String DEFAULT_JMX_OBJECT_NAME = "org.aion.mcf.config.receiver:id=1";
  
      private final CfgAion activeCfg;
      private final DynamicConfigKeyRegistry configKeyRegistry;
  
      private static final Logger LOG = AionLoggerFactory.getLogger(LogEnum.GEN.name());
  
      public InFlightConfigReceiver(CfgAion activeCfg, DynamicConfigKeyRegistry configKeyRegistry) {
          this.activeCfg = activeCfg;
          this.configKeyRegistry = configKeyRegistry;
      }
  
      /**
       * Attempt to apply a new config. If any error occurs, any modification to the config that has
       * already been applied will be rolled back. If successful, the new config will become the
       * active config.
       */
      @Override
      public synchronized ConfigProposalResult propose(String configXmlText)
              throws RollbackException {
          //TODO: [GUI] disable the dynamic config settings
          throw new UnsupportedOperationException();
  //        LOG.trace("Received new configuration XML");
  //        LOG.trace(configXmlText);
  //        CfgAion newCfg = new CfgAion();
  //        try {
  //            XMLStreamReader xmlStream =
  //                    XMLInputFactory.newInstance()
  //                            .createXMLStreamReader(CharSource.wrap(configXmlText).openStream());
  //            newCfg.fromXML(xmlStream);
  //        } catch (XMLStreamException | IOException | NumberFormatException ex) {
  //            LOG.error(
  //                    "Error constructing CfgAion from given XML.  Rejecting CfgAion proposal.  Exception was: {}",
  //                    ex);
  //            return new ConfigProposalResult(false, ex);
  //        }
  //
  //        ConfigProposalResult result = applyNewConfig(newCfg);
  //        if (result.isSuccess()) {
  //            // At kernel start-up time, values from CfgAion.inst() are read and copied
  //            // to other places.  Right now we don't do anything to find/update those
  //            // values (assume the IDynamicConfigApplier subclasses will all do the
  //            // necessary updating correctly).  Revisit this that becomes too
  //            // confusing/spaghetti-like.
  //
  //            CfgAion.setInst(newCfg);
  //        }
  //        return result;
      }
  
      @VisibleForTesting
      ConfigProposalResult applyNewConfig(CfgAion newCfg) throws RollbackException {
          // build up an undo stack as we apply each config change so we can rollback if error
          final Deque<InFlightConfigChangeResult> undoSteps = new ArrayDeque<>();
  
          for (String key : configKeyRegistry.getBoundKeys()) {
              Function<CfgAion, ?> getter = configKeyRegistry.getGetter(key);
              if (getter == null) {
                  throw new IllegalStateException(
                          String.format(
                                  "DynamicConfigKeyRegistry configuration error.  There is no getter for the bound key '%s'",
                                  key));
              }
              Object newVal = getter.apply(newCfg);
              Object oldVal = getter.apply(activeCfg);
              if (Objects.equals(newVal, oldVal)) {
                  continue;
              }
  
              Optional<IDynamicConfigApplier> maybeApplier = configKeyRegistry.getApplier(key);
              if (!maybeApplier.isPresent()) {
                  throw new IllegalArgumentException(
                          String.format(
                                  "The key '%s' does not support in-flight configuration change",
                                  key));
              }
  
              IDynamicConfigApplier applier = maybeApplier.get();
  
              final InFlightConfigChangeResult result;
              try {
                  LOG.trace("About to call applier.apply {} for key '{}'", applier, key);
                  result = applier.apply(activeCfg, newCfg);
              } catch (InFlightConfigChangeException ex) {
                  // if applier.apply worked partially then threw, kernel might be in some broken
                  // state now; might as well try to fix ourselves as much as possible with rollback
                  // though.
                  LOG.error(
                          String.format(
                                  "Error applying change to config key '%s'.  Will attempt rollback.",
                                  key),
                          ex);
                  rollback(undoSteps, newCfg);
                  return new ConfigProposalResult(false, ex);
              }
  
              LOG.trace("Applier {} returned '{}' for key '{}'", applier, result, key);
              if (!result.isSuccess()) {
                  LOG.error(
                          String.format(
                                  "Could not apply change to config key '%s'.  Will attempt rollback.",
                                  key));
                  rollback(undoSteps, newCfg);
                  return new ConfigProposalResult(false);
              }
  
              undoSteps.push(result);
          }
          return new ConfigProposalResult(true);
      }
  
      private void rollback(Deque<InFlightConfigChangeResult> steps, CfgAion newCfg)
              throws RollbackException {
          LOG.info("Trying to rollback.  Undo steps are: {}", steps.toString());
  
          List<InFlightConfigChangeException> exceptions = new LinkedList<>();
          while (!steps.isEmpty()) {
              InFlightConfigChangeResult result = steps.pop();
              try {
                  LOG.trace("About to call undo for application result {}", result);
                  result.getApplier().undo(activeCfg, newCfg);
              } catch (InFlightConfigChangeException ex) {
                  exceptions.add(ex);
                  LOG.error(
                          String.format("Rollback error while trying to undo %s", result.toString()));
              }
          }
          if (!exceptions.isEmpty()) {
              throw new RollbackException("Rollback had errors", exceptions);
          }
      }
  
      /** @return a JMX URL that client can use to connect to JMX server. */
      public static String createJmxUrl(int port) {
          return String.format("service:jmx:rmi:///jndi/rmi://127.0.0.1:%d/jmxrmi", port);
      }
  }
  