001/** 002 * 003 * Copyright 2015-2017 Ishan Khanna, Fernando Ramirez 2019-2020 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.geoloc; 018 019import java.util.Map; 020import java.util.WeakHashMap; 021 022import org.jivesoftware.smack.ConnectionCreationListener; 023import org.jivesoftware.smack.Manager; 024import org.jivesoftware.smack.SmackException.NoResponseException; 025import org.jivesoftware.smack.SmackException.NotConnectedException; 026import org.jivesoftware.smack.XMPPConnection; 027import org.jivesoftware.smack.XMPPConnectionRegistry; 028import org.jivesoftware.smack.XMPPException.XMPPErrorException; 029import org.jivesoftware.smack.packet.Message; 030 031import org.jivesoftware.smackx.geoloc.packet.GeoLocation; 032import org.jivesoftware.smackx.geoloc.provider.GeoLocationProvider; 033import org.jivesoftware.smackx.pep.PepEventListener; 034import org.jivesoftware.smackx.pep.PepManager; 035import org.jivesoftware.smackx.pubsub.PayloadItem; 036import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException; 037import org.jivesoftware.smackx.xdata.provider.FormFieldChildElementProviderManager; 038 039import org.jxmpp.jid.Jid; 040 041/** 042 * Entry point for Smacks API for XEP-0080: User Location. 043 * <br> 044 * To publish a UserLocation, please use {@link #publishGeoLocation(GeoLocation)} method. This will publish the node. 045 * <br> 046 * To stop publishing a UserLocation, please use {@link #stopPublishingGeolocation()} method. This will send a disable publishing signal. 047 * <br> 048 * To add a {@link PepEventListener} in order to remain updated with other users GeoLocation, use {@link #addGeoLocationListener(PepEventListener)} method. 049 * <br> 050 * To link a GeoLocation with {@link Message}, use `message.addExtension(geoLocation)`. 051 * <br> 052 * An example for illustration is provided inside GeoLocationTest inside the test package. 053 * <br> 054 * @see <a href="https://xmpp.org/extensions/xep-0080.html"> 055 * XEP-0080: User Location</a> 056 */ 057public final class GeoLocationManager extends Manager { 058 059 public static final String GEOLOCATION_NODE = GeoLocation.NAMESPACE; 060 061 private static final Map<XMPPConnection, GeoLocationManager> INSTANCES = new WeakHashMap<>(); 062 063 private final PepManager pepManager; 064 065 static { 066 FormFieldChildElementProviderManager.addFormFieldChildElementProvider( 067 GeoLocationProvider.GeoLocationFormFieldChildElementProvider.INSTANCE); 068 069 XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { 070 @Override 071 public void connectionCreated(XMPPConnection connection) { 072 getInstanceFor(connection); 073 } 074 }); 075 } 076 077 /** 078 * Retrieves a {@link GeoLocationManager} for the specified {@link XMPPConnection}, creating one if it doesn't 079 * already exist. 080 * 081 * @param connection The connection the manager is attached to. 082 * @return The new or existing manager. 083 */ 084 public static synchronized GeoLocationManager getInstanceFor(XMPPConnection connection) { 085 GeoLocationManager geoLocationManager = INSTANCES.get(connection); 086 if (geoLocationManager == null) { 087 geoLocationManager = new GeoLocationManager(connection); 088 INSTANCES.put(connection, geoLocationManager); 089 } 090 return geoLocationManager; 091 } 092 093 private GeoLocationManager(XMPPConnection connection) { 094 super(connection); 095 pepManager = PepManager.getInstanceFor(connection); 096 } 097 098 public void sendGeoLocationToJid(GeoLocation geoLocation, Jid jid) throws InterruptedException, 099 NotConnectedException { 100 101 final XMPPConnection connection = connection(); 102 103 Message geoLocationMessage = connection.getStanzaFactory().buildMessageStanza() 104 .to(jid) 105 .addExtension(geoLocation) 106 .build(); 107 108 connection.sendStanza(geoLocationMessage); 109 110 } 111 112 /** 113 * Returns true if the message contains a GeoLocation extension. 114 * 115 * @param message the message to check if contains a GeoLocation extension or not 116 * @return a boolean indicating whether the message is a GeoLocation message 117 */ 118 public static boolean isGeoLocationMessage(Message message) { 119 return GeoLocation.from(message) != null; 120 } 121 122 /** 123 * Publish the user's geographic location through the Personal Eventing Protocol (PEP). 124 * 125 * @param geoLocation the geographic location to publish. 126 * @throws InterruptedException if the calling thread was interrupted. 127 * @throws NotConnectedException if the XMPP connection is not connected. 128 * @throws XMPPErrorException if there was an XMPP error returned. 129 * @throws NoResponseException if there was no response from the remote entity. 130 * @throws NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node. 131 */ 132 public void publishGeoLocation(GeoLocation geoLocation) 133 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotALeafNodeException { 134 pepManager.publish(GEOLOCATION_NODE, new PayloadItem<GeoLocation>(geoLocation)); 135 } 136 137 /** 138 * Send empty geolocation through the PubSub node. 139 * 140 * @throws InterruptedException if the calling thread was interrupted. 141 * @throws NotConnectedException if the XMPP connection is not connected. 142 * @throws XMPPErrorException if there was an XMPP error returned. 143 * @throws NoResponseException if there was no response from the remote entity. 144 * @throws NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node. 145 */ 146 public void stopPublishingGeolocation() 147 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotALeafNodeException { 148 pepManager.publish(GEOLOCATION_NODE, new PayloadItem<GeoLocation>(GeoLocation.EMPTY_GEO_LOCATION)); 149 } 150 151 public boolean addGeoLocationListener(PepEventListener<GeoLocation> listener) { 152 return pepManager.addPepEventListener(GEOLOCATION_NODE, GeoLocation.class, listener); 153 } 154 155 public boolean removeGeoLocationListener(PepEventListener<GeoLocation> listener) { 156 return pepManager.removePepEventListener(listener); 157 } 158}