docs: public static synchronized Map<String, List<String>> startConfig(Map<String, Object> oldconfig, Map<String, Object> newconfig) {
          if (null == oldconfig) {
              // if no old config, then assume this is not a restart situation.  If "true" is passed for restart, then
              // other logic that throws an exception while trying to "unload" started chains needs to be modify not to throw
              // those exceptions, since sometimes the chains won't be loaded in the first place.
              return startConfig(newconfig, false);
          }
          final boolean bTrace = TraceComponent.isAnyTracingEnabled();
          if (bTrace && tc.isEventEnabled()) {
              Tr.event(tc, "startConfig(old,new)");
          }
          final ChannelFramework cf = ChannelFrameworkFactory.getChannelFramework();
          Map<String, Map<String, String[]>> oldc = extractConfig(oldconfig);
          Map<String, Map<String, String[]>> newc = extractConfig(newconfig);
  
          // TODO handle groups
  
          List<String> runningChains = new LinkedList<String>();
          Map<String, String[]> oldlist = oldc.get("chains");
          Map<String, String[]> newlist = newc.get("chains");
          List<String> chainsToDelete = new LinkedList<String>();
          List<String> chainsToStop = new LinkedList<String>();
          Map<String, String[]> chainsToStart = new HashMap<String, String[]>();
          // if an old chain name no longer exists, we need to stop it
          for (String oldname : oldlist.keySet()) {
              if (!newlist.containsKey(oldname)) {
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Defunct chain; " + oldname);
                  }
                  chainsToDelete.add(oldname);
              }
          }
          // check for new or updated chain definitions
          for (Entry<String, String[]> entry : newlist.entrySet()) {
              final String newname = entry.getKey();
              final String[] oldobj = oldlist.get(newname);
              if (null == oldobj) {
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "New chain; " + newname);
                  }
                  chainsToStart.put(newname, entry.getValue());
              } else {
                  // compare attributes
                  final String[] newobj = entry.getValue();
                  if (hasChanged(oldobj, newobj)) {
                      if (bTrace && tc.isDebugEnabled()) {
                          Tr.debug(tc, "Chain updated; " + newname);
                      }
                      chainsToStop.add(newname);
                      chainsToStart.put(newname, newobj);
                  } else {
                      if (bTrace && tc.isDebugEnabled()) {
                          Tr.debug(tc, "Chain unchanged; " + newname);
                      }
                      runningChains.add(newname);
                  }
              }
          }
  
          // check for new or updated channels
          List<String> runningChannels = new LinkedList<String>();
          Map<String, String[]> channelsToCreate = new HashMap<String, String[]>();
          oldlist = oldc.get("channels");
          newlist = newc.get("channels");
          for (String oldname : oldlist.keySet()) {
              if (!newlist.containsKey(oldname)) {
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Defunct channel: " + oldname);
                  }
                  try {
                      // must delete chains that were using the defunct channel
                      ChainData[] existingchains = cf.getAllChains(oldname);
                      for (ChainData cd : existingchains) {
                          final String name = cd.getName();
                          if (bTrace && tc.isDebugEnabled()) {
                              Tr.debug(tc, "Deleting chain; " + name);
                          }
                          chainsToDelete.add(name);
                          chainsToStart.remove(name);
                          runningChains.remove(name);
                      }
                  } catch (ChannelException e) {
                      FFDCFilter.processException(e, "ChannelUtils.startConfig",
                                                  "defunctChannel", new Object[] { oldname, cf });
                      if (bTrace && tc.isDebugEnabled()) {
                          Tr.debug(tc, "Unable to query defunct channel; " + e);
                      }
                  }
              }
          }
          for (String newname : newlist.keySet()) {
              String[] oldobj = oldlist.get(newname);
              String[] newobj = newlist.get(newname);
              if (null == oldobj) {
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "New channel; " + newname);
                  }
                  channelsToCreate.put(newname, newobj);
              } else {
                  if (hasChanged(oldobj, newobj)) {
                      if (bTrace && tc.isDebugEnabled()) {
                          Tr.debug(tc, "Channel updated; " + newname);
                      }
                      channelsToCreate.put(newname, newobj);
                      try {
                          // since we don't know if the channel supports runtime
                          // changes, stop chains using it
                          ChainData[] c = cf.getAllChains(newname);
                          for (ChainData cd : c) {
                              final String name = cd.getName();
                              if (runningChains.contains(name)) {
                                  if (bTrace && tc.isDebugEnabled()) {
                                      Tr.debug(tc, "Restarting chain; " + name);
                                  }
                                  chainsToStop.add(name);
                                  runningChains.remove(name);
                                  // if it wasn't already flagged to start, make sure
                                  // it restarts after the stop
                                  if (!chainsToStart.containsKey(name)) {
                                      chainsToStart.put(name, newc.get("chains").get(name));
                                  }
                              }
                          }
                      } catch (ChannelException e) {
                          FFDCFilter.processException(e, "ChannelUtils.startConfig",
                                                      "updatedChannel", new Object[] { newname, cf });
                          if (bTrace && tc.isDebugEnabled()) {
                              Tr.debug(tc, "Unable to query updated channel; " + e);
                          }
                      }
                  } else {
                      if (bTrace && tc.isDebugEnabled()) {
                          Tr.debug(tc, "Channel unchanged; " + newname);
                      }
                      runningChannels.add(newname);
                  }
              }
          }
  
          // check factories
          List<String> runningFactories = new LinkedList<String>();
          Map<String, String[]> factoriesToCreate = new HashMap<String, String[]>();
          oldlist = oldc.get("factories");
          newlist = newc.get("factories");
          for (String oldname : oldlist.keySet()) {
              if (!newlist.containsKey(oldname)) {
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Defunct factory; " + oldname);
                  }
              }
          }
          for (String newname : newlist.keySet()) {
              String[] oldobj = oldlist.get(newname);
              String[] newobj = newlist.get(newname);
              if (null == oldobj) {
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "New factory; " + newname);
                  }
                  factoriesToCreate.put(newname, newobj);
              } else {
                  if (hasChanged(oldobj, newobj)) {
                      if (bTrace && tc.isDebugEnabled()) {
                          Tr.debug(tc, "Factory updated; " + newname);
                      }
                      factoriesToCreate.put(newname, newobj);
                  } else {
                      if (bTrace && tc.isDebugEnabled()) {
                          Tr.debug(tc, "Factory unchanged; " + newname);
                      }
                      runningFactories.add(newname);
                  }
              }
          }
  
          // check for updated endpoints
          List<String> runningEndpoints = new LinkedList<String>();
          Map<String, String[]> endpointsToCreate = new HashMap<String, String[]>();
          List<String> updatedEndpoints = new LinkedList<String>();
          oldlist = oldc.get("endpoints");
          newlist = newc.get("endpoints");
          for (String oldname : oldlist.keySet()) {
              if (!newlist.containsKey(oldname)) {
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Defunct endpoint; " + oldname);
                  }
                  EndPointMgrImpl.getRef().removeEndPoint(oldname);
              }
          }
          for (String newname : newlist.keySet()) {
              String[] oldobj = oldlist.get(newname);
              String[] newobj = newlist.get(newname);
              if (null == oldobj) {
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "New endpoint; " + newname);
                  }
                  endpointsToCreate.put(newname, newobj);
              } else {
                  if (hasChanged(oldobj, newobj)) {
                      if (bTrace && tc.isDebugEnabled()) {
                          Tr.debug(tc, "Endpoint updated; " + newname);
                      }
                      endpointsToCreate.put(newname, newobj);
                      updatedEndpoints.add(newname);
                  } else {
                      if (bTrace && tc.isDebugEnabled()) {
                          Tr.debug(tc, "Endpoint unchanged; " + newname);
                      }
                      runningEndpoints.add(newname);
                  }
              }
          }
          // load new endpoint definitions
          runningEndpoints.addAll(loadEndPoints(endpointsToCreate));
          // TCP does not allow endpoint runtime changes, restart necessary chains
          if (!updatedEndpoints.isEmpty()) {
              final Map<String, String[]> newchains = newc.get("chains");
              final Map<String, String[]> newchannels = newc.get("channels");
              for (String chainname : runningChains) {
                  ChainData cd = cf.getChain(chainname);
                  ChannelData[] channels = (null != cd) ? cd.getChannelList() : null;
                  if (null != channels && 0 < channels.length) {
                      String ep = (String) channels[0].getPropertyBag().get("endPointName");
                      if (null != ep && updatedEndpoints.contains(ep)) {
                          if (bTrace && tc.isDebugEnabled()) {
                              Tr.debug(tc, "Chain [" + chainname + "] using updated endpoint " + ep);
                          }
                          chainsToStop.add(chainname);
                          chainsToStart.put(chainname, newchains.get(chainname));
                          String tcp = channels[0].getExternalName();
                          channelsToCreate.put(tcp, newchannels.get(tcp));
                      }
                  }
              }
          }
  
          Map<String, List<String>> rc = new HashMap<String, List<String>>();
          // stop chains that need it
          if (!chainsToDelete.isEmpty()) {
              unloadChains(chainsToDelete.iterator());
          }
  
          // first stop/quiesce the chains (AND WAIT FOR COMPLETION),
          // then destroy them for updates to happen correctly
          stopChains(chainsToStop, -1L, null);
          for (String chainname : chainsToStop) {
              runningChains.remove(chainname);
              try {
                  ChainData cd = cf.getChain(chainname);
                  if (null != cd) {
                      cf.destroyChain(cd);
                  }
              } catch (Exception e) {
                  FFDCFilter.processException(e, "ChannelUtils.startConfig",
                                              "stopChain", new Object[] { chainname, cf });
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Error destroying chain; " + chainname + " " + e);
                  }
                  chainsToStart.remove(chainname);
              }
          }
  
          runningFactories.addAll(loadFactories(factoriesToCreate));
          rc.put("factory", runningFactories);
          rc.put("endpoint", runningEndpoints);
          runningChannels.addAll(loadChannels(channelsToCreate));
          rc.put("channel", runningChannels);
          runningChains.addAll(loadChains(chainsToStart, true, true));
          for (String chain : chainsToStart.keySet()) {
              try {
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Starting chain: " + chain);
                  }
                  cf.startChain(chain);
              } catch (RetryableChannelException rce) {
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Error starting chain; " + rce);
                  }
              } catch (Exception e) {
                  FFDCFilter.processException(e, "ChannelUtils.startConfig",
                                              "chain", new Object[] { chain, cf });
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Error starting chain; " + e);
                  }
              }
          }
          rc.put("chain", runningChains);
          // rc.put("group", loadGroups(newc.get("group"), true, false));
          rc.put("group", new LinkedList<String>());
          return rc;
      }