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;
030import org.jivesoftware.smackx.iqprivate.PrivateDataManager;
031import org.jxmpp.jid.EntityBareJid;
032import org.jxmpp.jid.parts.Resourcepart;
033
034
035/**
036 * Provides methods to manage bookmarks in accordance with XEP-0048. Methods for managing URLs and
037 * Conferences are provided.
038 * </p>
039 * It should be noted that some extensions have been made to the XEP. There is an attribute on URLs
040 * that marks a url as a news feed and also a sub-element can be added to either a URL or conference
041 * indicated that it is shared amongst all users on a server.
042 *
043 * @author Alexander Wenckus
044 */
045public final class BookmarkManager {
046    private static final Map<XMPPConnection, BookmarkManager> bookmarkManagerMap = new WeakHashMap<XMPPConnection, BookmarkManager>();
047
048    static {
049        PrivateDataManager.addPrivateDataProvider("storage", "storage:bookmarks",
050                new Bookmarks.Provider());
051    }
052
053    /**
054     * Returns the <i>BookmarkManager</i> for a connection, if it doesn't exist it is created.
055     *
056     * @param connection the connection for which the manager is desired.
057     * @return Returns the <i>BookmarkManager</i> for a connection, if it doesn't
058     * exist it is created.
059     * @throws IllegalArgumentException when the connection is null.
060     */
061    public synchronized static BookmarkManager getBookmarkManager(XMPPConnection connection)
062    {
063        BookmarkManager manager = bookmarkManagerMap.get(connection);
064        if (manager == null) {
065            manager = new BookmarkManager(connection);
066            bookmarkManagerMap.put(connection, manager);
067        }
068        return manager;
069    }
070
071    private final PrivateDataManager privateDataManager;
072    private Bookmarks bookmarks;
073    private final Object bookmarkLock = new Object();
074
075    /**
076     * Default constructor. Registers the data provider with the private data manager in the
077     * storage:bookmarks namespace.
078     *
079     * @param connection the connection for persisting and retrieving bookmarks.
080     */
081    private BookmarkManager(XMPPConnection connection) {
082        privateDataManager = PrivateDataManager.getInstanceFor(connection);
083    }
084
085    /**
086     * Returns all currently bookmarked conferences.
087     *
088     * @return returns all currently bookmarked conferences
089     * @throws XMPPErrorException 
090     * @throws NoResponseException 
091     * @throws NotConnectedException 
092     * @throws InterruptedException 
093     * @see BookmarkedConference
094     */
095    public List<BookmarkedConference> getBookmarkedConferences() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
096        retrieveBookmarks();
097        return Collections.unmodifiableList(bookmarks.getBookmarkedConferences());
098    }
099
100    /**
101     * Adds or updates a conference in the bookmarks.
102     *
103     * @param name the name of the conference
104     * @param jid the jid of the conference
105     * @param isAutoJoin whether or not to join this conference automatically on login
106     * @param nickname the nickname to use for the user when joining the conference
107     * @param password the password to use for the user when joining the conference
108     * @throws XMPPErrorException thrown when there is an issue retrieving the current bookmarks from
109     * the server.
110     * @throws NoResponseException if there was no response from the server.
111     * @throws NotConnectedException 
112     * @throws InterruptedException 
113     */
114    public void addBookmarkedConference(String name, EntityBareJid jid, boolean isAutoJoin,
115            Resourcepart nickname, String password) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
116    {
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 
145     * @throws InterruptedException 
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 retriving bookmarks from the server.
170     * @throws NoResponseException if there was no response from the server.
171     * @throws NotConnectedException 
172     * @throws InterruptedException 
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 or not the url is an rss feed
185     * @throws XMPPErrorException thrown when there is an error retriving or saving bookmarks from or to
186     * the server
187     * @throws NoResponseException if there was no response from the server.
188     * @throws NotConnectedException 
189     * @throws InterruptedException 
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 retriving or saving bookmarks from or to
214     * the server.
215     * @throws NoResponseException if there was no response from the server.
216     * @throws NotConnectedException 
217     * @throws InterruptedException 
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
240     * @throws NotConnectedException
241     * @throws InterruptedException
242     * @throws XMPPErrorException
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}