Mercurial > hg > monetdb-java
view src/main/java/org/monetdb/util/FileTransferHandler.java @ 595:36df3c89845d
Extended FileTransferHandler utility class by adding support for reading from and writing to gzip compressed files when using file name extension .gz
Also improve error handling by testing on empty file name and provide more information when invalid file name is given or other compression formats are requested.
As the FileTransferHandler utility class is used by JdbcClient application, it will now also support this functionality.
author | Martin van Dinther <martin.van.dinther@monetdbsolutions.com> |
---|---|
date | Thu, 25 Nov 2021 19:06:48 +0100 (2021-11-25) |
parents | 5aef0ea654b1 |
children | d30b69ed8e95 |
line wrap: on
line source
/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V. */ package org.monetdb.util; import org.monetdb.jdbc.MonetConnection; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * Default implementation of UploadHandler and DownloadHandler interfaces * for reading from and writing to files on the local file system. * It enables support for: * COPY .. INTO table FROM 'file-name' ON CLIENT ... * and * COPY SELECT_query INTO 'file-name' ON CLIENT ... * handling. * * Currently only file compression format .gz is supported. This is intentionally * as other compression formats would introduce dependencies on external * libraries which complicates usage of JDBC driver or JdbcClient application. * Developers can of course build their own MyFileTransferHandler class * and use it instead of this default implementation. * * A FileTransferHandler object needs to be registered via * {@link MonetConnection#setUploadHandler(MonetConnection.UploadHandler)} and/or * {@link MonetConnection#setDownloadHandler(MonetConnection.DownloadHandler)}. * * @author Joeri van Ruth * @author Martin van Dinther * @version 1.1 */ public class FileTransferHandler implements MonetConnection.UploadHandler, MonetConnection.DownloadHandler { private final Path root; private final Charset encoding; /** * Create a new FileTransferHandler which serves the given directory. * * @param dir directory Path to read and write files from * @param encoding the specified characterSet encoding is used for all data files in the directory * when null the Charset.defaultCharset() is used. */ public FileTransferHandler(final Path dir, final Charset encoding) { this.root = dir.toAbsolutePath().normalize(); this.encoding = encoding != null ? encoding: Charset.defaultCharset(); } /** * Create a new FileTransferHandler which serves the given directory. * * @param dir directory String to read and write files from * @param encoding the specified characterSet encoding is used for all data files in the directory * when null the Charset.defaultCharset() is used. */ public FileTransferHandler(final String dir, final Charset encoding) { this(FileSystems.getDefault().getPath(dir), encoding); } /** * Read the data from the specified file (in the root directory) and upload it to the server. */ public void handleUpload(final MonetConnection.Upload handle, final String name, final boolean textMode, final long linesToSkip) throws IOException { if (name == null || name.isEmpty()) { handle.sendError("Missing file name"); return; } final Path path = root.resolve(name).normalize(); if (!path.startsWith(root)) { handle.sendError("File is not in upload directory: " + root.toString()); return; } if (!Files.isReadable(path)) { handle.sendError("Cannot read file " + path.toString()); return; } // In this implementation we ONLY support gzip compression format and none of the other compression formats. if (name.endsWith(".bz2") || name.endsWith(".lz4") || name.endsWith(".xz") || name.endsWith(".zip")) { final String extension = name.substring(name.lastIndexOf???(".")); handle.sendError("Specified file compression format " + extension + " is not supported. Only .gz is supported."); return; } final boolean useGZIP = name.endsWith(".gz"); if (!textMode || (linesToSkip == 0 && utf8Encoded())) { // when !textMode we must upload as a byte stream // when utf8Encoded and linesToSkip is 0 it is more efficient to upload as a byte stream final InputStream inputStream = Files.newInputStream(path); if (useGZIP) { final GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream, 128 * 1024); handle.uploadFrom(gzipInputStream); gzipInputStream.close(); } else { handle.uploadFrom(inputStream); } } else { // cannot upload as a byte stream, must deal with encoding and/or linesToSkip if (useGZIP) { // TODO add support for: useGZIP handle.sendError("Sorry, uploading compressed .gz file data with an offset or a none utf-8 encoding is not yet supported"); return; } else { final BufferedReader reader = Files.newBufferedReader(path, encoding); handle.uploadFrom(reader, linesToSkip); reader.close(); } } } /** * Download the data from the server and write it to a new created file in the root directory. * When a file with the same name already exists the download request will send an error and NOT overwrite the existing file. */ public void handleDownload(final MonetConnection.Download handle, final String name, final boolean textMode) throws IOException { if (name == null || name.isEmpty()) { handle.sendError("Missing file name"); return; } final Path path = root.resolve(name).normalize(); if (!path.startsWith(root)) { handle.sendError("File is not in download directory: " + root.toString()); return; } if (Files.exists(path)) { handle.sendError("File already exists: " + path.toString()); return; } // In this implementation we ONLY support gzip compression format and none of the other compression formats. if (name.endsWith(".bz2") || name.endsWith(".lz4") || name.endsWith(".xz") || name.endsWith(".zip")) { final String extension = name.substring(name.lastIndexOf???(".")); handle.sendError("Requested file compression format " + extension + " is not supported. Use .gz instead."); return; } final boolean useGZIP = name.endsWith(".gz"); if (!textMode || utf8Encoded()) { // when !textMode we must download as a byte stream // when utf8Encoded it is more efficient to download as a byte stream final OutputStream outputStream = Files.newOutputStream(path, StandardOpenOption.CREATE_NEW); if (useGZIP) { final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream, 128 * 1024); handle.downloadTo(gzipOutputStream); gzipOutputStream.flush(); gzipOutputStream.close(); } else { handle.downloadTo(outputStream); } } else { // cannot download as a byte stream, must deal with encoding if (useGZIP) { // TODO add support for: useGZIP handle.sendError("Sorry, downloading data in a none utf-8 encoding to a compressed .gz file is not yet supported"); return; } else { final BufferedWriter writer = Files.newBufferedWriter(path, encoding, StandardOpenOption.CREATE_NEW); handle.downloadTo(writer); writer.close(); } } } public boolean utf8Encoded() { return encoding.equals(StandardCharsets.UTF_8); } }