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 synchronized static BookmarkManager getBookmarkManager(XMPPConnection connection)
064    {
065        BookmarkManager manager = bookmarkManagerMap.get(connection);
066        if (manager == null) {
067            manager = new BookmarkManager(connection);
068            bookmarkManagerMap.put(connection, manager);
069        }
070        return manager;
071    }
072
073    private final PrivateDataManager privateDataManager;
074    private Bookmarks bookmarks;
075    private final Object bookmarkLock = new Object();
076
077    /**
078     * Default constructor. Registers the data provider with the private data manager in the
079     * storage:bookmarks namespace.
080     *
081     * @param connection the connection for persisting and retrieving bookmarks.
082     */
083    private BookmarkManager(XMPPConnection connection) {
084        privateDataManager = PrivateDataManager.getInstanceFor(connection);
085    }
086
087    /**
088     * Returns all currently bookmarked conferences.
089     *
090     * @return returns all currently bookmarked conferences
091     * @throws XMPPErrorException 
092     * @throws NoResponseException 
093     * @throws NotConnectedException 
094     * @throws InterruptedException 
095     * @see BookmarkedConference
096     */
097    public List<BookmarkedConference> getBookmarkedConferences() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
098        retrieveBookmarks();
099        return Collections.unmodifiableList(bookmarks.getBookmarkedConferences());
100    }
101
102    /**
103     * Adds or updates a conference in the bookmarks.
104     *
105     * @param name the name of the conference
106     * @param jid the jid of the conference
107     * @param isAutoJoin whether or not to join this conference automatically on login
108     * @param nickname the nickname to use for the user when joining the conference
109     * @param password the password to use for the user when joining the conference
110     * @throws XMPPErrorException thrown when there is an issue retrieving the current bookmarks from
111     * the server.
112     * @throws NoResponseException if there was no response from the server.
113     * @throws NotConnectedException 
114     * @throws InterruptedException 
115     */
116    public void addBookmarkedConference(String name, EntityBareJid jid, boolean isAutoJoin,
117            Resourcepart nickname, String password) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
118    {
119        retrieveBookmarks();
120        BookmarkedConference bookmark
121                = new BookmarkedConference(name, jid, isAutoJoin, nickname, password);
122        List<BookmarkedConference> conferences = bookmarks.getBookmarkedConferences();
123        if (conferences.contains(bookmark)) {
124            BookmarkedConference oldConference = conferences.get(conferences.indexOf(bookmark));
125            if (oldConference.isShared()) {
126                throw new IllegalArgumentException("Cannot modify shared bookmark");
127            }
128            oldConference.setAutoJoin(isAutoJoin);
129            oldConference.setName(name);
130            oldConference.setNickname(nickname);
131            oldConference.setPassword(password);
132        }
133        else {
134            bookmarks.addBookmarkedConference(bookmark);
135        }
136        privateDataManager.setPrivateData(bookmarks);
137    }
138
139    /**
140     * Removes a conference from the bookmarks.
141     *
142     * @param jid the jid of the conference to be removed.
143     * @throws XMPPErrorException thrown when there is a problem with the connection attempting to
144     * retrieve the bookmarks or persist the bookmarks.
145     * @throws NoResponseException if there was no response from the server.
146     * @throws NotConnectedException 
147     * @throws InterruptedException 
148     * @throws IllegalArgumentException thrown when the conference being removed is a shared
149     * conference
150     */
151    public void removeBookmarkedConference(EntityBareJid jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
152        retrieveBookmarks();
153        Iterator<BookmarkedConference> it = bookmarks.getBookmarkedConferences().iterator();
154        while (it.hasNext()) {
155            BookmarkedConference conference = it.next();
156            if (conference.getJid().equals(jid)) {
157                if (conference.isShared()) {
158                    throw new IllegalArgumentException("Conference is shared and can't be removed");
159                }
160                it.remove();
161                privateDataManager.setPrivateData(bookmarks);
162                return;
163            }
164        }
165    }
166
167    /**
168     * Returns an unmodifiable collection of all bookmarked urls.
169     *
170     * @return returns an unmodifiable collection of all bookmarked urls.
171     * @throws XMPPErrorException thrown when there is a problem retriving bookmarks from the server.
172     * @throws NoResponseException if there was no response from the server.
173     * @throws NotConnectedException 
174     * @throws InterruptedException 
175     */
176    public List<BookmarkedURL> getBookmarkedURLs() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
177        retrieveBookmarks();
178        return Collections.unmodifiableList(bookmarks.getBookmarkedURLS());
179    }
180
181    /**
182     * Adds a new url or updates an already existing url in the bookmarks.
183     *
184     * @param URL the url of the bookmark
185     * @param name the name of the bookmark
186     * @param isRSS whether or not the url is an rss feed
187     * @throws XMPPErrorException thrown when there is an error retriving or saving bookmarks from or to
188     * the server
189     * @throws NoResponseException if there was no response from the server.
190     * @throws NotConnectedException 
191     * @throws InterruptedException 
192     */
193    public void addBookmarkedURL(String URL, String name, boolean isRSS) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
194        retrieveBookmarks();
195        BookmarkedURL bookmark = new BookmarkedURL(URL, name, isRSS);
196        List<BookmarkedURL> urls = bookmarks.getBookmarkedURLS();
197        if (urls.contains(bookmark)) {
198            BookmarkedURL oldURL = urls.get(urls.indexOf(bookmark));
199            if (oldURL.isShared()) {
200                throw new IllegalArgumentException("Cannot modify shared bookmarks");
201            }
202            oldURL.setName(name);
203            oldURL.setRss(isRSS);
204        }
205        else {
206            bookmarks.addBookmarkedURL(bookmark);
207        }
208        privateDataManager.setPrivateData(bookmarks);
209    }
210
211    /**
212     *  Removes a url from the bookmarks.
213     *
214     * @param bookmarkURL the url of the bookmark to remove
215     * @throws XMPPErrorException thrown if there is an error retriving or saving bookmarks from or to
216     * the server.
217     * @throws NoResponseException if there was no response from the server.
218     * @throws NotConnectedException 
219     * @throws InterruptedException 
220     */
221    public void removeBookmarkedURL(String bookmarkURL) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
222        retrieveBookmarks();
223        Iterator<BookmarkedURL> it = bookmarks.getBookmarkedURLS().iterator();
224        while (it.hasNext()) {
225            BookmarkedURL bookmark = it.next();
226            if (bookmark.getURL().equalsIgnoreCase(bookmarkURL)) {
227                if (bookmark.isShared()) {
228                    throw new IllegalArgumentException("Cannot delete a shared bookmark.");
229                }
230                it.remove();
231                privateDataManager.setPrivateData(bookmarks);
232                return;
233            }
234        }
235    }
236
237    /**
238     * Check if the service supports bookmarks using private data.
239     *
240     * @return true if the service supports private data, false otherwise.
241     * @throws NoResponseException
242     * @throws NotConnectedException
243     * @throws InterruptedException
244     * @throws XMPPErrorException
245     * @see PrivateDataManager#isSupported()
246     * @since 4.2
247     */
248    public boolean isSupported() throws NoResponseException, NotConnectedException,
249                    XMPPErrorException, InterruptedException {
250        return privateDataManager.isSupported();
251    }
252
253    private Bookmarks retrieveBookmarks() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
254        synchronized (bookmarkLock) {
255            if (bookmarks == null) {
256                bookmarks = (Bookmarks) privateDataManager.getPrivateData("storage",
257                        "storage:bookmarks");
258            }
259            return bookmarks;
260        }
261    }
262}