001/**
002 *
003 * Copyright © 2015 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 */
017package org.jxmpp.xml.splitter;
018
019import java.io.IOException;
020import java.util.Collections;
021import java.util.Map;
022
023/**
024 * A XML splitter for XMPP. Unlike {@link XmlSplitter}, this splitter is aware
025 * of the special semantics of XMPP's {@code <stream:stream>} element.
026 */
027public class XmppXmlSplitter extends XmlSplitter {
028
029        private final XmppElementCallback xmppElementCallback;
030        private final int maxElementSize;
031
032        private String streamPrefix;
033
034        /**
035         * Construct a new XMPP XML splitter with a max element size of 10000.
036         * <p>
037         * RFC 6120 § 13.12 4. requires XMPP servers to use nothing less then 10000 as maximum stanza size.
038         * </p>
039         * @param xmppElementCallback the callback invoked once a complete element has been processed.
040         */
041        public XmppXmlSplitter(XmppElementCallback xmppElementCallback) {
042                this(10000, xmppElementCallback);
043        }
044
045        /**
046         * Construct a new XMPP XML splitter with a max element size of 10000.
047         * <p>
048         * RFC 6120 § 13.12 4. requires XMPP servers to use nothing less then 10000 as maximum stanza size.
049         * </p>
050         * @param xmppElementCallback the callback invoked once a complete element has been processed.
051         * @param declarationCallback a optional callback for XML Declarations.
052         * @param processingInstructionCallback a optional callback for XML Processing Instructions. 
053         */
054        public XmppXmlSplitter(XmppElementCallback xmppElementCallback, DeclarationCallback declarationCallback,
055                        ProcessingInstructionCallback processingInstructionCallback) {
056                this(10000, xmppElementCallback, declarationCallback, processingInstructionCallback);
057        }
058
059        /**
060         * Construct a new XMPP XML splitter.
061         *
062         * @param maxElementSize the maximum size of a single top level element in bytes.
063         * @param xmppElementCallback the callback invoked once a complete element has been processed.
064         */
065        public XmppXmlSplitter(int maxElementSize, XmppElementCallback xmppElementCallback) {
066                this(maxElementSize, xmppElementCallback, null, null);
067        }
068
069        /**
070         * Construct a new XMPP XML splitter.
071         * 
072         * @param maxElementSize the maximum size of a single top level element in bytes.
073         * @param xmppElementCallback the callback invoked once a complete element has been processed.
074         * @param declarationCallback a optional callback for XML Declarations.
075         * @param processingInstructionCallback a optional callback for XML Processing Instructions. 
076         */
077        public XmppXmlSplitter(int maxElementSize, XmppElementCallback xmppElementCallback, DeclarationCallback declarationCallback,
078                        ProcessingInstructionCallback processingInstructionCallback) {
079                super(maxElementSize, xmppElementCallback, declarationCallback, processingInstructionCallback);
080                this.maxElementSize = maxElementSize;
081                this.xmppElementCallback = xmppElementCallback;
082        }
083
084        @Override
085        protected void onNextChar() throws IOException {
086                if (getCurrentSplittedPartSize() >= maxElementSize) {
087                        throw new IOException("Max element size exceeded");
088                }
089        }
090
091        @Override
092        protected void onStartTag(String prefix, String localpart, Map<String, String> attributes) {
093                if (!"stream".equals(localpart)) {
094                        // If the open tag's name is not 'stream' then we are not interested.
095                        return;
096                }
097
098                if ("http://etherx.jabber.org/streams".equals(attributes.get("xmlns:" + prefix))) {
099                        streamPrefix = prefix;
100                        newSplittedPart();
101                        xmppElementCallback.streamOpened(prefix, Collections.unmodifiableMap(attributes));
102                }
103        }
104
105        @Override
106        protected void onEndTag(String qName) {
107                if (streamPrefix == null || !qName.startsWith(streamPrefix)) {
108                        // Shortcut if streamPrefix is not yet set or if qName does not even
109                        // start with it.
110                        return;
111                }
112
113                if ((streamPrefix + ":stream").equals(qName)) {
114                        xmppElementCallback.streamClosed();
115                }
116        }
117}