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