content: /*
   * Copyright 1999-2018 Alibaba Group Holding Ltd.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.alibaba.nacos.common.tls;
  
  import com.alibaba.nacos.api.common.Constants;
  import com.alibaba.nacos.common.executor.ExecutorFactory;
  import com.alibaba.nacos.common.executor.NameThreadFactory;
  import com.alibaba.nacos.common.utils.ClassUtils;
  import com.alibaba.nacos.common.utils.IoUtils;
  import com.alibaba.nacos.common.utils.MD5Utils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.ScheduledExecutorService;
  import java.util.concurrent.TimeUnit;
  import java.util.concurrent.atomic.AtomicBoolean;
  
  /**
   * Certificate file update monitoring
   *
   * <p>Considering that the current client needs to support jdk 1.6 and module dependencies ,
   * the WatchFileCenter in the core module is not used
   *
   * @author wangwei
   */
  public final class TlsFileWatcher {
      
      private static final Logger LOGGER = LoggerFactory.getLogger(TlsFileWatcher.class);
      
      private AtomicBoolean started = new AtomicBoolean(false);
      
      private final int checkInterval = TlsSystemConfig.tlsFileCheckInterval;
      
      private Map<String, String> fileMd5Map = new HashMap<>();
      
      private ConcurrentHashMap<String, FileChangeListener> watchFilesMap = new ConcurrentHashMap<>();
      
      private final ScheduledExecutorService service = ExecutorFactory.Managed
              .newSingleScheduledExecutorService(ClassUtils.getCanonicalName(TlsFileWatcher.class),
                      new NameThreadFactory("com.alibaba.nacos.core.common.tls"));
      
      private static TlsFileWatcher tlsFileWatcher = new TlsFileWatcher();
      
      private TlsFileWatcher() {
          start();
      }
      
      public static TlsFileWatcher getInstance() {
          return tlsFileWatcher;
      }
      
      /**
       * Add file change listener for specified path.
       *
       * @param fileChangeListener listener
       * @param filePaths          file paths
       * @throws IOException If an I/O error occurs
       */
      public void addFileChangeListener(FileChangeListener fileChangeListener, String... filePaths) throws IOException {
          for (String filePath : filePaths) {
              if (filePath != null && new File(filePath).exists()) {
                  watchFilesMap.put(filePath, fileChangeListener);
                  InputStream in = null;
                  try {
                      in = new FileInputStream(filePath);
                      fileMd5Map.put(filePath, MD5Utils.md5Hex(IoUtils.toString(in, Constants.ENCODE), Constants.ENCODE));
                  } finally {
                      IoUtils.closeQuietly(in);
                  }
              }
          }
      }
      
      /**
       * start file watch task. Notify when the MD5 of file changed
       */
      public void start() {
          if (started.compareAndSet(false, true)) {
              service.scheduleAtFixedRate(() -> {
                  for (Map.Entry<String, FileChangeListener> item : watchFilesMap.entrySet()) {
                      String filePath = item.getKey();
                      String newHash;
                      InputStream in = null;
                      try {
                          in = new FileInputStream(filePath);
                          newHash = MD5Utils.md5Hex(IoUtils.toString(in, Constants.ENCODE), Constants.ENCODE);
                      } catch (Exception exception) {
                          LOGGER.warn(" service has exception when calculate the file MD5. " + exception);
                          continue;
                      } finally {
                          IoUtils.closeQuietly(in);
                      }
                      if (!newHash.equals(fileMd5Map.get(filePath))) {
                          LOGGER.info(filePath + " file hash changed, need reload ssl context");
                          fileMd5Map.put(filePath, newHash);
                          item.getValue().onChanged(filePath);
                          LOGGER.info(filePath + " onChanged success!");
                      }
                  }
              }, 1, checkInterval, TimeUnit.MINUTES);
          }
      }
      
      public interface FileChangeListener {
          
          /**
           * listener onChanged event.
           *
           * @param filePath Path of changed file
           */
          void onChanged(String filePath);
      }
      
  }
  