/** * 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.IOException; import java.net.InetAddress; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.utils.SimpleCondition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; /** * This class manages the streaming of multiple files one after the other. */ public class StreamOutManager { private static Logger logger = LoggerFactory.getLogger( StreamOutManager.class ); // one host may have multiple stream contexts (think of them as sessions). each context gets its own manager. private static ConcurrentMap<StreamContext, StreamOutManager> streamManagers = new ConcurrentHashMap<StreamContext, StreamOutManager>(); public static final Multimap<InetAddress, StreamContext> destHosts = Multimaps.synchronizedMultimap(HashMultimap.<InetAddress, StreamContext>create()); public static StreamOutManager get(StreamContext context) { StreamOutManager manager = streamManagers.get(context); if (manager == null) { StreamOutManager possibleNew = new StreamOutManager(context); if ((manager = streamManagers.putIfAbsent(context, possibleNew)) == null) { manager = possibleNew; destHosts.put(context.host, context); } } return manager; } public static void remove(StreamContext context) { if (streamManagers.containsKey(context) && streamManagers.get(context).files.size() == 0) { streamManagers.remove(context); destHosts.remove(context.host, context); } } 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(destHosts.keySet()); 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(StreamContext context) { List<PendingFile> list = new ArrayList<PendingFile>(); StreamOutManager manager = streamManagers.get(context); if (manager != null) list.addAll(manager.getFiles()); return list; } public static List<PendingFile> getOutgoingFiles(InetAddress host) { List<PendingFile> list = new ArrayList<PendingFile>(); for(StreamContext context : destHosts.get(host)) { list.addAll(getPendingFiles(context)); } 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 StreamContext context; private final SimpleCondition condition = new SimpleCondition(); private StreamOutManager(StreamContext context) { this.context = context; } public void addFilesToStream(List<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 {} to be streamed.", pendingFile.getFilename()); files.add(pendingFile); fileMap.put(pendingFile.getFilename(), pendingFile); } } public void retry(String file) { PendingFile pf = fileMap.get(file); if (pf != null) streamFile(pf); } private void streamFile(PendingFile pf) { if (logger.isDebugEnabled()) logger.debug("Streaming {} ...", pf); MessagingService.instance.stream(new StreamHeader(context.sessionId, pf, true), context.host); } public void finishAndStartNext(String pfname) throws IOException { PendingFile pf = fileMap.remove(pfname); files.remove(pf); if (files.size() > 0) streamFile(files.get(0)); else { if (logger.isDebugEnabled()) logger.debug("Signalling that streaming is done for {} session {}", context.host, context.sessionId); remove(context); condition.signalAll(); } } public void removePending(PendingFile pf) { files.remove(pf); fileMap.remove(pf.getFilename()); if (files.size() == 0) remove(context); } public void waitForStreamCompletion() { try { condition.await(); } catch (InterruptedException e) { throw new AssertionError(e); } } List<PendingFile> getFiles() { return Collections.unmodifiableList(files); } }