001/**
002 *
003 * Copyright 2003-2007 Jive Software, 2017 Florian Schmaus.
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 */
017
018package org.jivesoftware.smack.debugger;
019
020import java.io.IOException;
021import java.io.Reader;
022import java.io.Writer;
023
024import org.jivesoftware.smack.XMPPConnection;
025import org.jivesoftware.smack.packet.TopLevelStreamElement;
026import org.jivesoftware.smack.util.ObservableReader;
027import org.jivesoftware.smack.util.ObservableWriter;
028
029import org.jxmpp.jid.EntityFullJid;
030import org.jxmpp.xml.splitter.XmlPrettyPrinter;
031import org.jxmpp.xml.splitter.XmppXmlSplitter;
032
033/**
034 * Interface that allows for implementing classes to debug XML traffic. That is a GUI window that
035 * displays XML traffic.<p>
036 *
037 * Every implementation of this interface <b>must</b> have a public constructor with the following
038 * arguments: XMPPConnection, Writer, Reader.
039 *
040 * @author Gaston Dombiak
041 */
042public abstract class SmackDebugger {
043
044    protected final XMPPConnection connection;
045
046    private XmppXmlSplitter outgoingStreamSplitterForPrettyPrinting;
047    private XmppXmlSplitter incomingStreamSplitterForPrettyPrinting;
048
049    protected SmackDebugger(XMPPConnection connection) {
050        this.connection = connection;
051    }
052
053    /**
054     * Called when a user has logged in to the server. The user could be an anonymous user, this
055     * means that the user would be of the form host/resource instead of the form
056     * user@host/resource.
057     *
058     * @param user the user@host/resource that has just logged in
059     */
060    // TODO: Should be replaced with a connection listener authenticed().
061    public abstract void userHasLogged(EntityFullJid user);
062
063    /**
064     * Note that the sequence of characters may be pretty printed.
065     *
066     * @param outgoingCharSequence the outgoing character sequence.
067     */
068    public abstract void outgoingStreamSink(CharSequence outgoingCharSequence);
069
070    public void onOutgoingElementCompleted() {
071    }
072
073    public abstract void incomingStreamSink(CharSequence incomingCharSequence);
074
075    public void onIncomingElementCompleted() {
076    }
077
078    /**
079     * Returns a new special Reader that wraps the new connection Reader. The connection
080     * has been secured so the connection is using a new reader and writer. The debugger
081     * needs to wrap the new reader and writer to keep being notified of the connection
082     * traffic.
083     *
084     * @param reader connection reader.
085     * @return a new special Reader that wraps the new connection Reader.
086     */
087    public final Reader newConnectionReader(Reader reader) {
088        XmlPrettyPrinter xmlPrettyPrinter = XmlPrettyPrinter.builder()
089                        .setPrettyWriter(sb -> incomingStreamSink(sb))
090                        .build();
091        incomingStreamSplitterForPrettyPrinting = new XmppXmlSplitter(xmlPrettyPrinter);
092
093        ObservableReader observableReader = new ObservableReader(reader);
094        observableReader.addReaderListener(readString -> {
095            try {
096                incomingStreamSplitterForPrettyPrinting.append(readString);
097            }
098            catch (IOException e) {
099                throw new AssertionError(e);
100            }
101        });
102        return observableReader;
103    }
104
105    /**
106     * Returns a new special Writer that wraps the new connection Writer. The connection
107     * has been secured so the connection is using a new reader and writer. The debugger
108     * needs to wrap the new reader and writer to keep being notified of the connection
109     * traffic.
110     *
111     * @param writer connection writer.
112     * @return a new special Writer that wraps the new connection Writer.
113     */
114    public final Writer newConnectionWriter(Writer writer) {
115        XmlPrettyPrinter xmlPrettyPrinter = XmlPrettyPrinter.builder()
116                        .setPrettyWriter(sb -> outgoingStreamSink(sb))
117                        .build();
118        outgoingStreamSplitterForPrettyPrinting = new XmppXmlSplitter(xmlPrettyPrinter);
119
120        ObservableWriter observableWriter = new ObservableWriter(writer);
121        observableWriter.addWriterListener(writtenString -> {
122            try {
123                outgoingStreamSplitterForPrettyPrinting.append(writtenString);
124            }
125            catch (IOException e) {
126                throw new AssertionError(e);
127            }
128        });
129        return observableWriter;
130    }
131
132    /**
133     * Used by the connection to notify about an incoming top level stream element.
134     * <p>
135     * This method is invoked right after the incoming stream was parsed.
136     * </p>
137     *
138     * @param streamElement the incoming top level stream element.
139     */
140    public abstract void onIncomingStreamElement(TopLevelStreamElement streamElement);
141
142    /**
143     * Used by the connection to notify about a outgoing top level stream element.
144     * <p>
145     * This method is invoked right before the element is serialized to XML and put into the outgoing stream.
146     * </p>
147     *
148     * @param streamElement the outgoing top level stream element.
149     */
150    public abstract void onOutgoingStreamElement(TopLevelStreamElement streamElement);
151
152}