package de.flapdoodle.embed.process.store;
import de.flapdoodle.embed.process.config.store.FileSet;
import de.flapdoodle.embed.process.config.store.FileType;
import de.flapdoodle.embed.process.extract.CommonsArchiveEntryAdapter;
import de.flapdoodle.embed.process.extract.FilesToExtract;
import de.flapdoodle.embed.process.extract.IArchiveEntry;
import de.flapdoodle.embed.process.extract.IExtractionMatch;
import de.flapdoodle.embed.process.extract.ITempNaming;
import de.flapdoodle.embed.process.io.directories.IDirectory;
import de.flapdoodle.embed.process.io.file.Files;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* @author Ilya Sadykov
* Hacky strategy of extraction. Allows to extract the full postgres binaries.
*/
public class PostgresFilesToExtract extends FilesToExtract {
private static final Logger LOGGER = LoggerFactory.getLogger(PostgresFilesToExtract.class);
final FileSet fileSet;
final IDirectory extractDir;
public PostgresFilesToExtract(IDirectory dirFactory, ITempNaming executableNaming, FileSet fileSet) {
super(dirFactory, executableNaming, fileSet);
this.fileSet = fileSet;
this.extractDir = dirFactory;
}
/**
* This is actually the very dirty hack method to adopt the Flapdoodle's API to the compatible way to extract and run
* TODO: hacky method. Should be considered for complete rewriting //NOSONAR
*/
@Override
public IExtractionMatch find(final IArchiveEntry entry) {//NOSONAR
return new IExtractionMatch() { //NOSONAR
@Override
public File write(InputStream source, long size) throws IOException { //NOSONAR
boolean isSymLink = false;
String linkName = "";
if (entry instanceof CommonsArchiveEntryAdapter) {
try {
// hack to allow symlinks extraction (ONLY tar archives are supported!)
Field archiveEntryField = CommonsArchiveEntryAdapter.class.getDeclaredField("_entry");
archiveEntryField.setAccessible(true);
ArchiveEntry archiveEntry = (ArchiveEntry) archiveEntryField.get(entry);
if (archiveEntry instanceof TarArchiveEntry
&& (isSymLink = ((TarArchiveEntry) archiveEntry).isSymbolicLink())) { //NOSONAR
linkName = ((TarArchiveEntry) archiveEntry).getLinkName();
}
archiveEntry.getSize();
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("Check the version of de.flapdoodle.embed.process API. " + //NOSONAR
"Has it changed?", e);
}
}
if (extractDir == null || extractDir.asFile() == null) {
return null;
}
// I got some problems with concurrency. Not sure this is required.
synchronized (PostgresFilesToExtract.class) {
final String basePath = extractDir.asFile().getPath();
final File outputFile = Paths.get(basePath, entry.getName()).toFile();
if (entry.isDirectory()) {
if (!outputFile.exists()) {
Files.createDir(outputFile);
}
} else {
if (!outputFile.exists()) { // prevent double extraction (for other binaries)
if (isSymLink) {
try { // NOSONAR
final Path target = outputFile.toPath().getParent().resolve(Paths.get(linkName));
java.nio.file.Files.createSymbolicLink(outputFile.toPath(), target);
} catch (Exception e) {
LOGGER.trace("Failed to extract symlink", e);
}
} else {
Files.write(source, outputFile);
}
}
// hack to mark binaries as executable
if (entry.getName().matches("pgsql/bin/.+")) {
outputFile.setExecutable(true);
}
}
return outputFile;
}
}
@Override
public FileType type() {
// does this archive entry match to any of the provided fileset entries?
for (FileSet.Entry matchingEntry : fileSet.entries()) {
if (matchingEntry.matchingPattern().matcher(entry.getName()).matches()) {
return matchingEntry.type();
}
}
// Otherwise - it's just an library file
return FileType.Library;
}
};
}
}