All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.hana.datalake.files.utils.filewatcher.CertificateWatcher Maven / Gradle / Ivy

Go to download

An implementation of org.apache.hadoop.fs.FileSystem targeting SAP HANA Data Lake Files.

There is a newer version: 3.0.27
Show newest version
// © 2023-2024 SAP SE or an SAP affiliate company. All rights reserved.
package com.sap.hana.datalake.files.utils.filewatcher;

import com.sap.hana.datalake.files.classification.InterfaceAudience;
import com.sap.hana.datalake.files.utils.threads.ThreadUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.hadoop.conf.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStoreException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicLong;

@InterfaceAudience.Private
public class CertificateWatcher {

  @FunctionalInterface
  public interface CertificateRotationHandler {
    void handleCertificateUpdate() throws CertificateException, IOException, KeyStoreException;
  }

  private static final Logger LOG = LoggerFactory.getLogger(CertificateWatcher.class);
  private static final AtomicLong INSTANCE_GLOBAL_INDEX = new AtomicLong(0);
  private static final String THREAD_NAME_PREFIX_FORMAT = "CertificateWatcher-%d-thread";

  protected final List certificateRotationHandlers;

  private final Map watchedCertificates;
  private final ExecutorService watcherThreadExecutor;

  public CertificateWatcher(final List certificatePaths,
                            final CertificateWatcher.CertificateRotationHandler certificateRotationHandler,
                            final Configuration conf) {
    this(certificatePaths, conf);
    this.registerCertificateHandler(certificateRotationHandler);
  }

  public CertificateWatcher(final List certificatePaths, final Configuration conf) {
    final String threadNamePrefix = String.format(THREAD_NAME_PREFIX_FORMAT, INSTANCE_GLOBAL_INDEX.getAndIncrement());
    final ExecutorService watcherThreadExecutor = ThreadUtils.newSingleDaemonThreadExecutor(threadNamePrefix, conf);

    this.certificateRotationHandlers = new ArrayList<>();
    this.watchedCertificates = new HashMap<>();
    this.watcherThreadExecutor = watcherThreadExecutor;

    try {
      this.setupWatchedCertificates(certificatePaths);
    } catch (final IOException ex) {
      final String errMsg = String.format("Could not initialize CertificateWatcher(certificatePaths=%s)", certificatePaths);
      LOG.error(errMsg, ex);
      throw new RuntimeException(errMsg);
    }
  }

  public void start() {
    final Set watchedDirectories = new HashSet<>();

    for (final WatchedCertificate watchedCertificate : this.watchedCertificates.values()) {
      final String certificateFilePath = watchedCertificate.getPath();
      final String fileDir = Paths.get(certificateFilePath).toAbsolutePath().getParent().toString();
      watchedDirectories.add(fileDir);
    }

    try {
      final DirectoryWatcher directoryWatcher = new DirectoryWatcher(new ArrayList<>(watchedDirectories), LOG, this::handleModifyEvent);
      this.watcherThreadExecutor.submit(directoryWatcher);
    } catch (final FileWatcherException ex) {
      final String errMsg = "Error occurred while setting up the Certificate Watcher";
      LOG.error(errMsg, ex);
      throw new RuntimeException(errMsg);
    }
  }

  public void shutdown() {
    this.watcherThreadExecutor.shutdownNow();
  }

  public void registerCertificateHandler(final CertificateRotationHandler certificateRotationHandler) {
    this.certificateRotationHandlers.add(certificateRotationHandler);
  }

  protected String calculateChecksum(final String filePath) throws IOException {
    try (final InputStream inputStream = Files.newInputStream(Paths.get(filePath))) {
      return DigestUtils.sha256Hex(inputStream);
    }
  }

  private void setupWatchedCertificates(final List certificatePaths) throws IOException {
    for (final String certPath : certificatePaths) {
      final String fileName = Paths.get(certPath).getFileName().toString();
      this.watchedCertificates.put(fileName, new WatchedCertificate(certPath, this.calculateChecksum(certPath)));
    }
  }

  private void handleModifyEvent(final String fileName) throws FileWatcherException {
    final WatchedCertificate modifiedCertificate = this.watchedCertificates.get(fileName);

    if (modifiedCertificate == null) {
      LOG.debug("File [{}] has been modified but it is not a watched certificate; ignore", fileName);
      return;
    }

    final String previousChecksum = modifiedCertificate.getChecksum();

    try {
      final String currentChecksum = this.calculateChecksum(modifiedCertificate.getPath());

      if (currentChecksum.equals(previousChecksum)) {
        LOG.debug("Certificate file [{}] has been modified, but its checksum has not changed; ignore", fileName);
        return;
      }

      LOG.info("Certificate file [{}] has been modified; handling certificate rotation", fileName);

      for (final CertificateRotationHandler certificateRotationHandler : this.certificateRotationHandlers) {
        certificateRotationHandler.handleCertificateUpdate();
      }

      modifiedCertificate.setChecksum(currentChecksum);
    } catch (final Exception ex) {
      final String errMsg = String.format("Error handling certificate [%s] rotation", fileName);
      LOG.error(errMsg, ex);
      throw new FileWatcherException(errMsg, ex);
    }
  }

  private static class WatchedCertificate {

    private final String path;

    private String checksum;

    private WatchedCertificate(final String path, final String checksum) {
      this.path = path;
      this.checksum = checksum;
    }

    public String getPath() {
      return this.path;
    }

    public String getChecksum() {
      return this.checksum;
    }

    public void setChecksum(final String checksum) {
      this.checksum = checksum;
    }
  }

}

// © 2023-2024 SAP SE or an SAP affiliate company. All rights reserved.




© 2015 - 2025 Weber Informatics LLC | Privacy Policy