org.firebirdsql.jna.embedded.classpath.ClasspathFirebirdEmbeddedLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaybird Show documentation
Show all versions of jaybird Show documentation
JDBC Driver for the Firebird RDBMS
/*
* Firebird Open Source JDBC Driver
*
* Distributable under LGPL license.
* You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* LGPL License for more details.
*
* This file was created by members of the firebird development team.
* All individual contributions remain the Copyright (C) of those
* individuals. Contributors to this file are either listed here or
* can be obtained from a source control history command.
*
* All rights reserved.
*/
package org.firebirdsql.jna.embedded.classpath;
import org.firebirdsql.jna.embedded.spi.FirebirdEmbeddedLoadingException;
import org.firebirdsql.jna.embedded.spi.FirebirdEmbeddedProvider;
import org.firebirdsql.logging.Logger;
import org.firebirdsql.logging.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.stream.Stream;
/**
* Locates and installs a Firebird Embedded library from the class path to a temporary location.
*
* @author Mark Rotteveel
* @since 5
*/
final class ClasspathFirebirdEmbeddedLoader {
private static final Logger log = LoggerFactory.getLogger(ClasspathFirebirdEmbeddedLoader.class);
private static final int LOAD_BUFFER_SIZE = 8192;
private static final String TEMP_FOLDER_PREFIX = "firebird-embedded";
private static final String DELETION_MARKER_SUFFIX = ".jaybird_x";
private final FirebirdEmbeddedProvider firebirdEmbeddedProvider;
private final ClasspathFirebirdEmbeddedResource classpathFirebirdEmbeddedResource;
private final Path targetDirectory;
private final Path libraryEntryPoint;
/**
* Creates a loader to install Firebird Embedded from the classpath to a temporary folder.
*
* @param firebirdEmbeddedProvider
* Firebird Embedded provider instance
* @param classpathFirebirdEmbeddedResource
* Information to identify the classpath resources to install
* @throws FirebirdEmbeddedLoadingException
* For errors creating the temporary folder, or if the entry point tries to escape the temporary folder
*/
ClasspathFirebirdEmbeddedLoader(FirebirdEmbeddedProvider firebirdEmbeddedProvider,
ClasspathFirebirdEmbeddedResource classpathFirebirdEmbeddedResource)
throws FirebirdEmbeddedLoadingException {
this.firebirdEmbeddedProvider = firebirdEmbeddedProvider;
this.classpathFirebirdEmbeddedResource = classpathFirebirdEmbeddedResource;
cleanupOldTemporaryFiles();
try {
targetDirectory = Files.createTempDirectory(TEMP_FOLDER_PREFIX);
// Make sure we delete even if subsequent check fails
targetDirectory.toFile().deleteOnExit();
libraryEntryPoint = getValidatedLibraryEntryPoint();
} catch (IOException e) {
throw new FirebirdEmbeddedLoadingException(
getProviderName() + ": Could not create temporary folder for Firebird Embedded: " + e, e);
}
}
Path getTargetDirectory() {
return targetDirectory;
}
Path getLibraryEntryPoint() {
return libraryEntryPoint;
}
/**
* Installs the Firebird Embedded library to a temporary folder.
*
* The temporary files and folder created by this methods are marked for deletion on exit. Deletion will not always
* work on Windows, as some of the DLLs are not released by Firebird on shutdown, and as a result Java cannot
* delete them on exit as the attempt to delete happens before the DLLs are released and the file handle is closed.
*
*
* @throws FirebirdEmbeddedLoadingException
* For errors creating directories or files
*/
void install() throws FirebirdEmbeddedLoadingException {
try {
log.infof("Extracting Firebird Embedded %s to %s", firebirdEmbeddedProvider.getVersion(), targetDirectory);
for (String resourceName : classpathFirebirdEmbeddedResource.getResourceList()) {
copyResourceToTargetDirectory(resourceName);
}
} catch (IOException e) {
throw new FirebirdEmbeddedLoadingException(
getProviderName() + ": Could not extract Firebird Embedded to local file system: " + e, e);
} finally {
// Make sure the JVM cleans up the files on (normal) exit
try (Stream tempFiles = Files.walk(targetDirectory)) {
tempFiles
.map(Path::toFile)
.forEach(File::deleteOnExit);
} catch (IOException e) {
// Only log warning, and allow usage
log.warnfe("Firebird Embedded files in %s could not be marked for deletion", targetDirectory, e);
}
}
}
/**
* Copies the resource {@code resourceName} to the target directory.
*
* @param resourceName
* relative resource name
* @throws FirebirdEmbeddedLoadingException
* When the target file escapes the target directory (e.g. by returning an absolute path or using
* {@code ..}, or when the resource does not exist
* @throws IOException
* For exceptions creating intermediate directories or writing the resource to the target file
*/
private void copyResourceToTargetDirectory(String resourceName)
throws FirebirdEmbeddedLoadingException, IOException {
Path targetFile = safeResolve(resourceName);
Path fileParent = targetFile.getParent();
if (!Files.isDirectory(fileParent)) {
Files.createDirectories(fileParent);
}
log.debugf("Saving %s to %s", resourceName, targetFile);
try (InputStream inputStream = firebirdEmbeddedProvider.getClass().getResourceAsStream(resourceName)) {
if (inputStream == null) {
throw new FirebirdEmbeddedLoadingException(
getProviderName() + ": File " + resourceName + " doesn't exist");
}
try (OutputStream outputStream = Files.newOutputStream(targetFile)) {
byte[] buffer = new byte[LOAD_BUFFER_SIZE];
int len;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
}
}
}
/**
* Checks if the entry point of the library does not escape the target directory
*
* @return validated library entry point path (file does not necessarily exist)
* @throws FirebirdEmbeddedLoadingException
* When the library entry point specified by the provider attempts to escape the target directory
*/
private Path getValidatedLibraryEntryPoint() throws FirebirdEmbeddedLoadingException {
return safeResolve(classpathFirebirdEmbeddedResource.getLibraryEntryPoint());
}
/**
* Resolves a relative path against the target directory, making sure the resulting path does not escape the target.
*
* @param relativePath
* Relative path to resolve
* @return Resolved path
* @throws FirebirdEmbeddedLoadingException
* When resolving {@code relativePath} against the target directory escaped the target.
*/
private Path safeResolve(String relativePath) throws FirebirdEmbeddedLoadingException {
Path targetFile = targetDirectory.resolve(relativePath).toAbsolutePath();
if (targetFile.startsWith(targetDirectory)) {
return targetFile;
}
throw new FirebirdEmbeddedLoadingException(
getProviderName() + ": File " + relativePath + " escapes the target directory");
}
private String getProviderName() {
return firebirdEmbeddedProvider.getClass().getName();
}
/**
* Cleans up the temporary files of a classpath Firebird Embedded library.
*
* @param classpathFirebirdEmbeddedLibrary
* Classpath Firebird Embedded library to identify temporary folder
*/
static void dispose(ClasspathFirebirdEmbeddedLibrary classpathFirebirdEmbeddedLibrary) {
boolean allDeleted;
Path pathToDelete = classpathFirebirdEmbeddedLibrary.getRootPath();
try {
allDeleted = deletePath(pathToDelete);
} catch (IOException e) {
log.errorfe("Error deleting Firebird Embedded temporary files in %s", pathToDelete, e);
allDeleted = false;
}
if (!allDeleted) {
log.infof("Could not fully delete %s, creating deletion marker for cleanup on next run", pathToDelete);
String deletionMarkerName = pathToDelete.getFileName().toString() + DELETION_MARKER_SUFFIX;
Path deletionMarkerPath = pathToDelete.resolveSibling(deletionMarkerName);
if (!Files.exists(deletionMarkerPath)) {
try {
Files.createFile(deletionMarkerPath);
} catch (IOException e) {
log.errorfe("Could not create deletion marker for %s manual cleanup will be necessary",
pathToDelete, e);
}
}
}
}
/**
* Delete the specified path, including any files inside the path if it is a directory.
*
* @param pathToDelete
* Path to delete
* @return {@code true} if all files were deleted
* @throws IOException
* if an I/O exception is thrown when accessing the starting file
*/
private static boolean deletePath(Path pathToDelete) throws IOException {
log.infof("Attempting to delete Firebird Embedded temporary files in %s", pathToDelete);
if (!Files.exists(pathToDelete)) return true;
try (Stream filesToDelete = Files.walk(pathToDelete)) {
return filesToDelete
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.map(File::delete)
.reduce(true, (a, b) -> a && b);
}
}
/**
* Finds deletion markers and (tries to) delete the associated temporary folder and the deletion marker.
*/
private static void cleanupOldTemporaryFiles() {
try {
Path fileToLocateTempFolder = Files.createTempFile(TEMP_FOLDER_PREFIX, DELETION_MARKER_SUFFIX);
Files.delete(fileToLocateTempFolder);
Path tempFolder = fileToLocateTempFolder.getParent();
try (Stream tempFiles = Files.list(tempFolder)) {
tempFiles
.filter(Files::isRegularFile)
.filter(ClasspathFirebirdEmbeddedLoader::isJaybirdDeletionMarker)
.forEach(ClasspathFirebirdEmbeddedLoader::handleDeletionMarker);
}
} catch (IOException e) {
log.error("Could not cleanup old Firebird Embedded temporary files", e);
}
}
/**
* Checks if a path is a Jaybird deletion marker.
*
* @param path
* Path to check
* @return {@code true} if the path denotes a Jaybird deletion marker, {@code false} otherwise
*/
private static boolean isJaybirdDeletionMarker(Path path) {
String fileName = path.getFileName().toString();
return fileName.endsWith(DELETION_MARKER_SUFFIX)
&& fileName.startsWith(TEMP_FOLDER_PREFIX);
}
/**
* Handle a deletion marker by deleting the identified temporary folder - if it exists - and the deletion marker.
*
* @param deletionMarkerPath
* Path of the deletion marker
*/
private static void handleDeletionMarker(Path deletionMarkerPath) {
log.debugf("Handling deletion marker %s", deletionMarkerPath);
String deletionMarkerName = deletionMarkerPath.getFileName().toString();
String tempFolderName = deletionMarkerName.substring(0, deletionMarkerName.lastIndexOf(DELETION_MARKER_SUFFIX));
Path tempFolder = deletionMarkerPath.resolveSibling(tempFolderName);
try {
if (deletePath(tempFolder)) {
if (!deletionMarkerPath.toFile().delete()) {
log.debugf("Could not delete deletion marker %s", deletionMarkerPath);
}
} else {
log.debugf("Could not fully delete %s", tempFolder);
}
} catch (IOException e) {
log.error("Error deleting old Firebird Embedded temporary folder", e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy