/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.cassandra.streaming; import java.net.InetSocketAddress; import java.nio.channels.FileChannel; import java.nio.channels.SocketChannel; import java.io.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.utils.Pair; public class IncomingStreamReader { private static Logger logger = LoggerFactory.getLogger(IncomingStreamReader.class); private PendingFile pendingFile; private PendingFile lastFile; private FileStatus streamStatus; private SocketChannel socketChannel; private StreamContext context; private boolean initiatedTransfer; public IncomingStreamReader(StreamHeader header, SocketChannel socketChannel) throws IOException { this.socketChannel = socketChannel; InetSocketAddress remoteAddress = (InetSocketAddress)socketChannel.socket().getRemoteSocketAddress(); // pendingFile gets the new context for the local node. pendingFile = StreamIn.getContextMapping(header.getStreamFile()); // lastFile has the old context, which was registered in the manager. lastFile = header.getStreamFile(); initiatedTransfer = header.initiatedTransfer; context = new StreamContext(remoteAddress.getAddress(), header.getSessionId()); StreamInManager.activeStreams.put(context, pendingFile); assert pendingFile != null; // For transfers setup the status and for replies to requests, prepare the list // of available files to request. if (initiatedTransfer) streamStatus = new FileStatus(lastFile.getFilename(), header.getSessionId()); else if (header.getPendingFiles() != null) StreamInManager.get(context).addFilesToRequest(header.getPendingFiles()); } public void read() throws IOException { if (logger.isDebugEnabled()) { logger.debug("Receiving stream"); logger.debug("Creating file for {}", pendingFile.getFilename()); } FileOutputStream fos = new FileOutputStream(pendingFile.getFilename(), true); FileChannel fc = fos.getChannel(); long offset = 0; try { for (Pair<Long, Long> section : pendingFile.sections) { long length = section.right - section.left; long bytesRead = 0; while (bytesRead < length) bytesRead += fc.transferFrom(socketChannel, offset + bytesRead, length - bytesRead); offset += length; } } catch (IOException ex) { logger.debug("Receiving stream: recovering from IO error"); /* Ask the source node to re-stream this file. */ if (initiatedTransfer) handleFileStatus(FileStatus.Action.STREAM); else StreamIn.requestFile(context, lastFile); /* Delete the orphaned file. */ FileUtils.deleteWithConfirm(new File(pendingFile.getFilename())); throw ex; } finally { fc.close(); StreamInManager.activeStreams.remove(context, pendingFile); } if (logger.isDebugEnabled()) logger.debug("Removing stream context {}", pendingFile); if (initiatedTransfer) handleFileStatus(FileStatus.Action.DELETE); else { FileStatusHandler.addSSTable(pendingFile, context); StreamInManager.get(context).finishAndRequestNext(lastFile); } } private void handleFileStatus(FileStatus.Action action) throws IOException { streamStatus.setAction(action); FileStatusHandler.onStatusChange(context, pendingFile, streamStatus); } }