BoBManager.java
/**
*
* Copyright 2016-2020 Fernando Ramirez, Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.bob;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.NotLoggedInException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPConnectionRegistry;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler;
import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.IQ.Type;
import org.jivesoftware.smack.util.SHA1;
import org.jivesoftware.smackx.bob.element.BoBIQ;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jxmpp.jid.Jid;
import org.jxmpp.util.cache.LruCache;
/**
* Bits of Binary manager class.
*
* @author Fernando Ramirez
* @author Florian Schmaus
* @see <a href="http://xmpp.org/extensions/xep-0231.html">XEP-0231: Bits of
* Binary</a>
*/
public final class BoBManager extends Manager {
public static final String NAMESPACE = "urn:xmpp:bob";
static {
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
@Override
public void connectionCreated(XMPPConnection connection) {
getInstanceFor(connection);
}
});
}
private static final Map<XMPPConnection, BoBManager> INSTANCES = new WeakHashMap<>();
/**
* Get the singleton instance of BoBManager.
*
* @param connection TODO javadoc me please
* @return the instance of BoBManager
*/
public static synchronized BoBManager getInstanceFor(XMPPConnection connection) {
BoBManager bobManager = INSTANCES.get(connection);
if (bobManager == null) {
bobManager = new BoBManager(connection);
INSTANCES.put(connection, bobManager);
}
return bobManager;
}
private static final LruCache<ContentId, BoBData> BOB_CACHE = new LruCache<>(128);
private final Map<ContentId, BoBInfo> bobs = new ConcurrentHashMap<>();
private BoBManager(XMPPConnection connection) {
super(connection);
ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
serviceDiscoveryManager.addFeature(NAMESPACE);
connection.registerIQRequestHandler(
new AbstractIqRequestHandler(BoBIQ.ELEMENT, BoBIQ.NAMESPACE, Type.get, Mode.async) {
@Override
public IQ handleIQRequest(IQ iqRequest) {
BoBIQ bobIQRequest = (BoBIQ) iqRequest;
ContentId contentId = bobIQRequest.getContentId();
BoBInfo bobInfo = bobs.get(contentId);
if (bobInfo == null) {
// TODO return item-not-found
return null;
}
BoBData bobData = bobInfo.getData();
BoBIQ responseBoBIQ = new BoBIQ(contentId, bobData);
responseBoBIQ.setType(Type.result);
responseBoBIQ.setTo(bobIQRequest.getFrom());
return responseBoBIQ;
}
});
}
/**
* Returns true if Bits of Binary is supported by the server.
*
* @return true if Bits of Binary is supported by the server.
* @throws NoResponseException if there was no response from the remote entity.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public boolean isSupportedByServer()
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
return ServiceDiscoveryManager.getInstanceFor(connection()).serverSupportsFeature(NAMESPACE);
}
/**
* Request BoB data.
*
* @param to TODO javadoc me please
* @param bobHash TODO javadoc me please
* @return the BoB data
* @throws NotLoggedInException if the XMPP connection is not authenticated.
* @throws NoResponseException if there was no response from the remote entity.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public BoBData requestBoB(Jid to, ContentId bobHash) throws NotLoggedInException, NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException {
BoBData bobData = BOB_CACHE.lookup(bobHash);
if (bobData != null) {
return bobData;
}
BoBIQ requestBoBIQ = new BoBIQ(bobHash);
requestBoBIQ.setType(Type.get);
requestBoBIQ.setTo(to);
XMPPConnection connection = getAuthenticatedConnectionOrThrow();
BoBIQ responseBoBIQ = connection.createStanzaCollectorAndSend(requestBoBIQ).nextResultOrThrow();
bobData = responseBoBIQ.getBoBData();
BOB_CACHE.put(bobHash, bobData);
return bobData;
}
public BoBInfo addBoB(BoBData bobData) {
// We only support SHA-1 for now.
ContentId bobHash = new ContentId(SHA1.hex(bobData.getContent()), "sha1");
Set<ContentId> bobHashes = Collections.singleton(bobHash);
bobHashes = Collections.unmodifiableSet(bobHashes);
BoBInfo bobInfo = new BoBInfo(bobHashes, bobData);
bobs.put(bobHash, bobInfo);
return bobInfo;
}
public BoBInfo removeBoB(ContentId bobHash) {
BoBInfo bobInfo = bobs.remove(bobHash);
if (bobInfo == null) {
return null;
}
for (ContentId otherBobHash : bobInfo.getHashes()) {
bobs.remove(otherBobHash);
}
return bobInfo;
}
}