001/** 002 * 003 * Copyright 2016-2020 Fernando Ramirez, 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.jivesoftware.smackx.bob; 018 019import java.util.Collections; 020import java.util.Map; 021import java.util.Set; 022import java.util.WeakHashMap; 023import java.util.concurrent.ConcurrentHashMap; 024 025import org.jivesoftware.smack.ConnectionCreationListener; 026import org.jivesoftware.smack.Manager; 027import org.jivesoftware.smack.SmackException.NoResponseException; 028import org.jivesoftware.smack.SmackException.NotConnectedException; 029import org.jivesoftware.smack.SmackException.NotLoggedInException; 030import org.jivesoftware.smack.XMPPConnection; 031import org.jivesoftware.smack.XMPPConnectionRegistry; 032import org.jivesoftware.smack.XMPPException.XMPPErrorException; 033import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler; 034import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode; 035import org.jivesoftware.smack.packet.IQ; 036import org.jivesoftware.smack.packet.IQ.Type; 037import org.jivesoftware.smack.util.SHA1; 038 039import org.jivesoftware.smackx.bob.element.BoBIQ; 040import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 041 042import org.jxmpp.jid.Jid; 043import org.jxmpp.util.cache.LruCache; 044 045/** 046 * Bits of Binary manager class. 047 * 048 * @author Fernando Ramirez 049 * @author Florian Schmaus 050 * @see <a href="http://xmpp.org/extensions/xep-0231.html">XEP-0231: Bits of 051 * Binary</a> 052 */ 053public final class BoBManager extends Manager { 054 055 public static final String NAMESPACE = "urn:xmpp:bob"; 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 static final Map<XMPPConnection, BoBManager> INSTANCES = new WeakHashMap<>(); 067 068 /** 069 * Get the singleton instance of BoBManager. 070 * 071 * @param connection TODO javadoc me please 072 * @return the instance of BoBManager 073 */ 074 public static synchronized BoBManager getInstanceFor(XMPPConnection connection) { 075 BoBManager bobManager = INSTANCES.get(connection); 076 if (bobManager == null) { 077 bobManager = new BoBManager(connection); 078 INSTANCES.put(connection, bobManager); 079 } 080 081 return bobManager; 082 } 083 084 private static final LruCache<ContentId, BoBData> BOB_CACHE = new LruCache<>(128); 085 086 private final Map<ContentId, BoBInfo> bobs = new ConcurrentHashMap<>(); 087 088 private BoBManager(XMPPConnection connection) { 089 super(connection); 090 ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); 091 serviceDiscoveryManager.addFeature(NAMESPACE); 092 093 connection.registerIQRequestHandler( 094 new AbstractIqRequestHandler(BoBIQ.ELEMENT, BoBIQ.NAMESPACE, Type.get, Mode.async) { 095 @Override 096 public IQ handleIQRequest(IQ iqRequest) { 097 BoBIQ bobIQRequest = (BoBIQ) iqRequest; 098 ContentId contentId = bobIQRequest.getContentId(); 099 100 BoBInfo bobInfo = bobs.get(contentId); 101 if (bobInfo == null) { 102 // TODO return item-not-found 103 return null; 104 } 105 106 BoBData bobData = bobInfo.getData(); 107 BoBIQ responseBoBIQ = new BoBIQ(contentId, bobData); 108 responseBoBIQ.setType(Type.result); 109 responseBoBIQ.setTo(bobIQRequest.getFrom()); 110 return responseBoBIQ; 111 } 112 }); 113 } 114 115 /** 116 * Returns true if Bits of Binary is supported by the server. 117 * 118 * @return true if Bits of Binary is supported by the server. 119 * @throws NoResponseException if there was no response from the remote entity. 120 * @throws XMPPErrorException if there was an XMPP error returned. 121 * @throws NotConnectedException if the XMPP connection is not connected. 122 * @throws InterruptedException if the calling thread was interrupted. 123 */ 124 public boolean isSupportedByServer() 125 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 126 return ServiceDiscoveryManager.getInstanceFor(connection()).serverSupportsFeature(NAMESPACE); 127 } 128 129 /** 130 * Request BoB data. 131 * 132 * @param to TODO javadoc me please 133 * @param bobHash TODO javadoc me please 134 * @return the BoB data 135 * @throws NotLoggedInException if the XMPP connection is not authenticated. 136 * @throws NoResponseException if there was no response from the remote entity. 137 * @throws XMPPErrorException if there was an XMPP error returned. 138 * @throws NotConnectedException if the XMPP connection is not connected. 139 * @throws InterruptedException if the calling thread was interrupted. 140 */ 141 public BoBData requestBoB(Jid to, ContentId bobHash) throws NotLoggedInException, NoResponseException, 142 XMPPErrorException, NotConnectedException, InterruptedException { 143 BoBData bobData = BOB_CACHE.lookup(bobHash); 144 if (bobData != null) { 145 return bobData; 146 } 147 148 BoBIQ requestBoBIQ = new BoBIQ(bobHash); 149 requestBoBIQ.setType(Type.get); 150 requestBoBIQ.setTo(to); 151 152 XMPPConnection connection = getAuthenticatedConnectionOrThrow(); 153 BoBIQ responseBoBIQ = connection.createStanzaCollectorAndSend(requestBoBIQ).nextResultOrThrow(); 154 155 bobData = responseBoBIQ.getBoBData(); 156 BOB_CACHE.put(bobHash, bobData); 157 158 return bobData; 159 } 160 161 public BoBInfo addBoB(BoBData bobData) { 162 // We only support SHA-1 for now. 163 ContentId bobHash = new ContentId(SHA1.hex(bobData.getContent()), "sha1"); 164 165 Set<ContentId> bobHashes = Collections.singleton(bobHash); 166 bobHashes = Collections.unmodifiableSet(bobHashes); 167 168 BoBInfo bobInfo = new BoBInfo(bobHashes, bobData); 169 170 bobs.put(bobHash, bobInfo); 171 172 return bobInfo; 173 } 174 175 public BoBInfo removeBoB(ContentId bobHash) { 176 BoBInfo bobInfo = bobs.remove(bobHash); 177 if (bobInfo == null) { 178 return null; 179 } 180 for (ContentId otherBobHash : bobInfo.getHashes()) { 181 bobs.remove(otherBobHash); 182 } 183 return bobInfo; 184 } 185}