How to differ updated document from created one according to web feed

No matter what web feed to use, it is difficult to tell the status of document, created or updated.
The publishDate and createdDate is alway same.

Please give advices or suggests to approach this requirement.
Thanks in advance.

Maybe the create and update events are better displayed in the Notifications application ?

I don’t have an easy answer for your request. You might need to look at the code and do the changes yourself in order to display what you want.

In case you need to keep the current feed, try to parse the Description and look for the version. If the page version is 1.1, than it means that the document has been created (although there might be some exceptions).

It would be nice if you could report an issue on the Platform’s Feed component.

Hi Ecaterina,

I am trying to setup the development environment but it is not easy.
I checked codes from git and use Eclipse Luna import as Maven projects.

Some dependencies like org.xwiki.rendering:xwiki-rendering-api:jar:8.4.6-SNAPSHOT could not be found when I look into single project like org.xwiki.platform:xwiki-platform-annotation-core:jar:8.4.6-SNAPSHOT.
If I try to build whole commons projects firstly, some error like “[ERROR] src\main\java\org\xwiki\test\jmock\JMockRule.java:[0] (misc) NewlineAtEndOfFile: 文件未以空行结尾。” stop me.

Do you have some guidance for environment setup?
Aside, when I look into ActiveStreamImpl class, which method should I look firstly?

Thanks a lot.

Issue is reported. [XWIKI-14679] Atom web feed not include deleted entries and contributor list

Hi Ecaterina,

I use following codes to obtain activity events and the first part, direct query gives me all events in contrast the second half doesn’t. The query I composed should be no difference according to source code of class ActivityStreamImpl.
I am not sure the difference is caused by not abling to cast list of ActivityEventImpl into list of ActivityEvent.

{{velocity}}
## Query events directly
#set ($query = 'select act from ActivityEventImpl as act, ActivityEventImpl as act2')
#set ($query = "$query where act.eventId=act2.eventId and")
#set ($query = "$query (act.hidden <> true or act.hidden is null) and")
#set ($query = "$query (act.space=:space OR act.space LIKE :space_nested)")
#set ($query = "$query group by act.requestId having (act.priority)=max(act2.priority) order by act.url")
#set ($queryObject = $services.query.xwql($query).setLimit(20).setOffset(0))
#set ($queryObject = $queryObject.bindValue('space', 'XWiki'))
#set ($queryObject = $queryObject.bindValue('space_nested', 'XWiki.%'))
#set ($events = $queryObject.execute())
#foreach ($event in $events)
  * $event.page | $event.url | $event.user | $event.stream | $event.date | $event.title | $event.body
#end

## Utilize activitystream to query events
#set ($query = '(act.hidden <> true or act.hidden is null)')
#set ($events = $xwiki.activitystream.searchEvents('', $query, true, false, 20, 0, [], $xcontext))
#foreach ($event in $events)
  * $event.page | $event.url | $event.user | $event.stream | $event.date | $event.title | $event.body
#end
#set ($feed = $xwiki.activitystream.getFeed($events, $xcontext))
$xwiki.activitystream.getFeedOutput($feed, $xwiki.getXWikiPreference('feed_type', 'atom_1.0'))
{{/velocity}}

Eventually I found following codes work and it will include additional authors’ names and update dates. And more it try to add deleted document information. The deleted entry is marked as non-nil uri and null link. It might be a bit odd. Anyway it works.

{{velocity}}
## Backward compatibility redirect, if the RSS feed is called without the outputSyntax query argument, force it.
#if (("$!request.xpage" == 'plain' || "$!request.xpage" == 'rdf') && "$!request.outputSyntax" != 'plain')
  $response.sendRedirect($xwiki.getURL($doc.fullName, 'view', 'xpage=plain&amp;outputSyntax=plain'))
