001package jmri.jmris;
002
003import java.io.DataOutputStream;
004import java.io.IOException;
005import java.util.Locale;
006
007import org.eclipse.jetty.websocket.api.RemoteEndpoint;
008import org.eclipse.jetty.websocket.api.Session;
009import org.eclipse.jetty.websocket.api.WebSocketException;
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013/**
014 * Abstraction of DataOutputStream and WebSocket.Connection classes.
015 * <p>
016 * Used so that that server objects need only to use a single object/method to
017 * send data to any supported object type.
018 *
019 * @author Randall Wood Copyright (C) 2012, 2014
020 */
021public class JmriConnection {
022
023    private final Session session;
024    private final DataOutputStream dataOutputStream;
025    private Locale locale = Locale.getDefault();
026    private static final Logger log = LoggerFactory.getLogger(JmriConnection.class);
027    private static final String EX_SENDING_MSG = "Exception sending message";
028
029    /**
030     * Create a JmriConnection that sends output to a WebSocket.
031     *
032     * @param connection WebSocket Session to use.
033     */
034    public JmriConnection(Session connection) {
035        this.session = connection;
036        this.dataOutputStream = null;
037    }
038
039    /**
040     * Create a JmriConnection that sends output to a DataOutputStream.
041     *
042     * @param output DataOutputStream to use
043     */
044    public JmriConnection(DataOutputStream output) {
045        this.dataOutputStream = output;
046        this.session = null;
047    }
048
049    /**
050     * Get the WebSocket session.
051     *
052     * @return the WebSocket session
053     */
054    public Session getSession() {
055        return this.session;
056    }
057
058    public DataOutputStream getDataOutputStream() {
059        return dataOutputStream;
060    }
061
062    /**
063     * Send a String to the instantiated connection.
064     * <p>
065     * This method throws an IOException so the server or servlet holding the
066     * connection open can respond to the exception if there is an immediate
067     * failure. If there is an asynchronous failure, the connection is closed.
068     *
069     * @param message message to send
070     * @throws IOException if problem sending message
071     */
072    public void sendMessage(String message) throws IOException {
073        log.trace("Sending \"{}\"", message);
074        if (this.dataOutputStream != null) {
075            this.dataOutputStream.writeBytes(message);
076        } else if (this.session.isOpen()) {
077            try {
078                RemoteEndpoint remote = this.session.getRemote();
079                // The JSON sockets keep an internal state variable and throw an
080                // IllegalStateException if more than one thread attempts to do
081                // sendString at the same time. This function gets normally
082                // called from a mixture of the Layout thread and the
083                // WebServer-NN threads.
084                synchronized (remote) {
085                    remote.sendString(message);
086                }
087            } catch (WebSocketException ex) {
088                // A WebSocketException is most likely a broken socket,
089                // so rethrow it as an IOException
090                if (ex.getMessage() == null) {
091                    // provide a generic message if ex has no message
092                    throw new IOException(EX_SENDING_MSG, ex);
093                }
094                throw new IOException(ex);
095            } catch (IOException ex) {
096                if (ex.getMessage() == null) {
097                    // provide a generic message if ex has no message
098                    throw new IOException(EX_SENDING_MSG, ex);
099                }
100                throw ex; // rethrow if complete
101            }
102        } else {
103            // immediately thrown an IOException to trigger closing
104            // actions up the call chain
105            throw new IOException("Will not send message on non-open session");
106        }
107    }
108
109    /**
110     * Close the connection.
111     * <p>
112     * Note: Objects using JmriConnection with a
113     * {@link org.eclipse.jetty.websocket.api.Session} may prefer to use
114     * <code>getSession().close()</code> since Session.close() does not throw an
115     * IOException.
116     *
117     * @throws IOException if problem closing connection
118     */
119    public void close() throws IOException {
120        if (this.dataOutputStream != null) {
121            this.dataOutputStream.close();
122        } else if (this.session != null) {
123            this.session.close();
124        }
125    }
126
127    /**
128     * @return the locale
129     */
130    public Locale getLocale() {
131        return locale;
132    }
133
134    /**
135     * @param locale the locale to set
136     */
137    public void setLocale(Locale locale) {
138        this.locale = locale;
139    }
140}