mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-09-27 18:19:33 +02:00
26b16665e5
or not. Since they interfer a lot with existing code (presence management etc.)
426 lines
18 KiB
Java
426 lines
18 KiB
Java
/**
|
|
*
|
|
* Copyright 2016 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.iot.discovery;
|
|
|
|
import java.util.Collection;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.WeakHashMap;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
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.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.util.Objects;
|
|
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
|
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
|
import org.jivesoftware.smackx.iot.IoTManager;
|
|
import org.jivesoftware.smackx.iot.Thing;
|
|
import org.jivesoftware.smackx.iot.control.IoTControlManager;
|
|
import org.jivesoftware.smackx.iot.data.IoTDataManager;
|
|
import org.jivesoftware.smackx.iot.discovery.element.Constants;
|
|
import org.jivesoftware.smackx.iot.discovery.element.IoTClaimed;
|
|
import org.jivesoftware.smackx.iot.discovery.element.IoTDisown;
|
|
import org.jivesoftware.smackx.iot.discovery.element.IoTDisowned;
|
|
import org.jivesoftware.smackx.iot.discovery.element.IoTMine;
|
|
import org.jivesoftware.smackx.iot.discovery.element.IoTRegister;
|
|
import org.jivesoftware.smackx.iot.discovery.element.IoTRemove;
|
|
import org.jivesoftware.smackx.iot.discovery.element.IoTRemoved;
|
|
import org.jivesoftware.smackx.iot.discovery.element.IoTUnregister;
|
|
import org.jivesoftware.smackx.iot.discovery.element.Tag;
|
|
import org.jivesoftware.smackx.iot.element.NodeInfo;
|
|
import org.jivesoftware.smackx.iot.provisioning.IoTProvisioningManager;
|
|
import org.jxmpp.jid.BareJid;
|
|
import org.jxmpp.jid.Jid;
|
|
|
|
/**
|
|
* A manager for XEP-0347: Internet of Things - Discovery. Used to register and discover things.
|
|
*
|
|
* @author Florian Schmaus {@literal <flo@geekplace.eu>}
|
|
* @see <a href="http://xmpp.org/extensions/xep-0347.html">XEP-0347: Internet of Things - Discovery</a>
|
|
*
|
|
*/
|
|
public final class IoTDiscoveryManager extends Manager {
|
|
|
|
private static final Logger LOGGER = Logger.getLogger(IoTDiscoveryManager.class.getName());
|
|
|
|
private static final Map<XMPPConnection, IoTDiscoveryManager> INSTANCES = new WeakHashMap<>();
|
|
|
|
// Ensure a IoTProvisioningManager exists for every connection.
|
|
static {
|
|
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
|
|
public void connectionCreated(XMPPConnection connection) {
|
|
if (!IoTManager.isAutoEnableActive()) return;
|
|
getInstanceFor(connection);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the manger instance responsible for the given connection.
|
|
*
|
|
* @param connection the XMPP connection.
|
|
* @return a manager instance.
|
|
*/
|
|
public static synchronized IoTDiscoveryManager getInstanceFor(XMPPConnection connection) {
|
|
IoTDiscoveryManager manager = INSTANCES.get(connection);
|
|
if (manager == null) {
|
|
manager = new IoTDiscoveryManager(connection);
|
|
INSTANCES.put(connection, manager);
|
|
}
|
|
return manager;
|
|
}
|
|
|
|
private Jid preconfiguredRegistry;
|
|
|
|
/**
|
|
* A set of all registries we have interacted so far. {@link #isRegistry(BareJid)} uses this to
|
|
* determine if the jid is a registry. Note that we currently do not record which thing
|
|
* interacted with which registry. This allows any registry we have interacted so far with, to
|
|
* send registry control stanzas about any other thing, and we would process them.
|
|
*/
|
|
private final Set<Jid> usedRegistries = new HashSet<>();
|
|
|
|
/**
|
|
* Internal state of the things. Uses <code>null</code> for the single thing without node info attached.
|
|
*/
|
|
private final Map<NodeInfo, ThingState> things = new HashMap<>();
|
|
|
|
private IoTDiscoveryManager(XMPPConnection connection) {
|
|
super(connection);
|
|
|
|
connection.registerIQRequestHandler(
|
|
new AbstractIqRequestHandler(IoTClaimed.ELEMENT, IoTClaimed.NAMESPACE, IQ.Type.set, Mode.sync) {
|
|
@Override
|
|
public IQ handleIQRequest(IQ iqRequest) {
|
|
if (!isRegistry(iqRequest.getFrom())) {
|
|
LOGGER.log(Level.SEVERE, "Received control stanza from non-registry entity: " + iqRequest);
|
|
return null;
|
|
}
|
|
|
|
IoTClaimed iotClaimed = (IoTClaimed) iqRequest;
|
|
Jid owner = iotClaimed.getJid();
|
|
NodeInfo nodeInfo = iotClaimed.getNodeInfo();
|
|
// Update the state.
|
|
ThingState state = getStateFor(nodeInfo);
|
|
state.setOwner(owner.asBareJid());
|
|
LOGGER.info("Our thing got claimed by " + owner + ". " + iotClaimed);
|
|
|
|
IoTProvisioningManager iotProvisioningManager = IoTProvisioningManager.getInstanceFor(
|
|
connection());
|
|
try {
|
|
iotProvisioningManager.sendFriendshipRequest(owner.asBareJid());
|
|
}
|
|
catch (NotConnectedException | InterruptedException e) {
|
|
LOGGER.log(Level.WARNING, "Could not friendship owner", e);
|
|
}
|
|
|
|
return IQ.createResultIQ(iqRequest);
|
|
}
|
|
});
|
|
|
|
connection.registerIQRequestHandler(new AbstractIqRequestHandler(IoTDisowned.ELEMENT, IoTDisowned.NAMESPACE,
|
|
IQ.Type.set, Mode.sync) {
|
|
@Override
|
|
public IQ handleIQRequest(IQ iqRequest) {
|
|
if (!isRegistry(iqRequest.getFrom())) {
|
|
LOGGER.log(Level.SEVERE, "Received control stanza from non-registry entity: " + iqRequest);
|
|
return null;
|
|
}
|
|
|
|
IoTDisowned iotDisowned = (IoTDisowned) iqRequest;
|
|
Jid from = iqRequest.getFrom();
|
|
|
|
NodeInfo nodeInfo = iotDisowned.getNodeInfo();
|
|
ThingState state = getStateFor(nodeInfo);
|
|
if (!from.equals(state.getRegistry())) {
|
|
LOGGER.severe("Received <disowned/> for " + nodeInfo + " from " + from
|
|
+ " but this is not the registry " + state.getRegistry() + " of the thing.");
|
|
return null;
|
|
}
|
|
|
|
if (state.isOwned()) {
|
|
state.setUnowned();
|
|
} else {
|
|
LOGGER.fine("Received <disowned/> for " + nodeInfo + " but thing was not owned.");
|
|
}
|
|
|
|
return IQ.createResultIQ(iqRequest);
|
|
}
|
|
});
|
|
|
|
// XEP-0347 § 3.9 (ex28-29): <removed/>
|
|
connection.registerIQRequestHandler(new AbstractIqRequestHandler(IoTRemoved.ELEMENT, IoTRemoved.NAMESPACE, IQ.Type.set, Mode.async) {
|
|
@Override
|
|
public IQ handleIQRequest(IQ iqRequest) {
|
|
if (!isRegistry(iqRequest.getFrom())) {
|
|
LOGGER.log(Level.SEVERE, "Received control stanza from non-registry entity: " + iqRequest);
|
|
return null;
|
|
}
|
|
|
|
IoTRemoved iotRemoved = (IoTRemoved) iqRequest;
|
|
|
|
ThingState state = getStateFor(iotRemoved.getNodeInfo());
|
|
state.setRemoved();
|
|
|
|
// Unfriend registry. "It does this, so the Thing can remove the friendship and stop any
|
|
// meta data updates to the Registry."
|
|
try {
|
|
IoTProvisioningManager.getInstanceFor(connection()).unfriend(iotRemoved.getFrom());
|
|
}
|
|
catch (NotConnectedException | InterruptedException e) {
|
|
LOGGER.log(Level.SEVERE, "Could not unfriend registry after <removed/>", e);
|
|
}
|
|
|
|
return IQ.createResultIQ(iqRequest);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Try to find an XMPP IoT registry.
|
|
*
|
|
* @return the JID of a Thing Registry if one could be found, <code>null</code> otherwise.
|
|
* @throws InterruptedException
|
|
* @throws NotConnectedException
|
|
* @throws XMPPErrorException
|
|
* @throws NoResponseException
|
|
* @see <a href="http://xmpp.org/extensions/xep-0347.html#findingregistry">XEP-0347 § 3.5 Finding Thing Registry</a>
|
|
*/
|
|
public Jid findRegistry()
|
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
|
if (preconfiguredRegistry != null) {
|
|
return preconfiguredRegistry;
|
|
}
|
|
|
|
final XMPPConnection connection = connection();
|
|
ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection);
|
|
List<DiscoverInfo> discoverInfos = sdm.findServicesDiscoverInfo(Constants.IOT_DISCOVERY_NAMESPACE, true, true);
|
|
if (!discoverInfos.isEmpty()) {
|
|
return discoverInfos.get(0).getFrom();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Thing Registration - XEP-0347 § 3.6 - 3.8
|
|
|
|
public ThingState registerThing(Thing thing)
|
|
throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException, IoTClaimedException {
|
|
Jid registry = findRegistry();
|
|
return registerThing(registry, thing);
|
|
}
|
|
|
|
public ThingState registerThing(Jid registry, Thing thing)
|
|
throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException, IoTClaimedException {
|
|
final XMPPConnection connection = connection();
|
|
IoTRegister iotRegister = new IoTRegister(thing.getMetaTags(), thing.getNodeInfo(), thing.isSelfOwened());
|
|
iotRegister.setTo(registry);
|
|
IQ result = connection.createPacketCollectorAndSend(iotRegister).nextResultOrThrow();
|
|
if (result instanceof IoTClaimed) {
|
|
IoTClaimed iotClaimedResult = (IoTClaimed) result;
|
|
throw new IoTClaimedException(iotClaimedResult);
|
|
}
|
|
|
|
ThingState state = getStateFor(thing.getNodeInfo());
|
|
state.setRegistry(registry.asBareJid());
|
|
|
|
interactWithRegistry(registry);
|
|
|
|
IoTDataManager.getInstanceFor(connection).installThing(thing);
|
|
IoTControlManager.getInstanceFor(connection).installThing(thing);
|
|
|
|
return state;
|
|
}
|
|
|
|
// Thing Claiming - XEP-0347 § 3.9
|
|
|
|
public IoTClaimed claimThing(Collection<Tag> metaTags) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
|
return claimThing(metaTags, true);
|
|
}
|
|
|
|
public IoTClaimed claimThing(Collection<Tag> metaTags, boolean publicThing) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
|
Jid registry = findRegistry();
|
|
return claimThing(registry, metaTags, publicThing);
|
|
}
|
|
|
|
/**
|
|
* Claim a thing by providing a collection of meta tags. If the claim was successful, then a {@link IoTClaimed}
|
|
* instance will be returned, which contains the XMPP address of the thing. Use {@link IoTClaimed#getJid()} to
|
|
* retrieve this address.
|
|
*
|
|
* @param registry the registry use to claim the thing.
|
|
* @param metaTags a collection of meta tags used to identify the thing.
|
|
* @param publicThing if this is a public thing.
|
|
* @return a {@link IoTClaimed} if successful.
|
|
* @throws NoResponseException
|
|
* @throws XMPPErrorException
|
|
* @throws NotConnectedException
|
|
* @throws InterruptedException
|
|
*/
|
|
public IoTClaimed claimThing(Jid registry, Collection<Tag> metaTags, boolean publicThing) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
|
interactWithRegistry(registry);
|
|
|
|
IoTMine iotMine = new IoTMine(metaTags, publicThing);
|
|
iotMine.setTo(registry);
|
|
IoTClaimed iotClaimed = connection().createPacketCollectorAndSend(iotMine).nextResultOrThrow();
|
|
|
|
// The 'jid' attribute of the <claimed/> response now represents the XMPP address of the thing we just successfully claimed.
|
|
Jid thing = iotClaimed.getJid();
|
|
|
|
IoTProvisioningManager.getInstanceFor(connection()).sendFriendshipRequest(thing.asBareJid());
|
|
|
|
return iotClaimed;
|
|
}
|
|
|
|
// Thing Removal - XEP-0347 § 3.10
|
|
|
|
public void removeThing(BareJid thing)
|
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
|
removeThing(thing, NodeInfo.EMPTY);
|
|
}
|
|
|
|
public void removeThing(BareJid thing, NodeInfo nodeInfo)
|
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
|
Jid registry = findRegistry();
|
|
removeThing(registry, thing, nodeInfo);
|
|
}
|
|
|
|
public void removeThing(Jid registry, BareJid thing, NodeInfo nodeInfo)
|
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
|
interactWithRegistry(registry);
|
|
|
|
IoTRemove iotRemove = new IoTRemove(thing, nodeInfo);
|
|
iotRemove.setTo(registry);
|
|
connection().createPacketCollectorAndSend(iotRemove).nextResultOrThrow();
|
|
|
|
// We no not update the ThingState here, as this is done in the <removed/> IQ handler above.;
|
|
}
|
|
|
|
// Thing Unregistering - XEP-0347 § 3.16
|
|
|
|
public void unregister()
|
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
|
unregister(NodeInfo.EMPTY);
|
|
}
|
|
|
|
public void unregister(NodeInfo nodeInfo)
|
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
|
Jid registry = findRegistry();
|
|
unregister(registry, nodeInfo);
|
|
}
|
|
|
|
public void unregister(Jid registry, NodeInfo nodeInfo)
|
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
|
interactWithRegistry(registry);
|
|
|
|
IoTUnregister iotUnregister = new IoTUnregister(nodeInfo);
|
|
iotUnregister.setTo(registry);
|
|
connection().createPacketCollectorAndSend(iotUnregister).nextResultOrThrow();
|
|
|
|
ThingState state = getStateFor(nodeInfo);
|
|
state.setUnregistered();
|
|
|
|
final XMPPConnection connection = connection();
|
|
IoTDataManager.getInstanceFor(connection).uninstallThing(nodeInfo);
|
|
IoTControlManager.getInstanceFor(connection).uninstallThing(nodeInfo);
|
|
}
|
|
|
|
// Thing Disowning - XEP-0347 § 3.17
|
|
|
|
public void disownThing(Jid thing)
|
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
|
disownThing(thing, NodeInfo.EMPTY);
|
|
}
|
|
|
|
public void disownThing(Jid thing, NodeInfo nodeInfo)
|
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
|
Jid registry = findRegistry();
|
|
disownThing(registry, thing, nodeInfo);
|
|
}
|
|
|
|
public void disownThing(Jid registry, Jid thing, NodeInfo nodeInfo)
|
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
|
interactWithRegistry(registry);
|
|
|
|
IoTDisown iotDisown = new IoTDisown(thing, nodeInfo);
|
|
iotDisown.setTo(registry);
|
|
connection().createPacketCollectorAndSend(iotDisown).nextResultOrThrow();
|
|
}
|
|
|
|
// Registry utility methods
|
|
|
|
public boolean isRegistry(BareJid jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
|
Objects.requireNonNull(jid, "JID argument must not be null");
|
|
// At some point 'usedRegistries' will also contain the registry returned by findRegistry(), but since this is
|
|
// not the case from the beginning, we perform findRegistry().equals(jid) too.
|
|
Jid registry = findRegistry();
|
|
if (jid.equals(registry)) {
|
|
return true;
|
|
}
|
|
if (usedRegistries.contains(jid)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean isRegistry(Jid jid) {
|
|
try {
|
|
return isRegistry(jid.asBareJid());
|
|
}
|
|
catch (NoResponseException | XMPPErrorException | NotConnectedException
|
|
| InterruptedException e) {
|
|
LOGGER.log(Level.WARNING, "Could not determine if " + jid + " is a registry", e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void interactWithRegistry(Jid registry) throws NotConnectedException, InterruptedException {
|
|
boolean isNew = usedRegistries.add(registry);
|
|
if (!isNew) {
|
|
return;
|
|
}
|
|
IoTProvisioningManager iotProvisioningManager = IoTProvisioningManager.getInstanceFor(connection());
|
|
iotProvisioningManager.sendFriendshipRequestIfRequired(registry.asBareJid());
|
|
}
|
|
|
|
public ThingState getStateFor(Thing thing) {
|
|
return things.get(thing.getNodeInfo());
|
|
}
|
|
|
|
private ThingState getStateFor(NodeInfo nodeInfo) {
|
|
ThingState state = things.get(nodeInfo);
|
|
if (state == null) {
|
|
state = new ThingState(nodeInfo);
|
|
things.put(nodeInfo, state);
|
|
}
|
|
return state;
|
|
}
|
|
|
|
}
|