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