/*
* The Sun Project JXTA(TM) Software License
*
* Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by Sun Microsystems, Inc. for JXTA(TM) technology."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this software
* without prior written permission. For written permission, please contact
* Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA", nor may
* "JXTA" appear in their name, without prior written permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
* MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JXTA is a registered trademark of Sun Microsystems, Inc. in the United
* States and other countries.
*
* Please see the license information page at :
* <http://www.jxta.org/project/www/license.html> for instructions on use of
* the license in source files.
*
* ====================================================================
* This software consists of voluntary contributions made by many individuals
* on behalf of Project JXTA. For more information on Project JXTA, please see
* http://www.jxta.org.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*/
package net.jxta.impl.content;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jxta.logging.Logging;
import net.jxta.content.Content;
import net.jxta.content.ContentID;
import net.jxta.content.ContentProvider;
import net.jxta.content.ContentProviderSPI;
import net.jxta.content.ContentSourceLocationState;
import net.jxta.content.ContentTransfer;
import net.jxta.content.ContentTransferAggregator;
import net.jxta.content.ContentTransferAggregatorListener;
import net.jxta.content.ContentTransferEvent;
import net.jxta.content.ContentTransferListener;
import net.jxta.content.ContentTransferState;
import net.jxta.content.TransferException;
import net.jxta.content.ContentTransferAggregatorEvent;
import net.jxta.content.TransferCancelledException;
import net.jxta.protocol.ContentShareAdvertisement;
import static net.jxta.content.ContentSourceLocationState.*;
import static net.jxta.content.ContentTransferState.*;
/**
* Provides multi-provider content transfer capabilities. Specifically, this
* class will randomly select one of the configured content providers
* and attempt to use it to complete the requested transfer. If the provider
* is unable to retrieve the remote Content, another provider will be selected
* at random to take it's place. This process will continue until the Content
* is either successfully retrieved or no providers remain.
*/
public class TransferAggregator
implements ContentTransferAggregator, ContentTransferListener {
/**
* Log4J logger.
*/
private static final Logger LOG = Logger.getLogger(
TransferAggregator.class.getName());
/**
* The number of transfer instances that will be held "in standby",
* meaning that active remote source location will continue until they
* have "enough" remote sources.
*/
private static final int standbyCount =
Integer.getInteger(TransferAggregator.class.getName()
+ ".standbyCount", 2).intValue();
/**
* ContentTransferListeners.
*/
private final Set<ContentTransferListener> ctListeners =
new CopyOnWriteArraySet<ContentTransferListener>();
/**
* ContentTransferAggregatorListeners.
*/
private final Set<ContentTransferAggregatorListener> ctaListeners =
new CopyOnWriteArraySet<ContentTransferAggregatorListener>();
/**
* ContentProvider which created and manages this transfer.
*/
private final ContentProvider provider;
/**
* List of providers left to select from.
*/
private List<ContentTransfer> idle = new ArrayList<ContentTransfer>();
/**
* List of providers left to select from.
*/
private List<ContentTransfer> standby = new ArrayList<ContentTransfer>();
/**
* Currently selected transfer.
*/
private ContentTransfer selected;
/**
* Destination file of the resulting transfer.
*/
private File destFile;
/**
* Resulting Content.
*/
private Content content;
/**
* Current source location state.
*/
private ContentSourceLocationState locationState = NOT_LOCATING;
/**
* Current transfer state.
*/
private ContentTransferState transferState = PENDING;
/**
* Captured exception describing the cause of the failure.
*/
private TransferException failureCause;
/**
* Creates a new tansfer with the specified providers. Provider list
* given is a private list that we can modify at will.
*
* @param origin content provider which created and manager this
* transfer
* @param providers list of providers
* @param adv ContentShareAdvertisement
* @throws TransferException if no providers can even attempt to retrieve
* the content in question
*/
public TransferAggregator(
ContentProvider origin,
List<ContentProviderSPI> providers,
ContentShareAdvertisement adv)
throws TransferException {
this(origin, providers, (Object) adv);
}
/**
* Creates a new tansfer with the specified providers. Provider list
* given is a private list that we can modify at will.
*
* @param origin content provider which created and manager this
* transfer
* @param providers list of providers
* @param contentID ID of the Content we would like to retrieve
* @throws TransferException if no providers can even attempt to retrieve
* the content in question
*/
public TransferAggregator(
ContentProvider origin,
List<ContentProviderSPI> providers,
ContentID contentID)
throws TransferException {
this(origin, providers, (Object) contentID);
}
/**
* Private constructor used to consolidate code which would be repeated
* in the available public constructors.
*
* @param origin content provider which created and manager this
* transfer
* @param providers list of providers
* @param adv ContentShareAdvertisement or ContentID object
* @throws TransferException if no providers can even attempt to retrieve
* the content in question
*/
private TransferAggregator(
ContentProvider origin,
List<ContentProviderSPI> providers,
Object obj)
throws TransferException {
provider = origin;
ContentTransfer transfer;
for (ContentProvider prov : providers) {
try {
// null test is for mock object testing
if (obj == null || obj instanceof ContentShareAdvertisement) {
transfer = prov.retrieveContent(
(ContentShareAdvertisement) obj);
} else {
transfer = prov.retrieveContent(
(ContentID) obj);
}
if (transfer == null) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine(hashHex() +
": Provider returned null transfer: " + prov);
}
} else {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.finer(hashHex() +
": Provider '" + prov + "' returned transfer: "
+ transfer);
}
idle.add(transfer);
}
if (content != null) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.finer(hashHex() + ": Provider '" + prov
+ "' found the Content. Skipping remaining "
+ "providers.");
}
break;
}
} catch (UnsupportedOperationException unsupx) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine(hashHex() +
": Provider does not support operation: " + prov);
}
}
}
if (idle.size() == 0) {
throw(new TransferException(
"No transfer providers are able to retrieve this Content"));
}
// Randomize the list, effectively randomizing which impl(s) we use
Collections.shuffle(idle);
/*
* Attach as a listener as the final step to ensure that events
* which are fired immediately don't cause false failures due to
* idle.size() becoming 0.
*/
for (ContentTransfer xfer : idle) {
xfer.addContentTransferListener(this);
}
}
/**
* Alternate form where the ContentTransfer instances have already been
* obtained and put into the desired attempt order. It is assumed that
* all the transfer instances refer to the same Content and thus will
* be multiplexed in the same manner as when the transfer instances must
* be discovered by interacting with the ContentProviders directly.
*
* @param origin content provider which created and manager this
* transfer
* @param transfers list of transfers to use for attempts
*/
public TransferAggregator(
ContentProvider origin,
List<ContentTransfer> transfers) {
provider = origin;
if (transfers.size() == 0) {
throw(new IllegalArgumentException(
"No transfer instances provided"));
}
for (ContentTransfer xfer : transfers) {
idle.add(xfer);
xfer.addContentTransferListener(this);
}
}
///////////////////////////////////////////////////////////////////////////
// ContentTransfer methods:
/**
* {@inheritDoc}
*/
public void addContentTransferListener(ContentTransferListener listener) {
/*
* Handle the special case where the transfer is compelted before
* the listener is added. In this case, we want to immediately
* fire a completion event to only that listener.
*/
ContentTransferEvent toFire = null;
synchronized(this) {
if (content != null) {
toFire = new ContentTransferEvent.Builder(this)
.locationState(locationState)
.transferState(transferState)
.build();
}
}
ctListeners.add(listener);
if (toFire != null) {
listener.contentTransferStateUpdated(toFire);
}
}
/**
* {@inheritDoc}
*/
public void removeContentTransferListener(ContentTransferListener listener) {
ctListeners.remove(listener);
}
/**
* {@inheritDoc}
*/
public ContentProvider getContentProvider() {
return provider;
}
/**
* {@inheritDoc}
*/
public void startSourceLocation() {
// Map to our next state or early out if we can
synchronized(this) {
if (transferState.isSuccessful() || locationState.isLocating()) {
// Nothing to do
return;
}
locationState = locationState.getEquivalent(true);
}
// Make sure we load up the selected and standby transfers
try {
populateSelected();
populateStandby();
} catch (TransferException transx) {
if (Logging.SHOW_FINEST && LOG.isLoggable(Level.FINEST)) {
LOG.log(Level.FINEST, hashHex() + ": Ignoring exception", transx);
}
}
// This indirectly provides event notification
checkSelectedAndStandby();
}
/**
* {@inheritDoc}
*/
public void stopSourceLocation() {
synchronized(this) {
if (!locationState.isLocating()) {
// Nothing to do
return;
}
locationState = locationState.getEquivalent(false);
checkSelectedAndStandby();
}
}
/**
* {@inheritDoc}
*/
public ContentSourceLocationState getSourceLocationState() {
return locationState;
}
/**
* {@inheritDoc}
*/
public void startTransfer(File destination) {
if (destination == null) {
throw(new IllegalArgumentException(
"Destination cannot be null"));
} else if (destFile != null) {
throw(new IllegalArgumentException(
"This transfer has already been started"));
}
startSourceLocation();
synchronized(this) {
destFile = destination;
try {
checkTransferState();
} catch (TransferException transx) {
notifyAll();
}
}
// After this, we rely on events to keep track of status.
}
/**
* {@inheritDoc}
*/
public ContentTransferState getTransferState() {
return transferState;
}
/**
* {@inheritDoc}
*/
public void cancel() {
LOG.fine(hashHex() + ": Cancelling transfer");
cancelAll(null, false);
synchronized(this) {
if (!transferState.isFinished()) {
transferState = CANCELLED;
}
selected = null;
standby.clear();
idle.clear();
notifyAll();
}
}
/**
* {@inheritDoc}
*/
public void waitFor()
throws InterruptedException, TransferException {
waitFor(0);
}
/**
* {@inheritDoc}
*/
public void waitFor(long timeout)
throws InterruptedException, TransferException {
long exitTime = System.currentTimeMillis() + timeout;
long remaining = timeout;
synchronized(this) {
checkTransferState();
while (!transferState.isFinished()) {
wait(remaining);
if (timeout > 0) {
remaining = exitTime - System.currentTimeMillis();
if (remaining <= 0) {
return;
}
}
}
}
}
/**
* {@inheritDoc}
*/
public Content getContent() throws TransferException {
checkTransferState();
do {
try {
waitFor();
return content;
} catch (InterruptedException intx) {
// Ignore
}
} while (true);
}
///////////////////////////////////////////////////////////////////////////
// ContentTransferAggregator methods:
/**
* {@inheritDoc}
*/
public void addContentTransferAggregatorListener(
ContentTransferAggregatorListener listener) {
ctaListeners.add(listener);
}
/**
* {@inheritDoc}
*/
public void removeContentTransferAggregatorListener(
ContentTransferAggregatorListener listener) {
ctaListeners.remove(listener);
}
/**
* {@inheritDoc}
*/
public ContentTransfer getCurrentContentTransfer() {
synchronized(this) {
return selected;
}
}
/**
* {@inheritDoc}
*/
public List<ContentTransfer> getContentTransferList() {
List<ContentTransfer> result = new ArrayList<ContentTransfer>();
synchronized(this) {
if (selected != null) {
result.add(selected);
}
result.addAll(standby);
result.addAll(idle);
}
return result;
}
///////////////////////////////////////////////////////////////////////////
// ContentTransferListener methods:
/**
* {@inheritDoc}
*/
public void contentLocationStateUpdated(ContentTransferEvent event) {
ContentTransfer transfer = event.getContentTransfer();
ContentTransferEvent toFire = null;
synchronized(this) {
// Update our state if transfer is selected, but manage the location
if (transfer == selected) {
ContentSourceLocationState state =
event.getSourceLocationState();
ContentSourceLocationState oldState = locationState;
locationState =
state.getEquivalent(locationState.isLocating());
Integer locationCount = event.getSourceLocationCount();
// Try to send events only when useful
if (oldState != locationState || locationCount != null) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.finer(hashHex()
+ ": Location update (location count: "
+ locationCount + ")");
LOG.finer(" Was : " + oldState);
LOG.finer(" Is : " + locationState);
LOG.finer(" Cause: " + state);
}
toFire = new ContentTransferEvent.Builder(this)
.locationCount(locationCount)
.locationState(locationState)
.build();
}
}
}
// Fire first, then check to see if we need to change states
if (toFire != null) {
fireLocationStateUpdated(toFire);
}
checkLocationState(transfer);
}
/**
* {@inheritDoc}
*/
public void contentTransferStateUpdated(ContentTransferEvent event) {
contentTransferStateUpdated(
event.getContentTransfer(), event.getTransferState());
}
/**
* This is our internal version which allows <code>populateSelected</code>
* to execute this functionality without creating a new event object.
* This allows us to notify of transfer state differences when selecting
* new transfer instances.
*/
private void contentTransferStateUpdated(
ContentTransfer transfer, ContentTransferState state) {
ContentTransferEvent toFire = null;
TransferException exception;
boolean terminateTransfer = false;
boolean doTransferStart = false;
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine(hashHex() + ": Transfer state updated: " + transfer
+ " (" + state + ")");
}
if (state.isSuccessful()) {
/* NOTE mcumings 20061229: This is a bit odd in that
* theoretically an idle transfer can declare that it is
* completed, bypassing the whole of the randomization process.
* This is probably acceptable and desirable though, since
* Content which is already available locally will have close
* to zero retrieval overhead as long as providers check
* their local sources prior to looking for remote sources.
*/
// make sure it isn't lying
try {
boolean eureka = false;
boolean newSelected = false;
synchronized(this) {
if (content == null) {
content = transfer.getContent();
if (content != null) {
eureka = true;
if (selected != transfer) {
// promote the transfer to being selected
if (selected != null) {
standby.add(selected);
}
standby.remove(transfer);
idle.remove(transfer);
newSelected = true;
selected = transfer;
}
transferState = COMPLETED;
toFire = new ContentTransferEvent.Builder(this)
.locationState(locationState)
.transferState(transferState)
.build();
notifyAll();
}
}
}
// Only go crazy if this is the first provider to succeed
if (eureka) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.finer(hashHex() + ": Eureka!");
}
if (newSelected) {
fireSelectedContentTransfer(transfer);
}
cancelAll(null, true);
} else {
terminateTransfer = true;
}
} catch (InterruptedException intx) {
// Bad timing?
terminateTransfer = true;
} catch (TransferException transx) {
// LIAR!
terminateTransfer = true;
}
} else if (state.isFinished()) {
try {
// Dummy call to gain access to exception
while(true) {
try {
transfer.getContent();
break;
} catch (InterruptedException inx) {
Thread.interrupted();
// Ignore and retry
}
}
} catch (TransferException transx) {
exception = transx;
}
terminateTransfer = true;
} else if (!state.isRetrieving()) {
/*
* This should currently only pick up PENDING states. Since this
* is the starting state, we never transition to new states based
* on the reception of this state. We use it to start the
* uninitiated transfer instances.
*/
synchronized(this) {
if (transferState.isFinished()) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.finer(hashHex() + " Ignoring event due to being "
+ "in a finished state");
}
} else if (transfer == selected) {
if (destFile != null) {
doTransferStart = true;
}
}
}
} else {
synchronized(this) {
if (transferState.isFinished()) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.finer(hashHex() + " Ignoring event due to being "
+ "in a finished state");
}
} else if (transfer == selected && transferState != state) {
/*
* We need to absorb or transform events which would
* otherwise be an illegal strate transition when viewed
* from our listeners' perspective. All final states are
* absorbed since we will generate them elsewhere. We
* are already in at least the PENDING state, so we can't
* go back to PENDING. Thus, only active retrieval states
* are allowed for transitions.
*/
if (state.isRetrieving()) {
transferState = state;
toFire = new ContentTransferEvent.Builder(this)
.locationState(locationState)
.transferState(transferState)
.build();
}
}
}
}
if (terminateTransfer) {
transfer.cancel();
synchronized(this) {
if (selected == transfer) {
selected = null;
}
if (standby.contains(transfer)) {
standby.remove(transfer);
}
if (idle.contains(transfer)) {
idle.remove(transfer);
}
}
try {
populateSelected();
populateStandby();
checkSelectedAndStandby();
} catch (TransferException transx) {
synchronized(this) {
failureCause = transx;
transferState = FAILED;
toFire = new ContentTransferEvent.Builder(this)
.locationState(locationState)
.transferState(transferState)
.build();
notifyAll();
}
}
}
if (toFire != null) {
fireTransferStateUpdated(toFire);
}
if (doTransferStart) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.finer(hashHex() + ": Starting transfer: " + transfer);
}
transfer.startTransfer(destFile);
}
}
/**
* {@inheritDoc}
*/
public void contentTransferProgress(ContentTransferEvent event) {
ContentTransfer source = event.getContentTransfer();
// Only notify listeners of progress of the selected transfer
synchronized(this) {
if (source != selected) {
return;
}
}
for (ContentTransferListener listener : ctListeners) {
listener.contentTransferProgress(event);
}
}
//////////////////////////////////////////////////////////////////////////
// Private methods:
/**
* Checks to make sure that there is a selected content transfer node,
* backfilling the standby queue if need be. If there is no selected
* transfer node and there are no transfers in the standby or idle
* queues, an exception is thrown. Upon new selected node promotion,
* notifies all CTA listeners.
*
* @throws TransferException when no transfers are remaining to become the
* selected transfer
*/
private void populateSelected() throws TransferException {
ContentTransfer newSelected = null;
ContentTransferState newState = null;
boolean selectNext;
synchronized(this) {
if (selected == null) {
selectNext = true;
} else if (selected.getTransferState().isSuccessful()) {
selectNext = false;
} else if (selected.getTransferState().isFinished()) {
selectNext = true;
} else {
selectNext = false;
}
if (selectNext) {
if (standby.size() == 0) {
if (idle.size() == 0) {
// Failed.
transferState = FAILED;
throw(new TransferException(
"No transfer providers remain"));
} else {
selected = idle.remove(0);
}
} else {
selected = standby.remove(0);
}
// Backfill as needed
populateStandby();
newSelected = selected;
newState = newSelected.getTransferState();
}
}
if (newSelected != null) {
fireSelectedContentTransfer(newSelected);
contentTransferStateUpdated(newSelected, newState);
}
}
/**
* Ensures that the configured number of transfer instances are held
* in the standby queue, if available.
*/
private void populateStandby() {
// Populate the standby list
synchronized(this) {
while (standby.size() < standbyCount) {
if (idle.size() == 0) {
// No more to add
break;
}
standby.add(idle.remove(0));
}
}
}
/**
* Checks the source location state of the specified transfer instance,
* starting or stopping source location as deemed appropriate. If the
* transfer instance is the selected node, source location proceeds until
* "many" sources have been identified. If the transfer instance is on
* the standby queue, source location proceeds until "enough" sources
* have been identified. In all cases, if source location has been
* programatically requested to stop, source location is stopped.
*
* @param transfer transfer instance to inspect
*/
private void checkLocationState(ContentTransfer transfer) {
// Protect against null selected transfer
if (transfer == null) {
return;
}
ContentSourceLocationState state = transfer.getSourceLocationState();
boolean doStart = false;
boolean doStop = false;
synchronized(this) {
if (locationState.isLocating()) {
boolean beyondThreshold = false;
if (transfer == selected && state.hasMany()) {
beyondThreshold = true;
} else if (standby.contains(transfer) && state.hasEnough()) {
beyondThreshold = true;
}
if (state.isLocating()) {
if (beyondThreshold) {
doStop = true;
}
} else {
if (!beyondThreshold) {
doStart = true;
}
}
} else {
// We've been asked to stop.
doStop = true;
}
}
/*
* Now affect changes outside synchronized block, as these actions
* may cause events to be thrown downstream.
*/
if (doStart) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine(hashHex() + ": Starting source location for transfer: "
+ transfer);
}
transfer.startSourceLocation();
} else if (doStop) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine(hashHex() + ": Stopping source location for transfer: "
+ transfer);
}
transfer.stopSourceLocation();
}
}
/**
* Checks the location state of the selected transfer as well as all
* standby transfers.
*/
private void checkSelectedAndStandby() {
checkLocationState(selected);
for (ContentTransfer transfer : standby) {
checkLocationState(transfer);
}
}
/**
* Checks the transfer state of the aggregated transfer, checking to
* to see if the cancelled or failed flags have been raised. If the
* cancelled flag is present a transfer cancelled exception is raised.
* If the failure flag is present the stored failure cause will be
* thrown if it exists, or a new transfer exception will be thrown
* if it does not exist.
*
* @throws TransferCancelledException if the transfer has been cancelled
* @throws TransferException if the transfer hsa failed
*/
private void checkTransferState() throws TransferException {
synchronized(this) {
if (transferState.isSuccessful()) {
return;
} else if (transferState.isFinished()) {
if (transferState == CANCELLED) {
throw(new TransferCancelledException());
} else {
if (failureCause != null) {
throw(failureCause);
}
throw(new TransferException("Transfer failed"));
}
} else if (!transferState.isRetrieving() && selected != null) {
// Make sure the transfer has started
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.finer(hashHex() + ": Starting transfer: " + selected);
}
selected.startTransfer(destFile);
}
}
}
/**
* Cancels all outstanding transfers, except for the one optionally
* provided. In the process of doing this, we stop listening to all
* transfers - even exceptThisOne - if the becomeDeaf param is set
* to true.
*
* @param exceptThisOne transfer to not cancel, or null
* @param becomeDeaf true if we should remove ourselves as a listener
* of the transfers prior to cancelling, false to spam ourselves with
* events
*/
private void cancelAll(ContentTransfer exceptThisOne, boolean becomeDeaf) {
// This list is built while synchronized
List<ContentTransfer> allTransfers = getContentTransferList();
// But we cancel outside of synchronization due to event throwing
for (ContentTransfer transfer : allTransfers) {
if (becomeDeaf) {
transfer.removeContentTransferListener(this);
}
if (transfer != exceptThisOne) {
transfer.cancel();
}
}
}
/**
* Notify listeners of potential changes to the source location
* status.
*
* @param event event information to send to listeners
*/
private void fireLocationStateUpdated(ContentTransferEvent event) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.finer(hashHex() + ": Firing event: " + event);
}
for (ContentTransferListener listener : ctListeners) {
try {
listener.contentLocationStateUpdated(event);
} catch (Throwable t) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING,
"Uncaught throwable from listener", t);
}
}
}
}
/**
* Notify listeners of potential changes to the transfer status.
*
* @param event event information to send to listeners
*/
private void fireTransferStateUpdated(ContentTransferEvent event) {
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.finer(hashHex() + ": Firing event: " + event);
}
for (ContentTransferListener listener : ctListeners) {
try {
listener.contentTransferStateUpdated(event);
} catch (Throwable t) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING,
"Uncaught throwable from listener", t);
}
}
}
}
/**
* Notify listeners that a new transfer instance has been selected as
* the current/main instance.
*
* @param newSelected the newly selected primary transfer instance
*/
private void fireSelectedContentTransfer(ContentTransfer newSelected) {
ContentTransferAggregatorEvent event = null;
if (Logging.SHOW_FINER && LOG.isLoggable(Level.FINER)) {
LOG.finer(hashHex() + ": Firing new selected: " + newSelected);
}
for (ContentTransferAggregatorListener listener : ctaListeners) {
try {
if (event == null) {
event = new ContentTransferAggregatorEvent.Builder(this)
.contentTransfer(newSelected)
.build();
}
listener.selectedContentTransfer(event);
} catch (Throwable t) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING,
"Uncaught throwable from listener", t);
}
}
}
}
/**
* Returns the hashCode value in hex.
*
* @return hex hashCode value
*/
private String hashHex() {
return Integer.toString(hashCode(), 16);
}
}