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 connection 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 ConnectionStatus.instance().setConnectionState( 059 getSystemConnectionMemo(), ConnectionStatus.CONNECTION_DOWN); 060 throw (e); 061 } 062 if (opened) { 063 ConnectionStatus.instance().setConnectionState( 064 getSystemConnectionMemo(), ConnectionStatus.CONNECTION_UP); 065 } 066 log.trace("connect ends"); 067 } 068 069 /** 070 * Remember the associated host name. 071 * 072 * @param s the host name; if empty will use MDNS to get host name 073 */ 074 @Override 075 public void setHostName(String s) { 076 log.trace("setHostName({})", s, new Exception("traceback only")); 077 m_HostName = s; 078 if ((s == null || s.equals("")) && !getMdnsConfigure()) { 079 m_HostName = JmrixConfigPane.NONE; 080 } 081 } 082 083 @Override 084 public String getHostName() { 085 final String envVar = "JMRI_HOSTNAME"; 086 String fromEnv = System.getenv(envVar); 087 log.debug("Environment {} {} was {}", envVar, fromEnv, m_HostName); 088 String fromProp = System.getProperty(envVar); 089 log.debug("Property {} {} was {}", envVar, fromProp, m_HostName); 090 if (fromEnv != null ) { 091 jmri.util.LoggingUtil.infoOnce(log,"{} set, using environment \"{}\" as Host Name", envVar, fromEnv); 092 return fromEnv; 093 } 094 if (fromProp != null ) { 095 jmri.util.LoggingUtil.infoOnce(log,"{} set, using property \"{}\" as Host Name", envVar, fromProp); 096 return fromProp; 097 } 098 return m_HostName; 099 } 100 101 /** 102 * Remember the associated IP Address This is used internally for mDNS 103 * configuration. Public access to the IP address is through the hostname 104 * field. 105 * 106 * @param s the address; if empty, will use the host name 107 */ 108 protected void setHostAddress(String s) { 109 log.trace("setHostAddress({})", s); 110 m_HostAddress = s; 111 if (s == null || s.equals("")) { 112 m_HostAddress = m_HostName; 113 } 114 } 115 116 protected String getHostAddress() { 117 if (m_HostAddress == null) { 118 return getHostName(); 119 } 120 log.info("getHostAddress is {}", m_HostAddress); 121 return m_HostAddress; 122 } 123 124 /** 125 * Remember the associated port number. 126 * 127 * @param p the port 128 */ 129 @Override 130 public void setPort(int p) { 131 log.trace("setPort(int {})", p); 132 m_port = p; 133 } 134 135 @Override 136 public void setPort(String p) { 137 log.trace("setPort(String {})", p); 138 m_port = Integer.parseInt(p); 139 } 140 141 @Override 142 public int getPort() { 143 return m_port; 144 } 145 146 /** 147 * Return the connection name for the network connection in the format of 148 * ip_address:port 149 * 150 * @return ip_address:port 151 */ 152 @Override 153 public String getCurrentPortName() { 154 String t; 155 if (getMdnsConfigure()) { 156 t = getHostAddress(); 157 } else { 158 t = getHostName(); 159 } 160 int p = getPort(); 161 if (t != null && !t.equals("")) { 162 if (p != 0) { 163 return t + ":" + p; 164 } 165 return t; 166 } else { 167 return JmrixConfigPane.NONE; 168 } 169 } 170 171 /* 172 * Set whether or not this adapter should be 173 * configured automatically via MDNS. 174 * Note: Default implementation ignores the parameter. 175 * 176 * @param autoconfig boolean value 177 */ 178 @Override 179 public void setMdnsConfigure(boolean autoconfig) { 180 } 181 182 /* 183 * Get whether or not this adapter is configured 184 * to use autoconfiguration via MDNS 185 * Default implemntation always returns false. 186 * 187 * @return true if configured using MDNS 188 */ 189 @Override 190 public boolean getMdnsConfigure() { 191 return false; 192 } 193 194 /* 195 * Set the server's host name and port 196 * using MDNS autoconfiguration. 197 * Default implementation does nothing. 198 */ 199 @Override 200 public void autoConfigure() { 201 } 202 203 /* 204 * Get and set the ZeroConf/mDNS advertisement name. 205 * Default implementation does nothing. 206 */ 207 @Override 208 public void setAdvertisementName(String AdName) { 209 } 210 211 @Override 212 public String getAdvertisementName() { 213 return null; 214 } 215 216 /* 217 * Get and set the ZeroConf/mDNS service type. 218 * Default implementation does nothing. 219 */ 220 @Override 221 public void setServiceType(String ServiceType) { 222 } 223 224 @Override 225 public String getServiceType() { 226 return null; 227 } 228 229 /** 230 * {@inheritDoc} 231 */ 232 @Override 233 public DataInputStream getInputStream() { 234 log.trace("getInputStream() starts"); 235 if (socketConn == null) { 236 log.error("getInputStream invoked with null socketConn"); 237 } 238 if (!opened) { 239 log.error("getInputStream called before load(), stream not available"); 240 ConnectionStatus.instance().setConnectionState( 241 getSystemConnectionMemo(), ConnectionStatus.CONNECTION_DOWN); 242 } 243 try { 244 log.trace("getInputStream() returns normally"); 245 return new DataInputStream(socketConn.getInputStream()); 246 } catch (java.io.IOException ex1) { 247 log.error("Exception getting input stream:", ex1); 248 return null; 249 } 250 } 251 252 /** 253 * {@inheritDoc} 254 */ 255 @Override 256 public DataOutputStream getOutputStream() { 257 if (!opened) { 258 log.error("getOutputStream called before load(), stream not available"); 259 } 260 try { 261 return new DataOutputStream(socketConn.getOutputStream()); 262 } catch (java.io.IOException e) { 263 log.error("getOutputStream exception:", e); 264 ConnectionStatus.instance().setConnectionState( 265 getSystemConnectionMemo(), ConnectionStatus.CONNECTION_DOWN); 266 } 267 return null; 268 } 269 270 /** 271 * {@inheritDoc} 272 */ 273 @Override 274 protected void closeConnection(){ 275 try { 276 socketConn.close(); 277 } catch (IOException e) { 278 log.trace("Unable to close socket", e); 279 } 280 opened=false; 281 } 282 283 /** 284 * Customizable method to deal with resetting a system connection after a 285 * successful recovery of a connection. 286 */ 287 @Override 288 protected void resetupConnection() { 289 } 290 291 /** 292 * {@inheritDoc} 293 */ 294 @Override 295 protected void reconnectFromLoop(int retryNum){ 296 try { 297 // if the device allows autoConfiguration, 298 // we need to run the autoConfigure() call 299 // before we try to reconnect. 300 if (getMdnsConfigure()) { 301 autoConfigure(); 302 } 303 connect(); 304 } catch (IOException ex) { 305 log.trace("restart failed", ex); // main warning to log.error done within connect(); 306 // if returned on exception stops thread and connection attempts 307 } 308 } 309 310 /* 311 * Set the connection timeout to the specified value. 312 * If the socket is not null, set the SO_TIMEOUT option on the 313 * socket as well. 314 * 315 * @param t timeout value in milliseconds 316 */ 317 protected void setConnectionTimeout(int t) { 318 connTimeout = t; 319 try { 320 if (socketConn != null) { 321 socketConn.setSoTimeout(getConnectionTimeout()); 322 } 323 } catch (java.net.SocketException se) { 324 log.debug("Unable to set socket timeout option on socket"); 325 } 326 } 327 328 /* 329 * Get the connection timeout value. 330 * 331 * @return timeout value in milliseconds 332 */ 333 protected int getConnectionTimeout() { 334 return connTimeout; 335 } 336 337 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractNetworkPortController.class); 338 339}