001package jmri.util.zeroconf;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.List;
006import javax.annotation.Nonnull;
007import javax.jmdns.JmDNS;
008import javax.jmdns.NetworkTopologyEvent;
009import javax.jmdns.NetworkTopologyListener;
010import javax.jmdns.ServiceEvent;
011import javax.jmdns.ServiceInfo;
012import javax.jmdns.ServiceListener;
013import javax.jmdns.impl.constants.DNSConstants;
014import jmri.InstanceManager;
015import org.slf4j.Logger;
016import org.slf4j.LoggerFactory;
017
018public class ZeroConfClient {
019
020    private ServiceListener mdnsServiceListener = null;
021    private final static Logger log = LoggerFactory.getLogger(ZeroConfClient.class);
022    private long timeout = DNSConstants.SERVICE_INFO_TIMEOUT;
023
024    // mdns related routines.
025    public void startServiceListener(@Nonnull String service) {
026        log.debug("StartServiceListener called for service: {}", service);
027        if (mdnsServiceListener == null) {
028            mdnsServiceListener = new NetworkServiceListener(service, this);
029        }
030        for (JmDNS server : InstanceManager.getDefault(ZeroConfServiceManager.class).getDNSes().values()) {
031            server.addServiceListener(service, mdnsServiceListener);
032        }
033    }
034
035    public void stopServiceListener(@Nonnull String service) {
036        for (JmDNS server : InstanceManager.getDefault(ZeroConfServiceManager.class).getDNSes().values()) {
037            server.removeServiceListener(service, mdnsServiceListener);
038        }
039    }
040
041    /**
042     * Request the first service of a particular service.
043     *
044     * @param service string service getName
045     * @return JmDNS service entry for the first service of a particular
046     *         service.
047     */
048    public ServiceInfo getService(@Nonnull String service) {
049        for (JmDNS server : InstanceManager.getDefault(ZeroConfServiceManager.class).getDNSes().values()) {
050            ServiceInfo[] infos = server.list(service, timeout);
051            if (infos != null) {
052                return infos[0];
053            }
054        }
055        return null;
056    }
057
058    /**
059     * Get all servers providing the specified service.
060     *
061     * @param service the name of service as generated using
062     *                {@link jmri.util.zeroconf.ZeroConfServiceManager#key(java.lang.String, java.lang.String) }
063     * @return A list of servers or an empty list.
064     */
065    @Nonnull
066    public List<ServiceInfo> getServices(@Nonnull String service) {
067        ArrayList<ServiceInfo> services = new ArrayList<>();
068        for (JmDNS server : InstanceManager.getDefault(ZeroConfServiceManager.class).getDNSes().values()) {
069            ServiceInfo[] infos = server.list(service, timeout);
070            if (infos != null) {
071                services.addAll(Arrays.asList(infos));
072            }
073        }
074        return services;
075    }
076
077    /**
078     * Request the first service of a particular service on a specified host.
079     *
080     * @param service  string service service
081     * @param hostname string host name
082     * @return JmDNS service entry for the first service of a particular service
083     *         on the specified host..
084     */
085    public ServiceInfo getServiceOnHost(@Nonnull String service, @Nonnull String hostname) {
086        for (JmDNS server : InstanceManager.getDefault(ZeroConfServiceManager.class).getDNSes().values()) {
087            ServiceInfo[] infos = server.list(service, timeout);
088            for (ServiceInfo info : infos) {
089                if (info.getServer().equals(hostname)) {
090                    return info;
091                }
092            }
093        }
094        return null;
095    }
096
097    /**
098     * Request the first service of a particular service with a particular
099     * service name.
100     *
101     * @param service string service service
102     * @param adName  string qualified service advertisement name
103     * @return JmDNS service entry for the first service of a particular service
104     *         on the specified host..
105     */
106    public ServiceInfo getServicebyAdName(@Nonnull String service, @Nonnull String adName) {
107        for (JmDNS server : InstanceManager.getDefault(ZeroConfServiceManager.class).getDNSes().values()) {
108            ServiceInfo[] infos = server.list(service, timeout);
109            for (ServiceInfo info : infos) {
110                log.debug("Found Name: {}", info.getQualifiedName());
111                if (info.getQualifiedName().equals(adName)) {
112                    return info;
113                }
114            }
115        }
116        return null;
117    }
118
119    @Nonnull
120    public String[] getHostList(@Nonnull String service) {
121        ArrayList<String> hostlist = new ArrayList<>();
122        for (JmDNS server : InstanceManager.getDefault(ZeroConfServiceManager.class).getDNSes().values()) {
123            ServiceInfo[] infos = server.list(service, timeout);
124            for (ServiceInfo info : infos) {
125                hostlist.add(info.getServer());
126            }
127        }
128        return hostlist.toArray(new String[hostlist.size()]);
129    }
130    
131    /**
132     * Get current timeout
133     * 
134     * @return timeout as long in milliseconds
135     */
136    public long getTimeout() {
137        return timeout;
138    }
139    
140    /**
141     * Set timeout
142     * 
143     * @param timeout in milliseconds
144     */
145    public void setTimeout(long timeout) {
146        this.timeout = timeout;
147    }
148
149    public static class NetworkServiceListener implements ServiceListener, NetworkTopologyListener {
150
151        private final String service;
152        private final ZeroConfClient client;
153
154        protected NetworkServiceListener(String service, ZeroConfClient client) {
155            this.service = service;
156            this.client = client;
157        }
158
159        @Override
160        public void inetAddressAdded(NetworkTopologyEvent nte) {
161            nte.getDNS().addServiceListener(service, this);
162        }
163
164        @Override
165        public void inetAddressRemoved(NetworkTopologyEvent nte) {
166            nte.getDNS().removeServiceListener(service, this);
167        }
168
169        @Override
170        public void serviceAdded(ServiceEvent se) {
171            log.debug("Service added: {}", se.getInfo().toString());
172            // notify the client when a service is added.
173            synchronized (client) {
174                client.notifyAll();
175            }
176        }
177
178        @Override
179        public void serviceRemoved(ServiceEvent se) {
180            log.debug("Service removed: {}", se.getInfo().toString());
181        }
182
183        @Override
184        public void serviceResolved(ServiceEvent se) {
185            log.debug("Service resolved: {}", se.getInfo().toString());
186        }
187
188    }
189}