How to create ATOM web feed

I need full-text feed, the complete list of contributor and corresponding operation like create/update and deleted entry information also. I found the RSS standard might not enough for hint my goal. I guess I have to develop ATOM feed based on activity stream application by myself.

Could anyone give me some suggestion about which articles, post and guidance I should go through to fulfill my requirement? or any example to follow?

Thanks a lot.

UPDATE
I found ATOM format feed will not help to include deleted entry since com.sun.syndication.feed.synd.SyndEntry doesn’t allow to be marked as deleted.
Then I figure out a final solution to meet my requirement and published in another thread How to differ-updated document from created one according to web feed

The feed plugin seems to support ATOM. See https://github.com/xwiki/xwiki-platform/blob/master/xwiki-platform-core/xwiki-platform-feed/xwiki-platform-feed-api/src/main/java/com/xpn/xwiki/plugin/feed/FeedPluginApi.java#L824 . The feed plugin in exposed in Velocity as:

{{velocity}}
$xwiki.feed
{{/velocity}}

Check the available methods http://platform.xwiki.org/xwiki/bin/view/SRD/xwiki-feed

Hope this helps,
Marius

Hi Marius,

Many thanks for your help.
I am quite new to XWiki and I search the forum to try to figure out how a plugin works. But no lucky, there is just a few articles mention it and they don’t really helpful. I guess I should invoke the method getWebFeedOutput of the feed plugin. When I look at the API of getWebFeedOutput, I get totally lost. A bunch of questions raised in my mind, what is a query, what metadata map should I set and etc.

I try coding as following which is based on activity stream macro rss output script but it does NOT give me expected response.

{{velocity}}
$response.setContentType('application/atom+xml')
#set ($query = "where (doc.space='XWiki') order by doc.date desc")
$xwiki.feed.getWebFeedOutput($query, 20, 0, {}, 'atom_1.0'))
{{/velocity}}

Could you please give an example for using this plugin to get atom xml only?

I refered the RSS Feeds and change the output MIME to “application/atom+xml” and feed type to “atom_1.0”, but nothing is exported.

I found this example, maybe it helps. See https://github.com/xwiki/xwiki-platform/blob/master/xwiki-platform-core/xwiki-platform-activitystream/xwiki-platform-activitystream-ui/src/main/resources/Main/WebRss.xml#L117

Hi Marius,

In my last reply, I mentioned have tried the your suggestion. In the code block, I pasted the abridged version

The exactly code block is as following.

{{velocity}}
## TODO: Rewrite this page completely so that it uses the same source as the Activity Stream Macro
## Backward compatibility redirect, if the ATOM 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&outputSyntax=plain'))
#end
#if ("$!request.xpage" == 'plain' && "$!request.outputSyntax" == 'plain')
  $response.setContentType('application/atom+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()))
  #foreach ($entry in $feed.entries)
    ## Clear the list of contributors. Ideally we should convert the String to a SyndPerson object but we can't do this from Velocity.
    #set ($discard = $entry.setContributors([]))
  #end
  #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')))
  $xwiki.feed.getFeedOutput($feed, $xwiki.getXWikiPreference('feed_type', 'atom_1.0'))
#end
{{/velocity}}

The output is blank (0 byte), I wonder what is wrong with my code.

Update the complete code block according Marius suggested workaround solution.

The feed output is empty because:

java.lang.ClassCastException: java.lang.String cannot be cast to com.sun.syndication.feed.synd.SyndPerson
  at com.sun.syndication.feed.synd.impl.ConverterForAtom03.createAtomPersons(ConverterForAtom03.java:350)
  at com.sun.syndication.feed.synd.impl.ConverterForAtom10.createAtomEntry(ConverterForAtom10.java:536)
  at com.sun.syndication.feed.synd.impl.ConverterForAtom10.createAtomEntries(ConverterForAtom10.java:419)
  at com.sun.syndication.feed.synd.impl.ConverterForAtom10.createRealFeed(ConverterForAtom10.java:400)
  at com.sun.syndication.feed.synd.SyndFeedImpl.createWireFeed(SyndFeedImpl.java:229)
  at com.sun.syndication.feed.synd.SyndFeedImpl.createWireFeed(SyndFeedImpl.java:211)
  at com.sun.syndication.io.SyndFeedOutput.output(SyndFeedOutput.java:134)
  at com.xpn.xwiki.plugin.feed.FeedPlugin.getFeedOutput(FeedPlugin.java:1098)
  at com.xpn.xwiki.plugin.feed.FeedPluginApi.getFeedOutput(FeedPluginApi.java:834)

The Java code that creates the feed based on the query results sets the contributors of each feed entry as a list of Strings (contributor names) rather than a list of SyndPerson. This works for RSS but not for Atom unfortunately, as indicated by the Javadoc of getContributors

For Atom feeds, this returns the contributors as a list of SyndPerson objects

See http://grepcode.com/file/repo1.maven.org/maven2/rome/rome/1.0/com/sun/syndication/feed/synd/SyndEntry.java#SyndEntry.getContributors() . The only workaround I can think of is to clear the list of contributors after creating the feed:

...
#set ($feed = $xwiki.feed.getWebFeed($queryObject.execute()))
#foreach ($entry in $feed.entries)
  ## Clear the list of contributors. Ideally we should convert the String to a SyndPerson object but we can't do this from Velocity.
  #set ($discard = $entry.setContributors([]))
#end
...

Hope this helps,
Marius

The workaround solution give the output, however it doesn’t include deleted entries and looks just as same as RSS.:disappointed:

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

According to another thread How To Differ Updated Document from Created One According to Web Feed, it seems I have to develop my own macro even the extension to implement full-feature ATOM feed.

Do you have any suggestion how to kickoff the whole development? I am totally new to XWiki framework and its design architecture. Thanks.

After studying for couple days, I guess the reason activity stream gives me different RSS or ATOM information against its macro is the scripts in WebRSS.xml which totally uses different query to get information (Hope it is correct).
So maybe I can manually update (add/change) the feed entries list according to result of activity stream query.

I have read the activity stream source code and use following code to get activity stream information.

{{velocity}}
#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
{{/velocity}}

I hope I can correct the create date/published date accroding to events and add deleted entries also.
Now I don’t know how to instantiate feed entries object and I found code block listed in snippet Create Object with Context Data EventListener doesn’t help.
It will be more convinience If I can call the activitystream service directly and utilize its method getFeed.
I tries follwoing codes but without lucky.

#set ($feed = $xwiki.activity.getFeed($events, $xcontext))
$xwiki.activity.getFeedOutput($feed, $xwiki.getXWikiPreference('feed_type', 'atom_1.0'))

Please give some advice.