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}