changeset 503:7e3987c16cde onclient

Succesful file uploading
author Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com>
date Wed, 18 Aug 2021 15:02:59 +0200 (2021-08-18)
parents 83354bd21320
children 8aa70bd8d21f
files src/main/java/org/monetdb/jdbc/MonetConnection.java src/main/java/org/monetdb/jdbc/MonetFileTransfer.java src/main/java/org/monetdb/jdbc/MonetUploadHandle.java
diffstat 3 files changed, 190 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/src/main/java/org/monetdb/jdbc/MonetConnection.java
+++ b/src/main/java/org/monetdb/jdbc/MonetConnection.java
@@ -153,6 +153,8 @@ public class MonetConnection
 	/** A cache to reduce the number of DatabaseMetaData objects created by getMetaData() to maximum 1 per connection */
 	private DatabaseMetaData dbmd;
 
+	/** A handler for ON CLIENT requests */
+	private MonetFileTransfer fileTransfer;
 
 	/**
 	 * Constructor of a Connection for MonetDB. At this moment the
@@ -1197,6 +1199,22 @@ public class MonetConnection
 	}
 
 	/**
+	 * Registers a MonetFileTransfer handler to support for example COPY ON CLIENT
+	 *
+	 * @param fileTransfer the handler to register, or null to deregister
+	 */
+	public void setFileTransfer(MonetFileTransfer fileTransfer) {
+		this.fileTransfer = fileTransfer;
+	}
+
+	/**
+	 * Returns the currently registerered MonetFileTransfer handler, or null
+	 */
+	public MonetFileTransfer setFileTransfer() {
+		return fileTransfer;
+	}
+
+	/**
 	 * Returns a string identifying this Connection to the MonetDB server.
 	 *
 	 * @return a String representing this Object
@@ -3174,53 +3192,41 @@ public class MonetConnection
 	// }}}
 
 	private String handleTransfer(String transferCommand) throws IOException {
-		String[] parts = transferCommand.split(" " , 3);
-		if (parts.length == 3) {
-			if (parts[0].equals("r")) {
-				int offset;
-				try {
-					offset = Integer.parseInt(parts[1]);
-				} catch (NumberFormatException e) {
-					return e.toString();
-				}
-				return handleUpload(parts[2], true, offset);
+		String[] parts = transferCommand.split(" ", 3);
+		if (transferCommand.startsWith("r ") && parts.length == 3) {
+			final int offset;
+			try {
+				offset = Integer.parseInt(parts[1]);
+			} catch (NumberFormatException e) {
+				return e.toString();
 			}
-			if (parts[0].equals("r")) {
-				int offset;
-				try {
-					offset = Integer.parseInt(parts[1]);
-				} catch (NumberFormatException e) {
-					return e.toString();
-				}
-				return handleUpload(parts[2], false, offset);
-			}
-		} else if (parts.length == 2) {
-			if (parts[0].equals("w")) {
-				return handleDownload(parts[1]);
-			}
+			return handleUpload(parts[2], true, offset);
+		} else if (transferCommand.startsWith("rb ")) {
+			return handleUpload(transferCommand.substring(3), false, 0);
+		} else {
+			return "JDBC does not support this file transfer yet: " + transferCommand;
 		}
-		return "JDBC does not support this file transfer yet: " + transferCommand;
 	}
 
 	private String handleUpload(String path, boolean textMode, int offset) throws IOException {
+		if (fileTransfer == null) {
+			return "No file transfer handler has been registered";
+		}
+
+		MonetUploadHandle handle = new MonetUploadHandle(server);
 		boolean wasFaking = server.setInsertFakeFlushes(false);
 		try {
-			MapiSocket.UploadStream us = server.uploadStream();
-			us.write('\n');
-			PrintStream ps = null;
-			try {
-				ps = new PrintStream(us, false, "UTF-8");
-			} catch (UnsupportedEncodingException e) {
-				return e.toString();
+			fileTransfer.handleUpload(handle, path, textMode, offset);
+			if (!handle.hasBeenUsed()) {
+				String message = String.format("Call to %s.handleUpload for path '%s' sent neither data nor an error message",
+						fileTransfer.getClass().getCanonicalName(), path);
+				throw new IOException(message);
 			}
-			for (int i = 0; i < 1200; i++) {
-				ps.println("banana " + i);
-			}
-			ps.close();
-			return null;
+			handle.close();
 		} finally {
 			server.setInsertFakeFlushes(wasFaking);
 		}
+		return handle.getError();
 	}
 
 	private String handleDownload(String path) {
new file mode 100644
--- /dev/null
+++ b/src/main/java/org/monetdb/jdbc/MonetFileTransfer.java
@@ -0,0 +1,53 @@
+package org.monetdb.jdbc;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+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;
+
+public abstract class MonetFileTransfer {
+	public void handleUpload(MonetUploadHandle handle, String name, boolean textMode, int offset) throws IOException {
+		throw new UnsupportedOperationException("This client is not prepared to handle uploads");
+	}
+
+	public static class UploadFromDirectory extends MonetFileTransfer {
+		private final Path root;
+		private final boolean utf8Encoded;
+
+		public UploadFromDirectory(Path dir, boolean utf8Encoded) {
+			root = dir.toAbsolutePath().normalize();
+			this.utf8Encoded = utf8Encoded;
+		}
+
+		public UploadFromDirectory(String dir, boolean utf8Encoded) {
+			this(FileSystems.getDefault().getPath(dir), utf8Encoded);
+		}
+
+		@Override
+		public void handleUpload(MonetUploadHandle handle, String name, boolean textMode, int offset) throws IOException {
+			Path path = root.resolve(name).normalize();
+			if (!path.startsWith(root)) {
+				handle.sendError("File is not in upload directory");
+				return;
+			}
+			if (!Files.isReadable(path)) {
+				handle.sendError("Cannot read " + name);
+				return;
+			}
+			if (textMode && (offset > 1 || !utf8Encoded)) {
+				Charset encoding = utf8Encoded ? StandardCharsets.UTF_8 : Charset.defaultCharset();
+				BufferedReader reader = Files.newBufferedReader(path, encoding);
+				int toSkip = offset > 1 ? offset - 1 : 0;
+				for (int i = 0; i < toSkip; i++) {
+					reader.readLine();
+				}
+				handle.uploadFrom(reader);
+			} else {
+				handle.uploadFrom(Files.newInputStream(path));
+			}
+		}
+	}
+}
new file mode 100644
--- /dev/null
+++ b/src/main/java/org/monetdb/jdbc/MonetUploadHandle.java
@@ -0,0 +1,95 @@
+package org.monetdb.jdbc;
+
+import org.monetdb.mcl.net.MapiSocket;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+
+public class MonetUploadHandle {
+	private final MapiSocket server;
+	private PrintStream print = null;
+	private String error = null;
+
+	MonetUploadHandle(MapiSocket server) {
+		this.server = server;
+	}
+
+	public void sendError(String errorMessage) throws IOException {
+		if (error != null) {
+			throw new IOException("another error has already been sent: " + error);
+		}
+		error = errorMessage;
+	}
+
+	public PrintStream getStream() throws IOException {
+		if (error != null) {
+			throw new IOException("Cannot send data after an error has been sent");
+		}
+		if (print == null) {
+			try {
+				MapiSocket.UploadStream up = server.uploadStream();
+				print = new PrintStream(up, false, "UTF-8");
+				up.write('\n');
+			} catch (UnsupportedEncodingException e) {
+				throw new RuntimeException("The system is guaranteed to support the UTF-8 encoding but apparently it doesn't", e);
+			}
+		}
+		return print;
+	}
+
+	public boolean hasBeenUsed() {
+		return print != null || error != null;
+	}
+
+	public String getError() {
+		return error;
+	}
+
+	public void uploadFrom(InputStream inputStream) throws IOException {
+		OutputStream s = getStream();
+		byte[] buffer = new byte[64 * 1024];
+		while (true) {
+			int nread = inputStream.read(buffer);
+			if (nread < 0) {
+				break;
+			}
+			s.write(buffer, 0, nread);
+		}
+	}
+
+	public void uploadFrom(BufferedReader reader, int offset) throws IOException {
+		// we're 1-based but also accept 0
+		if (offset > 0) {
+			offset -= 1;
+		}
+
+		for (int i = 0; i < offset; i++) {
+			String line = reader.readLine();
+			if (line == null) {
+				return;
+			}
+		}
+
+		uploadFrom(reader);
+	}
+
+	public void uploadFrom(BufferedReader reader) throws IOException {
+		OutputStream s = getStream();
+		OutputStreamWriter writer = new OutputStreamWriter(s, StandardCharsets.UTF_8);
+		char[] buffer = new char[64 * 1024];
+		while (true) {
+			int nread = reader.read(buffer, 0, buffer.length);
+			if (nread < 0) {
+				break;
+			}
+			writer.write(buffer, 0, nread);
+			writer.close();
+		}
+	}
+
+	public void close() {
+		if (print != null) {
+			print.close();
+		}
+	}
+}