001/**
002 *
003 * Copyright the original author or authors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.debugger;
018
019import java.io.Reader;
020import java.io.Writer;
021import java.util.logging.Logger;
022
023import org.jivesoftware.smack.AbstractConnectionListener;
024import org.jivesoftware.smack.AbstractXMPPConnection;
025import org.jivesoftware.smack.ConnectionListener;
026import org.jivesoftware.smack.ReconnectionListener;
027import org.jivesoftware.smack.ReconnectionManager;
028import org.jivesoftware.smack.StanzaListener;
029import org.jivesoftware.smack.XMPPConnection;
030import org.jivesoftware.smack.packet.Stanza;
031import org.jivesoftware.smack.util.ObservableReader;
032import org.jivesoftware.smack.util.ObservableWriter;
033import org.jivesoftware.smack.util.ReaderListener;
034import org.jivesoftware.smack.util.WriterListener;
035
036import org.jxmpp.jid.EntityFullJid;
037
038public abstract class AbstractDebugger implements SmackDebugger {
039
040    private static final Logger LOGGER = Logger.getLogger(AbstractDebugger.class.getName());
041
042    public static boolean printInterpreted = false;
043
044    private final XMPPConnection connection;
045
046    private final StanzaListener listener;
047    private final ConnectionListener connListener;
048    private final ReconnectionListener reconnectionListener;
049    private final ReaderListener readerListener;
050    private final WriterListener writerListener;
051
052    private ObservableWriter writer;
053    private ObservableReader reader;
054
055    public AbstractDebugger(final XMPPConnection connection, Writer writer, Reader reader) {
056        this.connection = connection;
057
058        // Create a special Reader that wraps the main Reader and logs data to the GUI.
059        this.reader = new ObservableReader(reader);
060        readerListener = new ReaderListener() {
061            @Override
062            public void read(String str) {
063                log("RECV (" + connection.getConnectionCounter() + "): " + str);
064            }
065        };
066        this.reader.addReaderListener(readerListener);
067
068        // Create a special Writer that wraps the main Writer and logs data to the GUI.
069        this.writer = new ObservableWriter(writer);
070        writerListener = new WriterListener() {
071            @Override
072            public void write(String str) {
073                log("SENT (" + connection.getConnectionCounter() + "): " + str);
074            }
075        };
076        this.writer.addWriterListener(writerListener);
077
078        // Create a thread that will listen for all incoming packets and write them to
079        // the GUI. This is what we call "interpreted" packet data, since it's the packet
080        // data as Smack sees it and not as it's coming in as raw XML.
081        listener = new StanzaListener() {
082            @Override
083            public void processStanza(Stanza packet) {
084                if (printInterpreted) {
085                    log("RCV PKT (" + connection.getConnectionCounter() + "): " + packet.toXML());
086                }
087            }
088        };
089
090        connListener = new AbstractConnectionListener() {
091            @Override
092            public void connected(XMPPConnection connection) {
093                log("XMPPConnection connected ("
094                                + connection + ")");
095            }
096            @Override
097            public void authenticated(XMPPConnection connection, boolean resumed) {
098                String logString = "XMPPConnection authenticated (" + connection + ")";
099                if (resumed) {
100                    logString += " and resumed";
101                }
102                log(logString);
103            }
104            @Override
105            public void connectionClosed() {
106                log(
107                       "XMPPConnection closed (" +
108                        connection +
109                        ")");
110            }
111            @Override
112            public void connectionClosedOnError(Exception e) {
113                log(
114                        "XMPPConnection closed due to an exception (" +
115                        connection +
116                        ")", e);
117            }
118        };
119
120        reconnectionListener = new ReconnectionListener() {
121            @Override
122            public void reconnectionFailed(Exception e) {
123                log(
124                        "Reconnection failed due to an exception (" +
125                        connection +
126                        ")", e);
127            }
128            @Override
129            public void reconnectingIn(int seconds) {
130                log(
131                        "XMPPConnection (" +
132                        connection +
133                        ") will reconnect in " + seconds);
134            }
135        };
136
137        if (connection instanceof AbstractXMPPConnection) {
138            AbstractXMPPConnection abstractXmppConnection = (AbstractXMPPConnection) connection;
139            ReconnectionManager.getInstanceFor(abstractXmppConnection).addReconnectionListener(reconnectionListener);
140        } else {
141            LOGGER.info("The connection instance " + connection
142                            + " is not an instance of AbstractXMPPConnection, thus we can not install the ReconnectionListener");
143        }
144    }
145
146    protected abstract void log(String logMessage);
147
148    protected abstract void log(String logMessage, Throwable throwable);
149
150    @Override
151    public Reader newConnectionReader(Reader newReader) {
152        reader.removeReaderListener(readerListener);
153        ObservableReader debugReader = new ObservableReader(newReader);
154        debugReader.addReaderListener(readerListener);
155        reader = debugReader;
156        return reader;
157    }
158
159    @Override
160    public Writer newConnectionWriter(Writer newWriter) {
161        writer.removeWriterListener(writerListener);
162        ObservableWriter debugWriter = new ObservableWriter(newWriter);
163        debugWriter.addWriterListener(writerListener);
164        writer = debugWriter;
165        return writer;
166    }
167
168    @Override
169    public void userHasLogged(EntityFullJid user) {
170        String localpart = user.getLocalpart().toString();
171        boolean isAnonymous = "".equals(localpart);
172        String title =
173                "User logged (" + connection.getConnectionCounter() + "): "
174                + (isAnonymous ? "" : localpart)
175                + "@"
176                + connection.getXMPPServiceDomain()
177                + ":"
178                + connection.getPort();
179        title += "/" + user.getResourcepart();
180        log(title);
181        // Add the connection listener to the connection so that the debugger can be notified
182        // whenever the connection is closed.
183        connection.addConnectionListener(connListener);
184    }
185
186    @Override
187    public Reader getReader() {
188        return reader;
189    }
190
191    @Override
192    public Writer getWriter() {
193        return writer;
194    }
195
196    @Override
197    public StanzaListener getReaderListener() {
198        return listener;
199    }
200
201    @Override
202    public StanzaListener getWriterListener() {
203        return null;
204    }
205}