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