/* * Copyright (C) 2015 SoftIndex LLC. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.datakernel.remotefs; import com.google.gson.Gson; import io.datakernel.async.*; import io.datakernel.bytebuf.ByteBuf; import io.datakernel.eventloop.AsyncTcpSocket; import io.datakernel.eventloop.AsyncTcpSocketImpl; import io.datakernel.eventloop.Eventloop; import io.datakernel.net.SocketSettings; import io.datakernel.stream.StreamProducer; import io.datakernel.stream.net.Messaging.ReceiveMessageCallback; import io.datakernel.stream.net.MessagingSerializer; import io.datakernel.stream.net.MessagingWithBinaryStreaming; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.SSLContext; import java.net.InetSocketAddress; import java.nio.channels.SocketChannel; import java.util.List; import java.util.concurrent.ExecutorService; import static io.datakernel.eventloop.AsyncSslSocket.wrapClientSocket; import static io.datakernel.eventloop.AsyncTcpSocketImpl.wrapChannel; import static io.datakernel.stream.net.MessagingSerializers.ofGson; public final class RemoteFsClient implements IRemoteFsClient { protected final Logger logger = LoggerFactory.getLogger(getClass()); protected final Eventloop eventloop; protected final SocketSettings socketSettings = SocketSettings.create(); // SSL protected final SSLContext sslContext; protected final ExecutorService sslExecutor; private final InetSocketAddress address; private MessagingSerializer<RemoteFsResponses.FsResponse, RemoteFsCommands.FsCommand> serializer = ofGson(getResponseGson(), RemoteFsResponses.FsResponse.class, getCommandGSON(), RemoteFsCommands.FsCommand.class); // creators & builders protected RemoteFsClient(Eventloop eventloop, InetSocketAddress address, SSLContext sslContext, ExecutorService sslExecutor) { this.eventloop = eventloop; this.address = address; this.sslContext = sslContext; this.sslExecutor = sslExecutor; } public static RemoteFsClient create(Eventloop eventloop, InetSocketAddress address) { return new RemoteFsClient(eventloop, address, null, null); } public RemoteFsClient withSslEnabled(SSLContext sslContext, ExecutorService sslExecutor) { return new RemoteFsClient(eventloop, address, sslContext, sslExecutor); } // endregion @Override public void upload(final String fileName, final StreamProducer<ByteBuf> producer, final CompletionCallback callback) { connect(address, new MessagingConnectCallback() { @Override public void onConnect(final MessagingWithBinaryStreaming<RemoteFsResponses.FsResponse, RemoteFsCommands.FsCommand> messaging) { final RemoteFsCommands.Upload upload = new RemoteFsCommands.Upload(fileName); messaging.send(upload, IgnoreCompletionCallback.create()); messaging.sendBinaryStreamFrom(producer, new CompletionCallback() { @Override public void onComplete() { logger.trace("send all bytes for {}", fileName); messaging.receive(new ReceiveMessageCallback<RemoteFsResponses.FsResponse>() { @Override public void onReceive(RemoteFsResponses.FsResponse msg) { logger.trace("received {}", msg); if (msg instanceof RemoteFsResponses.Acknowledge) { messaging.close(); callback.setComplete(); } else if (msg instanceof RemoteFsResponses.Err) { messaging.close(); callback.setException(new RemoteFsException(((RemoteFsResponses.Err) msg).msg)); } else { messaging.close(); callback.setException(new RemoteFsException("Invalid message received: " + msg)); } } @Override public void onReceiveEndOfStream() { logger.warn("received unexpected end of stream"); messaging.close(); callback.setException(new RemoteFsException("Unexpected end of stream for: " + fileName)); } @Override public void onException(Exception e) { messaging.close(); callback.setException(e); } }); } @Override public void onException(Exception e) { messaging.close(); callback.setException(e); } }); } @Override public void onException(Exception exception) { callback.setException(exception); } }); } @Override public void download(final String fileName, final long startPosition, final ResultCallback<StreamProducer<ByteBuf>> callback) { connect(address, new MessagingConnectCallback() { @Override public void onConnect(final MessagingWithBinaryStreaming<RemoteFsResponses.FsResponse, RemoteFsCommands.FsCommand> messaging) { messaging.send(new RemoteFsCommands.Download(fileName, startPosition), new CompletionCallback() { @Override public void onComplete() { logger.trace("command to download {} send", fileName); messaging.receive(new ReceiveMessageCallback<RemoteFsResponses.FsResponse>() { @Override public void onReceive(RemoteFsResponses.FsResponse msg) { logger.trace("received {}", msg); if (msg instanceof RemoteFsResponses.Ready) { long size = ((RemoteFsResponses.Ready) msg).size; StreamForwarderWithCounter forwarder = StreamForwarderWithCounter.create(eventloop, size - startPosition); messaging.receiveBinaryStreamTo(forwarder.getInput(), new CompletionCallback() { @Override public void onComplete() { messaging.close(); } @Override public void onException(Exception e) { messaging.close(); } }); callback.setResult(forwarder.getOutput()); } else if (msg instanceof RemoteFsResponses.Err) { messaging.close(); RemoteFsException exception = new RemoteFsException(((RemoteFsResponses.Err) msg).msg); callback.setException(exception); } else { messaging.close(); RemoteFsException exception = new RemoteFsException("Invalid message received: " + msg); callback.setException(exception); } } @Override public void onReceiveEndOfStream() { logger.warn("received unexpected end of stream"); messaging.close(); RemoteFsException exception = new RemoteFsException("Unexpected end of stream for: " + fileName); callback.setException(exception); } @Override public void onException(Exception e) { messaging.close(); callback.setException(new RemoteFsException(e)); } }); } @Override public void onException(Exception e) { messaging.close(); callback.setException(new RemoteFsException(e)); } }); } @Override public void onException(Exception e) { callback.setException(e); } }); } @Override public void delete(String fileName, CompletionCallback callback) { connect(address, new DeleteConnectCallback(fileName, callback)); } @Override public void list(final ResultCallback<List<String>> callback) { connect(address, new ListConnectCallback(callback)); } private abstract class MessagingConnectCallback extends ExceptionCallback { final void setConnect(MessagingWithBinaryStreaming<RemoteFsResponses.FsResponse, RemoteFsCommands.FsCommand> messaging) { CallbackRegistry.complete(this); onConnect(messaging); } protected abstract void onConnect(MessagingWithBinaryStreaming<RemoteFsResponses.FsResponse, RemoteFsCommands.FsCommand> messaging); } private void connect(InetSocketAddress address, final MessagingConnectCallback callback) { eventloop.connect(address, new ConnectCallback() { @Override public void onConnect(SocketChannel socketChannel) { AsyncTcpSocketImpl asyncTcpSocketImpl = wrapChannel(eventloop, socketChannel, socketSettings); AsyncTcpSocket asyncTcpSocket = sslContext != null ? wrapClientSocket(eventloop, asyncTcpSocketImpl, sslContext, sslExecutor) : asyncTcpSocketImpl; MessagingWithBinaryStreaming<RemoteFsResponses.FsResponse, RemoteFsCommands.FsCommand> messaging = MessagingWithBinaryStreaming.create(eventloop, asyncTcpSocket, serializer); asyncTcpSocket.setEventHandler(messaging); asyncTcpSocketImpl.register(); callback.setConnect(messaging); } @Override public void onException(Exception exception) { callback.setException(exception); } }); } protected Gson getCommandGSON() { return RemoteFsCommands.commandGSON; } protected Gson getResponseGson() { return RemoteFsResponses.responseGson; } private class DeleteConnectCallback extends MessagingConnectCallback { private final String fileName; private final CompletionCallback callback; DeleteConnectCallback(String fileName, CompletionCallback callback) { this.fileName = fileName; this.callback = callback; } @Override public void onConnect(final MessagingWithBinaryStreaming<RemoteFsResponses.FsResponse, RemoteFsCommands.FsCommand> messaging) { messaging.send(new RemoteFsCommands.Delete(fileName), new CompletionCallback() { @Override public void onComplete() { logger.trace("command to delete {} send", fileName); messaging.receive(new ReceiveMessageCallback<RemoteFsResponses.FsResponse>() { @Override public void onReceive(RemoteFsResponses.FsResponse msg) { logger.trace("received {}", msg); if (msg instanceof RemoteFsResponses.Ok) { messaging.close(); callback.setComplete(); } else if (msg instanceof RemoteFsResponses.Err) { messaging.close(); callback.setException(new RemoteFsException(((RemoteFsResponses.Err) msg).msg)); } else { messaging.close(); callback.setException(new RemoteFsException("Invalid message received: " + msg)); } } @Override public void onReceiveEndOfStream() { logger.warn("received unexpected end of stream"); messaging.close(); callback.setException(new RemoteFsException("Unexpected end of stream for: " + fileName)); } @Override public void onException(Exception e) { messaging.close(); callback.setException(e); } }); } @Override public void onException(Exception e) { messaging.close(); callback.setException(e); } }); } @Override public void onException(Exception e) { callback.setException(e); } } private class ListConnectCallback extends MessagingConnectCallback { private final ResultCallback<List<String>> callback; ListConnectCallback(ResultCallback<List<String>> callback) { this.callback = callback; } @Override public void onConnect(final MessagingWithBinaryStreaming<RemoteFsResponses.FsResponse, RemoteFsCommands.FsCommand> messaging) { messaging.send(new RemoteFsCommands.ListFiles(), new CompletionCallback() { @Override public void onComplete() { logger.trace("command to list files send"); messaging.receive(new ReceiveMessageCallback<RemoteFsResponses.FsResponse>() { @Override public void onReceive(RemoteFsResponses.FsResponse msg) { logger.trace("received {}", msg); if (msg instanceof RemoteFsResponses.ListOfFiles) { messaging.close(); callback.setResult(((RemoteFsResponses.ListOfFiles) msg).files); } else if (msg instanceof RemoteFsResponses.Err) { messaging.close(); callback.setException(new RemoteFsException(((RemoteFsResponses.Err) msg).msg)); } else { messaging.close(); callback.setException(new RemoteFsException("Invalid message received: " + msg)); } } @Override public void onReceiveEndOfStream() { logger.warn("received unexpected end of stream"); messaging.close(); callback.setException(new RemoteFsException("Unexpected end of stream while trying to list files")); } @Override public void onException(Exception e) { messaging.close(); callback.setException(e); } }); } @Override public void onException(Exception e) { messaging.close(); callback.setException(e); } }); } @Override public void onException(Exception e) { callback.setException(e); } } }