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