| /** |
| * |
| */ |
| package javax.jmdns.impl; |
| |
| import java.io.IOException; |
| import java.net.InetAddress; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| 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.Timer; |
| import java.util.TimerTask; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.TimeUnit; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| 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; |
| |
| /** |
| * This class enable multihomming 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 = Logger.getLogger(JmmDNSImpl.class.getName()); |
| |
| private final Set<NetworkTopologyListener> _networkListeners; |
| |
| /** |
| * Every JmDNS created. |
| */ |
| private final ConcurrentMap<InetAddress, JmDNS> _knownMDNS; |
| |
| /** |
| * This enable the service info text update. |
| */ |
| private final ConcurrentMap<String, ServiceInfo> _services; |
| |
| private final ExecutorService _ListenerExecutor; |
| |
| private final ExecutorService _jmDNSExecutor; |
| |
| private final Timer _timer; |
| |
| /** |
| * |
| */ |
| public JmmDNSImpl() { |
| super(); |
| _networkListeners = Collections.synchronizedSet(new HashSet<NetworkTopologyListener>()); |
| _knownMDNS = new ConcurrentHashMap<InetAddress, JmDNS>(); |
| _services = new ConcurrentHashMap<String, ServiceInfo>(20); |
| _ListenerExecutor = Executors.newSingleThreadExecutor(); |
| _jmDNSExecutor = Executors.newCachedThreadPool(); |
| _timer = new Timer("Multihommed mDNS.Timer", true); |
| (new NetworkChecker(this, NetworkTopologyDiscovery.Factory.getInstance())).start(_timer); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see java.io.Closeable#close() |
| */ |
| @Override |
| public void close() throws IOException { |
| if (logger.isLoggable(Level.FINER)) { |
| logger.finer("Cancelling JmmDNS: " + this); |
| } |
| _timer.cancel(); |
| _ListenerExecutor.shutdown(); |
| // We need to cancel all the DNS |
| ExecutorService executor = Executors.newCachedThreadPool(); |
| for (final JmDNS mDNS : _knownMDNS.values()) { |
| 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 |
| } |
| } |
| }); |
| } |
| executor.shutdown(); |
| try { |
| executor.awaitTermination(DNSConstants.CLOSE_TIMEOUT, TimeUnit.MILLISECONDS); |
| } catch (InterruptedException exception) { |
| logger.log(Level.WARNING, "Exception ", exception); |
| } |
| _knownMDNS.clear(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see javax.jmdns.JmmDNS#getNames() |
| */ |
| @Override |
| public String[] getNames() { |
| Set<String> result = new HashSet<String>(); |
| for (JmDNS mDNS : _knownMDNS.values()) { |
| result.add(mDNS.getName()); |
| } |
| return result.toArray(new String[result.size()]); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see javax.jmdns.JmmDNS#getHostNames() |
| */ |
| @Override |
| public String[] getHostNames() { |
| Set<String> result = new HashSet<String>(); |
| for (JmDNS mDNS : _knownMDNS.values()) { |
| 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<InetAddress> result = new HashSet<InetAddress>(); |
| for (JmDNS mDNS : _knownMDNS.values()) { |
| result.add(mDNS.getInetAddress()); |
| } |
| return result.toArray(new InetAddress[result.size()]); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see javax.jmdns.JmmDNS#getInterfaces() |
| */ |
| @Override |
| @Deprecated |
| public InetAddress[] getInterfaces() throws IOException { |
| Set<InetAddress> result = new HashSet<InetAddress>(); |
| for (JmDNS mDNS : _knownMDNS.values()) { |
| 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 Set<ServiceInfo> result = Collections.synchronizedSet(new HashSet<ServiceInfo>(_knownMDNS.size())); |
| ExecutorService executor = Executors.newCachedThreadPool(); |
| for (final JmDNS mDNS : _knownMDNS.values()) { |
| executor.submit(new Runnable() { |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void run() { |
| result.add(mDNS.getServiceInfo(type, name, persistent, timeout)); |
| } |
| }); |
| } |
| executor.shutdown(); |
| try { |
| executor.awaitTermination(timeout, TimeUnit.MILLISECONDS); |
| } catch (InterruptedException exception) { |
| logger.log(Level.WARNING, "Exception ", exception); |
| } |
| 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 : _knownMDNS.values()) { |
| _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 { |
| for (JmDNS mDNS : _knownMDNS.values()) { |
| mDNS.addServiceTypeListener(listener); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see javax.jmdns.JmmDNS#removeServiceTypeListener(javax.jmdns.ServiceTypeListener) |
| */ |
| @Override |
| public void removeServiceTypeListener(ServiceTypeListener listener) { |
| for (JmDNS mDNS : _knownMDNS.values()) { |
| mDNS.removeServiceTypeListener(listener); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see javax.jmdns.JmmDNS#addServiceListener(java.lang.String, javax.jmdns.ServiceListener) |
| */ |
| @Override |
| public void addServiceListener(String type, ServiceListener listener) { |
| for (JmDNS mDNS : _knownMDNS.values()) { |
| 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) { |
| for (JmDNS mDNS : _knownMDNS.values()) { |
| 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) { |
| synchronized (_services) { |
| for (JmDNS mDNS : _knownMDNS.values()) { |
| ServiceInfo info = ((JmDNSImpl) mDNS).getServices().get(target.getQualifiedName()); |
| if (info != null) { |
| info.setText(value); |
| } else { |
| logger.warning("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 { |
| // 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 : _knownMDNS.values()) { |
| 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) { |
| synchronized (_services) { |
| for (JmDNS mDNS : _knownMDNS.values()) { |
| mDNS.unregisterService(info); |
| } |
| ((ServiceInfoImpl) info).setDelegate(null); |
| _services.remove(info.getQualifiedName()); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see javax.jmdns.JmmDNS#unregisterAllServices() |
| */ |
| @Override |
| public void unregisterAllServices() { |
| synchronized (_services) { |
| for (JmDNS mDNS : _knownMDNS.values()) { |
| mDNS.unregisterAllServices(); |
| } |
| _services.clear(); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see javax.jmdns.JmmDNS#registerServiceType(java.lang.String) |
| */ |
| @Override |
| public void registerServiceType(String type) { |
| for (JmDNS mDNS : _knownMDNS.values()) { |
| 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) { |
| // We need to run this in parallel to respect the timeout. |
| final Set<ServiceInfo> result = Collections.synchronizedSet(new HashSet<ServiceInfo>(_knownMDNS.size() * 5)); |
| ExecutorService executor = Executors.newCachedThreadPool(); |
| for (final JmDNS mDNS : _knownMDNS.values()) { |
| executor.submit(new Runnable() { |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void run() { |
| result.addAll(Arrays.asList(mDNS.list(type, timeout))); |
| } |
| }); |
| } |
| executor.shutdown(); |
| try { |
| executor.awaitTermination(timeout, TimeUnit.MILLISECONDS); |
| } catch (InterruptedException exception) { |
| logger.log(Level.WARNING, "Exception ", exception); |
| } |
| return result.toArray(new ServiceInfo[result.size()]); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see javax.jmdns.JmmDNS#listBySubtype(java.lang.String) |
| */ |
| @Override |
| public Map<String, ServiceInfo[]> 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<String, ServiceInfo[]> listBySubtype(final String type, final long timeout) { |
| Map<String, List<ServiceInfo>> map = new HashMap<String, List<ServiceInfo>>(5); |
| for (ServiceInfo info : this.list(type, timeout)) { |
| String subtype = info.getSubtype(); |
| if (!map.containsKey(subtype)) { |
| map.put(subtype, new ArrayList<ServiceInfo>(10)); |
| } |
| map.get(subtype).add(info); |
| } |
| |
| Map<String, ServiceInfo[]> result = new HashMap<String, ServiceInfo[]>(map.size()); |
| for (String subtype : map.keySet()) { |
| List<ServiceInfo> infoForSubType = map.get(subtype); |
| 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 { |
| synchronized (this) { |
| if (!_knownMDNS.containsKey(address)) { |
| _knownMDNS.put(address, JmDNS.create(address)); |
| final NetworkTopologyEvent jmdnsEvent = new NetworkTopologyEventImpl(_knownMDNS.get(address), address); |
| for (final NetworkTopologyListener listener : this.networkListeners()) { |
| _ListenerExecutor.submit(new Runnable() { |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void run() { |
| listener.inetAddressAdded(jmdnsEvent); |
| } |
| }); |
| } |
| } |
| } |
| } catch (Exception e) { |
| logger.warning("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 { |
| synchronized (this) { |
| 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.warning("Unexpected unhandled exception: " + e); |
| } |
| } |
| |
| /** |
| * Checks the network state.<br/> |
| * If the network change, this class will reconfigure the list of DNS do adapt to the new configuration. |
| */ |
| static class NetworkChecker extends TimerTask { |
| private static Logger logger1 = Logger.getLogger(NetworkChecker.class.getName()); |
| |
| private final NetworkTopologyListener _mmDNS; |
| |
| private final NetworkTopologyDiscovery _topology; |
| |
| private Set<InetAddress> _knownAddresses; |
| |
| public NetworkChecker(NetworkTopologyListener mmDNS, NetworkTopologyDiscovery topology) { |
| super(); |
| this._mmDNS = mmDNS; |
| this._topology = topology; |
| _knownAddresses = Collections.synchronizedSet(new HashSet<InetAddress>()); |
| } |
| |
| public void start(Timer timer) { |
| timer.schedule(this, 0, DNSConstants.NETWORK_CHECK_INTERVAL); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void run() { |
| try { |
| InetAddress[] curentAddresses = _topology.getInetAddresses(); |
| Set<InetAddress> current = new HashSet<InetAddress>(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) { |
| logger1.warning("Unexpected unhandled exception: " + e); |
| } |
| } |
| |
| } |
| |
| } |