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.Collection;
021import java.util.Collections;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Map;
025import java.util.WeakHashMap;
026
027import org.jivesoftware.smack.SmackException;
028import org.jivesoftware.smack.SmackException.NoResponseException;
029import org.jivesoftware.smack.SmackException.NotConnectedException;
030import org.jivesoftware.smack.XMPPConnection;
031import org.jivesoftware.smack.XMPPException;
032import org.jivesoftware.smack.XMPPException.XMPPErrorException;
033import org.jivesoftware.smackx.iqprivate.PrivateDataManager;
034
035
036/**
037 * Provides methods to manage bookmarks in accordance with XEP-0048. Methods for managing URLs and
038 * Conferences are provided.
039 * </p>
040 * It should be noted that some extensions have been made to the XEP. There is an attribute on URLs
041 * that marks a url as a news feed and also a sub-element can be added to either a URL or conference
042 * indicated that it is shared amongst all users on a server.
043 *
044 * @author Alexander Wenckus
045 */
046public class BookmarkManager {
047    private static final Map<XMPPConnection, BookmarkManager> bookmarkManagerMap = new WeakHashMap<XMPPConnection, BookmarkManager>();
048
049    static {
050        PrivateDataManager.addPrivateDataProvider("storage", "storage:bookmarks",
051                new Bookmarks.Provider());
052    }
053
054    /**
055     * Returns the <i>BookmarkManager</i> for a connection, if it doesn't exist it is created.
056     *
057     * @param connection the connection for which the manager is desired.
058     * @return Returns the <i>BookmarkManager</i> for a connection, if it doesn't
059     * exist it is created.
060     * @throws XMPPException 
061     * @throws SmackException thrown has not been authenticated.
062     * @throws IllegalArgumentException when the connection is null.
063     */
064    public synchronized static BookmarkManager getBookmarkManager(XMPPConnection connection)
065                    throws XMPPException, SmackException
066    {
067        BookmarkManager manager = (BookmarkManager) bookmarkManagerMap.get(connection);
068        if (manager == null) {
069            manager = new BookmarkManager(connection);
070        }
071        return manager;
072    }
073
074    private PrivateDataManager privateDataManager;
075    private Bookmarks bookmarks;
076    private final Object bookmarkLock = new Object();
077
078    /**
079     * Default constructor. Registers the data provider with the private data manager in the
080     * storage:bookmarks namespace.
081     *
082     * @param connection the connection for persisting and retrieving bookmarks.
083     */
084    private BookmarkManager(XMPPConnection connection) throws XMPPException, SmackException {
085        privateDataManager = PrivateDataManager.getInstanceFor(connection);
086        bookmarkManagerMap.put(connection, this);
087    }
088
089    /**
090     * Returns all currently bookmarked conferences.
091     *
092     * @return returns all currently bookmarked conferences
093     * @throws XMPPErrorException 
094     * @throws NoResponseException 
095     * @throws NotConnectedException 
096     * @see BookmarkedConference
097     */
098    public Collection<BookmarkedConference> getBookmarkedConferences() throws NoResponseException, XMPPErrorException, NotConnectedException {
099        retrieveBookmarks();
100        return Collections.unmodifiableCollection(bookmarks.getBookmarkedConferences());
101    }
102
103    /**
104     * Adds or updates a conference in the bookmarks.
105     *
106     * @param name the name of the conference
107     * @param jid the jid of the conference
108     * @param isAutoJoin whether or not to join this conference automatically on login
109     * @param nickname the nickname to use for the user when joining the conference
110     * @param password the password to use for the user when joining the conference
111     * @throws XMPPErrorException thrown when there is an issue retrieving the current bookmarks from
112     * the server.
113     * @throws NoResponseException if there was no response from the server.
114     * @throws NotConnectedException 
115     */
116    public void addBookmarkedConference(String name, String jid, boolean isAutoJoin,
117            String nickname, String password) throws NoResponseException, XMPPErrorException, NotConnectedException
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 IllegalArgumentException thrown when the conference being removed is a shared
148     * conference
149     */
150    public void removeBookmarkedConference(String jid) throws NoResponseException, XMPPErrorException, NotConnectedException {
151        retrieveBookmarks();
152        Iterator<BookmarkedConference> it = bookmarks.getBookmarkedConferences().iterator();
153        while(it.hasNext()) {
154            BookmarkedConference conference = it.next();
155            if(conference.getJid().equalsIgnoreCase(jid)) {
156                if(conference.isShared()) {
157                    throw new IllegalArgumentException("Conference is shared and can't be removed");
158                }
159                it.remove();
160                privateDataManager.setPrivateData(bookmarks);
161                return;
162            }
163        }
164    }
165
166    /**
167     * Returns an unmodifiable collection of all bookmarked urls.
168     *
169     * @return returns an unmodifiable collection of all bookmarked urls.
170     * @throws XMPPErrorException thrown when there is a problem retriving bookmarks from the server.
171     * @throws NoResponseException if there was no response from the server.
172     * @throws NotConnectedException 
173     */
174    public Collection<BookmarkedURL> getBookmarkedURLs() throws NoResponseException, XMPPErrorException, NotConnectedException {
175        retrieveBookmarks();
176        return Collections.unmodifiableCollection(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     */
190    public void addBookmarkedURL(String URL, String name, boolean isRSS) throws NoResponseException, XMPPErrorException, NotConnectedException {
191        retrieveBookmarks();
192        BookmarkedURL bookmark = new BookmarkedURL(URL, name, isRSS);
193        List<BookmarkedURL> urls = bookmarks.getBookmarkedURLS();
194        if(urls.contains(bookmark)) {
195            BookmarkedURL oldURL = urls.get(urls.indexOf(bookmark));
196            if(oldURL.isShared()) {
197                throw new IllegalArgumentException("Cannot modify shared bookmarks");
198            }
199            oldURL.setName(name);
200            oldURL.setRss(isRSS);
201        }
202        else {
203            bookmarks.addBookmarkedURL(bookmark);
204        }
205        privateDataManager.setPrivateData(bookmarks);
206    }
207
208    /**
209     *  Removes a url from the bookmarks.
210     *
211     * @param bookmarkURL the url of the bookmark to remove
212     * @throws XMPPErrorException thrown if there is an error retriving or saving bookmarks from or to
213     * the server.
214     * @throws NoResponseException if there was no response from the server.
215     * @throws NotConnectedException 
216     */
217    public void removeBookmarkedURL(String bookmarkURL) throws NoResponseException, XMPPErrorException, NotConnectedException {
218        retrieveBookmarks();
219        Iterator<BookmarkedURL> it = bookmarks.getBookmarkedURLS().iterator();
220        while(it.hasNext()) {
221            BookmarkedURL bookmark = it.next();
222            if(bookmark.getURL().equalsIgnoreCase(bookmarkURL)) {
223                if(bookmark.isShared()) {
224                    throw new IllegalArgumentException("Cannot delete a shared bookmark.");
225                }
226                it.remove();
227                privateDataManager.setPrivateData(bookmarks);
228                return;
229            }
230        }
231    }
232
233    private Bookmarks retrieveBookmarks() throws NoResponseException, XMPPErrorException, NotConnectedException {
234        synchronized(bookmarkLock) {
235            if(bookmarks == null) {
236                bookmarks = (Bookmarks) privateDataManager.getPrivateData("storage",
237                        "storage:bookmarks");
238            }
239            return bookmarks;
240        }
241    }
242}