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}