/**
* 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.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.log4j.Logger;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.SimpleCondition;
/**
* This class manages the streaming of multiple files one after the other.
*/
public class StreamOutManager
{
private static Logger logger = Logger.getLogger( StreamOutManager.class );
private static ConcurrentMap<InetAddress, StreamOutManager> streamManagers = new ConcurrentHashMap<InetAddress, StreamOutManager>();
public static final Set<InetAddress> pendingDestinations = Collections.synchronizedSet(new HashSet<InetAddress>());
public static StreamOutManager get(InetAddress to)
{
StreamOutManager manager = streamManagers.get(to);
if (manager == null)
{
StreamOutManager possibleNew = new StreamOutManager(to);
if ((manager = streamManagers.putIfAbsent(to, possibleNew)) == null)
manager = possibleNew;
}
return manager;
}
public static void remove(InetAddress to)
{
if (streamManagers.containsKey(to) && streamManagers.get(to).files.size() == 0)
streamManagers.remove(to);
pendingDestinations.remove(to);
}
public static Set<InetAddress> getDestinations()
{
// the results of streamManagers.keySet() isn't serializable, so create a new set.
Set<InetAddress> hosts = new HashSet<InetAddress>();
hosts.addAll(streamManagers.keySet());
hosts.addAll(pendingDestinations);
return hosts;
}
/**
* this method exists so that we don't have to call StreamOutManager.get() which has a nasty side-effect of
* indicating that we are streaming to a particular host.
**/
public static List<PendingFile> getPendingFiles(InetAddress host)
{
List<PendingFile> list = new ArrayList<PendingFile>();
StreamOutManager manager = streamManagers.get(host);
if (manager != null)
list.addAll(manager.getFiles());
return list;
}
// we need sequential and random access to the files. hence, the map and the list.
private final List<PendingFile> files = new ArrayList<PendingFile>();
private final Map<String, PendingFile> fileMap = new HashMap<String, PendingFile>();
private final InetAddress to;
private long totalBytes = 0L;
private final SimpleCondition condition = new SimpleCondition();
private StreamOutManager(InetAddress to)
{
this.to = to;
}
public void addFilesToStream(PendingFile[] pendingFiles)
{
// reset the condition in case this SOM is getting reused before it can be removed.
condition.reset();
for (PendingFile pendingFile : pendingFiles)
{
if (logger.isDebugEnabled())
logger.debug("Adding file " + pendingFile.getSourceFile() + " to be streamed.");
files.add(pendingFile);
fileMap.put(pendingFile.getSourceFile() , pendingFile);
totalBytes += pendingFile.getExpectedBytes();
}
}
public void update(String path, long pos)
{
PendingFile pf = fileMap.get(path);
if (pf != null)
pf.update(pos);
}
public void startNext()
{
if (files.size() > 0)
{
File file = new File(files.get(0).getSourceFile() );
if (logger.isDebugEnabled())
logger.debug("Streaming " + file.length() + " length file " + file + " ...");
MessagingService.instance.stream(file.getAbsolutePath(), 0L, file.length(), FBUtilities.getLocalAddress(), to);
}
}
/**
* Drops all files to steam to to endpoint and remove them from temp storage.
*/
public void reset() {
while (files.size()>0) {
PendingFile file = files.remove(0);
if (file==null)
continue;
File f = new File( file.getSourceFile() );
logger.warn("Cancelling streaming to " + to + " of file "+ f);
FileUtils.delete(file.getSourceFile());
fileMap.remove(file.getSourceFile());
}
condition.signalAll();
}
public void finishAndStartNext(String file) throws IOException
{
File f = new File(file);
if (logger.isDebugEnabled())
logger.debug("Deleting file " + file + " after streaming " + f.length() + "/" + totalBytes + " bytes.");
FileUtils.delete(file);
PendingFile pf = files.remove(0);
if (pf != null)
fileMap.remove(pf.getSourceFile());
if (files.size() > 0)
{
startNext();
}
else
{
if (logger.isDebugEnabled())
logger.debug("Signalling that streaming is done for " + to);
condition.signalAll();
}
}
public void waitForStreamCompletion()
{
try
{
condition.await();
}
catch (InterruptedException e)
{
throw new AssertionError(e);
}
}
List<PendingFile> getFiles()
{
return Collections.unmodifiableList(files);
}
}