All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rocks.xmpp.core.session.XmppStreamWriter Maven / Gradle / Ivy

There is a newer version: 0.9.1
Show newest version
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2016 Christian Schudt
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package rocks.xmpp.core.session;

import rocks.xmpp.addr.Jid;
import rocks.xmpp.core.session.debug.XmppDebugger;
import rocks.xmpp.core.stanza.model.Stanza;
import rocks.xmpp.core.stream.model.StreamElement;
import rocks.xmpp.core.stream.model.StreamFeatures;
import rocks.xmpp.extensions.sm.StreamManager;
import rocks.xmpp.util.XmppUtils;

import javax.xml.XMLConstants;
import javax.xml.bind.Marshaller;
import javax.xml.stream.XMLStreamWriter;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.EnumSet;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * This class is responsible for opening and closing the XMPP stream as well as writing any XML elements to the stream.
 * 

* This class is thread-safe. * * @author Christian Schudt */ final class XmppStreamWriter { private final XmppSession xmppSession; private final ScheduledExecutorService executor; private final Marshaller marshaller; private final XmppDebugger debugger; private final String namespace; private final StreamManager streamManager; /** * Will be accessed only by the writer thread. */ private XMLStreamWriter prefixFreeCanonicalizationWriter; /** * Will be accessed only by the writer thread. */ private XMLStreamWriter xmlStreamWriter; /** * Will be accessed only by the writer thread. */ private ByteArrayOutputStream byteArrayOutputStream; /** * Indicates whether the stream has been opened. Will be accessed only by the writer thread. */ private boolean streamOpened; XmppStreamWriter(String namespace, StreamManager streamManager, final XmppSession xmppSession) { this.namespace = namespace; this.xmppSession = xmppSession; this.marshaller = xmppSession.createMarshaller(); this.debugger = xmppSession.getDebugger(); this.executor = Executors.newSingleThreadScheduledExecutor(XmppUtils.createNamedThreadFactory("XMPP Writer Thread")); this.streamManager = streamManager; } void initialize(int keepAliveInterval) { if (keepAliveInterval > 0) { executor.scheduleAtFixedRate(() -> { if (EnumSet.of(XmppSession.Status.CONNECTED, XmppSession.Status.AUTHENTICATED).contains(xmppSession.getStatus())) { try { xmlStreamWriter.writeCharacters(" "); xmlStreamWriter.flush(); } catch (Exception e) { notifyException(e); } } }, 0, keepAliveInterval, TimeUnit.SECONDS); } } synchronized CompletableFuture send(final StreamElement clientStreamElement) { Objects.requireNonNull(clientStreamElement); return CompletableFuture.runAsync(() -> { try { // When about to send a stanza, first put the stanza (paired with the current value of X) in an "unacknowledged" queue. if (clientStreamElement instanceof Stanza) { streamManager.markUnacknowledged((Stanza) clientStreamElement); } marshaller.marshal(clientStreamElement, prefixFreeCanonicalizationWriter); prefixFreeCanonicalizationWriter.flush(); if (clientStreamElement instanceof Stanza) { // Workaround: Simulate keep-alive packet to convince client to process the already transmitted packet. prefixFreeCanonicalizationWriter.writeCharacters(" "); prefixFreeCanonicalizationWriter.flush(); } if (debugger != null) { debugger.writeStanza(new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8).trim(), clientStreamElement); byteArrayOutputStream.reset(); } } catch (Exception e) { notifyException(e); throw new CompletionException(e); } }, executor); } synchronized void openStream(final OutputStream outputStream, final Jid from) { if (!executor.isShutdown()) { executor.execute(() -> { try { OutputStream xmppOutputStream = null; if (debugger != null) { byteArrayOutputStream = new ByteArrayOutputStream(); xmppOutputStream = XmppUtils.createBranchedOutputStream(outputStream, byteArrayOutputStream); OutputStream debuggerOutputStream = debugger.createOutputStream(xmppOutputStream); if (debuggerOutputStream != null) { xmppOutputStream = debuggerOutputStream; } } if (xmppOutputStream == null) { xmppOutputStream = outputStream; } xmlStreamWriter = xmppSession.getConfiguration().getXmlOutputFactory().createXMLStreamWriter(xmppOutputStream, "UTF-8"); prefixFreeCanonicalizationWriter = XmppUtils.createXmppStreamWriter(xmlStreamWriter, namespace); streamOpened = false; xmlStreamWriter.writeStartDocument("UTF-8", "1.0"); xmlStreamWriter.writeStartElement("stream", "stream", StreamFeatures.NAMESPACE); xmlStreamWriter.writeAttribute(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI, "lang", xmppSession.getConfiguration().getLanguage().toLanguageTag()); if (xmppSession.getDomain() != null) { xmlStreamWriter.writeAttribute("to", xmppSession.getDomain().toString()); } if (from != null) { xmlStreamWriter.writeAttribute("from", from.toEscapedString()); } xmlStreamWriter.writeAttribute("version", "1.0"); xmlStreamWriter.writeNamespace("", namespace); xmlStreamWriter.writeNamespace("stream", StreamFeatures.NAMESPACE); xmlStreamWriter.writeCharacters(""); xmlStreamWriter.flush(); if (debugger != null) { debugger.writeStanza(new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8).trim(), null); byteArrayOutputStream.reset(); } streamOpened = true; } catch (Exception e) { notifyException(e); } }); } } private void closeStream() { executor.execute(() -> { if (streamOpened) { // Close the stream. try { xmlStreamWriter.writeEndElement(); xmlStreamWriter.flush(); if (debugger != null) { debugger.writeStanza(new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8).trim(), null); byteArrayOutputStream.reset(); } xmlStreamWriter.close(); streamOpened = false; } catch (Exception e) { notifyException(e); } } }); } /** * Shuts down the executors, cleans up resources and notifies the session about the exception. * * @param exception The exception which occurred during writing. */ private void notifyException(Exception exception) { // Shutdown the executors. synchronized (this) { executor.shutdown(); if (prefixFreeCanonicalizationWriter != null) { try { prefixFreeCanonicalizationWriter.close(); prefixFreeCanonicalizationWriter = null; } catch (Exception e) { exception.addSuppressed(e); } } byteArrayOutputStream = null; } xmppSession.notifyException(exception); } /** * Closes the stream by sending a closing {@code } to the server. * This method waits until this task is completed, but not more than 0.25 seconds. *

* Make sure to synchronize this method. * Otherwise multiple threads could call {@link #closeStream()} which may result in a {@link RejectedExecutionException}, if it has been shutdown by another thread in the meantime. */ synchronized void shutdown() { // If the writer is still active, close the stream and afterwards shutdown the writer. if (!executor.isShutdown()) { // Send the closing stream tag. closeStream(); executor.shutdown(); try { // Wait for the closing stream element to be sent before we can close the socket. if (!executor.awaitTermination(50, TimeUnit.MILLISECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { // (Re-)Cancel if current thread also interrupted executor.shutdownNow(); Thread.currentThread().interrupt(); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy