/*
* Copyright (c) 2004-2007 Sun Microsystems, Inc. All rights reserved.
*
* The Sun Project JXTA(TM) Software License
*
* 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.endpoint.router;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jxta.endpoint.EndpointAddress;
import net.jxta.endpoint.EndpointService;
import net.jxta.endpoint.Messenger;
import net.jxta.impl.util.TimeUtils;
import net.jxta.impl.util.threads.TaskManager;
import net.jxta.logging.Logging;
/**
* This class is a repository of wisdom regarding destinations. It also provides
* a messenger if there is one. Currently, the wisdom is very limited and is
* only about direct destinations (for which a messenger once existed). The
* wisdom that can be obtained is:
*
* <p/><ul>
* <li> is there a messenger at hand (incoming or otherwise).</li>
*
* <li> is it likely that one can be made from this end, should the one we
* have break. (the last attempt succeeded, not only incoming, and that was
* not long ago).</li>
*
* <li> is either of the above true, (are we confident we can get a
* messenger as of right now one way or the other).</li>
*
* <li> are we supposed to send a welcome to that destination (we can't
* remember having done it).</li>
* </ul>
*
* <p/>This could be extended to manage more of the life cycle, such as knowing
* about messengers being resolved or having failed to. This primitive interface
* is temporary; it is only meant to replace messengerPool without having to
* change the router too much.
*/
class Destinations {
/**
* Logger
*/
private final static transient Logger LOG = Logger.getLogger(Destinations.class.getName());
/**
* Shared Timer which handles cleanup of expired Wisdom.
*/
private final static transient Timer cleanup = new Timer("Endpoint Destinations GC", true);
private final Map<EndpointAddress, Wisdom> wisdoms = new HashMap<EndpointAddress, Wisdom>(64);
/**
* If {@code true} then we are shutting down.
*/
private volatile boolean stopped = false;
/**
* The endpoint service we are working for.
*/
private final EndpointService endpoint;
/**
* The wisdom GC task for this instance.
*/
private final ScheduledFuture<?> wisdomGCHandle;
/**
* Stores knowledge about one particular destination.
*
* <p/>It does not provide any synchronization. This is provided by the Destinations class.
*/
final class Wisdom {
/**
* How long we consider that a past outgoingMessenger is an indication
* that one is possible in the future.
*/
static final long EXPIRATION = 10 * TimeUtils.AMINUTE;
/**
* The channel we last used, if any. They disappear faster than the
* canonical, but, as long as the canonical is around, they can be
* obtained at a near-zero cost.
*/
private Reference<Messenger> outgoingMessenger;
/**
* The channel we last used if it happens to be an incoming messenger. We keep
* a strong reference to it.
*/
private Messenger incomingMessenger;
/**
* The transport destination address of the messenger we're caching (if
* not incoming).
*/
private EndpointAddress xportDest;
/**
* This tells when the outgoing messenger information expires. Incoming
* messengers have no expiration per se. We draw no conclusion from
* their past presence; only current presence. A wisdom is totally
* expired (and may thus be removed) when its outgoing messenger
* information is expired AND it has no incoming messenger.
*/
private long expiresAt = 0;
/**
* When a new destination is added, we're supposed to send our welcome
* along with the first message. This tells whether isWelcomeNeeded was
* once invoked or not.
*/
private boolean welcomeNeeded = true;
/**
* @param messenger The messenger to cache information about.
* @param incoming If true, this is an incoming messenger, which means
* that if the channel is lost it cannot be re-obtained. It must
* strongly referenced until it closes for disuse, or breaks.
*/
Wisdom(Messenger messenger, boolean incoming) {
if (incoming) {
addIncomingMessenger(messenger);
} else {
addOutgoingMessenger(messenger);
}
}
/**
* Reports whether a welcome message is needed. The first time we're
* asked we say "true". Subsequently, always "false".
*
* <p/>This assumes that the welcome was sent following the first test.
* (so, ask only if you'll send the welcome when told to).
*
* @return {@code true} if this is the first time this method is invoked.
*/
synchronized boolean isWelcomeNeeded() {
boolean res = welcomeNeeded;
welcomeNeeded = false;
return res;
}
boolean addIncomingMessenger(Messenger m) {
// If we have no other incoming, we take it. No questions asked.
Messenger currentIncoming = getIncoming();
if (currentIncoming == null) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Accepted new incoming messenger for " + m.getDestinationAddress());
}
incomingMessenger = m;
return true;
}
// Now, check reachability (0 port number means no incoming connections).
// If the old one looks better, prefer it.
// Compute reachability of the new one.
String originAddr = m.getDestinationAddress().getProtocolAddress();
int index = originAddr.lastIndexOf(':');
int srcPort = (index != -1) ? Integer.parseInt(originAddr.substring(index + 1)) : 0;
boolean reachable = (srcPort != 0);
// Compute reachability of the old one.
originAddr = currentIncoming.getDestinationAddress().getProtocolAddress();
index = originAddr.lastIndexOf(':');
srcPort = (index != -1) ? Integer.parseInt(originAddr.substring(index + 1)) : 0;
boolean currentReachable = (srcPort != 0);
// The new one is less reachable than the old one. Keep the old one.
if (currentReachable && !reachable) {
return false;
}
incomingMessenger = m;
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Accepted new incoming messenger for " + m.getDestinationAddress());
}
return true;
}
boolean addOutgoingMessenger(Messenger m) {
if (getOutgoing() != null) {
return false;
}
this.outgoingMessenger = new SoftReference<Messenger>(m);
xportDest = m.getDestinationAddress();
expiresAt = TimeUtils.toAbsoluteTimeMillis(EXPIRATION);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Accepted new outgoing messenger for " + xportDest);
}
return true;
}
void noOutgoingMessenger() {
outgoingMessenger = null;
xportDest = null;
expiresAt = 0;
}
/**
* Returns an incoming messenger is there is a working one available.
*
* @return an incoming messenger, null if there's none
*/
private Messenger getIncoming() {
if (incomingMessenger != null) {
if ((incomingMessenger.getState() & Messenger.USABLE) != 0) {
return incomingMessenger;
}
// forget about this broken messenger.
incomingMessenger = null;
}
return null;
}
/**
* Returns an outgoingMessenger if there is one or one can be made without delay.
* Renews a broken one if it can be. Refreshes expiration time if a messenger is returned.
*
* @return an outgoing messenger, null if there's none
*/
private Messenger getOutgoing() {
if (outgoingMessenger == null) {
return null;
}
// (If messenger is not null, it means that we also have a xportDest).
Messenger messenger = outgoingMessenger.get();
// If it is gone or broken, try and get a new one.
if ((messenger == null) || ((messenger.getState() & Messenger.USABLE) == 0)) {
messenger = endpoint.getMessengerImmediate(xportDest, null);
// If this fails, it is hopeless: the address is bad or something like that. Make ourselves expired right away.
if (messenger == null) {
outgoingMessenger = null;
xportDest = null;
expiresAt = 0;
return null;
}
// Renew the ref. The xportDest is the same.
outgoingMessenger = new SoftReference<Messenger>(messenger);
}
// So we had one or could renew. But, does it work ?
if ((messenger.getState() & (Messenger.USABLE & Messenger.RESOLVED)) == 0) {
// We no-longer have the underlying connection. Let ourselves expire. Do not renew the expiration time.
outgoingMessenger = null;
xportDest = null;
return null;
}
// Ok, we do have an outgoing messenger at the ready after all.
expiresAt = TimeUtils.toAbsoluteTimeMillis(EXPIRATION);
return messenger;
}
/**
* Returns a channel for this destination if one is there or can be obtained
* readily and works.
* <p/>
* <p/>We prefer the incoming connection to the outgoing for two
* reasons:
* <ul>
* <li>The remote peer was able to reach us. We cannot be sure that
* we can reach the remote peer.</li>
* <li>The remote peer initiated the connection. It has a better
* sense of when the connection should be closed or reopened than
* we do.</li>
*
* @return a channel for this destination
*/
Messenger getCurrentMessenger() {
Messenger res = getIncoming();
if (res != null) {
return res;
}
return getOutgoing();
}
/**
* @return true if we do have an outgoing messenger or, failing that, we had one not too long ago.
*/
boolean isNormallyReachable() {
return ((getOutgoing() != null) || (TimeUtils.toRelativeTimeMillis(expiresAt) >= 0));
}
/**
* We think the destination is reachable somehow. Not sure how long.
*
* @return true if we have any kind of messenger or, failing that, we had an outgoing one not too long ago.
*/
boolean isCurrentlyReachable() {
return ((getIncoming() != null) || (getOutgoing() != null) || (TimeUtils.toRelativeTimeMillis(expiresAt) >= 0));
}
/**
* @return true if this wisdom carries no positive information whatsoever.
*/
boolean isExpired() {
return !isCurrentlyReachable();
}
}
/*
* Internal mechanisms
*/
/**
* Return any Wisdom for the specified destination. The address will
* be normalized to the base form.
*
* @param destination The address of the wisdom that is sought.
* @return The Wisdom for this address or {@code null} if no Wisdom found.
*/
private Wisdom getWisdom(EndpointAddress destination) {
if (destination.getServiceName() != null) {
destination = new EndpointAddress(destination, null, null);
}
return wisdoms.get(destination);
}
/**
* Add a Wisdom for the specified destination. The address will
* be normalized to the base form.
*
* @param destination The address of the Wisdom that is being added.
* @param wisdom The Wisdom for this address to be added to the map.
*/
private void addWisdom(EndpointAddress destination, Wisdom wisdom) {
destination = new EndpointAddress(destination, null, null);
wisdoms.put(destination, wisdom);
}
/*
* General house keeping.
*/
public Destinations(EndpointService endpoint) {
this.endpoint = endpoint;
ScheduledExecutorService executor = TaskManager.getTaskManager().getScheduledExecutorService();
wisdomGCHandle = executor.scheduleAtFixedRate(new WisdomGCTask(), 60, 60, TimeUnit.SECONDS);
}
/**
* Shutdown this cache. (stop the gc)
*/
public synchronized void close() {
stopped = true;
// forget everything.
wisdoms.clear();
wisdomGCHandle.cancel(false);
}
/**
* Handles cleanup of expired wisdoms
*/
class WisdomGCTask implements Runnable {
/**
* {@inheritDoc}
*
* <p/>garbage collector. We use soft references to messengers, but we use
* a strong hashmap to keep the wisdom around in a more predictable
* manner. Entries are simply removed when they no-longer carry
* relevant information; so there's no change in the total meaning of
* the map when an entry is removed.
*/
public void run() {
try {
synchronized (Destinations.this) {
Iterator<Wisdom> eachWisdom = wisdoms.values().iterator();
while (eachWisdom.hasNext()) {
Wisdom w = eachWisdom.next();
if (w.isExpired()) {
eachWisdom.remove();
}
}
}
} catch (Throwable all) {
if (Logging.SHOW_SEVERE && Destinations.LOG.isLoggable(Level.SEVERE)) {
LOG.log(Level.SEVERE, "Uncaught Throwable in ScheduledTask :" + Thread.currentThread().getName(), all);
}
}
}
}
public synchronized Collection<EndpointAddress> allDestinations() {
Set<EndpointAddress> allKeys = wisdoms.keySet();
List<EndpointAddress> res = new ArrayList<EndpointAddress>(allKeys);
return res;
}
/*
* information output
*/
/**
* If there is a messenger at hand (incoming or otherwise), return it.
*
* @param destination The destination as an endpoint address (is automatically normalized to protocol and address only).
* @return A messenger to that destination if a resolved and usable one is available or can be made instantly. null otherwise.
*/
public synchronized Messenger getCurrentMessenger(EndpointAddress destination) {
Wisdom wisdom = getWisdom(destination);
if (wisdom == null) {
return null;
}
return wisdom.getCurrentMessenger();
}
/**
* Is it likely that one can be made from this end. (the last attempt succeeded, not only incoming, and that was not long ago) ?
* This is a conservative test. It means that declaring that we can route to that destination is a very safe bet, as opposed
* to isNormallyReachable and getCurrentMessenger, which could be misleading if the only messenger we can ever get is incoming.
* Not currently used. Should likely be.
*
* @param destination The destination as an endpoint address (is automatically normalized to protocol and address only).
* @return true if it is likely that we can get a messenger to that destination in the future.
*/
public synchronized boolean isNormallyReachable(EndpointAddress destination) {
Wisdom wisdom = getWisdom(destination);
return ((wisdom != null) && wisdom.isNormallyReachable());
}
/**
* Do we already have a messenger or is it likely that we can make one?
* We is will return {@code true} more often than
* {@code isNormallyReachable()} since it can be true even when all we have
* is an incoming messenger.
*
* <p/>Just testing that there is an entry is no-longer the same because we
* may keep the entries beyond the point where we would keep them before, so
* that we can add some longer-lived information in the future, and do not
* interfere as much with the gc thread.
*
* @param destination The destination as an endpoint address (is automatically normalized to protocol and address only).
* @return true is we are confident that we can obtain a messenger, either because we can get one instantly, or because
* this destination is normally reachable. (So, it is ok to try and route to that destination, now).
*/
public synchronized boolean isCurrentlyReachable(EndpointAddress destination) {
Wisdom wisdom = getWisdom(destination);
return ((wisdom != null) && wisdom.isCurrentlyReachable());
}
/**
* Are we supposed to send a welcome to that destination (we can't remember having done it).
* It is assumed that once true was returned, it will be acted upon. So, true is not returned a second time.
*
* @param destination The destination as an endpoint address (is automatically normalized to protocol and address only).
* @return true if this a destination to whish we can't remember sending a welcome message.
*/
public synchronized boolean isWelcomeNeeded(EndpointAddress destination) {
Wisdom wisdom = getWisdom(destination);
return ((wisdom != null) && wisdom.isWelcomeNeeded());
}
/*
* information input.
*/
/**
* Here is a messenger that we were able to obtain.
*
* @param destination The destination as an endpoint address (is automatically normalized to protocol and address only).
* @param messenger The incoming messenger for that destination.
* @return true if this messenger was added (keep it open). false otherwise (do what you want with it).
*/
public synchronized boolean addOutgoingMessenger(EndpointAddress destination, Messenger messenger) {
Wisdom wisdom = getWisdom(destination);
if (wisdom != null) {
return wisdom.addOutgoingMessenger(messenger);
}
addWisdom(destination, new Wisdom(messenger, false));
return true;
}
/**
* Here is an incoming messenger that just popped out.
*
* @param destination The destination as an endpoint address (is automatically normalized to protocol and address only).
* @param messenger The incoming messenger for that destination.
* @return true if this messenger was added (keep it open). false otherwise (do what you want with it).
*/
public synchronized boolean addIncomingMessenger(EndpointAddress destination, Messenger messenger) {
Wisdom wisdom = getWisdom(destination);
if (wisdom != null) {
return wisdom.addIncomingMessenger(messenger);
}
addWisdom(destination, new Wisdom(messenger, true));
return true;
}
/**
* We tried to get a messenger but could not. We know that we do not have connectivity from our end, for now. we may still
* have an incoming. However, if we had to try and make a messenger, there probably isn't an incoming, but that's not our
* business here. isNormallyReachable becomes false; but we can still try when solicited.
*
* @param destination The destination as an endpoint address (is automatically normalized to protocol and address only).
*/
public synchronized void noOutgoingMessenger(EndpointAddress destination) {
Wisdom wisdom = getWisdom(destination);
if (wisdom != null) {
wisdom.noOutgoingMessenger();
}
}
}