From 340e186cf6cfc4c220b34a2dd91a9faed167f7e8 Mon Sep 17 00:00:00 2001 From: adiaholic Date: Sun, 20 Oct 2019 16:30:38 +0530 Subject: [PATCH] Make use of `pep` instead of `pubsub` in GeoLocationManager This mini assignment kicked-off with replacing `pubsub` with `pep`, but later transformed into something more. The alterations and additions in this commit: a) GeoLocation. 1) Add Documentation. 2) Add `EMPTY_GEO_LOCATION` to be used while `stopPublishingGeoLocation()` is called. b) Add GeoLocation IntegrationTest. c) Add GeoLocation Listener. d) GeoLocationManager. 1) Add Documentation. 2) Replace `pubsub` with `pep`. 3) Add methods to add-and-remove GeoLocationListeners. 4) Enable GeoLocation by default. e) Add `package.info` for GeoLocation Integration Test. --- .../smackx/geoloc/GeoLocationListener.java | 26 +++ .../smackx/geoloc/GeoLocationManager.java | 100 ++++++++-- .../smackx/geoloc/packet/GeoLocation.java | 183 +++++++++++++++++- .../GeolocationIntegrationTest.java | 107 ++++++++++ .../smackx/geolocation/package-info.java | 23 +++ 5 files changed, 423 insertions(+), 16 deletions(-) create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationListener.java create mode 100644 smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/GeolocationIntegrationTest.java create mode 100644 smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/package-info.java 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;