001package jmri.jmrix; 002 003import java.io.DataInputStream; 004import java.io.DataOutputStream; 005import java.io.IOException; 006import java.net.Socket; 007 008import jmri.SystemConnectionMemo; 009 010/** 011 * Enables basic setup of a network client interface for a jmrix implementation. 012 * 013 * @author Kevin Dickerson Copyright (C) 2010 014 * @author Based upon work originally done by Paul Bender Copyright (C) 2009 015 * @see jmri.jmrix.NetworkConfigException 016 */ 017abstract public class AbstractNetworkPortController extends AbstractPortController implements NetworkPortAdapter { 018 019 // the host name and port number identify what we are talking to. 020 protected String m_HostName = null; 021 private String m_HostAddress = null; // Internal IP address for ZeroConf 022 // configured clients. 023 protected int m_port = 0; 024 // keep the socket provides our connection. 025 protected Socket socketConn = null; 026 protected int connTimeout = 0; // connection timeout for read operations. 027 // Default is 0, an infinite timeout. 028 029 protected AbstractNetworkPortController(SystemConnectionMemo connectionMemo) { 030 super(connectionMemo); 031 setHostName(""); // give the host name a default value of the empty string. 032 } 033 034 @Override 035 public void connect(String host, int port) throws IOException { 036 setHostName(host); 037 setPort(port); 038 connect(); 039 } 040 041 @Override 042 public void connect() throws IOException { 043 log.debug("connect() starts to {}:{}", getHostName(), getPort()); 044 opened = false; 045 if (getHostAddress() == null || m_port == 0) { 046 log.error("No host name or port set: {}:{}", m_HostName, m_port); 047 return; 048 } 049 try { 050 var address = getHostAddress(); 051 log.info("Attempting to open connecton to {}:{}", address, m_port); 052 socketConn = new Socket(address, m_port); 053 socketConn.setKeepAlive(true); 054 socketConn.setSoTimeout(getConnectionTimeout()); 055 opened = true; 056 } catch (IOException e) { 057 log.error("Error opening network connection to {} because {}", getHostName(), e.getMessage()); // nothing to help user in full exception 058 if (m_port != 0) { 059 ConnectionStatus.instance().setConnectionState( 060 getUserName(), m_HostName + ":" + m_port, ConnectionStatus.CONNECTION_DOWN); 061 } else { 062 ConnectionStatus.instance().setConnectionState( 063 getUserName(), m_HostName, ConnectionStatus.CONNECTION_DOWN); 064 } 065 throw (e); 066 } 067 if (opened && m_port != 0) { 068 ConnectionStatus.instance().setConnectionState( 069 getUserName(), m_HostName + ":" + m_port, ConnectionStatus.CONNECTION_UP); 070 } else if (opened) { 071 ConnectionStatus.instance().setConnectionState( 072 getUserName(), m_HostName, ConnectionStatus.CONNECTION_UP); 073 } 074 log.trace("connect ends"); 075 } 076 077 /** 078 * Remember the associated host name. 079 * 080 * @param s the host name; if empty will use MDNS to get host name 081 */ 082 @Override 083 public void setHostName(String s) { 084 log.trace("setHostName({})", s, new Exception("traceback only")); 085 m_HostName = s; 086 if ((s == null || s.equals("")) && !getMdnsConfigure()) { 087 m_HostName = JmrixConfigPane.NONE; 088 } 089 } 090 091 @Override 092 public String getHostName() { 093 final String envVar = "JMRI_HOSTNAME"; 094 String fromEnv = System.getenv(envVar); 095 log.debug("Environment {} {} was {}", envVar, fromEnv, m_HostName); 096 String fromProp = System.getProperty(envVar); 097 log.debug("Property {} {} was {}", envVar, fromProp, m_HostName); 098 if (fromEnv != null ) { 099 jmri.util.LoggingUtil.infoOnce(log,"{} set, using environment \"{}\" as Host Name", envVar, fromEnv); 100 return fromEnv; 101 } 102 if (fromProp != null ) { 103 jmri.util.LoggingUtil.infoOnce(log,"{} set, using property \"{}\" as Host Name", envVar, fromProp); 104 return fromProp; 105 } 106 return m_HostName; 107 } 108 109 /** 110 * Remember the associated IP Address This is used internally for mDNS 111 * configuration. Public access to the IP address is through the hostname 112 * field. 113 * 114 * @param s the address; if empty, will use the host name 115 */ 116 protected void setHostAddress(String s) { 117 log.trace("setHostAddress({})", s); 118 m_HostAddress = s; 119 if (s == null || s.equals("")) { 120 m_HostAddress = m_HostName; 121 } 122 } 123 124 protected String getHostAddress() { 125 if (m_HostAddress == null) { 126 return getHostName(); 127 } 128 log.info("getHostAddress is {}", m_HostAddress); 129 return m_HostAddress; 130 } 131 132 /** 133 * Remember the associated port number. 134 * 135 * @param p the port 136 */ 137 @Override 138 public void setPort(int p) { 139 log.trace("setPort(int {})", p); 140 m_port = p; 141 } 142 143 @Override 144 public void setPort(String p) { 145 log.trace("setPort(String {})", p); 146 m_port = Integer.parseInt(p); 147 } 148 149 @Override 150 public int getPort() { 151 return m_port; 152 } 153 154 /** 155 * Return the connection name for the network connection in the format of 156 * ip_address:port 157 * 158 * @return ip_address:port 159 */ 160 @Override 161 public String getCurrentPortName() { 162 String t; 163 if (getMdnsConfigure()) { 164 t = getHostAddress(); 165 } else { 166 t = getHostName(); 167 } 168 int p = getPort(); 169 if (t != null && !t.equals("")) { 170 if (p != 0) { 171 return t + ":" + p; 172 } 173 return t; 174 } else { 175 return JmrixConfigPane.NONE; 176 } 177 } 178 179 /* 180 * Set whether or not this adapter should be 181 * configured automatically via MDNS. 182 * Note: Default implementation ignores the parameter. 183 * 184 * @param autoconfig boolean value 185 */ 186 @Override 187 public void setMdnsConfigure(boolean autoconfig) { 188 } 189 190 /* 191 * Get whether or not this adapter is configured 192 * to use autoconfiguration via MDNS 193 * Default implemntation always returns false. 194 * 195 * @return true if configured using MDNS 196 */ 197 @Override 198 public boolean getMdnsConfigure() { 199 return false; 200 } 201 202 /* 203 * Set the server's host name and port 204 * using MDNS autoconfiguration. 205 * Default implementation does nothing. 206 */ 207 @Override 208 public void autoConfigure() { 209 } 210 211 /* 212 * Get and set the ZeroConf/mDNS advertisement name. 213 * Default implementation does nothing. 214 */ 215 @Override 216 public void setAdvertisementName(String AdName) { 217 } 218 219 @Override 220 public String getAdvertisementName() { 221 return null; 222 } 223 224 /* 225 * Get and set the ZeroConf/mDNS service type. 226 * Default implementation does nothing. 227 */ 228 @Override 229 public void setServiceType(String ServiceType) { 230 } 231 232 @Override 233 public String getServiceType() { 234 return null; 235 } 236 237 /** 238 * {@inheritDoc} 239 */ 240 @Override 241 public DataInputStream getInputStream() { 242 log.trace("getInputStream() starts"); 243 if (socketConn == null) { 244 log.error("getInputStream invoked with null socketConn"); 245 } 246 if (!opened) { 247 log.error("getInputStream called before load(), stream not available"); 248 if (m_port != 0) { 249 ConnectionStatus.instance().setConnectionState( 250 getUserName(), m_HostName + ":" + m_port, ConnectionStatus.CONNECTION_DOWN); 251 } else { 252 ConnectionStatus.instance().setConnectionState( 253 getUserName(), m_HostName, ConnectionStatus.CONNECTION_DOWN); 254 } 255 } 256 try { 257 log.trace("getInputStream() returns normally"); 258 return new DataInputStream(socketConn.getInputStream()); 259 } catch (java.io.IOException ex1) { 260 log.error("Exception getting input stream:", ex1); 261 return null; 262 } 263 } 264 265 /** 266 * {@inheritDoc} 267 */ 268 @Override 269 public DataOutputStream getOutputStream() { 270 if (!opened) { 271 log.error("getOutputStream called before load(), stream not available"); 272 } 273 try { 274 return new DataOutputStream(socketConn.getOutputStream()); 275 } catch (java.io.IOException e) { 276 log.error("getOutputStream exception:", e); 277 if (m_port != 0) { 278 ConnectionStatus.instance().setConnectionState( 279 getUserName(), m_HostName + ":" + m_port, ConnectionStatus.CONNECTION_DOWN); 280 } else { 281 ConnectionStatus.instance().setConnectionState( 282 getUserName(), m_HostName, ConnectionStatus.CONNECTION_DOWN); 283 } 284 } 285 return null; 286 } 287 288 /** 289 * {@inheritDoc} 290 */ 291 @Override 292 protected void closeConnection(){ 293 try { 294 socketConn.close(); 295 } catch (IOException e) { 296 log.trace("Unable to close socket", e); 297 } 298 opened=false; 299 } 300 301 /** 302 * Customizable method to deal with resetting a system connection after a 303 * successful recovery of a connection. 304 */ 305 @Override 306 protected void resetupConnection() { 307 } 308 309 /** 310 * {@inheritDoc} 311 */ 312 @Override 313 protected void reconnectFromLoop(int retryNum){ 314 try { 315 // if the device allows autoConfiguration, 316 // we need to run the autoConfigure() call 317 // before we try to reconnect. 318 if (getMdnsConfigure()) { 319 autoConfigure(); 320 } 321 connect(); 322 } catch (IOException ex) { 323 log.trace("restart failed", ex); // main warning to log.error done within connect(); 324 // if returned on exception stops thread and connection attempts 325 } 326 } 327 328 /* 329 * Set the connection timeout to the specified value. 330 * If the socket is not null, set the SO_TIMEOUT option on the 331 * socket as well. 332 * 333 * @param t timeout value in milliseconds 334 */ 335 protected void setConnectionTimeout(int t) { 336 connTimeout = t; 337 try { 338 if (socketConn != null) { 339 socketConn.setSoTimeout(getConnectionTimeout()); 340 } 341 } catch (java.net.SocketException se) { 342 log.debug("Unable to set socket timeout option on socket"); 343 } 344 } 345 346 /* 347 * Get the connection timeout value. 348 * 349 * @return timeout value in milliseconds 350 */ 351 protected int getConnectionTimeout() { 352 return connTimeout; 353 } 354 355 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractNetworkPortController.class); 356 357}