content: package io.arivera.oss.embedded.rabbitmq.extract;
  
  import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig;
  import io.arivera.oss.embedded.rabbitmq.apache.commons.lang3.StopWatch;
  import io.arivera.oss.embedded.rabbitmq.util.ArchiveType;
  
  import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
  import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
  import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
  import org.apache.commons.compress.archivers.zip.ZipFile;
  import org.apache.commons.compress.utils.IOUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.tukaani.xz.XZInputStream;
  
  import java.io.BufferedInputStream;
  import java.io.BufferedOutputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.nio.file.Paths;
  import java.util.Enumeration;
  import java.util.zip.GZIPInputStream;
  
  class BasicExtractor implements Extractor {
  
    private static final Logger LOGGER = LoggerFactory.getLogger(BasicExtractor.class);
  
    private final EmbeddedRabbitMqConfig config;
  
    BasicExtractor(EmbeddedRabbitMqConfig config) {
      this.config = config;
    }
  
    @Override
    public void run() throws ExtractionException {
      Runnable extractor = getExtractor(config);
      extractor.run();
    }
  
    CompressedExtractor getExtractor(EmbeddedRabbitMqConfig config) {
      String downloadedFilename = config.getDownloadTarget().toString();
      if (ArchiveType.TAR_GZ.matches(downloadedFilename)) {
        return new TarGzExtractor(config);
      } else if (ArchiveType.TAR_XZ.matches(downloadedFilename)) {
        return new TarXzExtractor(config);
      } else if (ArchiveType.ZIP.matches(downloadedFilename)) {
        return new ZipExtractor(config);
      } else {
        throw new IllegalStateException("Could not determine compression format for file: " + downloadedFilename);
      }
    }
  
    abstract static class CompressedExtractor implements Runnable {
  
      protected final EmbeddedRabbitMqConfig config;
  
      CompressedExtractor(EmbeddedRabbitMqConfig config) {
        this.config = config;
      }
  
      protected static void createNewFile(File destPath) {
        try {
          boolean newFile = destPath.createNewFile();
          if (!newFile) {
            LOGGER.warn("File '{}' already exists. Will attempt to continue...", destPath);
          }
        } catch (IOException e) {
          LOGGER.warn("Could not extract file '" + destPath + "'. Will attempt to continue...", e);
        }
      }
  
      protected static void makeDirectory(File destPath) {
        boolean mkdirs = destPath.mkdirs();
        if (!mkdirs) {
          LOGGER.warn("Directory '{}' could not be created. Will attempt to continue...", destPath);
        }
      }
  
      protected static void extractFile(InputStream archive, File destPath, String fileName) {
        BufferedOutputStream output = null;
        try {
          LOGGER.debug("Extracting '{}'...", fileName);
          output = new BufferedOutputStream(new FileOutputStream(destPath));
          IOUtils.copy(archive, output);
        } catch (IOException e) {
          throw new ExtractionException("Error extracting file '" + fileName + "' ", e);
        } finally {
          IOUtils.closeQuietly(output);
        }
      }
  
    }
  
    abstract static class AbstractTarExtractor extends CompressedExtractor {
  
      AbstractTarExtractor(EmbeddedRabbitMqConfig config) {
        super(config);
      }
  
      @Override
      public void run() throws ExtractionException {
        String downloadedFile = config.getDownloadTarget().toString();
        TarArchiveInputStream archive;
        try {
          BufferedInputStream bufferedFileInput =
              new BufferedInputStream(new FileInputStream(config.getDownloadTarget()));
          InputStream compressedInputStream = getCompressedInputStream(downloadedFile, bufferedFileInput);
          archive = new TarArchiveInputStream(compressedInputStream);
        } catch (IOException e) {
          throw new ExtractionException(
              "Download file '" + config.getDownloadTarget() + "' was not found or is not accessible.", e);
        }
  
        try {
          LOGGER.info("Extracting '{}' to '{}'", config.getDownloadTarget(), config.getExtractionFolder());
          StopWatch stopWatch = new StopWatch();
          stopWatch.start();
          extractTar(archive);
          stopWatch.stop();
          LOGGER.info("Finished extracting files in {}ms", stopWatch.getTime());
        } finally {
          IOUtils.closeQuietly(archive);
        }
      }
  
      protected abstract InputStream getCompressedInputStream(String downloadedFile,
                                                              BufferedInputStream bufferedFileInput) throws IOException;
  
      private void extractTar(TarArchiveInputStream archive) {
        TarArchiveEntry fileToExtract;
        try {
          fileToExtract = archive.getNextTarEntry();
        } catch (IOException e) {
          throw new ExtractionException("Could not extract files from file '" + config.getDownloadTarget()
              + "' due to: " + e.getLocalizedMessage(), e);
        }
  
        while (fileToExtract != null) {
          File destPath = new File(config.getExtractionFolder(), fileToExtract.getName());
  
          if (fileToExtract.isDirectory()) {
            makeDirectory(destPath);
          } else if (fileToExtract.isLink()) {
            createLink(fileToExtract, destPath);
          } else {
            createNewFile(destPath);
  
            int mode = fileToExtract.getMode();     // example: 764
            int ownerBits = mode >> 2;              // owner bits: 7
            int isExecutable = ownerBits & 1;       // bits: RWX, where X = executable bit
            boolean madeExecutable = destPath.setExecutable(isExecutable == 1);
            if (!madeExecutable) {
              LOGGER.warn("File '{}' (original mode {}) could not be made executable probably due to permission issues.",
                  fileToExtract.getName(), mode);
            }
  
            boolean madeReadable = destPath.setReadable(true);
            if (!madeReadable) {
              LOGGER.warn("File '{}' (original mode {}) could not be made readable probably due to permission issues.",
                  fileToExtract.getName(), mode);
            }
  
            extractFile(archive, destPath, fileToExtract.getName());
          }
  
          try {
            fileToExtract = archive.getNextTarEntry();
          } catch (IOException e) {
            LOGGER.error("Could not find next file to extract.", e);
            break;
          }
        }
  
        File extractionFolder = config.getExtractionFolder();
        boolean madeReadable = extractionFolder.setReadable(true);
        if (!madeReadable) {
          LOGGER.warn("File '{}' could not be made readable probably due to permission issues.",
              extractionFolder);
        }
  
      }
  
      private void createLink(TarArchiveEntry fileToExtract, File destPath) {
        Path link = Paths.get(destPath.toURI());
        Path existingFile = Paths.get(config.getExtractionFolder().toString(), fileToExtract.getLinkName());
        try {
          LOGGER.debug("Extracting '{}'...", destPath);
          Files.createLink(link, existingFile);
        } catch (IOException e) {
          LOGGER.warn("Could not create link '{}' to '{}'", link, existingFile, e);
        }
      }
  
    }
  
    private static class TarGzExtractor extends AbstractTarExtractor {
  
      public TarGzExtractor(EmbeddedRabbitMqConfig config) {
        super(config);
      }
  
      @Override
      protected InputStream getCompressedInputStream(String downloadedFile,
                                                     BufferedInputStream bufferedFileInput)
          throws IOException {
        return new GZIPInputStream(bufferedFileInput);
      }
    }
  
    private static class TarXzExtractor extends AbstractTarExtractor {
  
      public TarXzExtractor(EmbeddedRabbitMqConfig config) {
        super(config);
      }
  
      protected InputStream getCompressedInputStream(String downloadedFile,
                                                     BufferedInputStream bufferedFileInput) throws IOException {
        return new XZInputStream(bufferedFileInput);
      }
    }
  
    private static class ZipExtractor extends CompressedExtractor {
  
      public ZipExtractor(EmbeddedRabbitMqConfig config) {
        super(config);
      }
  
      @Override
      public void run() throws ExtractionException {
        ZipFile zipFile;
        try {
          zipFile = new ZipFile(config.getDownloadTarget());
        } catch (IOException e) {
          throw new ExtractionException(
              "Download file '" + config.getDownloadTarget() + "' was not found or is not accessible.", e);
        }
  
        try {
          LOGGER.info("Extracting '{}' to '{}'", config.getDownloadTarget(), config.getExtractionFolder());
          StopWatch stopWatch = new StopWatch();
          stopWatch.start();
          extractZip(zipFile);
          stopWatch.stop();
          LOGGER.info("Finished extracting files in {}ms", stopWatch.getTime());
        } finally {
          IOUtils.closeQuietly(zipFile);
        }
      }
  
      private void extractZip(ZipFile zipFile) {
        Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
        while (entries.hasMoreElements()) {
  
          ZipArchiveEntry entry = entries.nextElement();
          String fileName = entry.getName();
          File outputFile = new File(config.getExtractionFolder(), fileName);
  
          if (entry.isDirectory()) {
            makeDirectory(outputFile);
          } else {
            createNewFile(outputFile);
            try {
              InputStream inputStream = zipFile.getInputStream(entry);
              extractFile(inputStream, outputFile, fileName);
            } catch (IOException e) {
              throw new ExtractionException("Error extracting file '" + fileName + "' "
                  + "from downloaded file: " + config.getDownloadTarget(), e);
            }
          }
        }
      }
    }
  
  }
  