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;
027import org.jivesoftware.smack.SmackException.NoResponseException;
028import org.jivesoftware.smack.SmackException.NotConnectedException;
029import org.jivesoftware.smack.XMPPConnection;
030import org.jivesoftware.smack.XMPPException;
031import org.jivesoftware.smack.XMPPException.XMPPErrorException;
032import org.jivesoftware.smackx.iqprivate.PrivateDataManager;
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 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 XMPPException 
060     * @throws SmackException thrown has not been authenticated.
061     * @throws IllegalArgumentException when the connection is null.
062     */
063    public synchronized static BookmarkManager getBookmarkManager(XMPPConnection connection)
064                    throws XMPPException, SmackException
065    {
066        BookmarkManager manager = (BookmarkManager) bookmarkManagerMap.get(connection);
067        if (manager == null) {
068            manager = new BookmarkManager(connection);
069        }
070        return manager;
071    }
072
073    private 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) throws XMPPException, SmackException {
084        privateDataManager = PrivateDataManager.getInstanceFor(connection);
085        bookmarkManagerMap.put(connection, this);
086    }
087
088    /**
089     * Returns all currently bookmarked conferences.
090     *
091     * @return returns all currently bookmarked conferences
092     * @throws XMPPErrorException 
093     * @throws NoResponseException 
094     * @throws NotConnectedException 
095     * @see BookmarkedConference
096     */
097    public List<BookmarkedConference> getBookmarkedConferences() throws NoResponseException, XMPPErrorException, NotConnectedException {
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     */
115    public void addBookmarkedConference(String name, String jid, boolean isAutoJoin,
116            String nickname, String password) throws NoResponseException, XMPPErrorException, NotConnectedException
117    {
118        retrieveBookmarks();
119        BookmarkedConference bookmark
120                = new BookmarkedConference(name, jid, isAutoJoin, nickname, password);
121        List<BookmarkedConference> conferences = bookmarks.getBookmarkedConferences();
122        if(conferences.contains(bookmark)) {
123            BookmarkedConference oldConference = conferences.get(conferences.indexOf(bookmark));
124            if(oldConference.isShared()) {
125                throw new IllegalArgumentException("Cannot modify shared bookmark");
126            }
127            oldConference.setAutoJoin(isAutoJoin);
128            oldConference.setName(name);
129            oldConference.setNickname(nickname);
130            oldConference.setPassword(password);
131        }
132        else {
133            bookmarks.addBookmarkedConference(bookmark);
134        }
135        privateDataManager.setPrivateData(bookmarks);
136    }
137
138    /**
139     * Removes a conference from the bookmarks.
140     *
141     * @param jid the jid of the conference to be removed.
142     * @throws XMPPErrorException thrown when there is a problem with the connection attempting to
143     * retrieve the bookmarks or persist the bookmarks.
144     * @throws NoResponseException if there was no response from the server.
145     * @throws NotConnectedException 
146     * @throws IllegalArgumentException thrown when the conference being removed is a shared
147     * conference
148     */
149    public void removeBookmarkedConference(String jid) throws NoResponseException, XMPPErrorException, NotConnectedException {
150        retrieveBookmarks();
151        Iterator<BookmarkedConference> it = bookmarks.getBookmarkedConferences().iterator();
152        while(it.hasNext()) {
153            BookmarkedConference conference = it.next();
154            if(conference.getJid().equalsIgnoreCase(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     */
173    public List<BookmarkedURL> getBookmarkedURLs() throws NoResponseException, XMPPErrorException, NotConnectedException {
174        retrieveBookmarks();
175        return Collections.unmodifiableList(bookmarks.getBookmarkedURLS());
176    }
177
178    /**
179     * Adds a new url or updates an already existing url in the bookmarks.
180     *
181     * @param URL the url of the bookmark
182     * @param name the name of the bookmark
183     * @param isRSS whether or not the url is an rss feed
184     * @throws XMPPErrorException thrown when there is an error retriving or saving bookmarks from or to
185     * the server
186     * @throws NoResponseException if there was no response from the server.
187     * @throws NotConnectedException 
188     */
189    public void addBookmarkedURL(String URL, String name, boolean isRSS) throws NoResponseException, XMPPErrorException, NotConnectedException {
190        retrieveBookmarks();
191        BookmarkedURL bookmark = new BookmarkedURL(URL, name, isRSS);
192        List<BookmarkedURL> urls = bookmarks.getBookmarkedURLS();
193        if(urls.contains(bookmark)) {
194            BookmarkedURL oldURL = urls.get(urls.indexOf(bookmark));
195            if(oldURL.isShared()) {
196                throw new IllegalArgumentException("Cannot modify shared bookmarks");
197            }
198            oldURL.setName(name);
199            oldURL.setRss(isRSS);
200        }
201        else {
202            bookmarks.addBookmarkedURL(bookmark);
203        }
204        privateDataManager.setPrivateData(bookmarks);
205    }
206
207    /**
208     *  Removes a url from the bookmarks.
209     *
210     * @param bookmarkURL the url of the bookmark to remove
211     * @throws XMPPErrorException thrown if there is an error retriving or saving bookmarks from or to
212     * the server.
213     * @throws NoResponseException if there was no response from the server.
214     * @throws NotConnectedException 
215     */
216    public void removeBookmarkedURL(String bookmarkURL) throws NoResponseException, XMPPErrorException, NotConnectedException {
217        retrieveBookmarks();
218        Iterator<BookmarkedURL> it = bookmarks.getBookmarkedURLS().iterator();
219        while(it.hasNext()) {
220            BookmarkedURL bookmark = it.next();
221            if(bookmark.getURL().equalsIgnoreCase(bookmarkURL)) {
222                if(bookmark.isShared()) {
223                    throw new IllegalArgumentException("Cannot delete a shared bookmark.");
224                }
225                it.remove();
226                privateDataManager.setPrivateData(bookmarks);
227                return;
228            }
229        }
230    }
231
232    private Bookmarks retrieveBookmarks() throws NoResponseException, XMPPErrorException, NotConnectedException {
233        synchronized(bookmarkLock) {
234            if(bookmarks == null) {
235                bookmarks = (Bookmarks) privateDataManager.getPrivateData("storage",
236                        "storage:bookmarks");
237            }
238            return bookmarks;
239        }
240    }
241}