#end
#if ("$!request.xpage" == 'plain' && "$!request.outputSyntax" == 'plain')
  $response.setContentType('application/rss+xml')
  ## ======================
  ## Compute Query to find documents
  ## ======================
  #if ("$!{request.tag}" != '')
    ## RSS feed for documents tagged with $request.tag
    #set ($query = 'from doc.object(XWiki.TagClass) as tag where (')
    #foreach ($tag in $request.getParameterValues('tag'))
      #set ($query = "$query :tag${foreach.count} member of tag.tags")
      #if ($foreach.hasNext)
        #set ($query = "$query OR ")
      #end
    #end
    #set ($query = "$query) AND ")
  #else
    #set ($query = 'where')
  #end
  #if ("$!{request.space}" == '')
    ## RSS feed for the whole wiki
    #set ($query = "$query 1=1")
  #else
    ## RSS feed for spaces
    #set ($query = "$query (")
    #foreach ($space in $request.getParameterValues('space'))
      #set ($query = "$query doc.space=:space${foreach.count} OR doc.space LIKE :space_nested${foreach.count}")
      #if ($foreach.hasNext)
        #set ($query = "$query OR ")
      #end
    #end
    #set ($query = "$query)")
  #end
  #set ($query = "$query order by doc.date desc")
  ## ==============
  ## Bind Query parameters
  ## ==============
  ## Bind query parameters depending on the passed query string parameters
  #set ($queryObject = $services.query.xwql($query).addFilter('hidden/document').addFilter('currentlanguage').setLimit(20).setOffset(0))
  #if ("$!{request.tag}" != '')
    #foreach ($tag in $request.getParameterValues('tag'))
      #set ($queryObject = $queryObject.bindValue("tag${foreach.count}", $tag))
    #end
  #end
  #if ("$!{request.space}" != '')
    #foreach ($space in $request.getParameterValues('space'))
      #set ($queryObject = $queryObject.bindValue("space${foreach.count}", $space))
      #set ($queryObject = $queryObject.bindValue("space_nested${foreach.count}", "${space}.%"))
    #end
  #end
  ## ================
  ## Compute feed description
  ## ================
  ## 4 cases to handle: no spaces and tags specified, only spaces specified, only tags specified and spaces and tags specified
  #if ("$!{request.space}" != '')
    #if ("$!{request.tag}" != '')
      #set ($description = $services.localization.render('activity.rss.feed.tagsAndSpaces.description', [$stringtool.join($request.getParameterValues('tag'), ','), $stringtool.join($request.getParameterValues('space'), ',')]))
    #else
      #set ($description = $services.localization.render('activity.rss.feed.spaces.description', [$stringtool.join($request.getParameterValues('space'), ',')]))
    #end
  #else
    #if ("$!{request.tag}" != '')
      #set ($description = $services.localization.render('activity.rss.feed.tags.description', [$stringtool.join($request.getParameterValues('tag'), ',')]))
    #else
      #set ($description = $services.localization.render('activity.rss.feed.description'))
    #end
  #end
  ## =====================
  ## Execute query and generate feed
  ## =====================
  #set ($feed = $xwiki.feed.getWebFeed($queryObject.execute()))
  #set ($feedURI = $xwiki.getDocument('Main.WebHome').getExternalURL('view'))
  #set ($discard = $feed.setLink($feedURI))
  #set ($discard = $feed.setUri($feedURI))
  #set ($discard = $feed.setAuthor('XWiki'))
  #set ($discard = $feed.setTitle($services.localization.render('activity.rss.feed.description')))
  #set ($discard = $feed.setDescription($description))
  #set ($discard = $feed.setLanguage("$xcontext.locale"))
  #set ($discard = $feed.setCopyright($xwiki.getXWikiPreference('copyright')))

  ## =====================
  ## Query activities
  ## =====================
  #set ($query = 'select act from ActivityEventImpl as act, ActivityEventImpl as act2')
  #set ($query = "$query where act.eventId=act2.eventId and")
  #set ($query = "$query (act.hidden <> true or act.hidden is null) and")
  #if ("$!{request.space}" == '')
    ## RSS feed for the whole wiki
    #set ($query = "$query 1=1")
  #else
    ## RSS feed for spaces
    #set ($query = "$query (")
    #foreach ($space in $request.getParameterValues('space'))
      #set ($query = "$query act.space=:space${foreach.count} OR act.space LIKE :space_nested${foreach.count}")
      #if ($foreach.hasNext)
        #set ($query = "$query OR ")
      #end
    #end
    #set ($query = "$query)")
  #end
  #set ($query = "$query group by act.requestId having (act.priority)=max(act2.priority) order by act.url, act.date desc")
  #set ($queryObject = $services.query.xwql($query).setOffset(0))
  #if ("$!{request.space}" != '')
    #foreach ($space in $request.getParameterValues('space'))
      #set ($queryObject = $queryObject.bindValue("space${foreach.count}", $space))
      #set ($queryObject = $queryObject.bindValue("space_nested${foreach.count}", "${space}.%"))
    #end
  #end
  #set ($activities = $queryObject.execute())
