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 disble 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}