org.piax.gtrans.tsd.TSD Maven / Gradle / Ivy
Show all versions of piax-compat Show documentation
/*
* TSD.java
*
* Copyright (c) 2012-2015 National Institute of Information and
* Communications Technology
*
* You can redistribute it and/or modify it under either the terms of
* the AGPLv3 or PIAX binary code license. See the file COPYING
* included in the PIAX package for more in detail.
*
* $Id: TSD.java 1176 2015-05-23 05:56:40Z teranisi $
*/
package org.piax.gtrans.tsd;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import org.piax.common.ObjectId;
import org.piax.common.PeerId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Transport Service Discovery (TSD) のためのテンプレートクラス。
*
* TSDは、mDNS のプロトコルに従って、近傍のピアとサービス情報の交換を行う仕組みを提供する。
* サービス情報として、ピアのアドレス情報、つまり peerId, endpoint の組を交換することが主な用途であるが、
* これ以外にも一般のオブジェクトを扱うことができる。
*
* このようにTSDは汎用性を持った近傍との情報交換のクラスであるが、交換したいサービス毎にインスタンスを生成
* することは非効率であるため、サービスを交換したいアプリが共通のTSDを使うようにしている。
* サービスを交換するアプリの識別にはObjectIdを用いる。交換されるサービス情報にはこのObjectIdが付与
* されて、同じObjectIdを持つリモートアプリに通知される。
*
* TSDを使ってピア情報を交換する代表的なアプリに、gtransのDiscoverableがある。Bluetooth以外の
* TCP/IP通信はTSDを使って、Discoverable にしている。
*
* TSDの実装には、UDPのマルチキャストを使う MulticastTSDとブロードキャストを使うBroadcastTSDの2つがある。
*
*/
public abstract class TSD {
/*--- logger ---*/
private static final Logger logger = LoggerFactory.getLogger(TSD.class);
/** schedule timer for TSD */
protected static final Timer timer = new Timer("TSDTimer", true);
/**
* TSDを使用する objectId ごと発行するTimerTask。
* advertiseAll と discardOldInfos を定期的に呼び出す。
*
* TODO
* checkDiscarding の呼び出しタイミングに改良の余地あり。
*/
class DiscoveryTask extends TimerTask {
final PeerId peerId;
final ObjectId receiver;
DiscoveryTask(PeerId peerId, ObjectId receiver) {
this.peerId = peerId;
this.receiver = receiver;
}
@Override
public void run() {
advertiseAll(peerId, receiver);
checkDiscarding(peerId, receiver);
}
}
/*
* TODO
* availableとlocalサービスを持つリスト。
* objectId毎に管理を分けた方がよい。
*/
private final List> localServices = new ArrayList>();
/*
* TODO
* リスナーとタイマーをobjectId毎に管理するためのMap。
* この2つは統合化できる。
* keyはpeerId+objIdの文字列(やや手抜き)
*/
private final Map listenersByUpper =
new ConcurrentHashMap();
private final Set usingPeers = new CopyOnWriteArraySet();
private final Map discoveryTasks =
new ConcurrentHashMap();
public abstract void fin();
private String getKey(PeerId peerId, ObjectId receiver) {
return peerId.toString() + "/" + receiver.toString();
}
public void setDiscoveryListener(PeerId peerId, ObjectId receiver, TSDListener listener) {
usingPeers.add(peerId);
if (listener == null) {
listenersByUpper.remove(getKey(peerId, receiver));
} else {
listenersByUpper.put(getKey(peerId, receiver), listener);
}
}
public TSDListener getDiscoveryListener(PeerId peerId, ObjectId receiver) {
return listenersByUpper.get(getKey(peerId, receiver));
}
/**
* ServiceInfoを広告する。
* このメソッドは下位層で実装する。
*
* @param info the information to advertise.
* @throws IOException an I/O error.
*/
protected abstract void advertise(ServiceInfo info) throws IOException;
public void registerService(PeerId peerId, ObjectId receiver, T info) {
ServiceInfo serv = new ServiceInfo(info, peerId, receiver);
synchronized (localServices) {
if (localServices.contains(serv)) return;
localServices.add(serv);
}
try {
advertise(serv);
} catch (IOException e) {
logger.warn("", e);
}
}
public void unregisterService(PeerId peerId, ObjectId receiver, T info) {
ServiceInfo serv = new ServiceInfo(info, peerId, receiver);
synchronized (localServices) {
localServices.remove(serv);
}
}
public void unregisterAllServices(PeerId peerId, ObjectId receiver) {
synchronized (localServices) {
Iterator> it = localServices.iterator();
while (it.hasNext()) {
ServiceInfo sinfo = it.next();
if (peerId.equals(sinfo.peerId) && receiver.equals(sinfo.objId))
it.remove();
}
}
}
/**
* peerId, receiverの区分で登録されているすべてのlocalServiceを広告する。
*
* @param peerId the peer id.
* @param receiver the object id of the receiver.
*/
protected void advertiseAll(PeerId peerId, ObjectId receiver) {
try {
synchronized (localServices) {
for (ServiceInfo sinfo : localServices) {
if (peerId.equals(sinfo.peerId) && receiver.equals(sinfo.objId))
advertise(sinfo);
}
}
} catch (IOException e) {
logger.warn("", e);
}
}
/**
* peerId, receiverの区分で、discoveryTasksのタイマー登録をする。
*
* @param peerId the peer id.
* @param receiver the object id of the receiver.
* @param delay the delay to start execution.
* @param period the period to execute.
*/
public void scheduleDiscovery(PeerId peerId, ObjectId receiver, long delay, long period) {
DiscoveryTask task = new DiscoveryTask(peerId, receiver);
DiscoveryTask old = discoveryTasks.put(getKey(peerId, receiver), task);
if (old != null)
old.cancel();
timer.schedule(task, delay, period);
}
/**
* peerId, receiverの区分でセットしたタイマーをキャンセルする。
*
* @param peerId the peer id.
* @param receiver the object id of the receiver.
*/
public void cancelDiscovery(PeerId peerId, ObjectId receiver) {
DiscoveryTask task = discoveryTasks.remove(getKey(peerId, receiver));
if (task != null)
task.cancel();
}
/**
* ServiceInfoが発見されたことを通知する。
* このメソッドは下位層から呼ばれる。
*
* @param serv the service info.
*/
protected void found(ServiceInfo serv) {
for (PeerId p : usingPeers) {
if (p.equals(serv.peerId)) continue;
TSDListener listener = listenersByUpper.get(getKey(p, serv.objId));
if (listener != null)
listener.onDiscovered(serv.info);
}
}
/**
* 削除タイミングをTSDListenerに通知する。
* @param peerId the peer id.
* @param receiver receiver object id.
*/
protected void checkDiscarding(PeerId peerId, ObjectId receiver) {
TSDListener listener = listenersByUpper.get(getKey(peerId, receiver));
if (listener != null)
listener.onFadeoutCheck();
}
}