#end
{{/velocity}}

{{groovy}}
if (request.xpage == 'plain' && request.outputSyntax == 'plain') {
  def mapDocumentActivities = [:]
  //println(activities)
  for (activity in activities) {
    def name = activity.page
    //println "$name " + name.length()
    if (!mapDocumentActivities.containsKey("$name")) {
      mapDocumentActivities[(name)] = []
    }
    mapDocumentActivities[(name)].add(activity)
  }
  //println mapDocCreateActivities

  //println(feed)
  for (entry in feed?.getEntries()) {
    def decodedUri = URLDecoder.decode(entry.uri, 'UTF-8')
    def pathStartPos = decodedUri.indexOf('view') + 5
    def pathEndPos = decodedUri.indexOf(entry.title.replaceAll(' ', ''))
    if (-1 == pathStartPos || -1 == pathEndPos) {
      continue
    }

    def fullname = decodedUri.substring(pathStartPos, pathEndPos) + entry.title + ".WebHome"
    fullname = fullname.replaceAll('/', '.')
    //println fullname

    // add document content
    def document = xwiki.getDocument(fullname)
    def content = xwiki.renderText(document.getContent(), document)
    def syndContent = new com.sun.syndication.feed.synd.SyndContentImpl()
    syndContent.setValue(content)
    syndContent.setType('text/html')
    //println(content)
    entry.setContents([syndContent])

    if (null != mapDocumentActivities[(fullname)]) {
      for (module in entry.getModules()) {
        //println "$module.date " + mapDocumentActivities[(fullname)].date
        def activities = mapDocumentActivities[(fullname)]
        def contributors = []
        module.setContributors(contributors)
        def dates = module.dates
        for (activity in activities) {
          //println xwiki.getUserName(activity.user, false)
          contributors.add(xwiki.getUserName(activity.user, false))
          dates.add(activity.date)
        }
      }
      //println entry
    }
  }

  // add deleted document entry
  for (activity in activities) {
    if ('delete'.equals(activity.type)) {
      def docFullName = activity.page
      def path = ''
      def inNamePeriodReplacement = UUID.randomUUID().toString()
      def encodedPageName = activity.page.replaceAll('\\\\.', inNamePeriodReplacement)
      def lastPeriodPos = encodedPageName.lastIndexOf('.')
      if (lastPeriodPos > -1) {
        docFullName = encodedPageName.substring(lastPeriodPos + 1).replaceAll(inNamePeriodReplacement, '\\.')
        path = encodedPageName.substring(0, lastPeriodPos).replaceAll('\\.', '/').replaceAll(inNamePeriodReplacement, '\\.')
      }

      def deletedEntry = new com.sun.syndication.feed.synd.SyndEntryImpl()
      deletedEntry.setTitle(docFullName)
      deletedEntry.setUri(path)
      deletedEntry.setPublishedDate(activity.date)
      deletedEntry.setAuthor(xwiki.getUserName(activity.user, false))
      feed.getEntries().add(deletedEntry)
    }
  }
}
{{/groovy}}

{{velocity}}
#if ("$!request.xpage" == 'plain' && "$!request.outputSyntax" == 'plain')
  $xwiki.feed.getFeedOutput($feed, $xwiki.getXWikiPreference('feed_type', 'rss_2.0'))
#end
{{/velocity}}