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