FallbackIndicationManager.java

  1. /**
  2.  *
  3.  * Copyright 2020 Paul Schaub
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.fallback_indication;

  18. import java.util.Map;
  19. import java.util.Set;
  20. import java.util.WeakHashMap;
  21. import java.util.concurrent.CopyOnWriteArraySet;

  22. import org.jivesoftware.smack.AsyncButOrdered;
  23. import org.jivesoftware.smack.ConnectionCreationListener;
  24. import org.jivesoftware.smack.Manager;
  25. import org.jivesoftware.smack.SmackException;
  26. import org.jivesoftware.smack.XMPPConnection;
  27. import org.jivesoftware.smack.XMPPConnectionRegistry;
  28. import org.jivesoftware.smack.XMPPException;
  29. import org.jivesoftware.smack.filter.AndFilter;
  30. import org.jivesoftware.smack.filter.StanzaExtensionFilter;
  31. import org.jivesoftware.smack.filter.StanzaFilter;
  32. import org.jivesoftware.smack.filter.StanzaTypeFilter;
  33. import org.jivesoftware.smack.packet.Message;
  34. import org.jivesoftware.smack.packet.MessageBuilder;
  35. import org.jivesoftware.smack.packet.Stanza;

  36. import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
  37. import org.jivesoftware.smackx.fallback_indication.element.FallbackIndicationElement;

  38. import org.jxmpp.jid.BareJid;
  39. import org.jxmpp.jid.EntityBareJid;

  40. /**
  41.  * Smacks API for XEP-0428: Fallback Indication.
  42.  * In some scenarios it might make sense to mark the body of a message as fallback for legacy clients.
  43.  * Examples are encryption mechanisms where the sender might include a hint for legacy clients stating that the
  44.  * body (eg. "This message is encrypted") should be ignored.
  45.  *
  46.  * @see <a href="https://xmpp.org/extensions/xep-0428.html">XEP-0428: Fallback Indication</a>
  47.  */
  48. public final class FallbackIndicationManager extends Manager {

  49.     private static final Map<XMPPConnection, FallbackIndicationManager> INSTANCES = new WeakHashMap<>();

  50.     static {
  51.         XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
  52.             @Override
  53.             public void connectionCreated(XMPPConnection connection) {
  54.                 getInstanceFor(connection);
  55.             }
  56.         });
  57.     }

  58.     private final Set<FallbackIndicationListener> listeners = new CopyOnWriteArraySet<>();
  59.     private final AsyncButOrdered<BareJid> asyncButOrdered = new AsyncButOrdered<>();
  60.     private final StanzaFilter fallbackIndicationElementFilter = new AndFilter(StanzaTypeFilter.MESSAGE,
  61.             new StanzaExtensionFilter(FallbackIndicationElement.ELEMENT, FallbackIndicationElement.NAMESPACE));

  62.     private void fallbackIndicationElementListener(Stanza packet) {
  63.         Message message = (Message) packet;
  64.         FallbackIndicationElement indicator = FallbackIndicationElement.fromMessage(message);
  65.         String body = message.getBody();
  66.         asyncButOrdered.performAsyncButOrdered(message.getFrom().asBareJid(), () -> {
  67.             for (FallbackIndicationListener l : listeners) {
  68.                 l.onFallbackIndicationReceived(message, indicator, body);
  69.             }
  70.         });
  71.     }

  72.     private FallbackIndicationManager(XMPPConnection connection) {
  73.         super(connection);
  74.         connection.addAsyncStanzaListener(this::fallbackIndicationElementListener, fallbackIndicationElementFilter);
  75.         ServiceDiscoveryManager.getInstanceFor(connection).addFeature(FallbackIndicationElement.NAMESPACE);
  76.     }

  77.     public static synchronized FallbackIndicationManager getInstanceFor(XMPPConnection connection) {
  78.         FallbackIndicationManager manager = INSTANCES.get(connection);
  79.         if (manager == null) {
  80.             manager = new FallbackIndicationManager(connection);
  81.             INSTANCES.put(connection, manager);
  82.         }
  83.         return manager;
  84.     }

  85.     /**
  86.      * Determine, whether or not a user supports Fallback Indications.
  87.      *
  88.      * @param jid BareJid of the user.
  89.      * @return feature support
  90.      *
  91.      * @throws XMPPException.XMPPErrorException if a protocol level error happens
  92.      * @throws SmackException.NotConnectedException if the connection is not connected
  93.      * @throws InterruptedException if the thread is being interrupted
  94.      * @throws SmackException.NoResponseException if the server doesn't send a response in time
  95.      */
  96.     public boolean userSupportsFallbackIndications(EntityBareJid jid)
  97.             throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
  98.             SmackException.NoResponseException {
  99.         return ServiceDiscoveryManager.getInstanceFor(connection())
  100.                 .supportsFeature(jid, FallbackIndicationElement.NAMESPACE);
  101.     }

  102.     /**
  103.      * Determine, whether or not the server supports Fallback Indications.
  104.      *
  105.      * @return server side feature support
  106.      *
  107.      * @throws XMPPException.XMPPErrorException if a protocol level error happens
  108.      * @throws SmackException.NotConnectedException if the connection is not connected
  109.      * @throws InterruptedException if the thread is being interrupted
  110.      * @throws SmackException.NoResponseException if the server doesn't send a response in time
  111.      */
  112.     public boolean serverSupportsFallbackIndications()
  113.             throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
  114.             SmackException.NoResponseException {
  115.         return ServiceDiscoveryManager.getInstanceFor(connection())
  116.                 .serverSupportsFeature(FallbackIndicationElement.NAMESPACE);
  117.     }

  118.     /**
  119.      * Set the body of the message to the provided fallback message and add a {@link FallbackIndicationElement}.
  120.      *
  121.      * @param messageBuilder message builder
  122.      * @param fallbackMessageBody fallback message body
  123.      * @return builder with set body and added fallback element
  124.      */
  125.     public static MessageBuilder addFallbackIndicationWithBody(MessageBuilder messageBuilder, String fallbackMessageBody) {
  126.         return addFallbackIndication(messageBuilder).setBody(fallbackMessageBody);
  127.     }

  128.     /**
  129.      * Add a {@link FallbackIndicationElement} to the provided message builder.
  130.      *
  131.      * @param messageBuilder message builder
  132.      * @return message builder with added fallback element
  133.      */
  134.     public static MessageBuilder addFallbackIndication(MessageBuilder messageBuilder) {
  135.         return messageBuilder.addExtension(new FallbackIndicationElement());
  136.     }

  137.     /**
  138.      * Register a {@link FallbackIndicationListener} that gets notified whenever a message that contains a
  139.      * {@link FallbackIndicationElement} is received.
  140.      *
  141.      * @param listener listener to be registered.
  142.      */
  143.     public synchronized void addFallbackIndicationListener(FallbackIndicationListener listener) {
  144.         listeners.add(listener);
  145.     }

  146.     /**
  147.      * Unregister a {@link FallbackIndicationListener}.
  148.      *
  149.      * @param listener listener to be unregistered.
  150.      */
  151.     public synchronized void removeFallbackIndicationListener(FallbackIndicationListener listener) {
  152.         listeners.remove(listener);
  153.     }
  154. }