001package jmri.util.zeroconf; 002 003import java.net.InetAddress; 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.HashMap; 007import java.util.List; 008import javax.jmdns.JmDNS; 009import javax.jmdns.ServiceInfo; 010import jmri.InstanceManager; 011 012/** 013 * ZeroConfService objects manage a zeroConf network service advertisement. 014 * <p> 015 * ZeroConfService objects encapsulate zeroConf network services created using 016 * JmDNS, providing methods to start and stop service advertisements and to 017 * query service state. Typical usage would be: 018 * <pre> 019 * ZeroConfService myService = ZeroConfService.create("_withrottle._tcp.local.", port); 020 * myService.publish(); 021 * </pre> or, if you do not wish to retain the ZeroConfService object: 022 * <pre> 023 * ZeroConfService.create("_http._tcp.local.", port).publish(); 024 * </pre> ZeroConfService objects can also be created with a HashMap of 025 * properties that are included in the TXT record for the service advertisement. 026 * This HashMap should remain small, but it could include information such as 027 * the default path (for a web server), a specific protocol version, or other 028 * information. Note that all service advertisements include the JMRI version, 029 * using the key "version", and the JMRI version numbers in a string 030 * "major.minor.test" with the key "jmri" 031 * <p> 032 * All ZeroConfServices are published with the computer's hostname as the mDNS 033 * hostname (unless it cannot be determined by JMRI), as well as the JMRI node 034 * name in the TXT record with the key "node". 035 * <p> 036 * All ZeroConfServices are automatically stopped when the JMRI application 037 * shuts down. Use {@link ZeroConfServiceManager#allServices()} to get a 038 * collection of all published ZeroConfService objects. 039 * <hr> 040 * This file is part of JMRI. 041 * <p> 042 * JMRI is free software; you can redistribute it and/or modify it under the 043 * terms of version 2 of the GNU General Public License as published by the Free 044 * Software Foundation. See the "COPYING" file for a copy of this license. 045 * <p> 046 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 047 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 048 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 049 * 050 * @author Randall Wood Copyright (C) 2011, 2013, 2018 051 * @see javax.jmdns.JmDNS 052 * @see javax.jmdns.ServiceInfo 053 */ 054public class ZeroConfService { 055 056 // internal data members 057 private final HashMap<InetAddress, ServiceInfo> serviceInfos = new HashMap<>(); 058 private ServiceInfo serviceInfo = null; 059 // static data objects 060 private final List<ZeroConfServiceListener> listeners = new ArrayList<>(); 061 // API constants 062 public static final String IPv4 = "IPv4"; 063 public static final String IPv6 = "IPv6"; 064 public static final String LOOPBACK = "loopback"; 065 public static final String LINKLOCAL = "linklocal"; 066 067 /** 068 * Create a ZeroConfService with the minimal required settings. This method 069 * calls {@link #create(java.lang.String, int, java.util.HashMap)} with an 070 * empty props HashMap. 071 * 072 * @param type The service protocol 073 * @param port The port the service runs over 074 * @return A new unpublished ZeroConfService, or an existing service 075 * @see #create(java.lang.String, java.lang.String, int, int, int, 076 * java.util.HashMap) 077 */ 078 public static ZeroConfService create(String type, int port) { 079 return InstanceManager.getDefault(ZeroConfServiceManager.class).create(type, port); 080 } 081 082 /** 083 * Create a ZeroConfService with an automatically detected server name. This 084 * method calls 085 * {@link #create(java.lang.String, java.lang.String, int, int, int, java.util.HashMap)} 086 * with the default weight and priority, and with the result of 087 * {@link jmri.web.server.WebServerPreferences#getRailroadName()} 088 * reformatted to replace dots and dashes with spaces. 089 * 090 * @param type The service protocol 091 * @param port The port the service runs over 092 * @param properties Additional information to be listed in service 093 * advertisement 094 * @return A new unpublished ZeroConfService, or an existing service 095 */ 096 public static ZeroConfService create(String type, int port, HashMap<String, String> properties) { 097 return InstanceManager.getDefault(ZeroConfServiceManager.class).create(type, port, properties); 098 } 099 100 /** 101 * Create a ZeroConfService. The property <i>version</i> is added or 102 * replaced with the current JMRI version as its value. The property 103 * <i>jmri</i> is added or replaced with the JMRI major.minor.test version 104 * string as its value. 105 * <p> 106 * If a service with the same key as the new service is already published, 107 * the original service is returned unmodified. 108 * 109 * @param type The service protocol 110 * @param name The name of the JMRI server listed on client devices 111 * @param port The port the service runs over 112 * @param weight Default value is 0 113 * @param priority Default value is 0 114 * @param properties Additional information to be listed in service 115 * advertisement 116 * @return A new unpublished ZeroConfService, or an existing service 117 */ 118 public static ZeroConfService create(String type, String name, int port, int weight, int priority, HashMap<String, String> properties) { 119 return InstanceManager.getDefault(ZeroConfServiceManager.class).create(type, name, port, weight, priority, properties); 120 } 121 122 /** 123 * Create a ZeroConfService object. 124 * 125 * @param service the JmDNS service information 126 */ 127 protected ZeroConfService(ServiceInfo service) { 128 this.serviceInfo = service; 129 } 130 131 /** 132 * Get the key of the ZeroConfService object. The key is fully qualified 133 * name of the service in all lowercase, for example 134 * {@code jmri._http.local }. 135 * 136 * @return The fully qualified name of the service 137 */ 138 public String getKey() { 139 return this.getServiceInfo().getKey(); 140 } 141 142 /** 143 * Get the name of the ZeroConfService object. The name can only be set when 144 * creating the object. 145 * 146 * @return The service name as reported by the 147 * {@link javax.jmdns.ServiceInfo} object 148 */ 149 public String getName() { 150 return this.getServiceInfo().getName(); 151 } 152 153 /** 154 * Get the type of the ZeroConfService object. The type can only be set when 155 * creating the object. 156 * 157 * @return The service type as reported by the 158 * {@link javax.jmdns.ServiceInfo} object 159 */ 160 public String getType() { 161 return this.getServiceInfo().getType(); 162 } 163 164 /** 165 * Get the ServiceInfo for the given address. Package private so can be 166 * managed by {@link jmri.util.zeroconf.ZeroConfServiceManager}, but not in 167 * public API. 168 * 169 * @param address the address associated with the ServiceInfo to get 170 * @return the ServiceInfo for the address or null if none exists 171 */ 172 ServiceInfo getServiceInfo(InetAddress address) { 173 return serviceInfos.get(address); 174 } 175 176 /** 177 * Add the ServiceInfo for the given address. Package private so can be 178 * managed by {@link jmri.util.zeroconf.ZeroConfServiceManager}, but not in 179 * public API. 180 * 181 * @param address the address associated with the ServiceInfo to add 182 * @return the added ServiceInfo for the address 183 */ 184 ServiceInfo addServiceInfo(InetAddress address) { 185 if (!this.serviceInfos.containsKey(address)) { 186 this.serviceInfos.put(address, this.getServiceInfo().clone()); 187 } 188 return this.serviceInfos.get(address); 189 } 190 191 /** 192 * Remove the ServiceInfo for the given address. Package private so can be 193 * managed by {@link jmri.util.zeroconf.ZeroConfServiceManager}, but not in 194 * public API. 195 * 196 * @param address the address associated with the ServiceInfo to remove 197 */ 198 void removeServiceInfo(InetAddress address) { 199 serviceInfos.remove(address); 200 } 201 202 /** 203 * Check if a ServiceInfo exists for the given address. Package private so 204 * can be managed by {@link jmri.util.zeroconf.ZeroConfServiceManager}, but 205 * not in public API. 206 * 207 * @param key the address associated with the ServiceInfo to check for 208 * @return true if the ServiceInfo exists; false otherwise 209 */ 210 boolean containsServiceInfo(InetAddress key) { 211 return serviceInfos.containsKey(key); 212 } 213 214 /** 215 * Get the reference ServiceInfo for the object. This is the JmDNS 216 * implementation of a zeroConf service. The reference ServiceInfo is never 217 * actually registered with a JmDNS service, since registrations with a 218 * JmDNS service are unique per InetAddress. 219 * 220 * @return The getServiceInfo object. 221 */ 222 public ServiceInfo getServiceInfo() { 223 return this.serviceInfo; 224 } 225 226 /** 227 * Get the state of the service. 228 * 229 * @return True if the service is being advertised, and false otherwise. 230 */ 231 public boolean isPublished() { 232 return InstanceManager.getDefault(ZeroConfServiceManager.class).isPublished(this); 233 } 234 235 /** 236 * Start advertising the service. 237 */ 238 public void publish() { 239 InstanceManager.getDefault(ZeroConfServiceManager.class).publish(this); 240 } 241 242 /** 243 * Stop advertising the service. 244 */ 245 public void stop() { 246 InstanceManager.getDefault(ZeroConfServiceManager.class).stop(this); 247 } 248 249 public void addEventListener(ZeroConfServiceListener l) { 250 this.listeners.add(l); 251 } 252 253 public void removeEventListener(ZeroConfServiceListener l) { 254 this.listeners.remove(l); 255 } 256 257 /** 258 * Get a list of the listeners for this service. 259 * @return the listeners or an empty list if none 260 */ 261 public List<ZeroConfServiceListener> getListeners() { 262 return new ArrayList<>(listeners); 263 } 264}