diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationListener.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationListener.java
new file mode 100644
index 000000000..8b1259ec7
--- /dev/null
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationListener.java
@@ -0,0 +1,26 @@
+/**
+ *
+ * Copyright 2020 Aditya Borikar.
+ *
+ * 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.geoloc;
+
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smackx.geoloc.packet.GeoLocation;
+
+import org.jxmpp.jid.BareJid;
+
+public interface GeoLocationListener {
+ void onGeoLocationUpdated(BareJid jid, GeoLocation geoLocation, Message message);
+}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationManager.java
index a12d975d1..858d1a83a 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationManager.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationManager.java
@@ -16,9 +16,13 @@
*/
package org.jivesoftware.smackx.geoloc;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.WeakHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import org.jivesoftware.smack.AsyncButOrdered;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException.NoResponseException;
@@ -26,22 +30,53 @@ 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.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
-
+import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.geoloc.packet.GeoLocation;
import org.jivesoftware.smackx.geoloc.provider.GeoLocationProvider;
-import org.jivesoftware.smackx.pubsub.LeafNode;
+import org.jivesoftware.smackx.pep.PepListener;
+import org.jivesoftware.smackx.pep.PepManager;
+import org.jivesoftware.smackx.pubsub.EventElement;
+import org.jivesoftware.smackx.pubsub.ItemsExtension;
import org.jivesoftware.smackx.pubsub.PayloadItem;
import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException;
-import org.jivesoftware.smackx.pubsub.PubSubManager;
import org.jivesoftware.smackx.xdata.provider.FormFieldChildElementProviderManager;
+import org.jxmpp.jid.BareJid;
+import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.Jid;
+/**
+ * Entry point for Smacks API for XEP-0080: User Location.
+ *
+ * To publish a UserLocation, please use {@link #sendGeolocation(GeoLocation)} method. This will publish the node.
+ *
+ * To stop publishing a UserLocation, please use {@link #stopPublishingGeolocation()} method. This will send a disble publishing signal.
+ *
+ * To add a {@link GeoLocationListener} in order to remain updated with other users GeoLocation, use {@link #addGeoLocationListener(GeoLocationListener)} method.
+ *
+ * To link a GeoLocation with {@link Message}, use `message.addExtension(geoLocation)`.
+ *
+ * An example for illustration is provided inside GeoLocationTest inside the test package.
+ *
+ * @see
+ * XEP-0080: User Location
+ */
public final class GeoLocationManager extends Manager {
+ public static final String GEOLOCATION_NODE = "http://jabber.org/protocol/geoloc";
+ public static final String GEOLOCATION_NOTIFY = GEOLOCATION_NODE + "+notify";
+
private static final Map INSTANCES = new WeakHashMap<>();
+ private static boolean ENABLE_USER_LOCATION_NOTIFICATIONS_BY_DEFAULT = true;
+
+ private final Set geoLocationListeners = new CopyOnWriteArraySet<>();
+ private final AsyncButOrdered asyncButOrdered = new AsyncButOrdered();
+ private final ServiceDiscoveryManager serviceDiscoveryManager;
+ private final PepManager pepManager;
+
static {
FormFieldChildElementProviderManager.addFormFieldChildElementProvider(
GeoLocationProvider.GeoLocationFormFieldChildElementProvider.INSTANCE);
@@ -54,11 +89,6 @@ public final class GeoLocationManager extends Manager {
});
}
- public GeoLocationManager(XMPPConnection connection) {
- super(connection);
-
- }
-
/**
* Retrieves a {@link GeoLocationManager} for the specified {@link XMPPConnection}, creating one if it doesn't
* already exist.
@@ -75,6 +105,36 @@ public final class GeoLocationManager extends Manager {
return geoLocationManager;
}
+ private GeoLocationManager(XMPPConnection connection) {
+ super(connection);
+ pepManager = PepManager.getInstanceFor(connection);
+ pepManager.addPepListener(new PepListener() {
+
+ @Override
+ public void eventReceived(EntityBareJid from, EventElement event, Message message) {
+ if (!GEOLOCATION_NODE.equals(event.getEvent().getNode())) {
+ return;
+ }
+
+ final BareJid contact = from.asBareJid();
+ asyncButOrdered.performAsyncButOrdered(contact, () -> {
+ ItemsExtension itemsExtension = (ItemsExtension) event.getEvent();
+ List items = itemsExtension.getExtensions();
+ @SuppressWarnings("unchecked")
+ PayloadItem payload = (PayloadItem) items.get(0);
+ GeoLocation geoLocation = payload.getPayload();
+ for (GeoLocationListener listener : geoLocationListeners) {
+ listener.onGeoLocationUpdated(contact, geoLocation, message);
+ }
+ });
+ }
+ });
+ serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
+ if (ENABLE_USER_LOCATION_NOTIFICATIONS_BY_DEFAULT) {
+ enableUserLocationNotifications();
+ }
+ }
+
public void sendGeoLocationToJid(GeoLocation geoLocation, Jid jid) throws InterruptedException,
NotConnectedException {
@@ -111,7 +171,7 @@ public final class GeoLocationManager extends Manager {
*/
public void sendGeolocation(GeoLocation geoLocation)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotALeafNodeException {
- getNode().publish(new PayloadItem(geoLocation));
+ pepManager.publish(GeoLocation.NAMESPACE, new PayloadItem(geoLocation));
}
/**
@@ -125,13 +185,25 @@ public final class GeoLocationManager extends Manager {
*/
public void stopPublishingGeolocation()
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotALeafNodeException {
- GeoLocation emptyGeolocation = new GeoLocation.Builder().build();
- getNode().publish(new PayloadItem(emptyGeolocation));
+ pepManager.publish(GeoLocation.NAMESPACE, new PayloadItem(GeoLocation.EMPTY_GEO_LOCATION));
}
- private LeafNode getNode()
- throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotALeafNodeException {
- return PubSubManager.getInstanceFor(connection()).getOrCreateLeafNode(GeoLocation.NAMESPACE);
+ public void setGeoLocationNotificationsEnabledByDefault(boolean bool) {
+ ENABLE_USER_LOCATION_NOTIFICATIONS_BY_DEFAULT = bool;
}
+ public void enableUserLocationNotifications() {
+ serviceDiscoveryManager.addFeature(GEOLOCATION_NOTIFY);
+ }
+
+ public void disableGeoLocationNotifications() {
+ serviceDiscoveryManager.removeFeature(GEOLOCATION_NOTIFY);
+ }
+
+ public boolean addGeoLocationListener(GeoLocationListener geoLocationListener) {
+ return geoLocationListeners.add(geoLocationListener);
+ }
+ public boolean removeGeoLocationListener(GeoLocationListener geoLocationListener) {
+ return geoLocationListeners.remove(geoLocationListener);
+ }
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/packet/GeoLocation.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/packet/GeoLocation.java
index 0b7b823fa..3cf633ee8 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/packet/GeoLocation.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/packet/GeoLocation.java
@@ -48,6 +48,8 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
+ public static final GeoLocation EMPTY_GEO_LOCATION = GeoLocation.builder().build();
+
private static final Logger LOGGER = Logger.getLogger(GeoLocation.class.getName());
private final Double accuracy;
@@ -264,6 +266,10 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi
return NAMESPACE;
}
+ /**
+ * Returns a new instance of {@link Builder}.
+ * @return Builder
+ */
public static Builder builder() {
return new GeoLocation.Builder();
}
@@ -273,14 +279,35 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi
return true;
}
+ /**
+ * Returns the first GeoLocation, or null
if it doesn't exist in {@link Message}.
+ *
+ * @param message The Message stanza containing GeoLocation
+ * @return GeoLocation
+ */
public static GeoLocation from(Message message) {
return message.getExtension(GeoLocation.class);
}
+ /**
+ * Returns the first GeoLocation, or null
if it doesn't exist in {@link FormField}.
+ *
+ * @param formField the Formfield containing GeoLocation
+ * @return GeoLocation
+ */
public static GeoLocation from(FormField formField) {
return (GeoLocation) formField.getFormFieldChildElement(QNAME);
}
+ /**
+ * This class defines a builder class for {@link GeoLocation}.
+ *
+ * {@link GeoLocation} instance can be obtained using {@link #build()} method as follows.
+ * GeoLocation.Builder builder = GeoLocation.builder();
+ * GeoLocation geoLocation = builder.build();
+ *
+ * To set GeoLocation parameters, use their respective setters.
+ */
public static class Builder {
private Double accuracy;
@@ -308,133 +335,285 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi
private String tzo;
private URI uri;
+ /**
+ * Sets accuracy of horizontal GPS error in meters.
+ *
+ * @param accuracy accuracy in meters
+ * @return Builder
+ */
public Builder setAccuracy(Double accuracy) {
this.accuracy = accuracy;
return this;
}
+ /**
+ * Sets Altitude in meters above or below sea level.
+ *
+ * @param alt altitude in meters
+ * @return Builder
+ */
public Builder setAlt(Double alt) {
this.alt = alt;
return this;
}
+ /**
+ * Sets Vertical GPS error in meters.
+ *
+ * @param altAccuracy altAccuracy in meters
+ * @return Builder
+ */
public Builder setAltAccuracy(Double altAccuracy) {
this.altAccuracy = altAccuracy;
return this;
}
+ /**
+ * Sets a named area such as a campus or neighborhood.
+ *
+ * @param area the named area
+ * @return Builder
+ */
public Builder setArea(String area) {
this.area = area;
return this;
}
+ /**
+ * Sets GPS bearing (direction in which the entity is heading
+ * to reach its next waypoint), measured in decimal degrees,
+ * relative to true north.
+ *
+ * @param bearing bearing in decimal degrees
+ * @return Builder
+ */
public Builder setBearing(Double bearing) {
this.bearing = bearing;
return this;
}
+ /**
+ * Sets a specific building on a street or in an area.
+ *
+ * @param building name of the building
+ * @return Builder
+ */
public Builder setBuilding(String building) {
this.building = building;
return this;
}
+ /**
+ * Sets the nation where the user is located.
+ *
+ * @param country user's country of location
+ * @return Builder
+ */
public Builder setCountry(String country) {
this.country = country;
return this;
}
+ /**
+ * Sets The ISO 3166 two-letter country code.
+ *
+ * @param countryCode two-letter country code
+ * @return Builder
+ */
public Builder setCountryCode(String countryCode) {
this.countryCode = countryCode;
return this;
}
+ /**
+ * Sets GPS Datum.
+ *
+ * @param datum GPS datum
+ * @return Builder
+ */
public Builder setDatum(String datum) {
this.datum = datum;
return this;
}
+ /**
+ * Sets A natural-language name for or description of the location.
+ *
+ * @param description description of the location
+ * @return Builder
+ */
public Builder setDescription(String description) {
this.description = description;
return this;
}
+ /**
+ * Sets Horizontal GPS error in arc minutes;
+ * this element is deprecated in favor of accuracy.
+ *
+ * @param error error in arc minutes
+ * @return Builder
+ */
public Builder setError(Double error) {
this.error = error;
return this;
}
+ /**
+ * Sets a particular floor in a building.
+ *
+ * @param floor floor in a building
+ * @return Builder
+ */
public Builder setFloor(String floor) {
this.floor = floor;
return this;
}
+ /**
+ * Sets Latitude in decimal degrees North.
+ *
+ * @param lat latitude in decimal degrees
+ * @return Builder
+ */
public Builder setLat(Double lat) {
this.lat = lat;
return this;
}
+ /**
+ * Sets Locality within the administrative region,
+ * such as a town or city.
+ *
+ * @param locality locality in a region
+ * @return Builder
+ */
public Builder setLocality(String locality) {
this.locality = locality;
return this;
}
+ /**
+ * Sets Longitude in decimal degrees East.
+ *
+ * @param lon longitude in decimal degrees
+ * @return Builder
+ */
public Builder setLon(Double lon) {
this.lon = lon;
return this;
}
+ /**
+ * Sets PostalCode used for postal delivery.
+ *
+ * @param postalcode code for postal delivery
+ * @return Builder
+ */
public Builder setPostalcode(String postalcode) {
this.postalcode = postalcode;
return this;
}
+ /**
+ * Sets an administrative region of the nation,
+ * such as a state or province.
+ *
+ * @param region an administrative region
+ * @return Builder
+ */
public Builder setRegion(String region) {
this.region = region;
return this;
}
+ /**
+ * Sets a particular room in a building.
+ *
+ * @param room room inside a building
+ * @return Builder
+ */
public Builder setRoom(String room) {
this.room = room;
return this;
}
+ /**
+ * Sets Speed at which the entity is moving, in meters per second.
+ *
+ * @param speed speed in meters per second
+ * @return Builder
+ */
public Builder setSpeed(Double speed) {
this.speed = speed;
return this;
}
+ /**
+ * Sets a thoroughfare within the locality, or a crossing of two thoroughfares.
+ *
+ * @param street name of the street
+ * @return Builder
+ */
public Builder setStreet(String street) {
this.street = street;
return this;
}
+ /**
+ * Sets a catch-all element that captures any other information about the location.
+ *
+ * @param text distinctive feature about the location
+ * @return Builder
+ */
public Builder setText(String text) {
this.text = text;
return this;
}
+ /**
+ * Sets UTC timestamp specifying the moment when the reading was taken.
+ *
+ * @param timestamp timestamp of the reading
+ * @return Builder
+ */
public Builder setTimestamp(Date timestamp) {
this.timestamp = timestamp;
return this;
}
+ /**
+ * Sets the time zone offset from UTC for the current location.
+ *
+ * @param tzo time zone offset
+ * @return Builder
+ */
public Builder setTzo(String tzo) {
this.tzo = tzo;
return this;
}
+ /**
+ * Sets URI or URL pointing to information about the location.
+ *
+ * @param uri uri to the location
+ * @return Builder
+ */
public Builder setUri(URI uri) {
this.uri = uri;
return this;
}
+ /**
+ * This method is called to build {@link GeoLocation} from the Builder.
+ *
+ * @return GeoLocation
+ */
public GeoLocation build() {
return new GeoLocation(accuracy, alt, altAccuracy, area, bearing, building, country, countryCode, datum, description,
error, floor, lat, locality, lon, postalcode, region, room, speed, street, text, timestamp,
tzo, uri);
}
-
}
-
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/GeolocationIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/GeolocationIntegrationTest.java
new file mode 100644
index 000000000..0c4c9f863
--- /dev/null
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/GeolocationIntegrationTest.java
@@ -0,0 +1,107 @@
+/**
+ *
+ * Copyright 2020 Aditya Borikar.
+ *
+ * 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.geolocation;
+
+import java.net.URI;
+import java.util.concurrent.TimeoutException;
+
+import org.jivesoftware.smack.SmackException.NoResponseException;
+import org.jivesoftware.smack.SmackException.NotConnectedException;
+import org.jivesoftware.smack.SmackException.NotLoggedInException;
+import org.jivesoftware.smack.XMPPException.XMPPErrorException;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smackx.geoloc.GeoLocationListener;
+import org.jivesoftware.smackx.geoloc.GeoLocationManager;
+import org.jivesoftware.smackx.geoloc.packet.GeoLocation;
+
+import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
+import org.igniterealtime.smack.inttest.SmackIntegrationTest;
+import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
+import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil;
+import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
+
+import org.junit.AfterClass;
+
+import org.jxmpp.jid.BareJid;
+import org.jxmpp.util.XmppDateTime;
+
+public class GeolocationIntegrationTest extends AbstractSmackIntegrationTest {
+
+ private final GeoLocationManager glm1;
+ private final GeoLocationManager glm2;
+
+ public GeolocationIntegrationTest(SmackIntegrationTestEnvironment environment) {
+ super(environment);
+ glm1 = GeoLocationManager.getInstanceFor(conOne);
+ glm2 = GeoLocationManager.getInstanceFor(conTwo);
+ }
+
+ @SmackIntegrationTest
+ public void test() throws TimeoutException, Exception {
+ GeoLocation.Builder builder = GeoLocation.builder();
+ GeoLocation geoLocation1 = builder.setAccuracy(23d)
+ .setAlt(1000d)
+ .setAltAccuracy(10d)
+ .setArea("Delhi")
+ .setBearing(10d)
+ .setBuilding("Small Building")
+ .setCountry("India")
+ .setCountryCode("IN")
+ .setDescription("My Description")
+ .setError(90d)
+ .setFloor("top")
+ .setLat(25.098345d)
+ .setLocality("awesome")
+ .setLon(77.992034)
+ .setPostalcode("110085")
+ .setRegion("North")
+ .setRoom("small")
+ .setSpeed(250.0d)
+ .setStreet("Wall Street")
+ .setText("Unit Testing GeoLocation")
+ .setTimestamp(XmppDateTime.parseDate("2004-02-19"))
+ .setTzo("+5:30")
+ .setUri(new URI("http://xmpp.org"))
+ .build();
+
+ IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
+ final SimpleResultSyncPoint geoLocationReceived = new SimpleResultSyncPoint();
+ final GeoLocationListener geoLocationListener = new GeoLocationListener() {
+
+ @Override
+ public void onGeoLocationUpdated(BareJid jid, GeoLocation geoLocation, Message message) {
+ if (geoLocation.equals(geoLocation1)) {
+ geoLocationReceived.signal();
+ }
+ }
+ };
+
+ glm2.addGeoLocationListener(geoLocationListener);
+
+ try {
+ glm1.sendGeolocation(geoLocation1);
+ geoLocationReceived.waitForResult(timeout);
+ } finally {
+ glm2.removeGeoLocationListener(geoLocationListener);
+ }
+ }
+
+ @AfterClass
+ public void unsubscribe() throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
+ IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo);
+ }
+}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/package-info.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/package-info.java
new file mode 100644
index 000000000..7ff880f4e
--- /dev/null
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/package-info.java
@@ -0,0 +1,23 @@
+/**
+ *
+ * Copyright 2020 Aditya Borikar.
+ *
+ * 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.
+ */
+/**
+ * Integration Tests for Smacks support for XEP-0080: User Location.
+ *
+ * @see
+ * XEP-0080: User Location
+ */
+package org.jivesoftware.smackx.geolocation;