/* * 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 com.xpn.xwiki.plugin.watchlist; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.ecs.html.Div; import org.apache.ecs.html.Span; import org.suigeneris.jrcs.rcs.Version; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.AttachmentDiff; import com.xpn.xwiki.doc.MetaDataDiff; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.ObjectDiff; import com.xpn.xwiki.plugin.activitystream.api.ActivityEvent; import com.xpn.xwiki.plugin.activitystream.api.ActivityEventType; import com.xpn.xwiki.plugin.diff.DiffPluginApi; /** * The class representing an event in the WatchList. The current implementation is a wrapper for one or more * ActivityEvent. * * @version $Id: f9d126e5387bea4ff7b6c446478d165319c45b98 $ */ @Deprecated public class WatchListEvent implements Comparable<WatchListEvent> { /** * Prefix used in inline style we put in HTML diffs. */ private static final String HTML_STYLE_PLACEHOLDER_PREFIX = "WATCHLIST_STYLE_DIFF_"; /** * Suffix used to insert images later in HTML diffs. */ private static final String HTML_IMG_PLACEHOLDER_SUFFIX = "_WATCHLIST_IMG_PLACEHOLDER"; /** * Prefix used to insert the metadata icon later in HTML diffs. */ private static final String HTML_IMG_METADATA_PREFIX = "metadata"; /** * Prefix used to insert the attachment icon later in HTML diffs. */ private static final String HTML_IMG_ATTACHMENT_PREFIX = "attach"; /** * Default document version on creation. */ private static final String INITIAL_DOCUMENT_VERSION = "1.1"; /** * The version before the initial version used for document, used to get empty versions of documents. */ private static final String PREINITIAL_DOCUMENT_VERSION = "1.0"; /** * Value to display in diffs for hidden properties (email, password, etc). */ private static final String HIDDEN_PROPERTIES_OBFUSCATED_VALUE = "******************"; /** * Name of the password class. */ private static final String PASSWORD_CLASS_NAME = "Password"; /** * Name of email property. */ private static final String EMAIL_PROPERTY_NAME = "email"; /** * Event hashcode. */ private final int hashCode; /** * Prefixed space in which the event happened. */ private final String prefixedSpace; /** * Prefixed document fullName in which the event happened. */ private final String prefixedFullName; /** * The XWiki context. */ private final XWikiContext context; /** * Type of the event (example: "update"). */ private String type; /** * Wrapped events. */ private List<ActivityEvent> activityEvents = new ArrayList<ActivityEvent>(); /** * Version of the document before the event happened. */ private String previousVersion; /** * List of versions affected by this event. It will contain only one entry if the event is not a composite event. */ private List<String> versions; /** * List of authors for this event. It will contain only one entry if the event is not a composite event. */ private List<String> authors; /** * List of dates for this event. It will contain only one entry if the event is not a composite event. */ private List<Date> dates; /** * Difference generated by update events in a document, formatted in HTML. */ private String htmlDiff; /** * Constructor. * * @param activityEvent activity stream event to wrap * @param context the XWiki context */ public WatchListEvent(ActivityEvent activityEvent, XWikiContext context) { this.context = context; this.activityEvents.add(activityEvent); type = activityEvent.getType(); prefixedSpace = activityEvent.getWiki() + WatchListStore.WIKI_SPACE_SEP + activityEvent.getSpace(); prefixedFullName = activityEvent.getWiki() + WatchListStore.WIKI_SPACE_SEP + activityEvent.getPage(); int hash = 3; if (ActivityEventType.UPDATE.equals(activityEvent)) { hashCode = 42 * hash + prefixedFullName.hashCode() + activityEvent.getType().hashCode(); } else { hashCode = 42 * hash + prefixedFullName.hashCode() + activityEvent.getType().hashCode() + activityEvent.getDate().hashCode(); } } /** * Add another event associated to this event. * * @param event The event to add. */ public void addEvent(WatchListEvent event) { if (ActivityEventType.DELETE.equals(event.getType())) { // If the document has been deleted, reset this event activityEvents.clear(); type = event.getType(); versions.clear(); versions = null; authors.clear(); authors = null; previousVersion = null; htmlDiff = null; } else if (ActivityEventType.UPDATE.equals(event.getType()) && ActivityEventType.DELETE.equals(getType())) { // If an update event had been fired before a delete, discard it return; } activityEvents.add(event.getActivityEvent()); } /** * @return The wiki in which the event happened. */ public String getWiki() { return getActivityEvent().getWiki(); } /** * @return The space in which the event happened. */ public String getSpace() { return getActivityEvent().getSpace(); } /** * @return The space, prefixed with the wiki name, in which the event happened (example: "xwiki:Main"). */ public String getPrefixedSpace() { return prefixedSpace; } /** * @return The fullName of the document which has generated this event (example: "Main.WebHome"). */ public String getFullName() { return getActivityEvent().getPage(); } /** * @return The fullName of the document which has generated this event, prefixed with the wiki name. Example: * "xwiki:Main.WebHome". */ public String getPrefixedFullName() { return prefixedFullName; } /** * @return The URL of the document which has fired the event */ public String getUrl() { String url = ""; try { url = context.getWiki().getDocument(getPrefixedFullName(), context).getExternalURL("view", context); } catch (Exception e) { // Do nothing, we don't want to throw exceptions in notification emails. } return url; } /** * @return The date when the event occurred. */ public Date getDate() { return getActivityEvent().getDate(); } /** * @return Get all the dates of a composite event, if this event is not a composite this list will contain single * entry. */ public List<Date> getDates() { if (dates == null) { dates = new ArrayList<Date>(); if (!isComposite()) { dates.add(getDate()); } else { for (ActivityEvent event : activityEvents) { dates.add(event.getDate()); } } } return dates; } /** * @return The type of this event (example: "update", "delete"). */ public String getType() { return type; } /** * @return The underlying ActivityEvent. */ private ActivityEvent getActivityEvent() { return activityEvents.get(0); } /** * @return The user who generated the event. */ public String getAuthor() { return getActivityEvent().getUser(); } /** * @return Get all the authors of a composite event, if this event is not a composite this list will contain single * entry. */ public List<String> getAuthors() { if (authors == null) { authors = new ArrayList<String>(); if (!isComposite()) { authors.add(getAuthor()); } else { for (ActivityEvent event : activityEvents) { String prefixedAuthor = event.getWiki() + WatchListStore.WIKI_SPACE_SEP + event.getUser(); if (!authors.contains(prefixedAuthor)) { authors.add(prefixedAuthor); } } } } return authors; } /** * @return The version of the document at the time it has generated the event. */ public String getVersion() { return getActivityEvent().getVersion(); } /** * @return All the versions from a composite event, if the event is not a composite the list will contain a single * entry */ public List<String> getVersions() { if (versions == null) { versions = new ArrayList<String>(); if (!isComposite()) { if (!StringUtils.isBlank(getActivityEvent().getVersion())) { versions.add(getActivityEvent().getVersion()); } } else { for (ActivityEvent event : activityEvents) { if (!StringUtils.isBlank(event.getVersion()) && !versions.contains(event.getVersion())) { versions.add(event.getVersion()); } } } } return versions; } /** * @return The version of the document which has generated the event, before the actual event. */ public String getPreviousVersion() { if (previousVersion == null) { String currentVersion = ""; previousVersion = ""; try { if (!isComposite()) { currentVersion = getActivityEvent().getVersion(); } else { List<String> allVersions = getVersions(); if (allVersions.size() > 1) { currentVersion = allVersions.get(allVersions.size() - 1); } } if (currentVersion.equals(INITIAL_DOCUMENT_VERSION)) { previousVersion = PREINITIAL_DOCUMENT_VERSION; } if (!StringUtils.isBlank(currentVersion) && StringUtils.isBlank(previousVersion)) { XWikiDocument doc = context.getWiki().getDocument(prefixedFullName, context); XWikiDocument docRev = context.getWiki().getDocument(doc, currentVersion, context); doc.loadArchive(context); Version version = doc.getDocumentArchive().getPrevVersion(docRev.getRCSVersion()); if (version != null) { previousVersion = version.toString(); } } } catch (XWikiException e) { // Catch the exception to be sure we won't send emails containing stacktraces to users. e.printStackTrace(); } } return previousVersion; } /** * @return True if the event is made of multiple events. */ public boolean isComposite() { return activityEvents.size() > 1; } /** * @param classAttr The class of the div to create * @return a HTML div element */ private Div createDiffDiv(String classAttr) { Div div = new Div(); div.setClass(classAttr); div.setStyle(HTML_STYLE_PLACEHOLDER_PREFIX + classAttr); return div; } /** * @param classAttr The class of the span to create * @return an opening span markup */ private Span createDiffSpan(String classAttr) { Span span = new Span(); span.setClass(classAttr); span.setStyle(HTML_STYLE_PLACEHOLDER_PREFIX + classAttr); return span; } /** * Compute the HTML diff for a given property. * * @param objectDiff object diff object * @param diff the diff plugin API * @return the HTML diff * @throws XWikiException if the diff plugin fails to compute the HTML diff */ private String getPropertyHTMLDiff(ObjectDiff objectDiff, DiffPluginApi diff) throws XWikiException { String propDiff = diff.getDifferencesAsHTML(objectDiff.getPrevValue().toString(), objectDiff.getNewValue().toString(), false); // We hide PasswordClass properties and properties named "email" from notifications for security reasons. if ((objectDiff.getPropType().equals(PASSWORD_CLASS_NAME) || objectDiff.getPropName().equals( EMAIL_PROPERTY_NAME)) && !StringUtils.isBlank(propDiff)) { propDiff = HIDDEN_PROPERTIES_OBFUSCATED_VALUE; } return propDiff; } /** * @param objectDiffs List of object diff * @param isXWikiClass is the diff to compute the diff for a xwiki class, the other possibility being a plain xwiki * object * @param documentFullName full name of the document the diff is computed for * @param diff the diff plugin API * @return The HTML diff */ private String getObjectsHTMLDiff(List<List<ObjectDiff>> objectDiffs, boolean isXWikiClass, String documentFullName, DiffPluginApi diff) { StringBuffer result = new StringBuffer(); String propSeparator = ": "; String prefix = (isXWikiClass) ? "class" : "object"; try { for (List<ObjectDiff> oList : objectDiffs) { if (oList.size() > 0) { Div mainDiv = createDiffDiv(prefix + "Diff"); Span objectName = createDiffSpan(prefix + "ClassName"); if (isXWikiClass) { objectName.addElement(getFullName()); } else { objectName.addElement(oList.get(0).getClassName()); } mainDiv.addElement(prefix + HTML_IMG_PLACEHOLDER_SUFFIX); mainDiv.addElement(objectName); for (ObjectDiff oDiff : oList) { String propDiff = getPropertyHTMLDiff(oDiff, diff); if (!StringUtils.isBlank(oDiff.getPropName()) && !StringUtils.isBlank(propDiff)) { Div propDiv = createDiffDiv("propDiffContainer"); Span propNameSpan = createDiffSpan("propName"); propNameSpan.addElement(oDiff.getPropName() + propSeparator); String shortPropType = StringUtils.removeEnd(oDiff.getPropType(), "Class").toLowerCase(); if (StringUtils.isBlank(shortPropType)) { // When the diff shows a property that has been deleted, its type is not available. shortPropType = HTML_IMG_METADATA_PREFIX; } propDiv.addElement(shortPropType + HTML_IMG_PLACEHOLDER_SUFFIX); propDiv.addElement(propNameSpan); Div propDiffDiv = createDiffDiv("propDiff"); propDiffDiv.addElement(propDiff); propDiv.addElement(propDiffDiv); mainDiv.addElement(propDiv); } } result.append(mainDiv); } } } catch (XWikiException e) { // Catch the exception to be sure we won't send emails containing stacktraces to users. e.printStackTrace(); } return result.toString(); } /** * @return The diff, formatted in HTML, to display to the user when a document has been updated, or null if an error * occurred while computing the diff */ public String getHTMLDiff() { if (htmlDiff == null) { try { DiffPluginApi diff = (DiffPluginApi) context.getWiki().getPluginApi("diff", context); StringBuffer result = new StringBuffer(); XWikiDocument d2 = context.getWiki().getDocument(getPrefixedFullName(), context); if (getType().equals(WatchListEventType.CREATE)) { d2 = context.getWiki().getDocument(d2, INITIAL_DOCUMENT_VERSION, context); } XWikiDocument d1 = context.getWiki().getDocument(d2, getPreviousVersion(), context); List<AttachmentDiff> attachDiffs = d2.getAttachmentDiff(d1, d2, context); List<List<ObjectDiff>> objectDiffs = d2.getObjectDiff(d1, d2, context); List<List<ObjectDiff>> classDiffs = d2.getClassDiff(d1, d2, context); List<MetaDataDiff> metaDiffs = d2.getMetaDataDiff(d1, d2, context); if (!d1.getContent().equals(d2.getContent())) { Div contentDiv = createDiffDiv("contentDiff"); String contentDiff = diff.getDifferencesAsHTML(d1.getContent(), d2.getContent(), false); contentDiv.addElement(contentDiff); result.append(contentDiv); } for (AttachmentDiff aDiff : attachDiffs) { Div attachmentDiv = createDiffDiv("attachmentDiff"); attachmentDiv.addElement(HTML_IMG_ATTACHMENT_PREFIX + HTML_IMG_PLACEHOLDER_SUFFIX); attachmentDiv.addElement(aDiff.toString()); result.append(attachmentDiv); } result.append(getObjectsHTMLDiff(objectDiffs, false, getFullName(), diff)); result.append(getObjectsHTMLDiff(classDiffs, true, getFullName(), diff)); for (MetaDataDiff mDiff : metaDiffs) { Div metaDiv = createDiffDiv("metaDiff"); metaDiv.addElement(HTML_IMG_METADATA_PREFIX + HTML_IMG_PLACEHOLDER_SUFFIX); metaDiv.addElement(mDiff.toString()); result.append(metaDiv); } htmlDiff = result.toString(); } catch (XWikiException e) { // Catch the exception to be sure we won't send emails containing stacktraces to users. e.printStackTrace(); } } return htmlDiff; } /** * Perform a string comparison on the prefixed fullName of the source document. * * @param event event to compare with * @return the result of the string comparison */ public int compareTo(WatchListEvent event) { return getPrefixedFullName().compareTo(event.getPrefixedFullName()); } /** * {@inheritDoc} */ @Override public int hashCode() { return hashCode; } /** * Overriding of the default equals method. * * @param obj the ActivityEvent to be compared with * @return True if the two events have been generated by the same document and are equals or conflicting */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof WatchListEvent)) { return false; } // At first this method was returning true when the documents were the same and the events were the same type. // Since we don't want to keep update events for documents that have been deleted this method has been modified // to a point were it performs something different from a equals(), it returns true when obj is a delete event // and 'this' is an update event. See WatchListEventManager#WatchListEventManager(Date, XWikiContext). // TODO: refactoring. WatchListEvent event = ((WatchListEvent) obj); return prefixedFullName.equals(event.getPrefixedFullName()) && WatchListEventType.UPDATE.equals(getType()) && (WatchListEventType.UPDATE.equals(event.getType()) || WatchListEventType.DELETE.equals(event.getType())); } /** * @return the event's internal data. It is a list because this could be a composite event. */ public List getData() { return this.activityEvents; } }