001/**
002 *
003 * Copyright 2003-2007 Jive Software.
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 */
017
018package org.jivesoftware.smackx.bookmarks;
019
020import java.util.Collections;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Map;
024import java.util.WeakHashMap;
025
026import org.jivesoftware.smack.SmackException.NoResponseException;
027import org.jivesoftware.smack.SmackException.NotConnectedException;
028import org.jivesoftware.smack.XMPPConnection;
029import org.jivesoftware.smack.XMPPException.XMPPErrorException;
030
031import org.jivesoftware.smackx.iqprivate.PrivateDataManager;
032
033import org.jxmpp.jid.EntityBareJid;
034import org.jxmpp.jid.parts.Resourcepart;
035
036
037/**
038 * Provides methods to manage bookmarks in accordance with XEP-0048. Methods for managing URLs and
039 * Conferences are provided.
040 *
041 * It should be noted that some extensions have been made to the XEP. There is an attribute on URLs
042 * that marks a url as a news feed and also a sub-element can be added to either a URL or conference
043 * indicated that it is shared amongst all users on a server.
044 *
045 * @author Alexander Wenckus
046 */
047public final class BookmarkManager {
048    private static final Map<XMPPConnection, BookmarkManager> bookmarkManagerMap = new WeakHashMap<>();
049
050    static {
051        PrivateDataManager.addPrivateDataProvider("storage", "storage:bookmarks",
052                new Bookmarks.Provider());
053    }
054
055    /**
056     * Returns the <i>BookmarkManager</i> for a connection, if it doesn't exist it is created.
057     *
058     * @param connection the connection for which the manager is desired.
059     * @return Returns the <i>BookmarkManager</i> for a connection, if it doesn't
060     * exist it is created.
061     * @throws IllegalArgumentException when the connection is null.
062     */
063    public static synchronized BookmarkManager getBookmarkManager(XMPPConnection connection) {
064        BookmarkManager manager = bookmarkManagerMap.get(connection);
065        if (manager == null) {
066            manager = new BookmarkManager(connection);
067            bookmarkManagerMap.put(connection, manager);
068        }
069        return manager;
070    }
071
072    private final PrivateDataManager privateDataManager;
073    private Bookmarks bookmarks;
074    private final Object bookmarkLock = new Object();
075
076    /**
077     * Default constructor. Registers the data provider with the private data manager in the
078     * storage:bookmarks namespace.
079     *
080     * @param connection the connection for persisting and retrieving bookmarks.
081     */
082    private BookmarkManager(XMPPConnection connection) {
083        privateDataManager = PrivateDataManager.getInstanceFor(connection);
084    }
085
086    /**
087     * Returns all currently bookmarked conferences.
088     *
089     * @return returns all currently bookmarked conferences
090     * @throws XMPPErrorException if there was an XMPP error returned.
091     * @throws NoResponseException if there was no response from the remote entity.
092     * @throws NotConnectedException if the XMPP connection is not connected.
093     * @throws InterruptedException if the calling thread was interrupted.
094     * @see BookmarkedConference
095     */
096    public List<BookmarkedConference> getBookmarkedConferences() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
097        retrieveBookmarks();
098        return Collections.unmodifiableList(bookmarks.getBookmarkedConferences());
099    }
100
101    /**
102     * Adds or updates a conference in the bookmarks.
103     *
104     * @param name the name of the conference
105     * @param jid the jid of the conference
106     * @param isAutoJoin whether to join this conference automatically on login
107     * @param nickname the nickname to use for the user when joining the conference
108     * @param password the password to use for the user when joining the conference
109     * @throws XMPPErrorException thrown when there is an issue retrieving the current bookmarks from
110     * the server.
111     * @throws NoResponseException if there was no response from the server.
112     * @throws NotConnectedException if the XMPP connection is not connected.
113     * @throws InterruptedException if the calling thread was interrupted.
114     */
115    public void addBookmarkedConference(String name, EntityBareJid jid, boolean isAutoJoin,
116            Resourcepart nickname, String password) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
117        retrieveBookmarks();
118        BookmarkedConference bookmark
119                = new BookmarkedConference(name, jid, isAutoJoin, nickname, password);
120        List<BookmarkedConference> conferences = bookmarks.getBookmarkedConferences();
121        if (conferences.contains(bookmark)) {
122            BookmarkedConference oldConference = conferences.get(conferences.indexOf(bookmark));
123            if (oldConference.isShared()) {
124                throw new IllegalArgumentException("Cannot modify shared bookmark");
125            }
126            oldConference.setAutoJoin(isAutoJoin);
127            oldConference.setName(name);
128            oldConference.setNickname(nickname);
129            oldConference.setPassword(password);
130        }
131        else {
132            bookmarks.addBookmarkedConference(bookmark);
133        }
134        privateDataManager.setPrivateData(bookmarks);
135    }
136
137    /**
138     * Removes a conference from the bookmarks.
139     *
140     * @param jid the jid of the conference to be removed.
141     * @throws XMPPErrorException thrown when there is a problem with the connection attempting to
142     * retrieve the bookmarks or persist the bookmarks.
143     * @throws NoResponseException if there was no response from the server.
144     * @throws NotConnectedException if the XMPP connection is not connected.
145     * @throws InterruptedException if the calling thread was interrupted.
146     * @throws IllegalArgumentException thrown when the conference being removed is a shared
147     * conference
148     */
149    public void removeBookmarkedConference(EntityBareJid jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
150        retrieveBookmarks();
151        Iterator<BookmarkedConference> it = bookmarks.getBookmarkedConferences().iterator();
152        while (it.hasNext()) {
153            BookmarkedConference conference = it.next();
154            if (conference.getJid().equals(jid)) {
155                if (conference.isShared()) {
156                    throw new IllegalArgumentException("Conference is shared and can't be removed");
157                }
158                it.remove();
159                privateDataManager.setPrivateData(bookmarks);
160                return;
161            }
162        }
163    }
164
165    /**
166     * Returns an unmodifiable collection of all bookmarked urls.
167     *
168     * @return returns an unmodifiable collection of all bookmarked urls.
169     * @throws XMPPErrorException thrown when there is a problem retrieving bookmarks from the server.
170     * @throws NoResponseException if there was no response from the server.
171     * @throws NotConnectedException if the XMPP connection is not connected.
172     * @throws InterruptedException if the calling thread was interrupted.
173     */
174    public List<BookmarkedURL> getBookmarkedURLs() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
175        retrieveBookmarks();
176        return Collections.unmodifiableList(bookmarks.getBookmarkedURLS());
177    }
178
179    /**
180     * Adds a new url or updates an already existing url in the bookmarks.
181     *
182     * @param URL the url of the bookmark
183     * @param name the name of the bookmark
184     * @param isRSS whether the url is an RSS feed
185     * @throws XMPPErrorException thrown when there is an error retrieving or saving bookmarks from or to
186     * the server
187     * @throws NoResponseException if there was no response from the server.
188     * @throws NotConnectedException if the XMPP connection is not connected.
189     * @throws InterruptedException if the calling thread was interrupted.
190     */
191    public void addBookmarkedURL(String URL, String name, boolean isRSS) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
192        retrieveBookmarks();
193        BookmarkedURL bookmark = new BookmarkedURL(URL, name, isRSS);
194        List<BookmarkedURL> urls = bookmarks.getBookmarkedURLS();
195        if (urls.contains(bookmark)) {
196            BookmarkedURL oldURL = urls.get(urls.indexOf(bookmark));
197            if (oldURL.isShared()) {
198                throw new IllegalArgumentException("Cannot modify shared bookmarks");
199            }
200            oldURL.setName(name);
201            oldURL.setRss(isRSS);
202        }
203        else {
204            bookmarks.addBookmarkedURL(bookmark);
205        }
206        privateDataManager.setPrivateData(bookmarks);
207    }
208
209    /**
210     *  Removes a url from the bookmarks.
211     *
212     * @param bookmarkURL the url of the bookmark to remove
213     * @throws XMPPErrorException thrown if there is an error retrieving or saving bookmarks from or to
214     * the server.
215     * @throws NoResponseException if there was no response from the server.
216     * @throws NotConnectedException if the XMPP connection is not connected.
217     * @throws InterruptedException if the calling thread was interrupted.
218     */
219    public void removeBookmarkedURL(String bookmarkURL) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
220        retrieveBookmarks();
221        Iterator<BookmarkedURL> it = bookmarks.getBookmarkedURLS().iterator();
222        while (it.hasNext()) {
223            BookmarkedURL bookmark = it.next();
224            if (bookmark.getURL().equalsIgnoreCase(bookmarkURL)) {
225                if (bookmark.isShared()) {
226                    throw new IllegalArgumentException("Cannot delete a shared bookmark.");
227                }
228                it.remove();
229                privateDataManager.setPrivateData(bookmarks);
230                return;
231            }
232        }
233    }
234
235    /**
236     * Check if the service supports bookmarks using private data.
237     *
238     * @return true if the service supports private data, false otherwise.
239     * @throws NoResponseException if there was no response from the remote entity.
240     * @throws NotConnectedException if the XMPP connection is not connected.
241     * @throws InterruptedException if the calling thread was interrupted.
242     * @throws XMPPErrorException if there was an XMPP error returned.
243     * @see PrivateDataManager#isSupported()
244     * @since 4.2
245     */
246    public boolean isSupported() throws NoResponseException, NotConnectedException,
247                    XMPPErrorException, InterruptedException {
248        return privateDataManager.isSupported();
249    }
250
251    private Bookmarks retrieveBookmarks() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
252        synchronized (bookmarkLock) {
253            if (bookmarks == null) {
254                bookmarks = (Bookmarks) privateDataManager.getPrivateData("storage",
255                        "storage:bookmarks");
256            }
257            return bookmarks;
258        }
259    }
260}