javax.jmdns.impl.JmmDNSImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmdns Show documentation
Show all versions of jmdns Show documentation
JmDNS is a Java implementation of multi-cast DNS and can be used for service registration and discovery in local area networks. JmDNS is fully compatible with Apple's Bonjour.
The project was originally started in December 2002 by Arthur van Hoff at Strangeberry.
/**
*
*/
package javax.jmdns.impl;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jmdns.JmDNS;
import javax.jmdns.JmmDNS;
import javax.jmdns.NetworkTopologyDiscovery;
import javax.jmdns.NetworkTopologyEvent;
import javax.jmdns.NetworkTopologyListener;
import javax.jmdns.ServiceInfo;
import javax.jmdns.ServiceListener;
import javax.jmdns.ServiceTypeListener;
import javax.jmdns.impl.constants.DNSConstants;
import javax.jmdns.impl.util.NamedThreadFactory;
/**
* This class enable multihoming mDNS. It will open a mDNS per IP address of the machine.
*
* @author Cédrik Lime, Pierre Frisch
*/
public class JmmDNSImpl implements JmmDNS, NetworkTopologyListener, ServiceInfoImpl.Delegate {
private static Logger logger = LoggerFactory.getLogger(JmmDNSImpl.class);
private final Set _networkListeners;
/**
* Every JmDNS created.
*/
private final ConcurrentMap _knownMDNS;
/**
* This enable the service info text update.
*/
private final ConcurrentMap _services;
/**
* List of registered services
*/
private final Set _serviceTypes;
/**
* Holds instances of ServiceListener's. Keys are Strings holding a fully qualified service type. Values are LinkedList's of ServiceListener's.
*/
private final ConcurrentMap> _serviceListeners;
/**
* Holds instances of ServiceTypeListener's.
*/
private final Set _typeListeners;
private final ExecutorService _listenerExecutor;
private final ExecutorService _jmDNSExecutor;
private final Timer _timer;
private final AtomicBoolean _isClosing;
private final AtomicBoolean _closed;
/**
*
*/
public JmmDNSImpl() {
super();
_networkListeners = Collections.synchronizedSet(new HashSet());
_knownMDNS = new ConcurrentHashMap();
_services = new ConcurrentHashMap(20);
_listenerExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("JmmDNS Listeners"));
_jmDNSExecutor = Executors.newCachedThreadPool(new NamedThreadFactory("JmmDNS"));
_timer = new Timer("Multihomed mDNS.Timer", true);
_serviceListeners = new ConcurrentHashMap>();
_typeListeners = Collections.synchronizedSet(new HashSet());
_serviceTypes = Collections.synchronizedSet(new HashSet());
(new NetworkChecker(this, NetworkTopologyDiscovery.Factory.getInstance())).start(_timer);
_isClosing = new AtomicBoolean(false);
_closed = new AtomicBoolean(false);
}
/*
* (non-Javadoc)
* @see java.io.Closeable#close()
*/
@Override
public void close() throws IOException {
if (_isClosing.compareAndSet(false, true)) {
logger.debug("Cancelling JmmDNS: {}", this);
_timer.cancel();
_listenerExecutor.shutdown();
_jmDNSExecutor.shutdown();
// We need to cancel all the DNS
ExecutorService executor = Executors.newCachedThreadPool(new NamedThreadFactory("JmmDNS.close"));
try {
for (final JmDNS mDNS : this.getDNS()) {
executor.submit(new Runnable() {
/**
* {@inheritDoc}
*/
@Override
public void run() {
try {
mDNS.close();
} catch (IOException exception) {
// JmDNS never throws this is only because of the closeable interface
}
}
});
}
} finally {
executor.shutdown();
}
try {
executor.awaitTermination(DNSConstants.CLOSE_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException exception) {
logger.warn("Exception ", exception);
}
_knownMDNS.clear();
_services.clear();
_serviceListeners.clear();
_typeListeners.clear();
_serviceTypes.clear();
_closed.set(true);
JmmDNS.Factory.close();
}
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#getNames()
*/
@Override
public String[] getNames() {
Set result = new HashSet();
for (JmDNS mDNS : this.getDNS()) {
result.add(mDNS.getName());
}
return result.toArray(new String[result.size()]);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#getHostNames()
*/
@Override
public String[] getHostNames() {
Set result = new HashSet();
for (JmDNS mDNS : this.getDNS()) {
result.add(mDNS.getHostName());
}
return result.toArray(new String[result.size()]);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#getInetAddresses()
*/
@Override
public InetAddress[] getInetAddresses() throws IOException {
Set result = new HashSet();
for (JmDNS mDNS : this.getDNS()) {
result.add(mDNS.getInetAddress());
}
return result.toArray(new InetAddress[result.size()]);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#getDNS()
*/
@Override
public JmDNS[] getDNS() {
synchronized (_knownMDNS) {
return _knownMDNS.values().toArray(new JmDNS[_knownMDNS.size()]);
}
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#getInterfaces()
*/
@Override
@Deprecated
public InetAddress[] getInterfaces() throws IOException {
Set result = new HashSet();
for (JmDNS mDNS : this.getDNS()) {
result.add(mDNS.getInterface());
}
return result.toArray(new InetAddress[result.size()]);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String)
*/
@Override
public ServiceInfo[] getServiceInfos(String type, String name) {
return this.getServiceInfos(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String, long)
*/
@Override
public ServiceInfo[] getServiceInfos(String type, String name, long timeout) {
return this.getServiceInfos(type, name, false, timeout);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String, boolean)
*/
@Override
public ServiceInfo[] getServiceInfos(String type, String name, boolean persistent) {
return this.getServiceInfos(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String, java.lang.String, boolean, long)
*/
@Override
public ServiceInfo[] getServiceInfos(final String type, final String name, final boolean persistent, final long timeout) {
// We need to run this in parallel to respect the timeout.
final JmDNS[] dnsArray = this.getDNS();
final Set result = new HashSet(dnsArray.length);
if (dnsArray.length > 0) {
List> tasks = new ArrayList>(dnsArray.length);
for (final JmDNS mDNS : dnsArray) {
tasks.add(new Callable() {
@Override
public ServiceInfo call() throws Exception {
return mDNS.getServiceInfo(type, name, persistent, timeout);
}
});
}
ExecutorService executor = Executors.newFixedThreadPool(tasks.size(), new NamedThreadFactory("JmmDNS.getServiceInfos"));
try {
List> results = Collections.emptyList();
try {
results = executor.invokeAll(tasks, timeout + 100, TimeUnit.MILLISECONDS);
} catch (InterruptedException exception) {
logger.debug("Interrupted ", exception);
Thread.currentThread().interrupt();
// Will terminate next loop early.
}
for (Future future : results) {
if (future.isCancelled()) {
continue;
}
try {
ServiceInfo info = future.get();
if (info != null) {
result.add(info);
}
} catch (InterruptedException exception) {
logger.debug("Interrupted ", exception);
Thread.currentThread().interrupt();
} catch (ExecutionException exception) {
logger.warn("Exception ", exception);
}
}
} finally {
executor.shutdown();
}
}
return result.toArray(new ServiceInfo[result.size()]);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String)
*/
@Override
public void requestServiceInfo(String type, String name) {
this.requestServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String, boolean)
*/
@Override
public void requestServiceInfo(String type, String name, boolean persistent) {
this.requestServiceInfo(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String, long)
*/
@Override
public void requestServiceInfo(String type, String name, long timeout) {
this.requestServiceInfo(type, name, false, timeout);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String, java.lang.String, boolean, long)
*/
@Override
public void requestServiceInfo(final String type, final String name, final boolean persistent, final long timeout) {
// We need to run this in parallel to respect the timeout.
for (final JmDNS mDNS : this.getDNS()) {
_jmDNSExecutor.submit(new Runnable() {
/**
* {@inheritDoc}
*/
@Override
public void run() {
mDNS.requestServiceInfo(type, name, persistent, timeout);
}
});
}
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#addServiceTypeListener(javax.jmdns.ServiceTypeListener)
*/
@Override
public void addServiceTypeListener(ServiceTypeListener listener) throws IOException {
_typeListeners.add(listener);
for (JmDNS mDNS : this.getDNS()) {
mDNS.addServiceTypeListener(listener);
}
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#removeServiceTypeListener(javax.jmdns.ServiceTypeListener)
*/
@Override
public void removeServiceTypeListener(ServiceTypeListener listener) {
_typeListeners.remove(listener);
for (JmDNS mDNS : this.getDNS()) {
mDNS.removeServiceTypeListener(listener);
}
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#addServiceListener(java.lang.String, javax.jmdns.ServiceListener)
*/
@Override
public void addServiceListener(String type, ServiceListener listener) {
final String loType = type.toLowerCase();
List list = _serviceListeners.get(loType);
if (list == null) {
_serviceListeners.putIfAbsent(loType, new LinkedList());
list = _serviceListeners.get(loType);
}
if (list != null) {
synchronized (list) {
if (!list.contains(listener)) {
list.add(listener);
}
}
}
for (JmDNS mDNS : this.getDNS()) {
mDNS.addServiceListener(type, listener);
}
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#removeServiceListener(java.lang.String, javax.jmdns.ServiceListener)
*/
@Override
public void removeServiceListener(String type, ServiceListener listener) {
String loType = type.toLowerCase();
List list = _serviceListeners.get(loType);
if (list != null) {
synchronized (list) {
list.remove(listener);
if (list.isEmpty()) {
_serviceListeners.remove(loType, list);
}
}
}
for (JmDNS mDNS : this.getDNS()) {
mDNS.removeServiceListener(type, listener);
}
}
/*
* (non-Javadoc)
* @see javax.jmdns.impl.ServiceInfoImpl.Delegate#textValueUpdated(javax.jmdns.ServiceInfo, byte[])
*/
@Override
public void textValueUpdated(ServiceInfo target, byte[] value) {
// We need to get the list out of the synchronized block to prevent dead locks
final JmDNS[] dnsArray = this.getDNS();
synchronized (_services) {
for (JmDNS mDNS : dnsArray) {
ServiceInfo info = ((JmDNSImpl) mDNS).getServices().get(target.getQualifiedName());
if (info != null) {
info.setText(value);
} else {
logger.warn("We have a mDNS that does not know about the service info being updated.");
}
}
}
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#registerService(javax.jmdns.ServiceInfo)
*/
@Override
public void registerService(ServiceInfo info) throws IOException {
// We need to get the list out of the synchronized block to prevent dead locks
final JmDNS[] dnsArray = this.getDNS();
// This is really complex. We need to clone the service info for each DNS but then we loose the ability to update it.
synchronized (_services) {
for (JmDNS mDNS : dnsArray) {
mDNS.registerService(info.clone());
}
((ServiceInfoImpl) info).setDelegate(this);
_services.put(info.getQualifiedName(), info);
}
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#unregisterService(javax.jmdns.ServiceInfo)
*/
@Override
public void unregisterService(ServiceInfo info) {
// We need to get the list out of the synchronized block to prevent dead locks
final JmDNS[] dnsArray = this.getDNS();
synchronized (_services) {
_services.remove(info.getQualifiedName());
for (JmDNS mDNS : dnsArray) {
mDNS.unregisterService(info);
}
((ServiceInfoImpl) info).setDelegate(null);
}
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#unregisterAllServices()
*/
@Override
public void unregisterAllServices() {
// We need to get the list out of the synchronized block to prevent dead locks
final JmDNS[] dnsArray = this.getDNS();
synchronized (_services) {
_services.clear();
for (JmDNS mDNS : dnsArray) {
mDNS.unregisterAllServices();
}
}
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#registerServiceType(java.lang.String)
*/
@Override
public void registerServiceType(String type) {
_serviceTypes.add(type);
for (JmDNS mDNS : this.getDNS()) {
mDNS.registerServiceType(type);
}
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#list(java.lang.String)
*/
@Override
public ServiceInfo[] list(String type) {
return this.list(type, DNSConstants.SERVICE_INFO_TIMEOUT);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#list(java.lang.String, long)
*/
@Override
public ServiceInfo[] list(final String type, final long timeout) {
final JmDNS[] dnsArray = this.getDNS();
// We need to run this in parallel to respect the timeout.
final Set result = new HashSet(dnsArray.length * 5);
if (dnsArray.length > 0) {
List>> tasks = new ArrayList>>(dnsArray.length);
for (final JmDNS mDNS : dnsArray) {
tasks.add(new Callable>() {
@Override
public List call() throws Exception {
return Arrays.asList(mDNS.list(type, timeout));
}
});
}
ExecutorService executor = Executors.newFixedThreadPool(tasks.size(), new NamedThreadFactory("JmmDNS.list"));
try {
List>> results = Collections.emptyList();
try {
results = executor.invokeAll(tasks, timeout + 100, TimeUnit.MILLISECONDS);
} catch (InterruptedException exception) {
logger.debug("Interrupted ", exception);
Thread.currentThread().interrupt();
// Will terminate next loop early.
}
for (Future> future : results) {
if (future.isCancelled()) {
continue;
}
try {
result.addAll(future.get());
} catch (InterruptedException exception) {
logger.debug("Interrupted ", exception);
Thread.currentThread().interrupt();
} catch (ExecutionException exception) {
logger.warn("Exception ", exception);
}
}
} finally {
executor.shutdown();
}
}
return result.toArray(new ServiceInfo[result.size()]);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#listBySubtype(java.lang.String)
*/
@Override
public Map listBySubtype(String type) {
return this.listBySubtype(type, DNSConstants.SERVICE_INFO_TIMEOUT);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#listBySubtype(java.lang.String, long)
*/
@Override
public Map listBySubtype(final String type, final long timeout) {
Map> map = new HashMap>(5);
for (ServiceInfo info : this.list(type, timeout)) {
String subtype = info.getSubtype();
if (!map.containsKey(subtype)) {
map.put(subtype, new ArrayList(10));
}
map.get(subtype).add(info);
}
Map result = new HashMap(map.size());
for (final Map.Entry> entry : map.entrySet()) {
final String subtype = entry.getKey();
final List infoForSubType = entry.getValue();
result.put(subtype, infoForSubType.toArray(new ServiceInfo[infoForSubType.size()]));
}
return result;
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#addNetworkTopologyListener(javax.jmdns.NetworkTopologyListener)
*/
@Override
public void addNetworkTopologyListener(NetworkTopologyListener listener) {
_networkListeners.add(listener);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#removeNetworkTopologyListener(javax.jmdns.NetworkTopologyListener)
*/
@Override
public void removeNetworkTopologyListener(NetworkTopologyListener listener) {
_networkListeners.remove(listener);
}
/*
* (non-Javadoc)
* @see javax.jmdns.JmmDNS#networkListeners()
*/
@Override
public NetworkTopologyListener[] networkListeners() {
return _networkListeners.toArray(new NetworkTopologyListener[_networkListeners.size()]);
}
/*
* (non-Javadoc)
* @see javax.jmdns.NetworkTopologyListener#inetAddressAdded(javax.jmdns.NetworkTopologyEvent)
*/
@Override
public void inetAddressAdded(NetworkTopologyEvent event) {
InetAddress address = event.getInetAddress();
try {
if (!_knownMDNS.containsKey(address)) {
synchronized (_knownMDNS) {
if (!_knownMDNS.containsKey(address)) {
final JmDNS dns = createJmDnsInstance(address);
if (_knownMDNS.putIfAbsent(address, dns) == null) {
// We need to register the services and listeners with the new JmDNS
final Collection types = _serviceTypes;
final Collection infos = _services.values();
final Collection typeListeners = _typeListeners;
final Map> serviceListeners = _serviceListeners;
_jmDNSExecutor.submit(new Runnable() {
/**
* {@inheritDoc}
*/
@Override
public void run() {
// Register Types
for (String type : types) {
dns.registerServiceType(type);
}
// Register services
for (ServiceInfo info : infos) {
try {
dns.registerService(info.clone());
} catch (IOException exception) {
// logger.warn("Unexpected unhandled exception: " + exception);
}
}
// Add ServiceType Listeners
for (ServiceTypeListener listener : typeListeners) {
try {
dns.addServiceTypeListener(listener);
} catch (IOException exception) {
// logger.warn("Unexpected unhandled exception: " + exception);
}
}
// Add Service Listeners
for (final Map.Entry> entry : serviceListeners.entrySet()) {
final String type = entry.getKey();
final List listeners = entry.getValue();
synchronized (listeners) {
for (ServiceListener listener : listeners) {
dns.addServiceListener(type, listener);
}
}
}
}
});
final NetworkTopologyEvent jmdnsEvent = new NetworkTopologyEventImpl(dns, address);
for (final NetworkTopologyListener listener : this.networkListeners()) {
_listenerExecutor.submit(new Runnable() {
/**
* {@inheritDoc}
*/
@Override
public void run() {
listener.inetAddressAdded(jmdnsEvent);
}
});
}
} else {
dns.close();
}
}
}
}
} catch (Exception e) {
logger.warn("Unexpected unhandled exception: ", e);
}
}
/*
* (non-Javadoc)
* @see javax.jmdns.NetworkTopologyListener#inetAddressRemoved(javax.jmdns.NetworkTopologyEvent)
*/
@Override
public void inetAddressRemoved(NetworkTopologyEvent event) {
InetAddress address = event.getInetAddress();
try {
if (_knownMDNS.containsKey(address)) {
synchronized (_knownMDNS) {
if (_knownMDNS.containsKey(address)) {
JmDNS mDNS = _knownMDNS.remove(address);
mDNS.close();
final NetworkTopologyEvent jmdnsEvent = new NetworkTopologyEventImpl(mDNS, address);
for (final NetworkTopologyListener listener : this.networkListeners()) {
_listenerExecutor.submit(new Runnable() {
/**
* {@inheritDoc}
*/
@Override
public void run() {
listener.inetAddressRemoved(jmdnsEvent);
}
});
}
}
}
}
} catch (Exception e) {
logger.warn("Unexpected unhandled exception: ", e);
}
}
/**
* Checks the network state.
* If the network change, this class will reconfigure the list of DNS do adapt to the new configuration.
*/
static class NetworkChecker extends TimerTask {
private final NetworkTopologyListener _mmDNS;
private final NetworkTopologyDiscovery _topology;
private Set _knownAddresses;
public NetworkChecker(NetworkTopologyListener mmDNS, NetworkTopologyDiscovery topology) {
super();
this._mmDNS = mmDNS;
this._topology = topology;
_knownAddresses = Collections.synchronizedSet(new HashSet());
}
public void start(Timer timer) {
// Run once up-front otherwise the list of servers will only appear after a delay.
timer.schedule(this, 0, DNSConstants.NETWORK_CHECK_INTERVAL);
}
/**
* {@inheritDoc}
*/
@Override
public void run() {
try {
InetAddress[] curentAddresses = _topology.getInetAddresses();
Set current = new HashSet(curentAddresses.length);
for (InetAddress address : curentAddresses) {
current.add(address);
if (!_knownAddresses.contains(address)) {
final NetworkTopologyEvent event = new NetworkTopologyEventImpl(_mmDNS, address);
_mmDNS.inetAddressAdded(event);
}
}
for (InetAddress address : _knownAddresses) {
if (!current.contains(address)) {
final NetworkTopologyEvent event = new NetworkTopologyEventImpl(_mmDNS, address);
_mmDNS.inetAddressRemoved(event);
}
}
_knownAddresses = current;
} catch (Exception e) {
logger.warn("Unexpected unhandled exception: ", e);
}
}
}
protected JmDNS createJmDnsInstance(InetAddress address) throws IOException
{
return JmDNS.create(address);
}
}