001/** 002 * 003 * Copyright 2020 Paul Schaub 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.smackx.fallback_indication; 018 019import java.util.Map; 020import java.util.Set; 021import java.util.WeakHashMap; 022import java.util.concurrent.CopyOnWriteArraySet; 023 024import org.jivesoftware.smack.AsyncButOrdered; 025import org.jivesoftware.smack.ConnectionCreationListener; 026import org.jivesoftware.smack.Manager; 027import org.jivesoftware.smack.SmackException; 028import org.jivesoftware.smack.XMPPConnection; 029import org.jivesoftware.smack.XMPPConnectionRegistry; 030import org.jivesoftware.smack.XMPPException; 031import org.jivesoftware.smack.filter.AndFilter; 032import org.jivesoftware.smack.filter.StanzaExtensionFilter; 033import org.jivesoftware.smack.filter.StanzaFilter; 034import org.jivesoftware.smack.filter.StanzaTypeFilter; 035import org.jivesoftware.smack.packet.Message; 036import org.jivesoftware.smack.packet.MessageBuilder; 037import org.jivesoftware.smack.packet.Stanza; 038import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 039import org.jivesoftware.smackx.fallback_indication.element.FallbackIndicationElement; 040 041import org.jxmpp.jid.BareJid; 042import org.jxmpp.jid.EntityBareJid; 043 044/** 045 * Smacks API for XEP-0428: Fallback Indication. 046 * In some scenarios it might make sense to mark the body of a message as fallback for legacy clients. 047 * Examples are encryption mechanisms where the sender might include a hint for legacy clients stating that the 048 * body (eg. "This message is encrypted") should be ignored. 049 * 050 * @see <a href="https://xmpp.org/extensions/xep-0428.html">XEP-0428: Fallback Indication</a> 051 */ 052public final class FallbackIndicationManager extends Manager { 053 054 private static final Map<XMPPConnection, FallbackIndicationManager> INSTANCES = new WeakHashMap<>(); 055 056 static { 057 XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { 058 @Override 059 public void connectionCreated(XMPPConnection connection) { 060 getInstanceFor(connection); 061 } 062 }); 063 } 064 065 private final Set<FallbackIndicationListener> listeners = new CopyOnWriteArraySet<>(); 066 private final AsyncButOrdered<BareJid> asyncButOrdered = new AsyncButOrdered<>(); 067 private final StanzaFilter fallbackIndicationElementFilter = new AndFilter(StanzaTypeFilter.MESSAGE, 068 new StanzaExtensionFilter(FallbackIndicationElement.ELEMENT, FallbackIndicationElement.NAMESPACE)); 069 070 private void fallbackIndicationElementListener(Stanza packet) { 071 Message message = (Message) packet; 072 FallbackIndicationElement indicator = FallbackIndicationElement.fromMessage(message); 073 String body = message.getBody(); 074 asyncButOrdered.performAsyncButOrdered(message.getFrom().asBareJid(), () -> { 075 for (FallbackIndicationListener l : listeners) { 076 l.onFallbackIndicationReceived(message, indicator, body); 077 } 078 }); 079 } 080 081 private FallbackIndicationManager(XMPPConnection connection) { 082 super(connection); 083 connection.addAsyncStanzaListener(this::fallbackIndicationElementListener, fallbackIndicationElementFilter); 084 ServiceDiscoveryManager.getInstanceFor(connection).addFeature(FallbackIndicationElement.NAMESPACE); 085 } 086 087 public static synchronized FallbackIndicationManager getInstanceFor(XMPPConnection connection) { 088 FallbackIndicationManager manager = INSTANCES.get(connection); 089 if (manager == null) { 090 manager = new FallbackIndicationManager(connection); 091 INSTANCES.put(connection, manager); 092 } 093 return manager; 094 } 095 096 /** 097 * Determine, whether or not a user supports Fallback Indications. 098 * 099 * @param jid BareJid of the user. 100 * @return feature support 101 * 102 * @throws XMPPException.XMPPErrorException if a protocol level error happens 103 * @throws SmackException.NotConnectedException if the connection is not connected 104 * @throws InterruptedException if the thread is being interrupted 105 * @throws SmackException.NoResponseException if the server doesn't send a response in time 106 */ 107 public boolean userSupportsFallbackIndications(EntityBareJid jid) 108 throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, 109 SmackException.NoResponseException { 110 return ServiceDiscoveryManager.getInstanceFor(connection()) 111 .supportsFeature(jid, FallbackIndicationElement.NAMESPACE); 112 } 113 114 /** 115 * Determine, whether or not the server supports Fallback Indications. 116 * 117 * @return server side feature support 118 * 119 * @throws XMPPException.XMPPErrorException if a protocol level error happens 120 * @throws SmackException.NotConnectedException if the connection is not connected 121 * @throws InterruptedException if the thread is being interrupted 122 * @throws SmackException.NoResponseException if the server doesn't send a response in time 123 */ 124 public boolean serverSupportsFallbackIndications() 125 throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, 126 SmackException.NoResponseException { 127 return ServiceDiscoveryManager.getInstanceFor(connection()) 128 .serverSupportsFeature(FallbackIndicationElement.NAMESPACE); 129 } 130 131 /** 132 * Set the body of the message to the provided fallback message and add a {@link FallbackIndicationElement}. 133 * 134 * @param messageBuilder message builder 135 * @param fallbackMessageBody fallback message body 136 * @return builder with set body and added fallback element 137 */ 138 public static MessageBuilder addFallbackIndicationWithBody(MessageBuilder messageBuilder, String fallbackMessageBody) { 139 return addFallbackIndication(messageBuilder).setBody(fallbackMessageBody); 140 } 141 142 /** 143 * Add a {@link FallbackIndicationElement} to the provided message builder. 144 * 145 * @param messageBuilder message builder 146 * @return message builder with added fallback element 147 */ 148 public static MessageBuilder addFallbackIndication(MessageBuilder messageBuilder) { 149 return messageBuilder.addExtension(new FallbackIndicationElement()); 150 } 151 152 /** 153 * Register a {@link FallbackIndicationListener} that gets notified whenever a message that contains a 154 * {@link FallbackIndicationElement} is received. 155 * 156 * @param listener listener to be registered. 157 */ 158 public synchronized void addFallbackIndicationListener(FallbackIndicationListener listener) { 159 listeners.add(listener); 160 } 161 162 /** 163 * Unregister a {@link FallbackIndicationListener}. 164 * 165 * @param listener listener to be unregistered. 166 */ 167 public synchronized void removeFallbackIndicationListener(FallbackIndicationListener listener) { 168 listeners.remove(listener); 169 } 170}