org.jivesoftware.smackx.iot.discovery.IoTDiscoveryManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of smack-experimental Show documentation
Show all versions of smack-experimental Show documentation
Smack experimental extensions.
Classes and methods for XEPs that are in status 'experimental' or that should
otherwise carefully considered for deployment. The API may change even
between patch versions.
/**
*
* 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 }
* @see XEP-0347: Internet of Things - Discovery
*
*/
public final class IoTDiscoveryManager extends Manager {
private static final Logger LOGGER = Logger.getLogger(IoTDiscoveryManager.class.getName());
private static final Map INSTANCES = new WeakHashMap<>();
// Ensure a IoTProvisioningManager exists for every connection.
static {
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
@Override
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 usedRegistries = new HashSet<>();
/**
* Internal state of the things. Uses null
for the single thing without node info attached.
*/
private final Map 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) {
@SuppressWarnings("ObjectToString")
@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 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 for " + nodeInfo + " but thing was not owned.");
}
return IQ.createResultIQ(iqRequest);
}
});
// XEP-0347 § 3.9 (ex28-29):
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 ", e);
}
return IQ.createResultIQ(iqRequest);
}
});
}
/**
* Try to find an XMPP IoT registry.
*
* @return the JID of a Thing Registry if one could be found, null
otherwise.
* @throws InterruptedException if the calling thread was interrupted.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @see XEP-0347 § 3.5 Finding Thing Registry
*/
public Jid findRegistry()
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
if (preconfiguredRegistry != null) {
return preconfiguredRegistry;
}
final XMPPConnection connection = connection();
ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection);
List 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.createStanzaCollectorAndSend(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 metaTags) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
return claimThing(metaTags, true);
}
public IoTClaimed claimThing(Collection 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 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 IoTClaimed claimThing(Jid registry, Collection metaTags, boolean publicThing) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
interactWithRegistry(registry);
IoTMine iotMine = new IoTMine(metaTags, publicThing);
iotMine.setTo(registry);
IoTClaimed iotClaimed = connection().createStanzaCollectorAndSend(iotMine).nextResultOrThrow();
// The 'jid' attribute of the 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().createStanzaCollectorAndSend(iotRemove).nextResultOrThrow();
// We no not update the ThingState here, as this is done in the 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().createStanzaCollectorAndSend(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().createStanzaCollectorAndSend(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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy