/**
  * Returns the configuration history entries for one {@link Slave}.
  *
  * @return history list for one {@link Slave}.
  * @throws IOException if {@link JobConfigHistoryConsts#HISTORY_FILE} might not be read or the
  *     path might not be urlencoded.
  */
 public final List<ConfigInfo> getSlaveConfigs() throws IOException {
   checkConfigurePermission();
   final ArrayList<ConfigInfo> configs = new ArrayList<ConfigInfo>();
   final ArrayList<HistoryDescr> values =
       new ArrayList<HistoryDescr>(getHistoryDao().getRevisions(slave).values());
   for (final HistoryDescr historyDescr : values) {
     final String timestamp = historyDescr.getTimestamp();
     final XmlFile oldRevision = getHistoryDao().getOldRevision(slave, timestamp);
     if (oldRevision.getFile() != null) {
       configs.add(ConfigInfo.create(slave.getNodeName(), true, historyDescr, true));
     } else if ("Deleted".equals(historyDescr.getOperation())) {
       configs.add(ConfigInfo.create(slave.getNodeName(), false, historyDescr, true));
     }
   }
   Collections.sort(configs, ParsedDateComparator.DESCENDING);
   return configs;
 }
 /**
  * Gets config history entries for the view options 'created', 'deleted' and 'jobs'. While 'jobs'
  * displays all available job config history entries, 'deleted' and 'created' only show the last
  * or the first one respectively.
  *
  * @param itemDir The job directory as File
  * @param folderName Something Jesse Glick came up with but never documented, probably the
  *     folderName.
  * @throws IOException If one of the entries cannot be read.
  */
 void getConfigsForType(File itemDir, String folderName) throws IOException {
   final String itemName =
       folderName.isEmpty() ? itemDir.getName() : folderName + "/" + itemDir.getName();
   final List<HistoryDescr> historyEntries =
       new ArrayList<HistoryDescr>(overViewhistoryDao.getJobHistory(itemName).values());
   if (historyEntries.isEmpty()) {
     return;
   }
   final boolean isADeletedJob = DeletedFileFilter.accepts(itemName);
   final boolean isNotADeletedJob = !isADeletedJob;
   if ("created".equals(type)) {
     if (isADeletedJob) {
       return;
     }
     HistoryDescr histDescr = historyEntries.get(0);
     if ("Created".equals(histDescr.getOperation())) {
       final ConfigInfo config = ConfigInfo.create(itemName, true, histDescr, true);
       configs.add(config);
     } else {
       // Why would the created entry not be the first one? Answer:
       // There's always a 'Changed' entry before the 'Created' entry,
       // because in the Jenkins core, when creating a new job,
       // the SaveableListener (which handles changes) fires
       // before the ItemListener (which handles creation, deletion etc.)
       // Older versions of the plugin didn't show this behaviour
       // since it was masked by some race condition.
       histDescr = historyEntries.get(1);
       if ("Created".equals(histDescr.getOperation())) {
         final ConfigInfo config = ConfigInfo.create(itemName, true, histDescr, true);
         configs.add(config);
       }
     }
   } else if ("deleted".equals(type)) {
     final HistoryDescr histDescr = historyEntries.get(historyEntries.size() - 1);
     if ("Deleted".equals(histDescr.getOperation())) {
       final ConfigInfo config = ConfigInfo.create(itemName, false, histDescr, false);
       configs.add(config);
     }
   } else {
     configs.addAll(
         HistoryDescrToConfigInfo.convert(itemName, true, historyEntries, isNotADeletedJob));
   }
 }
 @Override
 public void run() {
   try {
     long totalCount = configInfoLoader.getConfigInfoCount();
     if (totalCount <= 0) {
       if (LOGGER.isDebugEnabled()) {
         LOGGER.debug(String.format("%s没有加载到配置信息", ApiConfiguratorProvider.class));
       }
       return;
     }
     for (long startIndex = 0; startIndex < totalCount; startIndex += PAGE_SIZE) {
       List<ConfigInfo> configInfos = configInfoLoader.getConfigInfo(startIndex, PAGE_SIZE);
       if (LOGGER.isDebugEnabled()) {
         LOGGER.debug(
             String.format(
                 "%s加载配置信息, totalCount:%s, startIndex:%s, pageSize:%s",
                 ApiConfiguratorProvider.class, totalCount, startIndex, PAGE_SIZE));
       }
       for (ConfigInfo configInfo : configInfos) {
         if (configInfo.isValid()) {
           propertiesMap.put(wrapKey(configInfo.getKey()), configInfo.getValue());
           if (LOGGER.isDebugEnabled()) {
             LOGGER.debug(
                 String.format(
                     "%s覆盖配置, Key:%s, Value:%s",
                     ApiConfiguratorProvider.class, configInfo.getKey(), configInfo.getValue()));
           }
         } else {
           propertiesMap.remove(wrapKey(configInfo.getKey()));
           if (LOGGER.isDebugEnabled()) {
             LOGGER.debug(
                 String.format(
                     "%s移除配置, Key:%s, Value:%s",
                     ApiConfiguratorProvider.class, configInfo.getKey(), configInfo.getValue()));
           }
         }
       }
     }
   } catch (Exception e) {
     LOGGER.error(String.format("%s更新配置信息出错", ApiConfiguratorProvider.class), e);
   }
 }