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