diff --git a/documentation/developer/integrationtest.md b/documentation/developer/integrationtest.md index c02691cac..6a82e2481 100644 --- a/documentation/developer/integrationtest.md +++ b/documentation/developer/integrationtest.md @@ -55,6 +55,8 @@ debug=true | accountOnePassword | Password of the first XMPP account | | accountTwoUsername | Username of the second XMPP account | | accountTwoPassword | Password of the second XMPP account | +| accountThreeUsername | Username of the third XMPP account | +| accountThreePassword | Password of the third XMPP account | | debug | 'true' to enable debug output | | enabledTests | List of enabled tests | | disabledTests | List of disabled tests | diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index 3bb98ce32..28c556c14 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -1,4 +1,4 @@ -Smack Extensions User Manual +smackSmack Extensions User Manual ============================ The XMPP protocol includes a base protocol and many optional extensions @@ -77,8 +77,12 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental | Name | XEP | Description | |---------------------------------------------|----------------------------------------------------------|----------------------------------------------------------------------------------------------------------| | Message Carbons | [XEP-0280](http://xmpp.org/extensions/xep-0280.html) | Keep all IM clients for a user engaged in a conversation, by carbon-copy outbound messages to all interested resources. -| [HTTP over XMPP transport](hoxt.md) | [XEP-0332](http://xmpp.org/extensions/xep-0332.html) | Allows to transport HTTP communication over XMPP peer-to-peer networks. | +| [Internet of Things - Sensor Data](iot.md) | [XEP-0323](http://xmpp.org/extensions/xep-0323.html) | Sensor data interchange over XMPP. | +| [Internet of Things - Provisioning](iot.md) | [XEP-0324](http://xmpp.org/extensions/xep-0324.html) | Provisioning, access rights and user priviliges for the Internet of Things. | +| [Internet of Things - Control](iot.md) | [XEP-0325](http://xmpp.org/extensions/xep-0325.html) | Describes how to control devices or actuators in an XMPP-based sensor netowrk. | +| [HTTP over XMPP transport](hoxt.md) | [XEP-0332](http://xmpp.org/extensions/xep-0332.html) | Allows to transport HTTP communication over XMPP peer-to-peer networks. | | JSON Containers | [XEP-0335](http://xmpp.org/extensions/xep-0335.html) | Encapsulation of JSON data within XMPP Stanzas. | +| [Internet of Things - Discovery](iot.md) | [XEP-0347](http://xmpp.org/extensions/xep-0347.html) | Describes how Things can be installed and discovered by their owners. | | Google GCM JSON payload | n/a | Semantically the same as XEP-0335: JSON Containers | | Client State Indication | [XEP-0352](http://xmpp.org/extensions/xep-0352.html) | A way for the client to indicate its active/inactive state. | diff --git a/documentation/extensions/iot.md b/documentation/extensions/iot.md new file mode 100644 index 000000000..6955cafe9 --- /dev/null +++ b/documentation/extensions/iot.md @@ -0,0 +1,116 @@ +Internet of Things (XEP-0323, -0324, -0325, -0347) +================================================== + +The Internet of Things (IoT) XEPs are an experimental open standard how XMPP can be used for IoT. They currently consists of +- XEP-0323 Sensor Data +- XEP-0324 Provisioning +- XEP-0325 Control +- XEP-0326 Concentrators +- XEP-0347 Discovery + +Smack only supports a subset of the functionality described by the XEPs! + +Thing Builder +------------- + +The `org.jivesoftware.smackx.iot.Thing` class acts as basic entity representing a single "Thing" which can used to retrieve data from or to send control commands to. `Things` are constructed using a builder API. + + +Reading data from things +------------------------ + +For example, we can build a Thing which provides the current temperature with + +```java +Thing dataThing = Thing.builder().setKey(key).setSerialNumber(sn).setMomentaryReadOutRequestHandler(new ThingMomentaryReadOutRequest() { + @Override + public void momentaryReadOutRequest(ThingMomentaryReadOutResult callback) { + int temp = getCurrentTemperature(); + IoTDataField.IntField field = new IntField("temperature", temp); + callback.momentaryReadOut(Collections.singletonList(field)); + } +}).build(); +``` + +While not strictly required, most things are identified via a key and serial number. We also build the thing with a "momentary read out request handler" which when triggered, retrieved the current temperature and reports it back to the requestor. + +After the `Thing` is build, it needs to be made available so that other entities within the federated XMPP network can use it. Right now, we only intall the Thing in the `IoTDataManager`, which means the thing will act on read out requests but not be managed by a provisioning server. + +```java +IoTDataManager iotDataManager = IoTDataManager.getInstanceFor(connection); +iotDataManager.installThing(thing); +``` + +The data can be read out also by using the `IoTDataManager`: + +```java +FullJid jid = … +List values = iotDataManager.requestMomentaryValuesReadOut(jid); +``` + +Now you have to unwrap the `IoTDataField` instances from the `IoTFieldsExtension`. Note that Smack currently only supports a subset of the specified data types. + +Controlling a thing +------------------- + +Things can also be controlled, e.g. to turn on a light. Let's create thing which can be used to turn the light on and off. + +```java +Thing controlThing = Thing.builder().setKey(key).setSerialNumber(sn).setControlRequestHandler(new ThingControlRequest() { + @Override + public void processRequest(Jid from, Collection setData) throws XMPPErrorException { + for (final SetData data : setData) { + if (!data.getName().equals("light")) continue; + if (!(data instanceof SetBoolData)) continue; + SetBoolData boolData = (SetBoolData) data; + setLight(boolData.getBooleanValue()); + } + } +}).build(); +``` + +No we have to install this thing into the `IoTControlManager`: + +```java +IoTControlManager iotControlManager = IoTControlManager.getInstanceFor(connection); +iotControlManager.installThing(thing); +``` + +The `IoTControlManager` can also be used to control a thing: + +```java +FullJid jid = … +SetData setData = new SetBoolData("light", true); +iotControlManager.setUsingIq(jid, setData); +``` + +Smack currently only supports a subset of the possible data types for set data. + +Discovery +--------- + +You may wondered how a full JIDs of things can be determined. One approach is using the discovery mechanisms specified in XEP-0347. Smack provides the `IoTDiscoveryManager` as API for this. + +For example, instead of just installing the previous things in the `IoTDataManager` and/or `IoTControlManager`, we could also use the `IoTDiscoveryManger` to register the thing with a registry. Doing thing also installs the thing in the `IoTDataManager` and the `IoTControlManager`. + +```java +IoTDiscoveryManager iotDiscoveryManager = IoTDiscoveryManager.getInstanceFor(connection); +iotDiscovyerManager.registerThing(thing); +``` + +The registry will now make the thing known to a broader audience, and available for a potential owner. + +The `IoTDiscoveryManager` can also be used to claim, disown, remove and unregister a thing. + +Provisioning +------------ + +Things can usually only be used by other things if they are friends. Since a thing normally can't decide on its own if an incoming friendship request should be granted or not, we can delegate this decission to a provisioning service. Smack provides the `IoTProvisinoManager` to deal with friendship and provisioning. + +For example, if you want to befriend another thing: + +```java +BareJid jid = … +IoTProvisioningManager iotProvisioningManager = IoTProvisioningManager.getInstanceFor(connection); +iotProvisioningManager.sendFriendshipRequest(jid); +``` diff --git a/smack-core/src/main/java/org/jivesoftware/smack/ReconnectionManager.java b/smack-core/src/main/java/org/jivesoftware/smack/ReconnectionManager.java index bf45530e3..90a5f9a6a 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/ReconnectionManager.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/ReconnectionManager.java @@ -238,7 +238,11 @@ public final class ReconnectionManager { // Makes a reconnection attempt try { if (isReconnectionPossible(connection)) { - connection.connect(); + try { + connection.connect(); + } catch (SmackException.AlreadyConnectedException e) { + LOGGER.log(Level.FINER, "Connection was already connected on reconnection attempt", e); + } } // TODO Starting with Smack 4.2, connect() will no // longer login automatically. So change this and the diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/EmptyResultIQ.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/EmptyResultIQ.java index c97575b8a..fcd268a19 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/EmptyResultIQ.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/EmptyResultIQ.java @@ -25,13 +25,7 @@ public class EmptyResultIQ extends IQ { public EmptyResultIQ(IQ request) { this(); - if (!(request.getType() == Type.get || request.getType() == Type.set)) { - throw new IllegalArgumentException( - "IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML()); - } - setStanzaId(request.getStanzaId()); - setFrom(request.getTo()); - setTo(request.getFrom()); + initialzeAsResultFor(request); } @Override diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/IQ.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/IQ.java index cf70ff27d..700fef0bc 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/IQ.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/IQ.java @@ -215,6 +215,17 @@ public abstract class IQ extends Stanza { */ protected abstract IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml); + protected final void initialzeAsResultFor(IQ request) { + if (!(request.getType() == Type.get || request.getType() == Type.set)) { + throw new IllegalArgumentException( + "IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML()); + } + setStanzaId(request.getStanzaId()); + setFrom(request.getTo()); + setTo(request.getFrom()); + setType(Type.result); + } + /** * Convenience method to create a new empty {@link Type#result IQ.Type.result} * IQ based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} @@ -289,9 +300,7 @@ public abstract class IQ extends Stanza { * @throws IllegalArgumentException if the IQ stanza(/packet) does not have a type of * {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}. * @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ. - * @deprecated use {@link #createErrorResponse(IQ, org.jivesoftware.smack.packet.XMPPError.Builder)} instead. */ - @Deprecated public static ErrorIQ createErrorResponse(final IQ request, final XMPPError error) { return createErrorResponse(request, XMPPError.getBuilder(error)); } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/Presence.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/Presence.java index 7804e7777..cd9aedf77 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/Presence.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/Presence.java @@ -73,6 +73,8 @@ public final class Presence extends Stanza implements TypedCloneable { * @param type the type. */ public Presence(Type type) { + // Ensure that the stanza ID is set by calling super(). + super(); setType(type); } @@ -85,6 +87,8 @@ public final class Presence extends Stanza implements TypedCloneable { * @param mode the mode type for this presence update. */ public Presence(Type type, String status, int priority, Mode mode) { + // Ensure that the stanza ID is set by calling super(). + super(); setType(type); setStatus(status); setPriority(priority); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java index 8defdd6d9..2a1ea14b7 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java @@ -23,6 +23,7 @@ import java.text.ParseException; import java.util.Date; import java.util.Locale; +import org.jivesoftware.smack.SmackException; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.Jid; import org.jxmpp.jid.impl.JidCreate; @@ -122,6 +123,14 @@ public class ParserUtils { } } + public static int getIntegerAttributeOrThrow(XmlPullParser parser, String name, String throwMessage) throws SmackException { + Integer res = getIntegerAttribute(parser, name); + if (res == null) { + throw new SmackException(throwMessage); + } + return res; + } + public static Integer getIntegerAttribute(XmlPullParser parser, String name) { String valueString = parser.getAttributeValue("", name); if (valueString == null) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java index 127382740..a0d545706 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java @@ -238,6 +238,10 @@ public class XmlStringBuilder implements Appendable, CharSequence { return this; } + public XmlStringBuilder attribute(String name, boolean bool) { + return attribute(name, Boolean.toString(bool)); + } + /** * Add a new attribute to this builder, with the {@link java.util.Date} instance as its value, * which will get formated with {@link XmppDateTime#formatXEP0082Date(Date)}. diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/IoTException.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/IoTException.java new file mode 100644 index 000000000..1317b5cb9 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/IoTException.java @@ -0,0 +1,28 @@ +/** + * + * 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; + +import org.jivesoftware.smack.SmackException; + +public class IoTException extends SmackException { + + /** + * + */ + private static final long serialVersionUID = 1L; + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/IoTManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/IoTManager.java new file mode 100644 index 000000000..276d2cb3e --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/IoTManager.java @@ -0,0 +1,42 @@ +/** + * + * 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; + +import java.util.Map; +import java.util.WeakHashMap; + +import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.XMPPConnection; + +public final class IoTManager extends Manager { + + private static final Map INSTANCES = new WeakHashMap<>(); + + public static synchronized IoTManager getInstanceFor(XMPPConnection connection) { + IoTManager manager = INSTANCES.get(connection); + if (manager == null) { + manager = new IoTManager(connection); + INSTANCES.put(connection, manager); + } + return manager; + } + + private IoTManager(XMPPConnection connection) { + super(connection); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/Thing.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/Thing.java new file mode 100644 index 000000000..f68a7ebd4 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/Thing.java @@ -0,0 +1,161 @@ +/** + * + * 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; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; + +import org.jivesoftware.smackx.iot.control.ThingControlRequest; +import org.jivesoftware.smackx.iot.data.ThingMomentaryReadOutRequest; +import org.jivesoftware.smackx.iot.discovery.element.Tag; +import org.jivesoftware.smackx.iot.discovery.element.Tag.Type; +import org.jivesoftware.smackx.iot.element.NodeInfo; + +public final class Thing { + + private final HashMap metaTags; + private final boolean selfOwned; + private final NodeInfo nodeInfo; + + private final ThingMomentaryReadOutRequest momentaryReadOutRequestHandler; + private final ThingControlRequest controlRequestHandler; + + private Thing(Builder builder) { + this.metaTags = builder.metaTags; + this.selfOwned = builder.selfOwned; + + this.nodeInfo = builder.nodeInfo; + + this.momentaryReadOutRequestHandler = builder.momentaryReadOutRequest; + this.controlRequestHandler = builder.controlRequest; + } + + public Collection getMetaTags() { + return metaTags.values(); + } + + public boolean isSelfOwened() { + return selfOwned; + } + + public NodeInfo getNodeInfo() { + return nodeInfo; + } + + public String getNodeId() { + return nodeInfo.getNodeId(); + } + + public String getSourceId() { + return nodeInfo.getSourceId(); + } + + public String getCacheType() { + return nodeInfo.getCacheType(); + } + + public ThingMomentaryReadOutRequest getMomentaryReadOutRequestHandler() { + return momentaryReadOutRequestHandler; + } + + public ThingControlRequest getControlRequestHandler() { + return controlRequestHandler; + } + + private String toStringCache; + + @Override + public String toString() { + if (toStringCache == null) { + StringBuilder sb = new StringBuilder(); + sb.append( "Thing " + nodeInfo + " ["); + Iterator it = metaTags.values().iterator(); + while (it.hasNext()) { + Tag tag = it.next(); + sb.append(tag); + if (it.hasNext()) { + sb.append(' '); + } + } + sb.append(']'); + toStringCache = sb.toString(); + } + return toStringCache; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private HashMap metaTags = new HashMap<>(); + private boolean selfOwned; + private NodeInfo nodeInfo = NodeInfo.EMPTY; + private ThingMomentaryReadOutRequest momentaryReadOutRequest; + private ThingControlRequest controlRequest; + + public Builder setSerialNumber(String sn) { + final String name = "SN"; + Tag tag = new Tag(name, Type.str, sn); + metaTags.put(name, tag); + return this; + } + + public Builder setKey(String key) { + final String name = "KEY"; + Tag tag = new Tag(name, Type.str, key); + metaTags.put(name, tag); + return this; + } + + public Builder setManufacturer(String manufacturer) { + final String name = "MAN"; + Tag tag = new Tag(name, Type.str, manufacturer); + metaTags.put(name, tag); + return this; + } + + public Builder setModel(String model) { + final String name = "MODEL"; + Tag tag = new Tag(name, Type.str, model); + metaTags.put(name, tag); + return this; + } + + public Builder setVersion(String version) { + final String name = "V"; + Tag tag = new Tag(name, Type.num, version); + metaTags.put(name, tag); + return this; + } + + public Builder setMomentaryReadOutRequestHandler(ThingMomentaryReadOutRequest momentaryReadOutRequestHandler) { + this.momentaryReadOutRequest = momentaryReadOutRequestHandler; + return this; + } + + public Builder setControlRequestHandler(ThingControlRequest controlRequest) { + this.controlRequest = controlRequest; + return this; + } + + public Thing build() { + return new Thing(this); + } + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/IoTControlManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/IoTControlManager.java new file mode 100644 index 000000000..2f347edeb --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/IoTControlManager.java @@ -0,0 +1,149 @@ +/** + * + * 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.control; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; + +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.XMPPException.XMPPErrorException; +import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler; +import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.iot.Thing; +import org.jivesoftware.smackx.iot.control.element.IoTSetRequest; +import org.jivesoftware.smackx.iot.control.element.IoTSetResponse; +import org.jivesoftware.smackx.iot.control.element.SetData; +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.jxmpp.jid.FullJid; + +/** + * A manger for XEP-0325: Internet of Things - Control. + * + * @author Florian Schmaus {@literal } + * @see XEP-0323: Internet of Things - Control + */ +public final class IoTControlManager extends Manager { + + private static final Map INSTANCES = new WeakHashMap<>(); + + /** + * Get the manger instance responsible for the given connection. + * + * @param connection the XMPP connection. + * @return a manager instance. + */ + public static synchronized IoTControlManager getInstanceFor(XMPPConnection connection) { + IoTControlManager manager = INSTANCES.get(connection); + if (manager == null) { + manager = new IoTControlManager(connection); + INSTANCES.put(connection, manager); + } + return manager; + } + + private final Map things = new ConcurrentHashMap<>(); + + private IoTControlManager(XMPPConnection connection) { + super(connection); + connection.registerIQRequestHandler(new AbstractIqRequestHandler(IoTSetRequest.ELEMENT, IoTSetRequest.NAMESPACE, IQ.Type.set, Mode.async) { + @Override + public IQ handleIQRequest(IQ iqRequest) { + // TODO Lookup thing and provide data. + IoTSetRequest iotSetRequest = (IoTSetRequest) iqRequest; + + // TODO Add support for multiple things(/NodeInfos). + final Thing thing = things.get(NodeInfo.EMPTY); + if (thing == null) { + // TODO return error if not at least one thing registered. + return null; + } + + ThingControlRequest controlRequest = thing.getControlRequestHandler(); + if (controlRequest == null) { + // TODO return error if no request handler for things. + return null; + } + + try { + controlRequest.processRequest(iotSetRequest.getFrom(), iotSetRequest.getSetData()); + } catch (XMPPErrorException e) { + return IQ.createErrorResponse(iotSetRequest, e.getXMPPError()); + } + + return new IoTSetResponse(iotSetRequest); + } + }); + } + + /** + * Control a thing by sending a collection of {@link SetData} instructions. + * + * @param jid + * @param data + * @return + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + * @see #setUsingIq(FullJid, Collection) + */ + public IoTSetResponse setUsingIq(FullJid jid, SetData data) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + return setUsingIq(jid, Collections.singleton(data)); + } + + /** + * Control a thing by sending a collection of {@link SetData} instructions. + * + * @param jid the thing to control. + * @param data a collection of {@link SetData} instructions. + * @return the {@link IoTSetResponse} if successful. + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + */ + public IoTSetResponse setUsingIq(FullJid jid, Collection data) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + IoTSetRequest request = new IoTSetRequest(data); + request.setTo(jid); + IoTSetResponse response = connection().createPacketCollectorAndSend(request).nextResultOrThrow(); + return response; + } + + /** + * Install a thing in the manager. Activates control functionality (if provided by the thing). + * + * @param thing the thing to install. + */ + public void installThing(Thing thing) { + things.put(thing.getNodeInfo(), thing); + } + + public Thing uninstallThing(Thing thing) { + return uninstallThing(thing.getNodeInfo()); + } + + public Thing uninstallThing(NodeInfo nodeInfo) { + return things.remove(nodeInfo); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/ThingControlRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/ThingControlRequest.java new file mode 100644 index 000000000..3af221a83 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/ThingControlRequest.java @@ -0,0 +1,29 @@ +/** + * + * 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.control; + +import java.util.Collection; + +import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smackx.iot.control.element.SetData; +import org.jxmpp.jid.Jid; + +public interface ThingControlRequest { + + public void processRequest(Jid from, Collection setData) throws XMPPErrorException; + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/Constants.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/Constants.java new file mode 100644 index 000000000..a81345b03 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/Constants.java @@ -0,0 +1,23 @@ +/** + * + * 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.control.element; + +public class Constants { + + public static final String IOT_CONTROL_NAMESPACE = "urn:xmpp:iot:control"; + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/IoTSetRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/IoTSetRequest.java new file mode 100644 index 000000000..dca7293e4 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/IoTSetRequest.java @@ -0,0 +1,48 @@ +/** + * + * 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.control.element; + +import java.util.Collection; +import java.util.Collections; + +import org.jivesoftware.smack.packet.IQ; + +public class IoTSetRequest extends IQ { + + public static final String ELEMENT = "set"; + public static final String NAMESPACE = Constants.IOT_CONTROL_NAMESPACE; + + private final Collection setData; + + public IoTSetRequest(Collection setData) { + super(ELEMENT, NAMESPACE); + setType(Type.set); + this.setData = Collections.unmodifiableCollection(setData); + } + + public Collection getSetData() { + return setData; + } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + xml.rightAngleBracket(); + xml.append(setData); + return xml; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/IoTSetResponse.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/IoTSetResponse.java new file mode 100644 index 000000000..a742355af --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/IoTSetResponse.java @@ -0,0 +1,41 @@ +/** + * + * 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.control.element; + +import org.jivesoftware.smack.packet.IQ; + +public class IoTSetResponse extends IQ { + + public static final String ELEMENT = "setResponse"; + public static final String NAMESPACE = Constants.IOT_CONTROL_NAMESPACE; + + public IoTSetResponse() { + super(ELEMENT, NAMESPACE); + } + + public IoTSetResponse(IoTSetRequest iotSetRequest) { + this(); + initialzeAsResultFor(iotSetRequest); + } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + xml.setEmptyElement(); + return xml; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetBoolData.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetBoolData.java new file mode 100644 index 000000000..de1d27812 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetBoolData.java @@ -0,0 +1,38 @@ +/** + * + * 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.control.element; + +public class SetBoolData extends SetData { + + public SetBoolData(String name, boolean value) { + this(name, Boolean.toString(value)); + booleanCache = value; + } + + protected SetBoolData(String name, String value) { + super(name, Type.BOOL, value); + } + + private Boolean booleanCache; + + public Boolean getBooleanValue() { + if (booleanCache != null) { + booleanCache = Boolean.valueOf(getValue()); + } + return booleanCache; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetData.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetData.java new file mode 100644 index 000000000..1e7505a83 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetData.java @@ -0,0 +1,92 @@ +/** + * + * 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.control.element; + +import java.util.Locale; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public abstract class SetData implements NamedElement { + + public enum Type { + BOOL, + INT, + LONG, + DOUBLE, + ; + + private String toStringCache; + + private Type() { + toStringCache = this.name().toLowerCase(Locale.US); + } + + @Override + public String toString() { + return toStringCache; + } + } + + protected SetData(String name, Type type, String value) { + this.name = name; + this.type = type; + this.value = value; + } + + private final String name; + + private final Type type; + + private final String value; + + public final String getName() { + return name; + } + + public final String getValue() { + return value; + } + + public final Type getType() { + return type; + } + + /** + * Returns the root element name. + * + * @return the element name. + */ + @Override + public final String getElementName() { + return getType().toString(); + } + + /** + * Returns the XML representation of this Element. + * + * @return the stanza(/packet) extension as XML. + */ + @Override + public final XmlStringBuilder toXML() { + XmlStringBuilder xml = new XmlStringBuilder(this); + xml.attribute("name", name); + xml.attribute("value", value); + xml.closeEmptyElement(); + return xml; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetDoubleData.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetDoubleData.java new file mode 100644 index 000000000..f2e062901 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetDoubleData.java @@ -0,0 +1,38 @@ +/** + * + * 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.control.element; + +public class SetDoubleData extends SetData { + + public SetDoubleData(String name, double value) { + this(name, Double.toString(value)); + doubleCache = value; + } + + protected SetDoubleData(String name, String value) { + super(name, Type.DOUBLE, value); + } + + private Double doubleCache; + + public Double getDoubleValue() { + if (doubleCache != null) { + doubleCache = Double.valueOf(getValue()); + } + return doubleCache; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetIntData.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetIntData.java new file mode 100644 index 000000000..e704c08cc --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetIntData.java @@ -0,0 +1,38 @@ +/** + * + * 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.control.element; + +public class SetIntData extends SetData { + + public SetIntData(String name, int value) { + this(name, Integer.toString(value)); + integerCache = value; + } + + protected SetIntData(String name, String value) { + super(name, Type.INT, value); + } + + private Integer integerCache; + + public Integer getIntegerValue() { + if (integerCache != null) { + integerCache = Integer.valueOf(getValue()); + } + return integerCache; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetLongData.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetLongData.java new file mode 100644 index 000000000..736e97500 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/SetLongData.java @@ -0,0 +1,38 @@ +/** + * + * 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.control.element; + +public class SetLongData extends SetData { + + public SetLongData(String name, long value) { + this(name, Long.toString(value)); + longCache = value; + } + + protected SetLongData(String name, String value) { + super(name, Type.LONG, value); + } + + private Long longCache; + + public Long getLongValue() { + if (longCache != null) { + longCache = Long.valueOf(getValue()); + } + return longCache; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/package-info.java new file mode 100644 index 000000000..e14c7bce5 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/element/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot.control.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/package-info.java new file mode 100644 index 000000000..c5d9dac3e --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot.control; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/IoTSetRequestProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/IoTSetRequestProvider.java new file mode 100644 index 000000000..fdd5a63d7 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/IoTSetRequestProvider.java @@ -0,0 +1,82 @@ +/** + * + * 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.control.provider; + +import java.util.ArrayList; +import java.util.List; + +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.iot.control.element.IoTSetRequest; +import org.jivesoftware.smackx.iot.control.element.SetBoolData; +import org.jivesoftware.smackx.iot.control.element.SetData; +import org.jivesoftware.smackx.iot.control.element.SetDoubleData; +import org.jivesoftware.smackx.iot.control.element.SetIntData; +import org.jivesoftware.smackx.iot.control.element.SetLongData; +import org.xmlpull.v1.XmlPullParser; + +public class IoTSetRequestProvider extends IQProvider { + + @Override + public IoTSetRequest parse(XmlPullParser parser, int initialDepth) throws Exception { + List data = new ArrayList<>(4); + outerloop: while (true) { + final int eventType = parser.next(); + final String name = parser.getName(); + switch (eventType) { + case XmlPullParser.START_TAG: + switch (name) { + case "bool": { + String valueName = parser.getAttributeValue(null, "name"); + String valueString = parser.getAttributeValue(null, "value"); + boolean value = Boolean.parseBoolean(valueString); + data.add(new SetBoolData(valueName, value)); + } + break; + case "double": { + String valueName = parser.getAttributeValue(null, "name"); + String valueString = parser.getAttributeValue(null, "value"); + double value = Double.parseDouble(valueString); + data.add(new SetDoubleData(valueName, value)); + } + break; + case "int": { + String valueName = parser.getAttributeValue(null, "name"); + String valueString = parser.getAttributeValue(null, "value"); + int value = Integer.parseInt(valueString); + data.add(new SetIntData(valueName, value)); + } + break; + case "long": { + String valueName = parser.getAttributeValue(null, "name"); + String valueString = parser.getAttributeValue(null, "value"); + long value = Long.parseLong(valueString); + data.add(new SetLongData(valueName, value)); + } + break; + } + break; + case XmlPullParser.END_TAG: + if (parser.getDepth() == initialDepth) { + break outerloop; + } + break; + } + } + return new IoTSetRequest(data); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/IoTSetResponseProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/IoTSetResponseProvider.java new file mode 100644 index 000000000..7de60f00f --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/IoTSetResponseProvider.java @@ -0,0 +1,30 @@ +/** + * + * 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.control.provider; + +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.iot.control.element.IoTSetResponse; +import org.xmlpull.v1.XmlPullParser; + +public class IoTSetResponseProvider extends IQProvider { + + @Override + public IoTSetResponse parse(XmlPullParser parser, int initialDepth) throws Exception { + return new IoTSetResponse(); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/package-info.java new file mode 100644 index 000000000..c82af520a --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot.control.provider; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/IoTDataManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/IoTDataManager.java new file mode 100644 index 000000000..065893121 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/IoTDataManager.java @@ -0,0 +1,210 @@ +/** + * + * 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.data; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.ConnectionCreationListener; +import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.PacketCollector; +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.filter.StanzaFilter; +import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler; +import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.iot.Thing; +import org.jivesoftware.smackx.iot.data.element.IoTDataField; +import org.jivesoftware.smackx.iot.data.element.IoTDataReadOutAccepted; +import org.jivesoftware.smackx.iot.data.element.IoTDataRequest; +import org.jivesoftware.smackx.iot.data.element.IoTFieldsExtension; +import org.jivesoftware.smackx.iot.data.filter.IoTFieldsExtensionFilter; +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.jxmpp.jid.EntityFullJid; + +/** + * A manager for XEP-0323: Internet of Things - Sensor Data. + * + * @author Florian Schmaus {@literal } + * @see XEP-0323: Internet of Things - Sensor Data + */ +public final class IoTDataManager extends Manager { + + private static final Logger LOGGER = Logger.getLogger(IoTDataManager.class.getName()); + + private static final Map INSTANCES = new WeakHashMap<>(); + + // Ensure a IoTDataManager exists for every connection. + static { + XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { + public void connectionCreated(XMPPConnection connection) { + getInstanceFor(connection); + } + }); + } + + /** + * Get the manger instance responsible for the given connection. + * + * @param connection the XMPP connection. + * @return a manager instance. + */ + public static synchronized IoTDataManager getInstanceFor(XMPPConnection connection) { + IoTDataManager manager = INSTANCES.get(connection); + if (manager == null) { + manager = new IoTDataManager(connection); + INSTANCES.put(connection, manager); + } + return manager; + } + + private final AtomicInteger nextSeqNr = new AtomicInteger(); + + private final Map things = new ConcurrentHashMap<>(); + + private IoTDataManager(XMPPConnection connection) { + super(connection); + + connection.registerIQRequestHandler(new AbstractIqRequestHandler(IoTDataRequest.ELEMENT, + IoTDataRequest.NAMESPACE, IQ.Type.get, Mode.async) { + @Override + public IQ handleIQRequest(IQ iqRequest) { + // TODO Verify that iqRequest.from is friend? + + final IoTDataRequest dataRequest = (IoTDataRequest) iqRequest; + + if (!dataRequest.isMomentary()) { + // TODO return error IQ that non momentary requests are not implemented yet. + return null; + } + + // TODO Add support for multiple things(/NodeInfos). + final Thing thing = things.get(NodeInfo.EMPTY); + if (thing == null) { + // TODO return error if not at least one thing registered. + return null; + } + + ThingMomentaryReadOutRequest readOutRequest = thing.getMomentaryReadOutRequestHandler(); + if (readOutRequest == null) { + // TODO Thing does not provide momentary read-out + return null; + } + + // Callback hell begins here. :) XEP-0323 decouples the read-out results from the IQ result. I'm not + // sure if I would have made the same design decision but the reasons where likely being able to get a + // fast read-out acknowledgement back to the requester even with sensors that take "a long time" to + // read-out their values. I had designed that as special case and made the "results in IQ response" the + // normal case. + readOutRequest.momentaryReadOutRequest(new ThingMomentaryReadOutResult() { + @Override + public void momentaryReadOut(List results) { + IoTFieldsExtension iotFieldsExtension = IoTFieldsExtension.buildFor(dataRequest.getSequenceNr(), true, thing.getNodeInfo(), results); + Message message = new Message(dataRequest.getFrom()); + message.addExtension(iotFieldsExtension); + try { + connection().sendStanza(message); + } + catch (NotConnectedException | InterruptedException e) { + LOGGER.log(Level.SEVERE, "Could not send read-out response " + message, e); + } + } + }); + + return new IoTDataReadOutAccepted(dataRequest); + } + }); + } + + /** + * Install a thing in the manager. Activates data read out functionality (if provided by the + * thing). + * + * @param thing the thing to install. + */ + public void installThing(Thing thing) { + things.put(thing.getNodeInfo(), thing); + } + + public Thing uninstallThing(Thing thing) { + return uninstallThing(thing.getNodeInfo()); + } + + public Thing uninstallThing(NodeInfo nodeInfo) { + return things.remove(nodeInfo); + } + + /** + * Try to read out a things momentary values. + * + * @param jid the full JID of the thing to read data from. + * @return a list with the read out data. + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + */ + public List requestMomentaryValuesReadOut(EntityFullJid jid) + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + final XMPPConnection connection = connection(); + final int seqNr = nextSeqNr.incrementAndGet(); + IoTDataRequest iotDataRequest = new IoTDataRequest(seqNr, true); + iotDataRequest.setTo(jid); + + StanzaFilter doneFilter = new IoTFieldsExtensionFilter(seqNr, true); + StanzaFilter dataFilter = new IoTFieldsExtensionFilter(seqNr, false); + + // Setup the IoTFieldsExtension message collectors before sending the IQ to avoid a data race. + PacketCollector doneCollector = connection.createPacketCollector(doneFilter); + + PacketCollector.Configuration dataCollectorConfiguration = PacketCollector.newConfiguration().setStanzaFilter( + dataFilter).setCollectorToReset(doneCollector); + PacketCollector dataCollector = connection.createPacketCollector(dataCollectorConfiguration); + + try { + connection.createPacketCollectorAndSend(iotDataRequest).nextResultOrThrow(); + // Wait until a message with an IoTFieldsExtension and the done flag comes in. + doneCollector.nextResult(); + } + finally { + // Ensure that the two collectors are canceled in any case. + dataCollector.cancel(); + doneCollector.cancel(); + } + + int collectedCount = dataCollector.getCollectedCount(); + List res = new ArrayList<>(collectedCount); + for (int i = 0; i < collectedCount; i++) { + Message message = dataCollector.pollResult(); + IoTFieldsExtension iotFieldsExtension = IoTFieldsExtension.from(message); + res.add(iotFieldsExtension); + } + + return res; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/ThingMomentaryReadOutRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/ThingMomentaryReadOutRequest.java new file mode 100644 index 000000000..f7d2a1b59 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/ThingMomentaryReadOutRequest.java @@ -0,0 +1,23 @@ +/** + * + * 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.data; + +public interface ThingMomentaryReadOutRequest { + + public void momentaryReadOutRequest(ThingMomentaryReadOutResult callback); + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/ThingMomentaryReadOutResult.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/ThingMomentaryReadOutResult.java new file mode 100644 index 000000000..4662552af --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/ThingMomentaryReadOutResult.java @@ -0,0 +1,27 @@ +/** + * + * 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.data; + +import java.util.List; + +import org.jivesoftware.smackx.iot.data.element.IoTDataField; + +public interface ThingMomentaryReadOutResult { + + public void momentaryReadOut(List results); + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/Constants.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/Constants.java new file mode 100644 index 000000000..5efe5a058 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/Constants.java @@ -0,0 +1,23 @@ +/** + * + * 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.data.element; + +public class Constants { + + public static final String IOT_SENSORDATA_NAMESPACE = "urn:xmpp:iot:data"; + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/IoTDataField.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/IoTDataField.java new file mode 100644 index 000000000..eb558571b --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/IoTDataField.java @@ -0,0 +1,111 @@ +/** + * + * 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.data.element; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public abstract class IoTDataField implements NamedElement { + + enum Type { + integer("int"), + bool("boolean"), + ; + + Type(String stringRepresentation) { + this.stringRepresentation = stringRepresentation; + } + + private final String stringRepresentation; + } + + private final Type type; + + private final String name; + + protected IoTDataField(Type type, String name) { + this.type = type; + this.name = name; + } + + public final String getName() { + return name; + } + + @Override + public final String getElementName() { + return type.stringRepresentation; + } + + @Override + public final XmlStringBuilder toXML() { + XmlStringBuilder xml = new XmlStringBuilder(this); + xml.attribute("name", name).attribute("value", getValueString()); + // TODO handle 'unit' attribute as special case if is implemented. + xml.closeEmptyElement(); + return xml; + } + + private String valueString; + + public final String getValueString() { + if (valueString == null) { + valueString = getValueInternal(); + } + return valueString; + } + + protected abstract String getValueInternal(); + + public static class IntField extends IoTDataField { + + private final int value; + + public IntField(String name, int value) { + super(Type.integer, name); + this.value = value; + } + + @Override + protected String getValueInternal() { + return Integer.toString(value); + } + + public int getValue() { + return value; + } + } + + public static class BooleanField extends IoTDataField { + + private final boolean value; + + public BooleanField(String name, boolean value) { + super(Type.bool, name); + this.value = value; + } + + @Override + protected String getValueInternal() { + return Boolean.toString(value); + } + + public boolean getValue() { + return value; + } + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/IoTDataReadOutAccepted.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/IoTDataReadOutAccepted.java new file mode 100644 index 000000000..20d0a125d --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/IoTDataReadOutAccepted.java @@ -0,0 +1,54 @@ +/** + * + * 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.data.element; + +import org.jivesoftware.smack.packet.IQ; + +public class IoTDataReadOutAccepted extends IQ { + + public static final String ELEMENT = "accepted"; + public static final String NAMESPACE = Constants.IOT_SENSORDATA_NAMESPACE; + + /** + * The sequence number. According to XEP-0323 an xs:int. + */ + private final int seqNr; + + private final boolean queued; + + public IoTDataReadOutAccepted(int seqNr, boolean queued) { + super(ELEMENT, NAMESPACE); + this.seqNr = seqNr; + this.queued = queued; + setType(Type.result); + } + + public IoTDataReadOutAccepted(IoTDataRequest dataRequest) { + this(dataRequest.getSequenceNr(), false); + setStanzaId(dataRequest.getStanzaId()); + setTo(dataRequest.getFrom()); + } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + xml.attribute("seqnr", seqNr); + xml.optBooleanAttribute("queued", queued); + xml.setEmptyElement(); + return xml; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/IoTDataRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/IoTDataRequest.java new file mode 100644 index 000000000..70f3df80d --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/IoTDataRequest.java @@ -0,0 +1,54 @@ +/** + * + * 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.data.element; + +import org.jivesoftware.smack.packet.IQ; + +public class IoTDataRequest extends IQ { + + public static final String ELEMENT = "req"; + public static final String NAMESPACE = Constants.IOT_SENSORDATA_NAMESPACE; + + /** + * The sequence nummber. According to XEP-0323 an xs:int. + */ + private final int seqNr; + + private final boolean momentary; + + public IoTDataRequest(int seqNr, boolean momentary) { + super(ELEMENT, NAMESPACE); + this.seqNr = seqNr; + this.momentary = momentary; + } + + public int getSequenceNr() { + return seqNr; + } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + xml.attribute("seqnr", seqNr); + xml.optBooleanAttribute("momentary", momentary); + xml.setEmptyElement(); + return xml; + } + + public boolean isMomentary() { + return momentary; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/IoTFieldsExtension.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/IoTFieldsExtension.java new file mode 100644 index 000000000..2a9651f49 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/IoTFieldsExtension.java @@ -0,0 +1,92 @@ +/** + * + * 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.data.element; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.iot.element.NodeInfo; + +public class IoTFieldsExtension implements ExtensionElement { + + public static final String ELEMENT = "fields"; + public static final String NAMESPACE = Constants.IOT_SENSORDATA_NAMESPACE; + + private final int seqNr; + private final boolean done; + private final List nodes; + + public IoTFieldsExtension(int seqNr, boolean done, NodeElement node) { + this(seqNr, done, Collections.singletonList(node)); + } + + public IoTFieldsExtension(int seqNr, boolean done, List nodes) { + this.seqNr = seqNr; + this.done = done; + this.nodes = Collections.unmodifiableList(nodes); + } + + public int getSequenceNr() { + return seqNr; + } + + public boolean isDone() { + return done; + } + + public List getNodes() { + return nodes; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public XmlStringBuilder toXML() { + XmlStringBuilder xml = new XmlStringBuilder(this); + xml.attribute("seqnr", Integer.toString(seqNr)); + xml.attribute("done", done); + xml.rightAngleBracket(); + + xml.append(nodes); + + xml.closeElement(this); + return xml; + } + + public static IoTFieldsExtension buildFor(int seqNr, boolean done, NodeInfo nodeInfo, + List data) { + TimestampElement timestampElement = new TimestampElement(new Date(), data); + NodeElement nodeElement = new NodeElement(nodeInfo, timestampElement); + return new IoTFieldsExtension(seqNr, done, nodeElement); + } + + public static IoTFieldsExtension from(Message message) { + return message.getExtension(ELEMENT, NAMESPACE); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/NodeElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/NodeElement.java new file mode 100644 index 000000000..495366896 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/NodeElement.java @@ -0,0 +1,63 @@ +/** + * + * 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.data.element; + +import java.util.Collections; +import java.util.List; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.iot.element.NodeInfo; + +public class NodeElement implements NamedElement { + + public static final String ELEMENT = "node"; + + private final NodeInfo nodeInfo; + private final List timestampElements; + + public NodeElement(NodeInfo nodeInfo, TimestampElement timestampElement) { + this(nodeInfo, Collections.singletonList(timestampElement)); + } + + public NodeElement(NodeInfo nodeInfo, List timestampElements) { + this.nodeInfo = nodeInfo; + this.timestampElements = Collections.unmodifiableList(timestampElements); + } + + public List getTimestampElements() { + return timestampElements; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public XmlStringBuilder toXML() { + XmlStringBuilder xml = new XmlStringBuilder(this); + nodeInfo.appendTo(xml); + xml.rightAngleBracket(); + + xml.append(timestampElements); + + xml.closeElement(this); + return xml; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/TimestampElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/TimestampElement.java new file mode 100644 index 000000000..bbc1c9389 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/TimestampElement.java @@ -0,0 +1,59 @@ +/** + * + * 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.data.element; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class TimestampElement implements NamedElement { + + public static final String ELEMENT = "timestamp"; + + private final Date date; + private final List fields; + + public TimestampElement(Date date, List fields) { + this.date = date; + this.fields = Collections.unmodifiableList(fields); + } + + public List getDataFields() { + return fields; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public XmlStringBuilder toXML() { + XmlStringBuilder xml = new XmlStringBuilder(this); + xml.attribute("value", date); + xml.rightAngleBracket(); + + xml.append(fields); + + xml.closeElement(this); + return xml; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/package-info.java new file mode 100644 index 000000000..9302bcc00 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/element/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot.data.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/filter/IoTFieldsExtensionFilter.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/filter/IoTFieldsExtensionFilter.java new file mode 100644 index 000000000..d9a1b484a --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/filter/IoTFieldsExtensionFilter.java @@ -0,0 +1,48 @@ +/** + * + * 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.data.filter; + +import org.jivesoftware.smack.filter.FlexibleStanzaTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.iot.data.element.IoTFieldsExtension; + +public class IoTFieldsExtensionFilter extends FlexibleStanzaTypeFilter { + + private final int seqNr; + private final boolean onlyDone; + + public IoTFieldsExtensionFilter(int seqNr, boolean onlyDone) { + this.seqNr = seqNr; + this.onlyDone = onlyDone; + } + + @Override + protected boolean acceptSpecific(Message message) { + IoTFieldsExtension iotFieldsExtension = IoTFieldsExtension.from(message); + if (iotFieldsExtension == null) { + return false; + } + if (iotFieldsExtension.getSequenceNr() != seqNr) { + return false; + } + if (onlyDone && !iotFieldsExtension.isDone()) { + return false; + } + return true; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/filter/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/filter/package-info.java new file mode 100644 index 000000000..20aa3720e --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/filter/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot.data.filter; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/package-info.java new file mode 100644 index 000000000..981183ead --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot.data; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTDataReadOutAcceptedProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTDataReadOutAcceptedProvider.java new file mode 100644 index 000000000..7d2769493 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTDataReadOutAcceptedProvider.java @@ -0,0 +1,33 @@ +/** + * + * 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.data.provider; + +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.iot.data.element.IoTDataReadOutAccepted; +import org.xmlpull.v1.XmlPullParser; + +public class IoTDataReadOutAcceptedProvider extends IQProvider { + + @Override + public IoTDataReadOutAccepted parse(XmlPullParser parser, int initialDepth) throws Exception { + int seqNr = ParserUtils.getIntegerAttributeOrThrow(parser, "seqnr", "IoT data request without sequence number"); + boolean queued = ParserUtils.getBooleanAttribute(parser, "queued", false); + return new IoTDataReadOutAccepted(seqNr, queued); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTDataRequestProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTDataRequestProvider.java new file mode 100644 index 000000000..575d746bf --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTDataRequestProvider.java @@ -0,0 +1,33 @@ +/** + * + * 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.data.provider; + +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.iot.data.element.IoTDataRequest; +import org.xmlpull.v1.XmlPullParser; + +public class IoTDataRequestProvider extends IQProvider { + + @Override + public IoTDataRequest parse(XmlPullParser parser, int initialDepth) throws Exception { + int seqNr = ParserUtils.getIntegerAttributeOrThrow(parser, "seqnr", "IoT data request without sequence number"); + boolean momentary = ParserUtils.getBooleanAttribute(parser, "momentary", false); + return new IoTDataRequest(seqNr, momentary); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTFieldsExtensionProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTFieldsExtensionProvider.java new file mode 100644 index 000000000..cd3ef6afe --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTFieldsExtensionProvider.java @@ -0,0 +1,136 @@ +/** + * + * 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.data.provider; + +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.logging.Logger; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.iot.data.element.IoTDataField; +import org.jivesoftware.smackx.iot.data.element.IoTFieldsExtension; +import org.jivesoftware.smackx.iot.data.element.NodeElement; +import org.jivesoftware.smackx.iot.data.element.TimestampElement; +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.jivesoftware.smackx.iot.parser.NodeInfoParser; +import org.jxmpp.util.XmppDateTime; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +public class IoTFieldsExtensionProvider extends ExtensionElementProvider { + + private static final Logger LOGGER = Logger.getLogger(IoTFieldsExtensionProvider.class.getName()); + + @Override + public IoTFieldsExtension parse(XmlPullParser parser, int initialDepth) throws Exception { + int seqNr = ParserUtils.getIntegerAttributeOrThrow(parser, "seqnr", "IoT data request without sequence number"); + boolean done = ParserUtils.getBooleanAttribute(parser, "done", false); + List nodes = new ArrayList<>(); + outerloop: while (true) { + final int eventType = parser.next(); + final String name = parser.getName(); + switch (eventType) { + case XmlPullParser.START_TAG: + switch (name) { + case NodeElement.ELEMENT: + NodeElement node = parseNode(parser); + nodes.add(node); + break; + } + break; + case XmlPullParser.END_TAG: + if (parser.getDepth() == initialDepth) { + break outerloop; + } + break; + } + } + return new IoTFieldsExtension(seqNr, done, nodes); + } + + public NodeElement parseNode(XmlPullParser parser) throws XmlPullParserException, IOException, ParseException { + final int initialDepth = parser.getDepth(); + final NodeInfo nodeInfo = NodeInfoParser.parse(parser); + List timestampElements = new ArrayList<>(); + outerloop: while (true) { + final int eventType = parser.next(); + final String name = parser.getName(); + switch (eventType) { + case XmlPullParser.START_TAG: + switch (name){ + case TimestampElement.ELEMENT: + TimestampElement timestampElement = parseTimestampElement(parser); + timestampElements.add(timestampElement); + break; + } + break; + case XmlPullParser.END_TAG: + if (parser.getDepth() == initialDepth) { + break outerloop; + } + break; + } + } + return new NodeElement(nodeInfo, timestampElements); + } + + public TimestampElement parseTimestampElement(XmlPullParser parser) throws ParseException, XmlPullParserException, IOException { + final int initialDepth = parser.getDepth(); + final String dateString = parser.getAttributeValue(null, "value"); + final Date date = XmppDateTime.parseDate(dateString); + List fields = new ArrayList<>(); + outerloop: while (true) { + final int eventType = parser.next(); + final String name = parser.getName(); + switch (eventType) { + case XmlPullParser.START_TAG: + IoTDataField field = null; + final String fieldName = parser.getAttributeValue(null, "name"); + final String fieldValue = parser.getAttributeValue(null, "value"); + switch (name) { + case "int": { + int value = Integer.parseInt(fieldValue); + field = new IoTDataField.IntField(fieldName, value); + } + break; + case "boolean": { + boolean value = Boolean.parseBoolean(fieldValue); + field = new IoTDataField.BooleanField(fieldName, value); + } + break; + default: + LOGGER.warning("IoT Data field type '" + name + "' not implement yet. Ignoring."); + break; + } + if (field != null) { + fields.add(field); + } + break; + case XmlPullParser.END_TAG: + if (parser.getDepth() == initialDepth) { + break outerloop; + } + break; + } + } + return new TimestampElement(date, fields); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/package-info.java new file mode 100644 index 000000000..41cd9dc5e --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot.data.provider; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/AbstractThingStateChangeListener.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/AbstractThingStateChangeListener.java new file mode 100644 index 000000000..32015a981 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/AbstractThingStateChangeListener.java @@ -0,0 +1,27 @@ +/** + * + * 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 org.jxmpp.jid.BareJid; + +public abstract class AbstractThingStateChangeListener implements ThingStateChangeListener { + + @Override + public void owned(BareJid owner) { + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/IoTClaimedException.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/IoTClaimedException.java new file mode 100644 index 000000000..c8b8ff76f --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/IoTClaimedException.java @@ -0,0 +1,38 @@ +/** + * + * 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 org.jivesoftware.smackx.iot.IoTException; +import org.jivesoftware.smackx.iot.discovery.element.IoTClaimed; + +public class IoTClaimedException extends IoTException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private final IoTClaimed iotClaimed; + + public IoTClaimedException(IoTClaimed iotClaimed) { + this.iotClaimed = iotClaimed; + } + + public IoTClaimed getIoTClaimed() { + return iotClaimed; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/IoTDiscoveryManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/IoTDiscoveryManager.java new file mode 100644 index 000000000..8851f84e1 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/IoTDiscoveryManager.java @@ -0,0 +1,420 @@ +/** + * + * 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.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.disco.packet.DiscoverInfo; +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() { + public void connectionCreated(XMPPConnection connection) { + 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) { + @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. + * @throws InterruptedException + * @throws NotConnectedException + * @throws XMPPErrorException + * @throws NoResponseException + * @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.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 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 + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + */ + 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().createPacketCollectorAndSend(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().createPacketCollectorAndSend(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().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 { + // 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. + if (findRegistry().equals(jid)) { + 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; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/ThingState.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/ThingState.java new file mode 100644 index 000000000..959bf8896 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/ThingState.java @@ -0,0 +1,95 @@ +/** + * + * 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.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.jivesoftware.smack.util.Async; +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.jxmpp.jid.BareJid; + +public class ThingState { + + private final NodeInfo nodeInfo; + + private BareJid registry; + private BareJid owner; + private boolean removed; + + private final List listeners = new CopyOnWriteArrayList<>(); + + ThingState(NodeInfo nodeInfo) { + this.nodeInfo = nodeInfo; + } + + void setRegistry(BareJid registry) { + this.registry = registry; + } + + void setUnregistered() { + this.registry = null; + } + + void setOwner(final BareJid owner) { + this.owner = owner; + Async.go(new Runnable() { + @Override + public void run() { + for (ThingStateChangeListener thingStateChangeListener : listeners) { + thingStateChangeListener.owned(owner); + } + } + }); + } + + void setUnowned() { + this.owner = null; + } + + void setRemoved() { + removed = true; + } + + public NodeInfo getNodeInfo() { + return nodeInfo; + } + + public BareJid getRegistry() { + return registry; + } + + public BareJid getOwner() { + return owner; + } + + public boolean isOwned() { + return owner != null; + } + + public boolean isRemoved() { + return removed; + } + + public boolean setThingStateChangeListener(ThingStateChangeListener thingStateChangeListener) { + return listeners.add(thingStateChangeListener); + } + + public boolean removeThingStateChangeListener(ThingStateChangeListener thingStateChangeListener) { + return listeners.remove(thingStateChangeListener); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/ThingStateChangeListener.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/ThingStateChangeListener.java new file mode 100644 index 000000000..b64d67d21 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/ThingStateChangeListener.java @@ -0,0 +1,25 @@ +/** + * + * 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 org.jxmpp.jid.BareJid; + +public interface ThingStateChangeListener { + + public void owned(BareJid owner); + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/Constants.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/Constants.java new file mode 100644 index 000000000..83e304ce3 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/Constants.java @@ -0,0 +1,23 @@ +/** + * + * 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.element; + +public class Constants { + + public static final String IOT_DISCOVERY_NAMESPACE = "urn:xmpp:iot:discovery"; + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTClaimed.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTClaimed.java new file mode 100644 index 000000000..f62c665b3 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTClaimed.java @@ -0,0 +1,66 @@ +/** + * + * 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.element; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.jxmpp.jid.Jid; + +public class IoTClaimed extends IQ { + + public static final String ELEMENT = "claimed"; + public static final String NAMESPACE = Constants.IOT_DISCOVERY_NAMESPACE; + + private final Jid jid; + + private final NodeInfo nodeInfo; + + public IoTClaimed(Jid jid) { + this(jid, NodeInfo.EMPTY); + } + + public IoTClaimed(Jid jid, NodeInfo nodeInfo) { + super(ELEMENT, NAMESPACE); + this.jid = jid; + this.nodeInfo = nodeInfo; + } + + public Jid getJid() { + return jid; + } + + public String getNodeId() { + return nodeInfo.getNodeId(); + } + + public String getSourceId() { + return nodeInfo.getSourceId(); + } + + public NodeInfo getNodeInfo() { + return nodeInfo; + } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + xml.attribute("jid", jid); + nodeInfo.appendTo(xml); + xml.setEmptyElement(); + return xml; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTDisown.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTDisown.java new file mode 100644 index 000000000..04324579d --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTDisown.java @@ -0,0 +1,62 @@ +/** + * + * 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.element; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.jxmpp.jid.Jid; + +public class IoTDisown extends IQ { + + public static final String ELEMENT = "disown"; + public static final String NAMESPACE = Constants.IOT_DISCOVERY_NAMESPACE; + + private final Jid jid; + + private final NodeInfo nodeInfo; + + public IoTDisown(Jid jid) { + this(jid, NodeInfo.EMPTY); + } + + public IoTDisown(Jid jid, NodeInfo nodeInfo) { + super(ELEMENT, NAMESPACE); + this.jid = jid; + this.nodeInfo = nodeInfo; + } + + public Jid getJid() { + return jid; + } + + public String getNodeId() { + return nodeInfo != null ? nodeInfo.getNodeId() : null; + } + + public String getSourceId() { + return nodeInfo != null ? nodeInfo.getSourceId() : null; + } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + xml.attribute("jid", jid); + nodeInfo.appendTo(xml); + xml.rightAngleBracket(); + return xml; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTDisowned.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTDisowned.java new file mode 100644 index 000000000..69aa1b6ba --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTDisowned.java @@ -0,0 +1,53 @@ +/** + * + * 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.element; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.iot.element.NodeInfo; + +public class IoTDisowned extends IQ { + + public static final String ELEMENT = "disown"; + public static final String NAMESPACE = Constants.IOT_DISCOVERY_NAMESPACE; + + private final NodeInfo nodeInfo; + + public IoTDisowned(NodeInfo nodeInfo) { + super(ELEMENT, NAMESPACE); + this.nodeInfo = nodeInfo; + } + + public String getNodeId() { + return nodeInfo.getNodeId(); + } + + public String getSourceId() { + return nodeInfo.getSourceId(); + } + + public NodeInfo getNodeInfo() { + return nodeInfo; + } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + nodeInfo.appendTo(xml); + xml.setEmptyElement(); + return xml; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTMine.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTMine.java new file mode 100644 index 000000000..05b00e746 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTMine.java @@ -0,0 +1,52 @@ +/** + * + * 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.element; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.jivesoftware.smack.packet.IQ; + +public class IoTMine extends IQ { + + public static final String ELEMENT = "mine"; + public static final String NAMESPACE = Constants.IOT_DISCOVERY_NAMESPACE; + + private final List metaTags; + private final boolean publicThing; + + public IoTMine(Collection metaTags, boolean publicThing) { + this(new ArrayList<>(metaTags), publicThing); + } + + public IoTMine(List metaTags, boolean publicThing) { + super(ELEMENT, NAMESPACE); + this.metaTags = metaTags; + this.publicThing = publicThing; + } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + xml.optBooleanAttributeDefaultTrue("public", publicThing); + xml.rightAngleBracket(); + xml.append(metaTags); + + return xml; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTRegister.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTRegister.java new file mode 100644 index 000000000..ebc535c1d --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTRegister.java @@ -0,0 +1,54 @@ +/** + * + * 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.element; + +import java.util.Collection; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.iot.element.NodeInfo; + +public class IoTRegister extends IQ { + + public static final String ELEMENT = "register"; + public static final String NAMESPACE = Constants.IOT_DISCOVERY_NAMESPACE; + + private final Collection tags; + private final NodeInfo nodeInfo; + private final boolean selfOwned; + + public IoTRegister(Collection tags, NodeInfo nodeInfo, boolean selfOwned) { + super(ELEMENT, NAMESPACE); + if (tags.isEmpty()) { + throw new IllegalArgumentException(); + } + this.tags = tags; + this.nodeInfo = nodeInfo; + this.selfOwned = selfOwned; + } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + nodeInfo.appendTo(xml); + xml.optBooleanAttribute("selfOwned", selfOwned); + xml.rightAngleBracket(); + + xml.append(tags); + + return xml; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTRemove.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTRemove.java new file mode 100644 index 000000000..2485fc765 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTRemove.java @@ -0,0 +1,67 @@ +/** + * + * 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.element; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.Jid; + +public class IoTRemove extends IQ { + + public static final String ELEMENT = "remove"; + public static final String NAMESPACE = Constants.IOT_DISCOVERY_NAMESPACE; + + /** + * The XMPP address of the Thing to be removed from the registry. According to XEP-0347 § 3.10 the + * "resource-less JID of the Thing" has to be used, therefore we use {@link BareJid} here. + */ + private final BareJid jid; + + private final NodeInfo nodeInfo; + + public IoTRemove(BareJid jid) { + this(jid, NodeInfo.EMPTY); + } + + public IoTRemove(BareJid jid, NodeInfo nodeInfo) { + super(ELEMENT, NAMESPACE); + this.jid = jid; + this.nodeInfo = nodeInfo; + } + + public Jid getJid() { + return jid; + } + + public String getNodeId() { + return nodeInfo != null ? nodeInfo.getNodeId() : null; + } + + public String getSourceId() { + return nodeInfo != null ? nodeInfo.getSourceId() : null; + } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + xml.attribute("jid", jid); + nodeInfo.appendTo(xml); + xml.setEmptyElement(); + return xml; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTRemoved.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTRemoved.java new file mode 100644 index 000000000..3a1d47034 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTRemoved.java @@ -0,0 +1,57 @@ +/** + * + * 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.element; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.iot.element.NodeInfo; + +public class IoTRemoved extends IQ { + + public static final String ELEMENT = "removed"; + public static final String NAMESPACE = Constants.IOT_DISCOVERY_NAMESPACE; + + private final NodeInfo nodeInfo; + + public IoTRemoved() { + this(NodeInfo.EMPTY); + } + + public IoTRemoved(NodeInfo nodeInfo) { + super(ELEMENT, NAMESPACE); + this.nodeInfo = nodeInfo; + } + + public String getNodeId() { + return nodeInfo.getNodeId(); + } + + public String getSourceId() { + return nodeInfo.getSourceId(); + } + + public NodeInfo getNodeInfo() { + return nodeInfo; + } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + nodeInfo.appendTo(xml); + xml.setEmptyElement(); + return xml; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTUnregister.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTUnregister.java new file mode 100644 index 000000000..767117418 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/IoTUnregister.java @@ -0,0 +1,41 @@ +/** + * + * 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.element; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.iot.element.NodeInfo; + +public class IoTUnregister extends IQ { + + public static final String ELEMENT = "unregister"; + public static final String NAMESPACE = Constants.IOT_DISCOVERY_NAMESPACE; + + private final NodeInfo nodeInfo; + + public IoTUnregister(NodeInfo nodeInfo) { + super(ELEMENT, NAMESPACE); + this.nodeInfo = nodeInfo; + setType(Type.set); + } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + nodeInfo.appendTo(xml); + xml.rightAngleBracket(); + return xml; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/Tag.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/Tag.java new file mode 100644 index 000000000..0ea8c6b4f --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/Tag.java @@ -0,0 +1,78 @@ +/** + * + * 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.element; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class Tag implements NamedElement { + + public enum Type { + str, + num; + } + + private final String name; + private final Type type; + private final String value; + + public Tag(String name, Type type, String value) { + // TODO According to XEP-0347 § 5.2 names are case insensitive. Uppercase them all? + this.name = StringUtils.requireNotNullOrEmpty(name, "name must not be null or empty"); + this.type = Objects.requireNonNull(type); + this.value = StringUtils.requireNotNullOrEmpty(value, "value must not be null or empty"); + if (this.name.length() > 32) { + throw new IllegalArgumentException("Meta Tag names must not be longer then 32 characters (XEP-0347 § 5.2"); + } + if (this.type == Type.str && this.value.length() > 128) { + throw new IllegalArgumentException("Meta Tag string values must not be longer then 128 characters (XEP-0347 § 5.2"); + } + } + + public String getName() { + return name; + } + + public Type getType() { + return type; + } + + public String getValue() { + return value; + } + + @Override + public XmlStringBuilder toXML() { + XmlStringBuilder xml = new XmlStringBuilder(this); + xml.attribute("name", name); + xml.attribute("value", value); + xml.closeEmptyElement(); + return xml; + } + + @Override + public String getElementName() { + return getType().toString(); + } + + @Override + public String toString() { + return name + '(' + type + "):" + value; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/package-info.java new file mode 100644 index 000000000..53724e231 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/element/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot.discovery.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/package-info.java new file mode 100644 index 000000000..b8988a635 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT XEP-0347 Discovery. See {@link org.jivesoftware.smackx.iot.discovery.IoTDiscoveryManager}. + */ +package org.jivesoftware.smackx.iot.discovery; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTClaimedProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTClaimedProvider.java new file mode 100644 index 000000000..7297a5f18 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTClaimedProvider.java @@ -0,0 +1,36 @@ +/** + * + * 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.provider; + +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.iot.discovery.element.IoTClaimed; +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.jivesoftware.smackx.iot.parser.NodeInfoParser; +import org.jxmpp.jid.Jid; +import org.xmlpull.v1.XmlPullParser; + +public class IoTClaimedProvider extends IQProvider { + + @Override + public IoTClaimed parse(XmlPullParser parser, int initialDepth) throws Exception { + Jid jid = ParserUtils.getJidAttribute(parser); + NodeInfo nodeInfo = NodeInfoParser.parse(parser); + return new IoTClaimed(jid, nodeInfo); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTDisownProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTDisownProvider.java new file mode 100644 index 000000000..1711130f3 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTDisownProvider.java @@ -0,0 +1,36 @@ +/** + * + * 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.provider; + +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.iot.discovery.element.IoTDisown; +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.jivesoftware.smackx.iot.parser.NodeInfoParser; +import org.jxmpp.jid.Jid; +import org.xmlpull.v1.XmlPullParser; + +public class IoTDisownProvider extends IQProvider { + + @Override + public IoTDisown parse(XmlPullParser parser, int initialDepth) throws Exception { + Jid jid = ParserUtils.getJidAttribute(parser); + NodeInfo nodeInfo = NodeInfoParser.parse(parser); + return new IoTDisown(jid, nodeInfo); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTDisownedProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTDisownedProvider.java new file mode 100644 index 000000000..4200e8040 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTDisownedProvider.java @@ -0,0 +1,33 @@ +/** + * + * 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.provider; + +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.iot.discovery.element.IoTDisowned; +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.jivesoftware.smackx.iot.parser.NodeInfoParser; +import org.xmlpull.v1.XmlPullParser; + +public class IoTDisownedProvider extends IQProvider { + + @Override + public IoTDisowned parse(XmlPullParser parser, int initialDepth) throws Exception { + NodeInfo nodeInfo = NodeInfoParser.parse(parser); + return new IoTDisowned(nodeInfo); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTRegisterProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTRegisterProvider.java new file mode 100644 index 000000000..672a17cc4 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTRegisterProvider.java @@ -0,0 +1,62 @@ +/** + * + * 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.provider; + +import java.util.ArrayList; +import java.util.List; + +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.iot.discovery.element.IoTRegister; +import org.jivesoftware.smackx.iot.discovery.element.Tag; +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.jivesoftware.smackx.iot.parser.NodeInfoParser; +import org.xmlpull.v1.XmlPullParser; + +public class IoTRegisterProvider extends IQProvider { + + @Override + public IoTRegister parse(XmlPullParser parser, int initialDepth) throws Exception { + boolean selfOwned = ParserUtils.getBooleanAttribute(parser, "selfOwned", false); + NodeInfo nodeInfo = NodeInfoParser.parse(parser); + List tags = new ArrayList<>(); + while (parser.getDepth() != initialDepth) { + int event = parser.next(); + if (event != XmlPullParser.START_TAG) { + continue; + } + final String element = parser.getName(); + Tag.Type type = null; + switch (element) { + case "str": + type = Tag.Type.str; + break; + case "num": + type = Tag.Type.num; + break; + } + if (type == null) { + continue; + } + String name = parser.getAttributeValue(null, "name"); + String value = parser.getAttributeValue(null, "value"); + tags.add(new Tag(name, type, value)); + } + return new IoTRegister(tags, nodeInfo, selfOwned); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTRemoveProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTRemoveProvider.java new file mode 100644 index 000000000..528871d1e --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTRemoveProvider.java @@ -0,0 +1,42 @@ +/** + * + * 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.provider; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.iot.discovery.element.IoTRemove; +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.jivesoftware.smackx.iot.parser.NodeInfoParser; +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.Jid; +import org.xmlpull.v1.XmlPullParser; + +public class IoTRemoveProvider extends IQProvider { + + @Override + public IoTRemove parse(XmlPullParser parser, int initialDepth) throws Exception { + Jid jid = ParserUtils.getJidAttribute(parser); + if (jid.hasResource()) { + throw new SmackException("JID must be without resourcepart"); + } + BareJid bareJid = jid.asBareJid(); + NodeInfo nodeInfo = NodeInfoParser.parse(parser); + return new IoTRemove(bareJid, nodeInfo); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTRemovedProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTRemovedProvider.java new file mode 100644 index 000000000..be54303db --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTRemovedProvider.java @@ -0,0 +1,33 @@ +/** + * + * 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.provider; + +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.iot.discovery.element.IoTRemoved; +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.jivesoftware.smackx.iot.parser.NodeInfoParser; +import org.xmlpull.v1.XmlPullParser; + +public class IoTRemovedProvider extends IQProvider { + + @Override + public IoTRemoved parse(XmlPullParser parser, int initialDepth) throws Exception { + NodeInfo nodeInfo = NodeInfoParser.parse(parser); + return new IoTRemoved(nodeInfo); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTUnregisterProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTUnregisterProvider.java new file mode 100644 index 000000000..4352bf4a3 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/IoTUnregisterProvider.java @@ -0,0 +1,33 @@ +/** + * + * 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.provider; + +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.iot.discovery.element.IoTUnregister; +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.jivesoftware.smackx.iot.parser.NodeInfoParser; +import org.xmlpull.v1.XmlPullParser; + +public class IoTUnregisterProvider extends IQProvider { + + @Override + public IoTUnregister parse(XmlPullParser parser, int initialDepth) throws Exception { + NodeInfo nodeInfo = NodeInfoParser.parse(parser); + return new IoTUnregister(nodeInfo); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/package-info.java new file mode 100644 index 000000000..9e9254421 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/discovery/provider/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot.discovery.provider; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/element/NodeInfo.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/element/NodeInfo.java new file mode 100644 index 000000000..08ce2db11 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/element/NodeInfo.java @@ -0,0 +1,105 @@ +/** + * + * 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.element; + +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public final class NodeInfo { + + public static final NodeInfo EMPTY = new NodeInfo(); + + private final String nodeId; + private final String sourceId; + private final String cacheType; + + /** + * The internal constructor for the {@link EMPTY} node info marker class. + */ + private NodeInfo() { + this.nodeId = null; + this.sourceId = null; + this.cacheType = null; + } + + public NodeInfo(String nodeId, String sourceId, String cacheType) { + this.nodeId = StringUtils.requireNotNullOrEmpty(nodeId, "Node ID must not be null or empty"); + this.sourceId = sourceId; + this.cacheType = cacheType; + } + + public String getNodeId() { + return nodeId; + } + + public String getSourceId() { + return sourceId; + } + + public String getCacheType() { + return cacheType; + } + + public void appendTo(XmlStringBuilder xml) { + if (nodeId == null) { + return; + } + xml.attribute("nodeId", nodeId).optAttribute("sourceId", sourceId).optAttribute("cacheType", cacheType); + } + + @Override + public int hashCode() { + if (this == EMPTY) { + return 0; + } + final int prime = 31; + int result = 1; + result = prime * result + nodeId.hashCode(); + result = prime * result + ((sourceId == null) ? 0 : sourceId.hashCode()); + result = prime * result + ((cacheType == null) ? 0 : cacheType.hashCode()); + return result; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + if (!(other instanceof NodeInfo)) { + return false; + } + NodeInfo otherNodeInfo = (NodeInfo) other; + if (!nodeId.equals(otherNodeInfo.nodeId)) { + return false; + } + if (StringUtils.nullSafeCharSequenceEquals(sourceId, otherNodeInfo.sourceId) + && StringUtils.nullSafeCharSequenceEquals(cacheType, otherNodeInfo.cacheType)) { + return true; + } + return false; + } + +// public static void eventuallyAppend(NodeInfo nodeInfo, XmlStringBuilder xml) { +// if (nodeInfo == null) +// return; +// +// nodeInfo.appendTo(xml); +// } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/element/package-info.java new file mode 100644 index 000000000..2a6b65555 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/element/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/package-info.java new file mode 100644 index 000000000..f60c57e84 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/parser/NodeInfoParser.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/parser/NodeInfoParser.java new file mode 100644 index 000000000..7cc7ae2c9 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/parser/NodeInfoParser.java @@ -0,0 +1,35 @@ +/** + * + * 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.parser; + +import org.jivesoftware.smackx.iot.element.NodeInfo; +import org.xmlpull.v1.XmlPullParser; + +import static org.jivesoftware.smack.util.StringUtils.isNullOrEmpty; + +public class NodeInfoParser { + + public static NodeInfo parse(XmlPullParser parser) { + String nodeId = parser.getAttributeValue(null, "nodeId"); + String sourceId = parser.getAttributeValue(null, "sourceId"); + String cacheType = parser.getAttributeValue(null, "cacheType"); + if (isNullOrEmpty(nodeId) && isNullOrEmpty(sourceId) && isNullOrEmpty(cacheType)) { + return NodeInfo.EMPTY; + } + return new NodeInfo(nodeId, sourceId, cacheType); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/parser/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/parser/package-info.java new file mode 100644 index 000000000..ef34b326a --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/parser/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot.parser; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/IoTProvisioningManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/IoTProvisioningManager.java new file mode 100644 index 000000000..077a06882 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/IoTProvisioningManager.java @@ -0,0 +1,319 @@ +/** + * + * 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.provisioning; + +import java.util.List; +import java.util.Map; +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.StanzaListener; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPConnectionRegistry; +import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.StanzaExtensionFilter; +import org.jivesoftware.smack.filter.StanzaFilter; +import org.jivesoftware.smack.filter.StanzaTypeFilter; +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.packet.Message; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smack.roster.Roster; +import org.jivesoftware.smack.roster.RosterEntry; +import org.jivesoftware.smack.roster.SubscribeListener; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.disco.packet.DiscoverInfo; +import org.jivesoftware.smackx.iot.discovery.IoTDiscoveryManager; +import org.jivesoftware.smackx.iot.provisioning.element.ClearCache; +import org.jivesoftware.smackx.iot.provisioning.element.ClearCacheResponse; +import org.jivesoftware.smackx.iot.provisioning.element.Constants; +import org.jivesoftware.smackx.iot.provisioning.element.IoTIsFriend; +import org.jivesoftware.smackx.iot.provisioning.element.IoTIsFriendResponse; +import org.jivesoftware.smackx.iot.provisioning.element.Unfriend; +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.DomainBareJid; +import org.jxmpp.jid.Jid; +import org.jxmpp.util.cache.LruCache; + +/** + * A manager for XEP-0324: Internet of Things - Provisioning. + * + * @author Florian Schmaus {@literal } + * @see XEP-0324: Internet of Things - Provisioning + */ +public final class IoTProvisioningManager extends Manager { + + private static final Logger LOGGER = Logger.getLogger(IoTProvisioningManager.class.getName()); + + private static final StanzaFilter UNFRIEND_MESSAGE = new AndFilter(StanzaTypeFilter.MESSAGE, + new StanzaExtensionFilter(Unfriend.ELEMENT, Unfriend.NAMESPACE)); + + private static final Map INSTANCES = new WeakHashMap<>(); + + // Ensure a IoTProvisioningManager exists for every connection. + static { + XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { + public void connectionCreated(XMPPConnection connection) { + getInstanceFor(connection); + } + }); + } + + /** + * Get the manger instance responsible for the given connection. + * + * @param connection the XMPP connection. + * @return a manager instance. + */ + public static synchronized IoTProvisioningManager getInstanceFor(XMPPConnection connection) { + IoTProvisioningManager manager = INSTANCES.get(connection); + if (manager == null) { + manager = new IoTProvisioningManager(connection); + INSTANCES.put(connection, manager); + } + return manager; + } + + private final Roster roster; + private final LruCache> negativeFriendshipRequestCache = new LruCache<>(8); + + private Jid configuredProvisioningServer; + + private IoTProvisioningManager(XMPPConnection connection) { + super(connection); + + // Stanza listener for XEP-0324 § 3.2.3. + connection.addAsyncStanzaListener(new StanzaListener() { + @Override + public void processPacket(Stanza stanza) throws NotConnectedException, InterruptedException { + if (!isFromProvisioningService(stanza)) { + return; + } + + Message message = (Message) stanza; + Unfriend unfriend = Unfriend.from(message); + BareJid unfriendJid = unfriend.getJid(); + final XMPPConnection connection = connection(); + Roster roster = Roster.getInstanceFor(connection); + if (!roster.isSubscribedToMyPresence(unfriendJid)) { + LOGGER.warning("Ignoring request '" + stanza + "' because " + unfriendJid + + " is already not subscribed to our presence."); + return; + } + Presence unsubscribed = new Presence(Presence.Type.unsubscribed); + unsubscribed.setTo(unfriendJid); + connection.sendStanza(unsubscribed); + } + }, UNFRIEND_MESSAGE); + + connection.registerIQRequestHandler( + new AbstractIqRequestHandler(ClearCache.ELEMENT, ClearCache.NAMESPACE, Type.set, Mode.async) { + @Override + public IQ handleIQRequest(IQ iqRequest) { + if (!isFromProvisioningService(iqRequest)) { + return null; + } + + ClearCache clearCache = (ClearCache) iqRequest; + + // Handle request. + Jid from = iqRequest.getFrom(); + LruCache cache = negativeFriendshipRequestCache.get(from); + if (cache != null) { + cache.clear(); + } + + return new ClearCacheResponse(clearCache); + } + }); + + roster = Roster.getInstanceFor(connection); + roster.setSubscribeListener(new SubscribeListener() { + @Override + public SubscribeAnswer processSubscribe(Jid from, Presence subscribeRequest) { + // First check if the subscription request comes from a known registry and accept the request if so. + try { + if (IoTDiscoveryManager.getInstanceFor(connection()).isRegistry(from.asBareJid())) { + return SubscribeAnswer.Approve; + } + } + catch (NoResponseException | XMPPErrorException | NotConnectedException | InterruptedException e) { + LOGGER.log(Level.WARNING, "Could not determine if " + from + " is a registry", e); + } + + Jid provisioningServer = null; + try { + provisioningServer = getConfiguredProvisioningServer(); + } + catch (NoResponseException | XMPPErrorException | NotConnectedException | InterruptedException e) { + LOGGER.log(Level.WARNING, + "Could not determine privisioning server. Ignoring friend request from " + from, e); + } + if (provisioningServer == null) { + return null; + } + + boolean isFriend; + try { + isFriend = isFriend(provisioningServer, from.asBareJid()); + } + catch (NoResponseException | XMPPErrorException | NotConnectedException | InterruptedException e) { + LOGGER.log(Level.WARNING, "Could not determine if " + from + " is a friend.", e); + return null; + } + + if (isFriend) { + return SubscribeAnswer.Approve; + } + else { + return SubscribeAnswer.Deny; + } + } + }); + } + + /** + * Set the configured provisioning server. Use null as provisioningServer to use + * automatic discovery of the provisioning server (the default behavior). + * + * @param provisioningServer + */ + public void setConfiguredProvisioningServer(Jid provisioningServer) { + this.configuredProvisioningServer = provisioningServer; + } + + public Jid getConfiguredProvisioningServer() + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + if (configuredProvisioningServer == null) { + configuredProvisioningServer = findProvisioningServerComponent(); + } + return configuredProvisioningServer; + } + + /** + * Try to find a provisioning server component. + * + * @return the XMPP address of the provisioning server component if one was found. + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + * @see XEP-0324 § 3.1.2 Provisioning Server as a server component + */ + public DomainBareJid findProvisioningServerComponent() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + final XMPPConnection connection = connection(); + ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection); + List discoverInfos = sdm.findServicesDiscoverInfo(Constants.IOT_PROVISIONING_NAMESPACE, true, true); + if (discoverInfos.isEmpty()) { + return null; + } + Jid jid = discoverInfos.get(0).getFrom(); + assert (jid.isDomainBareJid()); + return jid.asDomainBareJid(); + } + + /** + * As the given provisioning server is the given JID is a friend. + * + * @param provisioningServer the provisioning server to ask. + * @param friendInQuestion the JID to ask about. + * @return true if the JID is a friend, false otherwise. + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + */ + public boolean isFriend(Jid provisioningServer, BareJid friendInQuestion) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + LruCache cache = negativeFriendshipRequestCache.get(provisioningServer); + if (cache != null && cache.containsKey(friendInQuestion)) { + // We hit a cached negative isFriend response for this provisioning server. + return false; + } + + IoTIsFriend iotIsFriend = new IoTIsFriend(friendInQuestion); + iotIsFriend.setTo(provisioningServer); + IoTIsFriendResponse response = connection().createPacketCollectorAndSend(iotIsFriend).nextResultOrThrow(); + assert (response.getJid().equals(friendInQuestion)); + boolean isFriend = response.getIsFriendResult(); + if (!isFriend) { + // Cache the negative is friend response. + if (cache == null) { + cache = new LruCache<>(1024); + negativeFriendshipRequestCache.put(provisioningServer, cache); + } + cache.put(friendInQuestion, null); + } + return isFriend; + } + + public void sendFriendshipRequest(BareJid bareJid) throws NotConnectedException, InterruptedException { + Presence presence = new Presence(Presence.Type.subscribe); + presence.setTo(bareJid); + connection().sendStanza(presence); + } + + public void sendFriendshipRequestIfRequired(BareJid jid) throws NotConnectedException, InterruptedException { + RosterEntry entry = roster.getEntry(jid); + if (entry != null && entry.canSeeHisPresence()) { + return; + } + sendFriendshipRequest(jid); + } + + public boolean isBefriended(Jid friendInQuestion) { + return roster.isSubscribedToMyPresence(friendInQuestion); + } + + public void unfriend(Jid friend) throws NotConnectedException, InterruptedException { + if (isBefriended(friend)) { + Presence presence = new Presence(Presence.Type.unsubscribed); + presence.setTo(friend); + connection().sendStanza(presence); + } + } + + private boolean isFromProvisioningService(Stanza stanza) { + Jid provisioningServer; + try { + provisioningServer = getConfiguredProvisioningServer(); + } + catch (NotConnectedException | InterruptedException | NoResponseException | XMPPErrorException e) { + LOGGER.log(Level.WARNING, "Could determine provisioning server", e); + return false; + } + if (provisioningServer == null) { + LOGGER.warning("Ignoring request '" + stanza + + "' because no provisioning server configured."); + return false; + } + if (!provisioningServer.equals(stanza.getFrom())) { + LOGGER.warning("Ignoring request '" + stanza + "' because not from provising server '" + + provisioningServer + "'."); + return false; + } + return true; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/ClearCache.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/ClearCache.java new file mode 100644 index 000000000..fe98b32b3 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/ClearCache.java @@ -0,0 +1,32 @@ +/** + * + * 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.provisioning.element; + +import org.jivesoftware.smack.packet.SimpleIQ; + +public class ClearCache extends SimpleIQ { + + public static final String ELEMENT = "clearCache"; + public static final String NAMESPACE = Constants.IOT_PROVISIONING_NAMESPACE; + + public ClearCache() { + super(ELEMENT, NAMESPACE); + // IQs are always of type 'get' (XEP-0324 § 3.5.1, see also the XEPs history remarks) + setType(Type.get); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/ClearCacheResponse.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/ClearCacheResponse.java new file mode 100644 index 000000000..1c229d153 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/ClearCacheResponse.java @@ -0,0 +1,37 @@ +/** + * + * 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.provisioning.element; + +import org.jivesoftware.smack.packet.SimpleIQ; + +public class ClearCacheResponse extends SimpleIQ { + + public static final String ELEMENT = "clearCacheResponse"; + public static final String NAMESPACE = Constants.IOT_PROVISIONING_NAMESPACE; + + public ClearCacheResponse() { + super(ELEMENT, NAMESPACE); + // IQs are always of type 'result' (XEP-0324 § 3.5.1, see also the XEPs history remarks) + setType(Type.result); + } + + public ClearCacheResponse(ClearCache clearCacheRequest) { + this(); + setStanzaId(clearCacheRequest.getStanzaId()); + setTo(clearCacheRequest.getFrom()); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/Constants.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/Constants.java new file mode 100644 index 000000000..dc010231a --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/Constants.java @@ -0,0 +1,23 @@ +/** + * + * 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.provisioning.element; + +public class Constants { + + public static final String IOT_PROVISIONING_NAMESPACE = "urn:xmpp:iot:provisioning"; + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/IoTIsFriend.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/IoTIsFriend.java new file mode 100644 index 000000000..1aab7d87d --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/IoTIsFriend.java @@ -0,0 +1,41 @@ +/** + * + * 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.provisioning.element; + +import org.jivesoftware.smack.packet.IQ; +import org.jxmpp.jid.Jid; + +public class IoTIsFriend extends IQ { + + public static final String ELEMENT = "isFriend"; + public static final String NAMESPACE = Constants.IOT_PROVISIONING_NAMESPACE; + + private final Jid jid; + + public IoTIsFriend(Jid jid) { + super(ELEMENT, NAMESPACE); + this.jid = jid; + } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + xml.attribute("jid", jid); + xml.setEmptyElement(); + return xml; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/IoTIsFriendResponse.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/IoTIsFriendResponse.java new file mode 100644 index 000000000..6892b7030 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/IoTIsFriendResponse.java @@ -0,0 +1,52 @@ +/** + * + * 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.provisioning.element; + +import org.jivesoftware.smack.packet.IQ; +import org.jxmpp.jid.BareJid; + +public class IoTIsFriendResponse extends IQ { + + public static final String ELEMENT = "isFriend"; + public static final String NAMESPACE = Constants.IOT_PROVISIONING_NAMESPACE; + + private final BareJid jid; + + private final boolean result; + + public IoTIsFriendResponse(BareJid jid, boolean result) { + super(ELEMENT, NAMESPACE); + this.jid = jid; + this.result = result; + } + + public BareJid getJid() { + return jid; + } + + public boolean getIsFriendResult() { + return result; + } + + @Override + protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { + xml.attribute("jid", jid); + xml.attribute("result", result); + return xml; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/Unfriend.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/Unfriend.java new file mode 100644 index 000000000..9a1ae5256 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/Unfriend.java @@ -0,0 +1,59 @@ +/** + * + * 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.provisioning.element; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jxmpp.jid.BareJid; + +public class Unfriend implements ExtensionElement { + public static final String ELEMENT = "UNFRIEND"; + public static final String NAMESPACE = Constants.IOT_PROVISIONING_NAMESPACE; + + private final BareJid jid; + + public Unfriend(BareJid jid) { + this.jid = jid; + } + + public BareJid getJid() { + return jid; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public XmlStringBuilder toXML() { + XmlStringBuilder xml = new XmlStringBuilder(this); + xml.attribute("jid", jid); + xml.closeEmptyElement(); + return xml; + } + + public static Unfriend from(Message message) { + return message.getExtension(ELEMENT, NAMESPACE); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/package-info.java new file mode 100644 index 000000000..ad8b08296 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/element/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot.provisioning.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/package-info.java new file mode 100644 index 000000000..03be5be79 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot.provisioning; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/ClearCacheProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/ClearCacheProvider.java new file mode 100644 index 000000000..b8f7e7565 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/ClearCacheProvider.java @@ -0,0 +1,31 @@ +/** + * + * 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.provisioning.provider; + +import org.jivesoftware.smack.provider.IQProvider; + +import org.jivesoftware.smackx.iot.provisioning.element.ClearCache; +import org.xmlpull.v1.XmlPullParser; + +public class ClearCacheProvider extends IQProvider { + + @Override + public ClearCache parse(XmlPullParser parser, int initialDepth) throws Exception { + return new ClearCache(); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/ClearCacheResponseProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/ClearCacheResponseProvider.java new file mode 100644 index 000000000..d535d9469 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/ClearCacheResponseProvider.java @@ -0,0 +1,31 @@ +/** + * + * 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.provisioning.provider; + +import org.jivesoftware.smack.provider.IQProvider; + +import org.jivesoftware.smackx.iot.provisioning.element.ClearCacheResponse; +import org.xmlpull.v1.XmlPullParser; + +public class ClearCacheResponseProvider extends IQProvider { + + @Override + public ClearCacheResponse parse(XmlPullParser parser, int initialDepth) throws Exception { + return new ClearCacheResponse(); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/IoTIsFriendProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/IoTIsFriendProvider.java new file mode 100644 index 000000000..9a4fa21a6 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/IoTIsFriendProvider.java @@ -0,0 +1,34 @@ +/** + * + * 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.provisioning.provider; + +import org.jivesoftware.smack.provider.IQProvider; + +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.iot.provisioning.element.IoTIsFriend; +import org.jxmpp.jid.Jid; +import org.xmlpull.v1.XmlPullParser; + +public class IoTIsFriendProvider extends IQProvider { + + @Override + public IoTIsFriend parse(XmlPullParser parser, int initialDepth) throws Exception { + Jid jid = ParserUtils.getJidAttribute(parser); + return new IoTIsFriend(jid); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/IoTIsFriendResponseProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/IoTIsFriendResponseProvider.java new file mode 100644 index 000000000..7080b8dc3 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/IoTIsFriendResponseProvider.java @@ -0,0 +1,37 @@ +/** + * + * 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.provisioning.provider; + +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.iot.provisioning.element.IoTIsFriendResponse; +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.Jid; +import org.xmlpull.v1.XmlPullParser; + +public class IoTIsFriendResponseProvider extends IQProvider { + + @Override + public IoTIsFriendResponse parse(XmlPullParser parser, int initialDepth) throws Exception { + Jid jid = ParserUtils.getJidAttribute(parser); + BareJid bareJid = jid.asBareJid(); + boolean result = ParserUtils.getBooleanAttribute(parser, "result"); + + return new IoTIsFriendResponse(bareJid, result); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/UnfriendProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/UnfriendProvider.java new file mode 100644 index 000000000..73fd940ee --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/UnfriendProvider.java @@ -0,0 +1,33 @@ +/** + * + * 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.provisioning.provider; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.iot.provisioning.element.Unfriend; +import org.jxmpp.jid.BareJid; +import org.xmlpull.v1.XmlPullParser; + +public class UnfriendProvider extends ExtensionElementProvider { + + @Override + public Unfriend parse(XmlPullParser parser, int initialDepth) throws Exception { + BareJid jid = ParserUtils.getBareJidAttribute(parser); + return new Unfriend(jid); + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/package-info.java new file mode 100644 index 000000000..aed466ce0 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/provider/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XMPP IoT. + */ +package org.jivesoftware.smackx.iot.provisioning.provider; diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers index d0adb9453..e4906726e 100644 --- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers +++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers @@ -52,4 +52,97 @@ org.jivesoftware.smackx.gcm.provider.GcmExtensionProvider + + + register + urn:xmpp:iot:discovery + org.jivesoftware.smackx.iot.discovery.provider.IoTRegisterProvider + + + claimed + urn:xmpp:iot:discovery + org.jivesoftware.smackx.iot.discovery.provider.IoTClaimedProvider + + + disown + urn:xmpp:iot:discovery + org.jivesoftware.smackx.iot.discovery.provider.IoTDisownProvider + + + disowned + urn:xmpp:iot:discovery + org.jivesoftware.smackx.iot.discovery.provider.IoTDisownedProvider + + + remove + urn:xmpp:iot:discovery + org.jivesoftware.smackx.iot.discovery.provider.IoTRemoveProvider + + + disown + urn:xmpp:iot:discovery + org.jivesoftware.smackx.iot.discovery.provider.IoTRemovedProvider + + + unregister + urn:xmpp:iot:discovery + org.jivesoftware.smackx.iot.discovery.provider.IoTUnregisterProvider + + + + + isFriend + urn:xmpp:iot:provisioning + org.jivesoftware.smackx.iot.provisioning.provider.IoTIsFriendProvider + + + isFriendResponse + urn:xmpp:iot:provisioning + org.jivesoftware.smackx.iot.provisioning.provider.IoTIsFriendResponseProvider + + + clearCache + urn:xmpp:iot:provisioning + org.jivesoftware.smackx.iot.provisioning.provider.ClearCacheProvider + + + clearCacheResponse + urn:xmpp:iot:provisioning + org.jivesoftware.smackx.iot.provisioning.provider.ClearCacheResponseProvider + + + unfriend + urn:xmpp:iot:provisioning + org.jivesoftware.smackx.iot.provisioning.provider.UnfriendProvider + + + + + req + urn:xmpp:iot:data + org.jivesoftware.smackx.iot.data.provider.IoTDataRequestProvider + + + accepted + urn:xmpp:iot:data + org.jivesoftware.smackx.iot.data.provider.IoTDataReadOutAcceptedProvider + + + fields + urn:xmpp:iot:data + org.jivesoftware.smackx.iot.data.provider.IoTFieldsExtensionProvider + + + + + set + urn:xmpp:iot:control + org.jivesoftware.smackx.iot.control.provider.IoTSetRequestProvider + + + setResponse + urn:xmpp:iot:control + org.jivesoftware.smackx.iot.control.provider.IoTSetResponseProvider + + diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml index 359f44a5b..c621a0d43 100644 --- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml +++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml @@ -1,5 +1,8 @@ org.jivesoftware.smackx.hoxt.HOXTManager + org.jivesoftware.smackx.iot.data.IoTDataManager + org.jivesoftware.smackx.iot.discovery.IoTDiscoveryManager + org.jivesoftware.smackx.iot.provisioning.IoTProvisioningManager - \ No newline at end of file + diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java index c61db699a..59513f454 100644 --- a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java +++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java @@ -1074,13 +1074,7 @@ public final class Roster extends Manager { if (entry == null) { return false; } - switch (entry.getType()) { - case from: - case both: - return true; - default: - return false; - } + return entry.canSeeMyPresence(); } /** diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterEntry.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterEntry.java index 7f38c7095..a47f8a6ed 100644 --- a/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterEntry.java +++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterEntry.java @@ -176,6 +176,39 @@ public final class RosterEntry extends Manager { return item.isSubscriptionPending(); } + /** + * Check if the contact is subscribed to "my" presence. This allows the contact to see the presence information. + * + * @return true if the contact has a presence subscription. + * @since 4.2 + */ + public boolean canSeeMyPresence() { + switch (getType()) { + case from: + case both: + return true; + default: + return false; + } + } + + /** + * Check if we are subscribed to the contact's presence. If true then the contact has allowed us to + * receive presence information. + * + * @return true if we are subscribed to the contact's presence. + * @since 4.2 + */ + public boolean canSeeHisPresence() { + switch (getType()) { + case to: + case both: + return true; + default: + return false; + } + } + public String toString() { StringBuilder buf = new StringBuilder(); if (getName() != null) { diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterUtil.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterUtil.java new file mode 100644 index 000000000..7f13a4aff --- /dev/null +++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterUtil.java @@ -0,0 +1,83 @@ +/** + * + * 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.smack.roster; + +import java.util.Collection; +import java.util.Date; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.Jid; + +public class RosterUtil { + + public static void waitUntilOtherEntityIsSubscribed(Roster roster, BareJid otherEntity, long timeoutMillis) + throws InterruptedException, TimeoutException { + Date deadline = new Date(System.currentTimeMillis() + timeoutMillis); + waitUntilOtherEntityIsSubscribed(roster, otherEntity, deadline); + } + + public static void waitUntilOtherEntityIsSubscribed(Roster roster, final BareJid otherEntity, Date deadline) + throws InterruptedException, TimeoutException { + final Lock lock = new ReentrantLock(); + final Condition maybeSubscribed = lock.newCondition(); + RosterListener rosterListener = new AbstractRosterListener() { + private void signal() { + lock.lock(); + try { + // No need to use signalAll() here. + maybeSubscribed.signal(); + } + finally { + lock.unlock(); + } + } + + @Override + public void entriesAdded(Collection addresses) { + signal(); + } + + @Override + public void entriesUpdated(Collection addresses) { + signal(); + } + }; + + roster.addRosterListener(rosterListener); + + boolean stillWaiting = true; + // Using the example code pattern from Condition.awaitUntil(Date) javadoc. + lock.lock(); + try { + while (!roster.isSubscribedToMyPresence(otherEntity)) { + if (!stillWaiting) { + throw new TimeoutException(); + } + stillWaiting = maybeSubscribed.awaitUntil(deadline); + } + } + finally { + lock.unlock(); + // Make sure the listener is removed, so we don't leak it. + roster.removeRosterListener(rosterListener); + } + } +} diff --git a/smack-integration-test/build.gradle b/smack-integration-test/build.gradle index 9141ea2ac..461039058 100644 --- a/smack-integration-test/build.gradle +++ b/smack-integration-test/build.gradle @@ -10,6 +10,7 @@ dependencies { compile project(':smack-java7') compile project(':smack-tcp') compile project(':smack-extensions') + compile project(':smack-experimental') compile 'org.reflections:reflections:0.9.9-RC1' compile 'eu.geekplace.javapinning:java-pinning-java7:1.1.0-alpha1' compile "junit:junit:$junitVersion" diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java index 176241075..8c8e93536 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java @@ -16,10 +16,13 @@ */ package org.igniterealtime.smack.inttest; +import java.util.Random; import java.util.logging.Logger; public abstract class AbstractSmackIntTest { protected static final Logger LOGGER = Logger.getLogger(AbstractSmackIntTest.class.getName()); + protected static final Random INSECURE_RANDOM = new Random(); + } diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java index 0e9294982..d3f62d619 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java @@ -30,6 +30,11 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest */ protected final XMPPConnection conTwo; + /** + * The third connection. + */ + protected final XMPPConnection conThree; + /** * An alias for the first connection {@link #conOne}. */ @@ -42,6 +47,7 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest public AbstractSmackIntegrationTest(SmackIntegrationTestEnvironment environment) { this.connection = this.conOne = environment.conOne; this.conTwo = environment.conTwo; + this.conThree = environment.conThree; if (environment.configuration.replyTimeout > 0) { this.defaultTimeout = environment.configuration.replyTimeout; } else { diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java index a30694f34..9f36a4c99 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java @@ -53,6 +53,10 @@ public final class Configuration { public final String accountTwoPassword; + public final String accountThreeUsername; + + public final String accountThreePassword; + public final boolean debug; public final Set enabledTests; @@ -63,7 +67,7 @@ public final class Configuration { private Configuration(DomainBareJid service, String serviceTlsPin, SecurityMode securityMode, int replyTimeout, boolean debug, String accountOneUsername, String accountOnePassword, String accountTwoUsername, - String accountTwoPassword, Set enabledTests, Set disabledTests, + String accountTwoPassword, String accountThreeUsername, String accountThreePassword, Set enabledTests, Set disabledTests, Set testPackages) { this.service = Objects.requireNonNull(service, "'service' must be set. Either via 'properties' files or via system property 'sinttest.service'."); @@ -83,6 +87,8 @@ public final class Configuration { this.accountOnePassword = accountOnePassword; this.accountTwoUsername = accountTwoUsername; this.accountTwoPassword = accountTwoPassword; + this.accountThreeUsername = accountThreeUsername; + this.accountThreePassword = accountThreePassword; this.enabledTests = enabledTests; this.disabledTests = disabledTests; this.testPackages = testPackages; @@ -110,6 +116,10 @@ public final class Configuration { private String accountTwoPassword; + public String accountThreeUsername; + + public String accountThreePassword; + private boolean debug; private Set enabledTests; @@ -153,11 +163,13 @@ public final class Configuration { } public Builder setUsernamesAndPassword(String accountOneUsername, String accountOnePassword, - String accountTwoUsername, String accountTwoPassword) { - this.accountOneUsername = accountOneUsername; - this.accountOnePassword = accountOnePassword; - this.accountTwoUsername = accountTwoUsername; - this.accountTwoPassword = accountTwoPassword; + String accountTwoUsername, String accountTwoPassword, String accountThreeUsername, String accountThreePassword) { + this.accountOneUsername = StringUtils.requireNotNullOrEmpty(accountOneUsername, "accountOneUsername must not be null or empty"); + this.accountOnePassword = StringUtils.requireNotNullOrEmpty(accountOnePassword, "accountOnePassword must not be null or empty"); + this.accountTwoUsername = StringUtils.requireNotNullOrEmpty(accountTwoUsername, "accountTwoUsername must not be null or empty"); + this.accountTwoPassword = StringUtils.requireNotNullOrEmpty(accountTwoPassword, "accountTwoPasswordmust not be null or empty"); + this.accountThreeUsername = StringUtils.requireNotNullOrEmpty(accountThreeUsername, "accountThreeUsername must not be null or empty"); + this.accountThreePassword = StringUtils.requireNotNullOrEmpty(accountThreePassword, "accountThreePassword must not be null or empty"); return this; } @@ -213,7 +225,7 @@ public final class Configuration { public Configuration build() { return new Configuration(service, serviceTlsPin, securityMode, replyTimeout, debug, accountOneUsername, - accountOnePassword, accountTwoUsername, accountTwoPassword, enabledTests, disabledTests, + accountOnePassword, accountTwoUsername, accountTwoPassword, accountThreeUsername, accountThreePassword, enabledTests, disabledTests, testPackages); } } @@ -252,7 +264,9 @@ public final class Configuration { String accountOnePassword = properties.getProperty("accountOnePassword"); String accountTwoUsername = properties.getProperty("accountTwoUsername"); String accountTwoPassword = properties.getProperty("accountTwoPassword"); - builder.setUsernamesAndPassword(accountOneUsername, accountOnePassword, accountTwoUsername, accountTwoPassword); + String accountThreeUsername = properties.getProperty("accountThreeUsername"); + String accountThreePassword = properties.getProperty("accountThreePassword"); + builder.setUsernamesAndPassword(accountOneUsername, accountOnePassword, accountTwoUsername, accountTwoPassword, accountThreeUsername, accountThreePassword); builder.setDebug(properties.getProperty("debug")); builder.setEnabledTests(properties.getProperty("enabledTests")); @@ -262,7 +276,7 @@ public final class Configuration { return builder.build(); } - private static File findPropertiesFile() throws IOException { + private static File findPropertiesFile() { List possibleLocations = new LinkedList<>(); possibleLocations.add("properties"); String userHome = System.getProperty("user.home"); diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestEnvironment.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestEnvironment.java index 84c5eddb2..ccfe2f131 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestEnvironment.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestEnvironment.java @@ -24,14 +24,17 @@ public class SmackIntegrationTestEnvironment { public final XMPPTCPConnection conTwo; + public final XMPPTCPConnection conThree; + public final String testRunId; public final Configuration configuration; - SmackIntegrationTestEnvironment(XMPPTCPConnection conOne, XMPPTCPConnection conTwo, String testRunId, + SmackIntegrationTestEnvironment(XMPPTCPConnection conOne, XMPPTCPConnection conTwo, XMPPTCPConnection conThree, String testRunId, Configuration configuration) { this.conOne = conOne; this.conTwo = conTwo; + this.conThree = conThree; this.testRunId = testRunId; this.configuration = configuration; } diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java index b112c5108..cc26d751b 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java @@ -50,7 +50,6 @@ import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.NoResponseException; -import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; @@ -166,6 +165,7 @@ public class SmackIntegrationTestFramework { // Ensure that the accounts are deleted and disconnected before we continue disconnectAndMaybeDelete(environment.conOne); disconnectAndMaybeDelete(environment.conTwo); + disconnectAndMaybeDelete(environment.conThree); } return testRunResult; @@ -173,16 +173,16 @@ public class SmackIntegrationTestFramework { @SuppressWarnings({"unchecked", "Finally"}) private void runTests(Set> classes) - throws NoResponseException, NotConnectedException, InterruptedException { + throws NoResponseException, InterruptedException { for (Class testClass : classes) { final String testClassName = testClass.getName(); - if (config.enabledTests != null && !config.enabledTests.contains(testClassName)) { + if (config.enabledTests != null && !isInSet(testClass, config.enabledTests)) { LOGGER.info("Skipping test class " + testClassName + " because it is not enabled"); continue; } - if (config.disabledTests != null && config.disabledTests.contains(testClassName)) { + if (isInSet(testClass, config.disabledTests)) { LOGGER.info("Skipping test class " + testClassName + " because it is disalbed"); continue; } @@ -233,9 +233,8 @@ public class SmackIntegrationTestFramework { while (it.hasNext()) { final Method method = it.next(); final String methodName = method.getName(); - final String className = method.getDeclaringClass().getName(); - if (config.enabledTests != null && !config.enabledTests.contains(methodName) - && !config.enabledTests.contains(className)) { + if (config.enabledTests != null && !(config.enabledTests.contains(methodName) + || isInSet(testClass, config.enabledTests))) { LOGGER.fine("Skipping test method " + methodName + " because it is not enabled"); it.remove(); continue; @@ -472,26 +471,33 @@ public class SmackIntegrationTestFramework { NoSuchAlgorithmException { XMPPTCPConnection conOne = null; XMPPTCPConnection conTwo = null; + XMPPTCPConnection conThree = null; try { conOne = getConnectedConnectionFor(AccountNum.One); conTwo = getConnectedConnectionFor(AccountNum.Two); + conThree = getConnectedConnectionFor(AccountNum.Three); } catch (Exception e) { + // TODO Reverse the order, i.e. conThree should be disconnected first. if (conOne != null) { conOne.disconnect(); } if (conTwo != null) { conTwo.disconnect(); } + if (conThree != null) { + conThree.disconnect(); + } throw e; } - return new SmackIntegrationTestEnvironment(conOne, conTwo, testRunResult.testRunId, config); + return new SmackIntegrationTestEnvironment(conOne, conTwo, conThree, testRunResult.testRunId, config); } enum AccountNum { One, Two, + Three, } private static final String USERNAME_PREFIX = "smack-inttest"; @@ -513,6 +519,11 @@ public class SmackIntegrationTestFramework { accountPassword = config.accountTwoPassword; middlefix = "two"; break; + case Three: + accountUsername = config.accountThreeUsername; + accountPassword = config.accountThreePassword; + middlefix = "three"; + break; default: throw new IllegalStateException(); } @@ -584,6 +595,15 @@ public class SmackIntegrationTestFramework { return (Exception) e; } + private static boolean isInSet(Class clz, Set classes) { + if (classes == null) { + return false; + } + final String className = clz.getName(); + final String unqualifiedClassName = clz.getSimpleName(); + return (classes.contains(className) || classes.contains(unqualifiedClassName)); + } + public static final class TestRunResult { /** diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTControlIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTControlIntegrationTest.java new file mode 100644 index 000000000..36cb5a527 --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTControlIntegrationTest.java @@ -0,0 +1,97 @@ +/** + * + * 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; + +import static org.junit.Assert.assertNotNull; + +import java.util.Collection; +import java.util.concurrent.TimeoutException; + +import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; +import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smack.roster.RosterIntegrationTest; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.iot.control.IoTControlManager; +import org.jivesoftware.smackx.iot.control.ThingControlRequest; +import org.jivesoftware.smackx.iot.control.element.IoTSetResponse; +import org.jivesoftware.smackx.iot.control.element.SetBoolData; +import org.jivesoftware.smackx.iot.control.element.SetData; +import org.jxmpp.jid.Jid; + +public class IoTControlIntegrationTest extends AbstractSmackIntegrationTest { + + private final IoTControlManager IoTControlManagerOne; + + private final IoTControlManager IoTControlManagerTwo; + + public IoTControlIntegrationTest(SmackIntegrationTestEnvironment environment) { + super(environment); + IoTControlManagerOne = IoTControlManager.getInstanceFor(conOne); + IoTControlManagerTwo = IoTControlManager.getInstanceFor(conTwo); + } + + /** + * Connection one provides a thing, which is controlled by connection two. + * + * @throws Exception + * @throws TimeoutException + */ + @SmackIntegrationTest + // @SmackSerialIntegrationTest + public void controlTest() throws TimeoutException, Exception { + final String key = StringUtils.randomString(12); + final String sn = StringUtils.randomString(12); + final SimpleResultSyncPoint syncPoint = new SimpleResultSyncPoint(); + + Thing controlThing = Thing.builder().setKey(key).setSerialNumber(sn).setControlRequestHandler(new ThingControlRequest() { + @Override + public void processRequest(Jid from, Collection setData) throws XMPPErrorException { + if (!from.equals(conTwo.getUser())) { + return; + } + for (final SetData data : setData) { + if (!data.getName().equals(testRunId)) continue; + if (!(data instanceof SetBoolData)) continue; + SetBoolData boolData = (SetBoolData) data; + if (boolData.getBooleanValue()) { + syncPoint.signal(); + break; + } + } + } + }).build(); + + IoTControlManagerOne.installThing(controlThing); + + try { + RosterIntegrationTest.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, defaultTimeout); + + SetData data = new SetBoolData(testRunId, true); + IoTSetResponse response = IoTControlManagerTwo.setUsingIq(conOne.getUser(), data); + assertNotNull(response); + } + finally { + IoTControlManagerOne.uninstallThing(controlThing); + RosterIntegrationTest.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); + } + + syncPoint.waitForResult(defaultTimeout); + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDataIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDataIntegrationTest.java new file mode 100644 index 000000000..67edcb7b0 --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDataIntegrationTest.java @@ -0,0 +1,104 @@ +/** + * + * 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeoutException; + +import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.jivesoftware.smack.roster.RosterIntegrationTest; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.iot.data.IoTDataManager; +import org.jivesoftware.smackx.iot.data.ThingMomentaryReadOutRequest; +import org.jivesoftware.smackx.iot.data.ThingMomentaryReadOutResult; +import org.jivesoftware.smackx.iot.data.element.IoTDataField; +import org.jivesoftware.smackx.iot.data.element.IoTDataField.IntField; +import org.jivesoftware.smackx.iot.data.element.IoTFieldsExtension; +import org.jivesoftware.smackx.iot.data.element.NodeElement; +import org.jivesoftware.smackx.iot.data.element.TimestampElement; + +public class IoTDataIntegrationTest extends AbstractSmackIntegrationTest { + + private final IoTDataManager iotDataManagerOne; + + private final IoTDataManager iotDataManagerTwo; + + public IoTDataIntegrationTest(SmackIntegrationTestEnvironment environment) { + super(environment); + iotDataManagerOne = IoTDataManager.getInstanceFor(conOne); + iotDataManagerTwo = IoTDataManager.getInstanceFor(conTwo); + } + + /** + * Connection one provides a thing, which momentary value is read out by connection two. + * + * @throws Exception + * @throws TimeoutException + */ + @SmackIntegrationTest + public void dataTest() throws TimeoutException, Exception { + final String key = StringUtils.randomString(12); + final String sn = StringUtils.randomString(12); + final int value = INSECURE_RANDOM.nextInt(); + + Thing dataThing = Thing.builder().setKey(key).setSerialNumber(sn).setMomentaryReadOutRequestHandler(new ThingMomentaryReadOutRequest() { + @Override + public void momentaryReadOutRequest(ThingMomentaryReadOutResult callback) { + IoTDataField.IntField field = new IntField(testRunId, value); + callback.momentaryReadOut(Collections.singletonList(field)); + } + }).build(); + + iotDataManagerOne.installThing(dataThing); + + List values; + try { + RosterIntegrationTest.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, defaultTimeout); + + values = iotDataManagerTwo.requestMomentaryValuesReadOut(conOne.getUser()); + } + finally { + iotDataManagerOne.uninstallThing(dataThing); + RosterIntegrationTest.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); + } + + assertEquals(1, values.size()); + IoTFieldsExtension iotFieldsExtension = values.get(0); + List nodes = iotFieldsExtension.getNodes(); + + assertEquals(1, nodes.size()); + NodeElement node = nodes.get(0); + List timestamps = node.getTimestampElements(); + + assertEquals(1, timestamps.size()); + TimestampElement timestamp = timestamps.get(0); + List fields = timestamp.getDataFields(); + + assertEquals(1, fields.size()); + IoTDataField dataField = fields.get(0); + assertTrue(dataField instanceof IoTDataField.IntField); + IoTDataField.IntField intDataField = (IoTDataField.IntField) dataField; + assertEquals(testRunId, intDataField.getName()); + assertEquals(value, intDataField.getValue()); + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDiscoveryIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDiscoveryIntegrationTest.java new file mode 100644 index 000000000..f2ed4452c --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDiscoveryIntegrationTest.java @@ -0,0 +1,92 @@ +/** + * + * 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; + +import static org.junit.Assert.assertEquals; + +import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.TestNotPossibleException; +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.SmackException.NoResponseException; +import org.jivesoftware.smack.SmackException.NotConnectedException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.iot.discovery.IoTClaimedException; +import org.jivesoftware.smackx.iot.discovery.IoTDiscoveryManager; +import org.jivesoftware.smackx.iot.discovery.ThingState; +import org.jivesoftware.smackx.iot.discovery.element.IoTClaimed; +import org.jxmpp.jid.Jid; + +public class IoTDiscoveryIntegrationTest extends AbstractSmackIntegrationTest { + + private final IoTDiscoveryManager discoveryManagerOne; + private final IoTDiscoveryManager discoveryManagerTwo; + + public IoTDiscoveryIntegrationTest(SmackIntegrationTestEnvironment environment) throws NoResponseException, + XMPPErrorException, NotConnectedException, InterruptedException, TestNotPossibleException { + super(environment); + discoveryManagerOne = IoTDiscoveryManager.getInstanceFor(conOne); + discoveryManagerTwo = IoTDiscoveryManager.getInstanceFor(conTwo); + checkPrerequisites(conOne); + } + + @SmackIntegrationTest + public void registerClaimAndUnregisterThing() + throws XMPPErrorException, InterruptedException, SmackException { + final String key = StringUtils.randomString(12); + final String sn = StringUtils.randomString(12); + final Thing thing = Thing.builder().setKey(key).setSerialNumber(sn).setManufacturer("Ignite Realtime").setModel( + "Smack").setVersion("0.1").build(); + + registerThing(discoveryManagerOne, thing); + + IoTClaimed iotClaimed = discoveryManagerTwo.claimThing(thing.getMetaTags()); + assertEquals(conOne.getUser().asBareJid(), iotClaimed.getJid()); + + discoveryManagerTwo.disownThing(iotClaimed.getJid()); + + discoveryManagerOne.unregister(); + } + + static void checkPrerequisites(XMPPConnection connection) throws NoResponseException, XMPPErrorException, + NotConnectedException, InterruptedException, TestNotPossibleException { + IoTDiscoveryManager discoveryManager = IoTDiscoveryManager.getInstanceFor(connection); + Jid registry = discoveryManager.findRegistry(); + if (registry == null) { + throw new TestNotPossibleException("Could not find IoT Registry"); + } + } + + public static ThingState registerThing(IoTDiscoveryManager iotDiscoveryManager, Thing thing) throws XMPPErrorException, InterruptedException, SmackException { + int attempts = 0; + while (true) { + try { + return iotDiscoveryManager.registerThing(thing); + } + catch (IoTClaimedException e) { + iotDiscoveryManager.unregister(); + } + if (attempts++ > 3) { + throw new SmackException("Could no register thing"); + } + } + } + +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/package-info.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/package-info.java new file mode 100644 index 000000000..6e800e994 --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2015 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. + */ + +/** + * TODO describe me. + */ +package org.jivesoftware.smackx.iot; diff --git a/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/DummySmackIntegrationTestFramework.java b/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/DummySmackIntegrationTestFramework.java index f69334b9d..c1140646a 100644 --- a/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/DummySmackIntegrationTestFramework.java +++ b/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/DummySmackIntegrationTestFramework.java @@ -26,7 +26,7 @@ public class DummySmackIntegrationTestFramework extends SmackIntegrationTestFram @Override protected SmackIntegrationTestEnvironment prepareEnvironment() { - return new SmackIntegrationTestEnvironment(null, null, testRunResult.getTestRunId(), config); + return new SmackIntegrationTestEnvironment(null, null, null, testRunResult.getTestRunId(), config); } @Override diff --git a/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/SmackIntegrationTestUnitTestUtil.java b/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/SmackIntegrationTestUnitTestUtil.java index 41af6e33f..7032c008e 100644 --- a/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/SmackIntegrationTestUnitTestUtil.java +++ b/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/SmackIntegrationTestUnitTestUtil.java @@ -24,7 +24,7 @@ public class SmackIntegrationTestUnitTestUtil { // @formatter:off Configuration configuration = Configuration.builder() .setService(JidTestUtil.DOMAIN_BARE_JID_1) - .setUsernamesAndPassword("dummy1", "dummy1pass", "dummy2", "dummy2pass") + .setUsernamesAndPassword("dummy1", "dummy1pass", "dummy2", "dummy2pass", "dummy3", "dummy3pass") .addEnabledTest(unitTest).build(); // @formatter:on return new DummySmackIntegrationTestFramework(configuration); diff --git a/smack-repl/build.gradle b/smack-repl/build.gradle index 6bb8a5bf8..8a3742c72 100644 --- a/smack-repl/build.gradle +++ b/smack-repl/build.gradle @@ -10,6 +10,7 @@ dependencies { compile project(':smack-extensions') compile project(':smack-experimental') compile project(':smack-legacy') + compile project(':smack-integration-test') compile "com.lihaoyi:ammonite-repl_$scalaVersion:0.5.5" testCompile project(path: ":smack-core", configuration: "testRuntime") testCompile project(path: ":smack-core", configuration: "archives") diff --git a/smack-repl/scala.repl b/smack-repl/scala.repl index 3cd2946f3..1da355d19 100644 --- a/smack-repl/scala.repl +++ b/smack-repl/scala.repl @@ -4,3 +4,5 @@ import org.jivesoftware.smack._ import org.jivesoftware.smack.util.TLSUtils import org.jivesoftware.smack.tcp._ import org.jxmpp.jid.impl.JidCreate + +import org.igniterealtime.smack.smackrepl.IoT._ diff --git a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/IoT.java b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/IoT.java new file mode 100644 index 000000000..4ed7c60e0 --- /dev/null +++ b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/IoT.java @@ -0,0 +1,158 @@ +/** + * + * 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.igniterealtime.smack.smackrepl; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.roster.Roster; +import org.jivesoftware.smack.roster.RosterUtil; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeoutException; + +import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; +import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.tcp.XMPPTCPConnection; +import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.iot.IoTDiscoveryIntegrationTest; +import org.jivesoftware.smackx.iot.Thing; +import org.jivesoftware.smackx.iot.data.IoTDataManager; +import org.jivesoftware.smackx.iot.data.ThingMomentaryReadOutRequest; +import org.jivesoftware.smackx.iot.data.ThingMomentaryReadOutResult; +import org.jivesoftware.smackx.iot.data.element.IoTDataField; +import org.jivesoftware.smackx.iot.data.element.IoTDataField.IntField; +import org.jivesoftware.smackx.iot.data.element.IoTFieldsExtension; +import org.jivesoftware.smackx.iot.discovery.AbstractThingStateChangeListener; +import org.jivesoftware.smackx.iot.discovery.IoTDiscoveryManager; +import org.jivesoftware.smackx.iot.discovery.ThingState; +import org.jivesoftware.smackx.iot.provisioning.IoTProvisioningManager; +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.impl.JidCreate; + +public class IoT { + + // A 10 minute timeout. + private static final long TIMEOUT = 10 * 60 * 1000; + + public static void iotScenario(String dataThingJidString, String dataThingPassword, String readingThingJidString, + String readingThingPassword) + throws TimeoutException, Exception { + final EntityBareJid dataThingJid = JidCreate.entityBareFrom(dataThingJidString); + final EntityBareJid readingThingJid = JidCreate.entityBareFrom(readingThingJidString); + + final XMPPTCPConnectionConfiguration dataThingConnectionConfiguration = XMPPTCPConnectionConfiguration.builder() + .setUsernameAndPassword(dataThingJid.getLocalpart(), dataThingPassword) + .setXmppDomain(dataThingJid.asDomainBareJid()) + .setSecurityMode(SecurityMode.disabled) + .setDebuggerEnabled(true) + .build(); + final XMPPTCPConnectionConfiguration readingThingConnectionConfiguration = XMPPTCPConnectionConfiguration.builder() + .setUsernameAndPassword(readingThingJid.getLocalpart(), readingThingPassword) + .setXmppDomain(readingThingJid.asDomainBareJid()) + .setSecurityMode(SecurityMode.disabled) + .setDebuggerEnabled(true) + .build(); + + final XMPPTCPConnection dataThingConnection = new XMPPTCPConnection(dataThingConnectionConfiguration); + final XMPPTCPConnection readingThingConnection = new XMPPTCPConnection(readingThingConnectionConfiguration); + + dataThingConnection.setPacketReplyTimeout(TIMEOUT); + readingThingConnection.setPacketReplyTimeout(TIMEOUT); + + try { + iotScenario(dataThingConnection, readingThingConnection); + } + finally { + dataThingConnection.disconnect(); + readingThingConnection.disconnect(); + } + } + + public static void iotScenario(XMPPTCPConnection dataThingConnection, XMPPTCPConnection readingThingConnection) + throws TimeoutException, Exception { + dataThingConnection.connect().login(); + readingThingConnection.connect().login(); + ThingState dataThingState = actAsDataThing(dataThingConnection); + + final SimpleResultSyncPoint syncPoint = new SimpleResultSyncPoint(); + dataThingState.setThingStateChangeListener(new AbstractThingStateChangeListener() { + @Override + public void owned(BareJid jid) { + syncPoint.signal(); + } + }); + // Wait until the thing is owned. + syncPoint.waitForResult(TIMEOUT); + printStatus("OWNED - Thing now onwed by " + dataThingState.getOwner()); + + // Make sure things are befriended. + IoTProvisioningManager readingThingProvisioningManager = IoTProvisioningManager.getInstanceFor(readingThingConnection); + readingThingProvisioningManager.sendFriendshipRequestIfRequired(dataThingConnection.getUser().asBareJid()); + + Roster dataThingRoster = Roster.getInstanceFor(dataThingConnection); + RosterUtil.waitUntilOtherEntityIsSubscribed(dataThingRoster, readingThingConnection.getUser().asBareJid(), TIMEOUT); + printStatus("FRIENDSHIP ACCEPTED - Trying to read out data"); + + IoTDataManager readingThingDataManager = IoTDataManager.getInstanceFor(readingThingConnection); + List values = readingThingDataManager.requestMomentaryValuesReadOut(dataThingConnection.getUser()); + if (values.size() != 1) { + throw new IllegalStateException("Unexpected number of values returned: " + values.size()); + } + IoTFieldsExtension field = values.get(0); + printStatus("DATA READ-OUT SUCCESS: " + field.toXML()); + printStatus("IoT SCENARIO FINISHED SUCCESSFULLY"); + } + + private static ThingState actAsDataThing(XMPPTCPConnection connection) throws XMPPException, SmackException, InterruptedException { + final String key = StringUtils.randomString(12); + final String sn = StringUtils.randomString(12); + Thing dataThing = Thing.builder() + .setKey(key) + .setSerialNumber(sn) + .setManufacturer("Ignite Realtime") + .setModel("Smack") + .setVersion("0.1") + .setMomentaryReadOutRequestHandler(new ThingMomentaryReadOutRequest() { + @Override + public void momentaryReadOutRequest(ThingMomentaryReadOutResult callback) { + IoTDataField.IntField field = new IntField("timestamp", (int) (System.currentTimeMillis() / 1000)); + callback.momentaryReadOut(Collections.singletonList(field)); + } + }) + .build(); + IoTDiscoveryManager iotDiscoveryManager = IoTDiscoveryManager.getInstanceFor(connection); + ThingState state = IoTDiscoveryIntegrationTest.registerThing(iotDiscoveryManager, dataThing); + printStatus("SUCCESS: Thing registered:" + dataThing); + return state; + } + + private static void printStatus(CharSequence status) { + // CHECKSTYLE:OFF + System.out.println(status); + // CHECKSTYLE:ON + } + + public static void main(String[] args) throws TimeoutException, Exception { + if (args.length != 4) { + throw new IllegalArgumentException(); + } + iotScenario(args[0], args[1], args[2], args[3]); + } +}