/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.watchlist.internal; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.xwiki.component.annotation.Component; import org.xwiki.localization.ContextualLocalizationManager; import org.xwiki.watchlist.internal.api.AutomaticWatchMode; import org.xwiki.watchlist.internal.api.WatchListStore; import org.xwiki.watchlist.internal.api.WatchedElementType; import org.xwiki.watchlist.internal.documents.WatchListClassDocumentInitializer; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; /** * WatchList store class. Handles user subscription storage. * * @version $Id: 01e7f81e4f408002aa63cde25e44b3a0b08a2ac3 $ */ @Component @Singleton public class DefaultWatchListStore implements WatchListStore { /** * Character used to separated wiki and space in XWiki model. */ public static final String WIKI_SPACE_SEP = ":"; /** * Character used to separated space and page in XWiki model. */ public static final String SPACE_PAGE_SEP = "."; /** * Character used to separated values in XProperties lists. */ public static final String PIPE_SEP = "|"; /** * XWiki Class used to store user. */ public static final String USERS_CLASS = "XWiki.XWikiUsers"; /** * Logging helper object. */ @Inject private Logger logger; /** * Context provider. */ @Inject private Provider<XWikiContext> contextProvider; /** * Used to read cached notification related information. */ @Inject private Provider<WatchListNotificationCache> notificationCache; /** * Used to resolve translations. */ @Inject private ContextualLocalizationManager localization; @Override public Collection<String> getWatchedElements(String user, WatchedElementType type) throws XWikiException { BaseObject watchListObject = getWatchListObject(user); List<String> watchedItems = watchListObject.getListValue(getWatchListClassPropertyForType(type)); return watchedItems; } /** * Gets the WatchList XWiki Object from a user's profile's page. * * @param user the user to check * @return the WatchList XWiki BaseObject * @throws XWikiException if BaseObject creation fails or if user does not exists */ public BaseObject getWatchListObject(String user) throws XWikiException { XWikiContext context = contextProvider.get(); XWikiDocument userDocument = context.getWiki().getDocument(user, context); if (userDocument.isNew() || userDocument.getObject(USERS_CLASS) == null) { throw new XWikiException(XWikiException.MODULE_XWIKI_PLUGINS, XWikiException.ERROR_XWIKI_UNKNOWN, "User [" + user + "] does not exists"); } BaseObject obj = userDocument.getObject(WatchListClassDocumentInitializer.DOCUMENT_FULL_NAME); if (obj == null) { obj = createWatchListObject(user, context); } return obj; } /** * Creates a WatchList XWiki Object in the user's profile's page. * * @param user XWiki User * @param context Context of the request * @return the watchlist object that has been created * @throws XWikiException if the document cannot be saved */ public BaseObject createWatchListObject(String user, XWikiContext context) throws XWikiException { XWikiDocument userDocument = context.getWiki().getDocument(user, context); BaseObject object = userDocument.newObject(WatchListClassDocumentInitializer.DOCUMENT_FULL_NAME, context); context.getWiki().saveDocument(userDocument, this.localization.getTranslationPlain("watchlist.create.object"), true, context); return object; } /** * Get the name of the XClass property the given type is stored in. * * @param type type to retrieve * @return the name of the XClass property */ private String getWatchListClassPropertyForType(WatchedElementType type) { String result = StringUtils.EMPTY; switch (type) { case WIKI: result = WatchListClassDocumentInitializer.WIKIS_PROPERTY; break; case SPACE: result = WatchListClassDocumentInitializer.SPACES_PROPERTY; break; case DOCUMENT: result = WatchListClassDocumentInitializer.DOCUMENTS_PROPERTY; break; case USER: result = WatchListClassDocumentInitializer.USERS_PROPERTY; break; default: break; } return result; } @Override public boolean isWatched(String element, String user, WatchedElementType type) throws XWikiException { // TODO: Can this be optimized by a direct "exists" query on the list item? Would it e better than what we // currently have with the document cache? If we try a query, it would also need to be performed on the user's // wiki/database, not the current one. Collection<String> watchedElements = getWatchedElements(user, type); if (WatchedElementType.SPACE.equals(type)) { // Special handling for Nested Spaces for (String watchedSpace : watchedElements) { // Check if there is an exact match on the watched space or if the current space is nested inside a // watched space. String watchedSpacePrefix = String.format("%s.", watchedSpace); if (element.equals(watchedSpace) || element.startsWith(watchedSpacePrefix)) { return true; } } return false; } else { return watchedElements.contains(element); } } @Override public boolean addWatchedElement(String user, String newWatchedElement, WatchedElementType type) throws XWikiException { XWikiContext context = contextProvider.get(); String elementToWatch = newWatchedElement; if (!WatchedElementType.WIKI.equals(type) && !newWatchedElement.contains(WIKI_SPACE_SEP)) { elementToWatch = context.getWikiId() + WIKI_SPACE_SEP + newWatchedElement; } if (isWatched(elementToWatch, user, type)) { return false; } // Copy the list of watched elements because it could be unmodifiable. List<String> watchedElements = new ArrayList<String>(getWatchedElements(user, type)); watchedElements.add(elementToWatch); setWatchListElementsProperty(user, type, watchedElements); return true; } /** * Sets a DBList property in the user's WatchList Object, then saves the user's profile. * * @param user the user whose watchlist to set * @param type the element type as defined by {@link WatchedElementType} * @param elements the elements to store * @throws XWikiException if the user's profile cannot be saved */ private void setWatchListElementsProperty(String user, WatchedElementType type, Collection<String> elements) throws XWikiException { XWikiContext context = contextProvider.get(); XWikiDocument userDocument = context.getWiki().getDocument(user, context); List<String> elementsList = new ArrayList<String>(elements); userDocument.setDBStringListValue(WatchListClassDocumentInitializer.DOCUMENT_FULL_NAME, getWatchListClassPropertyForType(type), elementsList); context.getWiki().saveDocument(userDocument, localization.getTranslationPlain("watchlist.save.object"), true, context); } @Override public boolean removeWatchedElement(String user, String watchedElement, WatchedElementType type) throws XWikiException { XWikiContext context = contextProvider.get(); String elementToRemove = watchedElement; if (!WatchedElementType.WIKI.equals(type) && !watchedElement.contains(WIKI_SPACE_SEP)) { elementToRemove = context.getWikiId() + WIKI_SPACE_SEP + watchedElement; } if (!this.isWatched(elementToRemove, user, type)) { return false; } Collection<String> watchedElements = getWatchedElements(user, type); watchedElements.remove(elementToRemove); this.setWatchListElementsProperty(user, type, watchedElements); return true; } @Override public AutomaticWatchMode getAutomaticWatchMode(String user) { XWikiContext context = contextProvider.get(); AutomaticWatchMode mode = null; try { BaseObject watchObject = getWatchListObject(user); String value = watchObject.getStringValue(WatchListClassDocumentInitializer.AUTOMATICWATCH_PROPERTY); if (StringUtils.isNotBlank(value) && !WatchListClassDocumentInitializer.AUTOMATICWATCH_DEFAULT_VALUE.equals(value)) { mode = AutomaticWatchMode.valueOf(value); } } catch (Exception e) { // Failed for some reason, now try getting it from xwiki.cfg logger.error("Failed to get automatic watch mode for user [{}], using fallbacks", user, e); } if (mode == null) { String value = context.getWiki().Param("xwiki.plugin.watchlist.automaticwatch"); if (value != null) { try { mode = AutomaticWatchMode.valueOf(value.toUpperCase()); } catch (Exception e) { logger.warn("Invalid configuration in xwiki.plugin.watchlist.automaticwatch", e); } } } return mode != null ? mode : AutomaticWatchMode.MAJOR; } @Override public List<String> getIntervals() { return notificationCache.get().getIntervals(); } @Override public String getInterval(String user) { String result = ""; try { BaseObject watchObject = getWatchListObject(user); result = watchObject.getStringValue(WatchListClassDocumentInitializer.INTERVAL_PROPERTY); } catch (Exception e) { // Failed for some reason, now try getting it from xwiki.cfg logger.error("Failed to get notification interval for user [{}], using fallbacks", user, e); } return result; } @Override public Collection<String> getSubscribers(String intervalId) { return notificationCache.get().getSubscribers(intervalId); } }