Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/SyndEntryDocumentSource.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/SyndEntryDocumentSource.java (revision 0) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/SyndEntryDocumentSource.java (revision 0) @@ -0,0 +1,114 @@ +/* + * 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.feed; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.sun.syndication.feed.synd.SyndEntry; +import com.xpn.xwiki.XWiki; +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.XWikiException; +import com.xpn.xwiki.doc.XWikiDocument; + +/** + * Concrete strategy for computing the field values of a feed entry from any {@link XWikiDocument} + * instance. + */ +public class SyndEntryDocumentSource extends AbstractSyndEntrySource +{ + /** + * @see AbstractSyndEntrySource#getDefaultParams() + */ + public static final Map defaultParams; + + static { + defaultParams = new HashMap(); + defaultParams.put(CONTENT_TYPE, "text/html"); + defaultParams.put(CONTENT_LENGTH, new Integer(250)); + } + + public SyndEntryDocumentSource() + { + this(Collections.EMPTY_MAP); + } + + public SyndEntryDocumentSource(Map params) + { + super(params); + } + + /** + * {@inheritDoc} + * + * @see AbstractSyndEntrySource#getDefaultParams() + */ + protected Map getDefaultParams() + { + return defaultParams; + } + + /** + * {@inheritDoc} + * + * @see SyndEntrySource#source(SyndEntry, Object, Map, XWikiContext) + */ + public void source(SyndEntry entry, Object obj, Map params, XWikiContext context) + throws XWikiException + { + // cast source + XWikiDocument doc = castDocument(obj, context); + + // test access rights + if (!context.getWiki().getRightService().hasAccessLevel("view", context.getUser(), + context.getDatabase() + ":" + doc.getFullName(), context)) { + throw new XWikiException(); + } + + // prepare parameters (overwrite instance parameters) + Map trueParams = joinParams(params, getParams()); + String contentType = (String) trueParams.get(CONTENT_TYPE); + + // compute field values + XWiki xwiki = context.getWiki(); + + String url = doc.getExternalURL("view", "language=" + doc.getRealLanguage(), context); + String title = doc.getDisplayTitle(context); + String creator = xwiki.getUserName(doc.getCreator(), null, false, context); + String author = xwiki.getUserName(doc.getAuthor(), null, false, context); + String description = + "Version " + doc.getVersion() + " edited by " + author + " on " + doc.getDate(); + List contributors = new ArrayList(); + contributors.add(author); + + // fill the feed entry with computed field values + entry.setUri(url); + entry.setLink(url); + entry.setTitle(title); + entry.setDescription(getSyndContent(contentType, description)); + entry.setPublishedDate(doc.getCreationDate()); + entry.setUpdatedDate(doc.getDate()); + entry.setAuthor(creator); + entry.setContributors(contributors); + } +} Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/SyndEntrySource.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/SyndEntrySource.java (revision 0) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/SyndEntrySource.java (revision 0) @@ -0,0 +1,46 @@ +/* + * 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.feed; + +import java.util.Map; + +import com.sun.syndication.feed.synd.SyndEntry; +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.XWikiException; + +/** + * Abstracts a strategy for computing the field values of a feed entry from a generic source. + */ +public interface SyndEntrySource +{ + /** + * Overwrites the current values of the given feed entry with new ones computed from the + * specified source object. + * + * @param entry the feed entry whose fields are going to be overwritten + * @param obj the source for the new values to be set on the fields of the feed entry + * @param params parameters to adjust the computation. Each concrete strategy may define its own + * (key, value) pairs + * @param context the XWiki context + * @throws XWikiException + */ + void source(SyndEntry entry, Object obj, Map params, XWikiContext context) + throws XWikiException; +} Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/FeedPlugin.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/FeedPlugin.java (revision 9044) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/FeedPlugin.java (working copy) @@ -22,17 +22,22 @@ package com.xpn.xwiki.plugin.feed; import java.io.IOException; +import java.io.StringWriter; +import java.lang.reflect.Constructor; import java.net.URL; import java.util.*; import com.sun.syndication.feed.synd.SyndCategory; import com.sun.syndication.feed.synd.SyndContent; import com.sun.syndication.feed.synd.SyndEntry; +import com.sun.syndication.feed.synd.SyndEntryImpl; import com.sun.syndication.feed.synd.SyndFeed; import com.sun.syndication.feed.synd.SyndFeedImpl; +import com.sun.syndication.feed.synd.SyndImage; +import com.sun.syndication.feed.synd.SyndImageImpl; +import com.sun.syndication.io.SyndFeedOutput; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; -import com.xpn.xwiki.web.XWikiEngineContext; import com.xpn.xwiki.api.Api; import com.xpn.xwiki.cache.api.XWikiCache; import com.xpn.xwiki.cache.api.XWikiCacheNeedsRefreshException; @@ -705,4 +710,156 @@ return null; } + /** + * @see FeedPluginApi#getSyndEntrySource(String, Map) + */ + public SyndEntrySource getSyndEntrySource(String className, Map params, XWikiContext context) + throws XWikiException + { + try { + Class sesc = Class.forName(className).asSubclass(SyndEntrySource.class); + Constructor ctor = null; + if (params != null) { + try { + ctor = sesc.getConstructor(new Class[] {Map.class}); + return (SyndEntrySource) ctor.newInstance(new Object[] {params}); + } catch (Throwable t) { + } + } + ctor = sesc.getConstructor(new Class[] {}); + return (SyndEntrySource) ctor.newInstance(new Object[] {}); + } catch (Throwable t) { + throw new XWikiException(XWikiException.MODULE_XWIKI_PLUGINS, + XWikiException.ERROR_XWIKI_UNKNOWN, + "", + t); + } + } + + /** + * @see FeedPluginApi#getFeedEntry() + */ + public SyndEntry getFeedEntry(XWikiContext context) + { + return new SyndEntryImpl(); + } + + /** + * @see FeedPluginApi#getFeedImage() + */ + public SyndImage getFeedImage(XWikiContext context) + { + return new SyndImageImpl(); + } + + /** + * @see FeedPluginApi#getFeed() + */ + public SyndFeed getFeed(XWikiContext context) + { + return new SyndFeedImpl(); + } + + /** + * @see FeedPluginApi#getFeed(List, SyndEntrySourceApi, Map) + */ + public SyndFeed getFeed(List list, SyndEntrySource source, Map sourceParams, + XWikiContext context) throws XWikiException + { + SyndFeed feed = getFeed(context); + List entries = new ArrayList(); + for (int i = 0; i < list.size(); i++) { + SyndEntry entry = getFeedEntry(context); + try { + source.source(entry, list.get(i), sourceParams, context); + entries.add(entry); + } catch (Throwable t) { + // skip this entry + } + } + feed.setEntries(entries); + return feed; + } + + /** + * @see FeedPluginApi#getFeed(String, int, int, SyndEntrySourceApi, Map) + */ + public SyndFeed getFeed(String query, int count, int start, SyndEntrySource source, + Map sourceParams, XWikiContext context) throws XWikiException + { + List entries = + context.getWiki().getStore().searchDocumentsNames(query, count, start, context); + return getFeed(entries, source, sourceParams, context); + } + + /** + * @see FeedPluginApi#getFeed(List, SyndEntrySourceApi, Map, Map) + */ + public SyndFeed getFeed(List list, SyndEntrySource source, Map sourceParams, Map metadata, + XWikiContext context) throws XWikiException + { + SyndFeed feed = getFeed(list, source, sourceParams, context); + fillFeedMetadata(feed, metadata); + return feed; + } + + /** + * @see FeedPluginApi#getFeed(String, int, int, SyndEntrySourceApi, Map, Map) + */ + public SyndFeed getFeed(String query, int count, int start, SyndEntrySource source, + Map sourceParams, Map metadata, XWikiContext context) throws XWikiException + { + SyndFeed feed = getFeed(query, count, start, source, sourceParams, context); + fillFeedMetadata(feed, metadata); + return feed; + } + + private void fillFeedMetadata(SyndFeed feed, Map metadata) + { + feed.setAuthor(String.valueOf(metadata.get("author"))); + feed.setDescription(String.valueOf(metadata.get("description"))); + feed.setCopyright(String.valueOf(metadata.get("copyright"))); + feed.setEncoding(String.valueOf(metadata.get("encoding"))); + feed.setLink(String.valueOf(metadata.get("url"))); + feed.setTitle(String.valueOf(metadata.get("title"))); + feed.setLanguage(String.valueOf(metadata.get("language"))); + } + + /** + * @see FeedPluginApi#getFeedOutput(SyndFeed, String) + */ + public String getFeedOutput(SyndFeed feed, String type, XWikiContext context) + { + feed.setFeedType(type); + StringWriter writer = new StringWriter(); + SyndFeedOutput output = new SyndFeedOutput(); + try { + output.output(feed, writer); + writer.close(); + return writer.toString(); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + /** + * @see FeedPluginApi#getFeedOutput(List, SyndEntrySourceApi, Map, Map, String) + */ + public String getFeedOutput(List list, SyndEntrySource source, Map sourceParams, + Map metadata, String type, XWikiContext context) throws XWikiException + { + SyndFeed feed = getFeed(list, source, sourceParams, metadata, context); + return getFeedOutput(feed, type, context); + } + + /** + * @see FeedPluginApi#getFeedOutput(String, int, int, SyndEntrySourceApi, Map, Map, String) + */ + public String getFeedOutput(String query, int count, int start, SyndEntrySource source, + Map sourceParams, Map metadata, String type, XWikiContext context) throws XWikiException + { + SyndFeed feed = getFeed(query, count, start, source, sourceParams, metadata, context); + return getFeedOutput(feed, type, context); + } } Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/SyndEntryArticleSource.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/SyndEntryArticleSource.java (revision 0) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/SyndEntryArticleSource.java (revision 0) @@ -0,0 +1,120 @@ +/* + * 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.feed; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.sun.syndication.feed.synd.SyndEntry; +import com.xpn.xwiki.XWiki; +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.XWikiException; +import com.xpn.xwiki.doc.XWikiDocument; +import com.xpn.xwiki.objects.BaseObject; + +/** + * Concrete strategy for computing the field values of a feed entry from a {@link XWikiDocument} + * instance containing an XWiki.ArticleClass object. + */ +public class SyndEntryArticleSource extends AbstractSyndEntrySource +{ + /** + * @see AbstractSyndEntrySource#getDefaultParams() + */ + public static final Map defaultParams; + + static { + defaultParams = new HashMap(); + defaultParams.put(CONTENT_TYPE, "text/html"); + defaultParams.put(CONTENT_LENGTH, new Integer(400)); + } + + public SyndEntryArticleSource() + { + this(Collections.EMPTY_MAP); + } + + public SyndEntryArticleSource(Map params) + { + super(params); + } + + /** + * {@inheritDoc} + * + * @see AbstractSyndEntrySource#getDefaultParams() + */ + protected Map getDefaultParams() + { + return defaultParams; + } + + /** + * {@inheritDoc} + * + * @see SyndEntrySource#source(SyndEntry, Object, Map, XWikiContext) + */ + public void source(SyndEntry entry, Object obj, Map params, XWikiContext context) + throws XWikiException + { + // cast source + XWikiDocument doc = castDocument(obj, context); + + // test access rights + if (!context.getWiki().getRightService().hasAccessLevel("view", context.getUser(), + context.getDatabase() + ":" + doc.getFullName(), context)) { + throw new XWikiException(); + } + + // prepare parameters (overwrite instance parameters) + Map trueParams = joinParams(params, getParams()); + String contentType = (String) trueParams.get(CONTENT_TYPE); + int contentLength = ((Number) trueParams.get(CONTENT_LENGTH)).intValue(); + + // compute field values + XWiki xwiki = context.getWiki(); + BaseObject article = doc.getObject("XWiki.ArticleClass"); + + String url = doc.getExternalURL("view", "language=" + doc.getRealLanguage(), context); + String title = doc.display("title", "view", article, context); + String content = doc.display("content", "view", article, context); + String description = getDescription(content, contentLength, url, context); + List categories = + Arrays.asList(doc.display("category", "view", article, context).split(",")); + String creator = xwiki.getUserName(doc.getCreator(), null, false, context); + List contributors = new ArrayList(); + contributors.add(xwiki.getUserName(doc.getAuthor(), null, false, context)); + + // fill the feed entry with computed field values + entry.setUri(url); + entry.setLink(url); + entry.setTitle(title); + entry.setDescription(getSyndContent(contentType, description)); + entry.setCategories(categories); + entry.setPublishedDate(doc.getCreationDate()); + entry.setUpdatedDate(doc.getDate()); + entry.setAuthor(creator); + entry.setContributors(contributors); + } +} Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/AbstractSyndEntrySource.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/AbstractSyndEntrySource.java (revision 0) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/AbstractSyndEntrySource.java (revision 0) @@ -0,0 +1,130 @@ +/* + * 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.feed; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import com.sun.syndication.feed.synd.SyndContent; +import com.sun.syndication.feed.synd.SyndContentImpl; +import com.sun.syndication.feed.synd.SyndEntry; +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.XWikiException; +import com.xpn.xwiki.api.Document; +import com.xpn.xwiki.doc.XWikiDocument; + +/** + * Abstract strategy for computing the field values of a feed entry + */ +public abstract class AbstractSyndEntrySource implements SyndEntrySource +{ + public static final String CONTENT_TYPE = "ContentType"; + + public static final String CONTENT_LENGTH = "ContentLength"; + + /** + * Strategy instance parameters. Each concrete strategy can define its own (paramName, + * paramValue) pairs. These parameters are overwritten by those used when calling + * {@link SyndEntrySource#source(SyndEntry, Object, Map, XWikiContext)} method + */ + private Map params; + + public AbstractSyndEntrySource(Map params) + { + setParams(params); + } + + public Map getParams() + { + return params; + } + + public void setParams(Map params) + { + this.params = joinParams(params, getDefaultParams()); + } + + /** + * Strategy class parameters + */ + protected Map getDefaultParams() + { + return Collections.EMPTY_MAP; + } + + /** + * @return base + (extra - base) + */ + protected Map joinParams(Map base, Map extra) + { + Map params = new HashMap(); + params.putAll(base); + Iterator it = extra.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + if (params.get(entry.getKey()) == null) { + params.put(entry.getKey(), entry.getValue()); + } + } + return params; + } + + protected String getDescription(String content, int contentLength, String url, + XWikiContext context) + { + if (content.length() > contentLength) { + int spaceIndex = content.indexOf(" ", contentLength); + if (spaceIndex < 0) { + spaceIndex = contentLength; + } + if (spaceIndex < 0) { + spaceIndex = 0; + } + content = content.substring(0, spaceIndex); + content = content.concat(" ..."); + } + return context.getDoc().getRenderedContent(content, context); + } + + protected SyndContent getSyndContent(String type, String value) + { + SyndContent content = new SyndContentImpl(); + content.setType(type); + content.setValue(value); + return content; + } + + protected XWikiDocument castDocument(Object obj, XWikiContext context) throws XWikiException + { + if (obj instanceof Document) { + return ((Document) obj).getDocument(); + } else if (obj instanceof XWikiDocument) { + return (XWikiDocument) obj; + } else if (obj instanceof String) { + return context.getWiki().getDocument((String) obj, context); + } else { + throw new XWikiException(XWikiException.MODULE_XWIKI_PLUGINS, + XWikiException.ERROR_XWIKI_DOES_NOT_EXIST, + ""); + } + } +} Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/SyndEntrySourceApi.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/SyndEntrySourceApi.java (revision 0) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/SyndEntrySourceApi.java (revision 0) @@ -0,0 +1,97 @@ +/* + * 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.feed; + +import java.util.Collections; +import java.util.Map; + +import com.sun.syndication.feed.synd.SyndEntry; +import com.sun.syndication.feed.synd.SyndEntryImpl; +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.XWikiException; +import com.xpn.xwiki.api.Api; + +/** + * API for {@link SyndEntrySource} + */ +public class SyndEntrySourceApi extends Api +{ + public static final String SYND_ENTRY_SOURCE_EXCEPTION = "SyndEntrySourceException"; + + private SyndEntrySource source; + + public SyndEntrySourceApi(SyndEntrySource source, XWikiContext context) + { + super(context); + this.source = source; + } + + protected SyndEntrySource getSyndEntrySource() + { + return this.source; + } + + /** + * @see SyndEntrySource#source(SyndEntry, Object, java.util.Map, XWikiContext) + */ + public boolean source(SyndEntry entry, Object obj, Map params) + { + getXWikiContext().remove(SYND_ENTRY_SOURCE_EXCEPTION); + try { + this.source.source(entry, obj, params, getXWikiContext()); + return true; + } catch (XWikiException e) { + getXWikiContext().put(SYND_ENTRY_SOURCE_EXCEPTION, e); + return false; + } + } + + /** + * @see SyndEntrySource#source(SyndEntry, Object, java.util.Map, XWikiContext) + */ + public boolean source(SyndEntry entry, Object obj) + { + return this.source(entry, obj, Collections.EMPTY_MAP); + } + + /** + * @see SyndEntrySource#source(SyndEntry, Object, java.util.Map, XWikiContext) + */ + public SyndEntry source(Object obj, Map params) + { + getXWikiContext().remove(SYND_ENTRY_SOURCE_EXCEPTION); + try { + SyndEntry entry = new SyndEntryImpl(); + this.source.source(entry, obj, params, getXWikiContext()); + return entry; + } catch (XWikiException e) { + getXWikiContext().put(SYND_ENTRY_SOURCE_EXCEPTION, e); + return null; + } + } + + /** + * @see SyndEntrySource#source(SyndEntry, Object, java.util.Map, XWikiContext) + */ + public SyndEntry source(Object obj) + { + return this.source(obj, Collections.EMPTY_MAP); + } +} Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/FeedPluginApi.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/FeedPluginApi.java (revision 9044) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/plugin/feed/FeedPluginApi.java (working copy) @@ -23,13 +23,24 @@ import java.io.IOException; import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import com.sun.syndication.feed.synd.SyndEntry; import com.sun.syndication.feed.synd.SyndFeed; +import com.sun.syndication.feed.synd.SyndImage; +import com.xpn.xwiki.XWiki; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.api.Api; +import com.xpn.xwiki.api.Document; +import com.xpn.xwiki.doc.XWikiDocument; +import com.xpn.xwiki.web.XWikiRequest; public class FeedPluginApi extends Api { + public static final String FEED_PLUGIN_EXCEPTION = "FeedPluginException"; + private FeedPlugin plugin; public FeedPluginApi(FeedPlugin plugin, XWikiContext context) { @@ -168,4 +179,685 @@ return plugin.getActiveUpdateThreads(); } + /** + * Tries to instantiate a class implementing the {@link SyndEntrySource} interface using the + * given parameters + * + * @param className the name of a class implementing {@link SyndEntrySource} interface + * @param params constructor parameters + * @return a new SyndEntrySource instance + */ + public SyndEntrySourceApi getSyndEntrySource(String className, Map params) + { + getXWikiContext().remove(FEED_PLUGIN_EXCEPTION); + try { + SyndEntrySource source = + plugin.getSyndEntrySource(className, params, getXWikiContext()); + return new SyndEntrySourceApi(source, getXWikiContext()); + } catch (XWikiException e) { + getXWikiContext().put(FEED_PLUGIN_EXCEPTION, e); + return null; + } + } + + /** + * @see #getSyndEntrySource(String, Map) + */ + public SyndEntrySourceApi getSyndEntrySource(String className) + { + return this.getSyndEntrySource(className, null); + } + + /** + * Instantiates the default strategy for converting documents in feed entries. + * + * @return a new {@link SyndEntrySourceApi} wrapping a {@link SyndEntryDocumentSource} object + */ + public SyndEntrySourceApi getSyndEntryDocumentSource() + { + return this.getSyndEntrySource(SyndEntryDocumentSource.class.getName()); + } + + /** + * Instantiates the default strategy for converting articles in feed entries. + * + * @return a new {@link SyndEntrySourceApi} wrapping a {@link SyndEntryArticleSource} object + */ + public SyndEntrySourceApi getSyndEntryArticleSource() + { + return this.getSyndEntrySource(SyndEntryArticleSource.class.getName()); + } + + /** + * Creates an empty feed entry + * + * @return a new feed entry + */ + public SyndEntry getFeedEntry() + { + return plugin.getFeedEntry(getXWikiContext()); + } + + /** + * Creates an empty feed image + * + * @return a new feed image + */ + public SyndImage getFeedImage() + { + return plugin.getFeedImage(context); + } + + /** + * Creates a new feed image having the given properties. + * + * @param url image URL + * @param title image title + * @param description image description + * @return a new feed image + */ + public SyndImage getFeedImage(String url, String link, String title, String description) + { + SyndImage image = getFeedImage(); + image.setUrl(url); + image.setLink(link); + image.setTitle(title); + image.setDescription(description); + return image; + } + + /** + * Creates a new instance of the default feed image. The default image file name is taken from + * the logo skin preference. If this preference is missing, logo.png is used + * instead. + * + * @return a new feed image + */ + public SyndImage getDefaultFeedImage() + { + // Currently, getSkinFile method returns relative (internal) URLs. I couldn't find a way to + // get the external URL for a skin file. Is this something forbidden? I've noticed that we + // actually compute the full URL but we strip it with urlFactory.getURL(url, context). So + // what do you think of overloading the getSkinFile method by adding a absoluteURL flag? + XWiki xwiki = getXWikiContext().getWiki(); + String fileName = xwiki.getSkinPreference("logo", "logo.png", getXWikiContext()); + String url = xwiki.getSkinFile(fileName, getXWikiContext()); + String port = ""; + XWikiRequest request = getXWikiContext().getRequest(); + if (("http".equals(request.getScheme()) && request.getServerPort() != 80) + || ("https".equals(request.getScheme()) && request.getServerPort() != 443)) { + port = ":" + request.getServerPort(); + } + url = request.getScheme() + "://" + request.getServerName() + port + url; + String link = "http://" + request.getServerName(); + return getFeedImage(url, link, "XWiki Logo", "XWiki Logo"); + } + + /** + * Creates an empty feed + * + * @return a new feed + */ + public SyndFeed getFeed() + { + return plugin.getFeed(getXWikiContext()); + } + + /** + * Computes a new feed from a list of source items and a corresponding strategy for converting + * them in feed entries + * + * @param list the list of source items + * @param sourceApi the strategy to use for computing feed entries from source items + * @param sourceParams strategy parameters + * @return a new feed + */ + public SyndFeed getFeed(List list, SyndEntrySourceApi sourceApi, Map sourceParams) + { + return getFeed(list, sourceApi, sourceParams, Collections.EMPTY_MAP); + } + + /** + * Creates a new feed from a list of documents, using the default strategy for converting + * documents in feed entries. + * + * @param list a list of {@link Document} objects, {@link XWikiDocument} objects or document + * names + * @return a new feed + * @see Document + * @see #getFeed(List, SyndEntrySourceApi, Map) + * @see SyndEntryDocumentSource + */ + public SyndFeed getDocumentFeed(List list) + { + return getDocumentFeed(list, Collections.EMPTY_MAP); + } + + /** + * Creates a new feed from a list of articles, using the default strategy for converting + * articles in feed entries. By articles we mean any document containing an + * XWiki.ArticleClass object. + * + * @param list a list of articles + * @return a new feed + * @see Document + * @see #getFeed(List, SyndEntrySourceApi, Map) + * @see SyndEntryArticleSource + */ + public SyndFeed getArticleFeed(List list) + { + return getArticleFeed(list, Collections.EMPTY_MAP); + } + + /** + * Instantiates the default document feed. + * + * @param list a list of {@link Document} objects, {@link XWikiDocument} objects or document + * names + * @return a new feed + * @see #getDocumentFeed(List) + */ + public SyndFeed getWebFeed(List list) + { + return getWebFeed(list, Collections.EMPTY_MAP); + } + + /** + * Instantiates the default article feed. + * + * @param list a list of articles (as document instances or document names) + * @return a new feed + * @see #getArticleFeed(List) + */ + public SyndFeed getBlogFeed(List list) + { + return getBlogFeed(list, Collections.EMPTY_MAP); + } + + /** + * Creates a new feed from the result of an HQL query and a corresponding strategy for + * converting the retrieved documents in feed entries. + * + * @param query the HQL query used for retrieving the documents + * @param count the maximum number of documents to retrieve + * @param start the start index + * @param sourceApi the strategy to use for computing feed entries from source items + * @param sourceParams strategy parameters + * @return a new feed + */ + public SyndFeed getFeed(String query, int count, int start, SyndEntrySourceApi sourceApi, + Map sourceParams) + { + return getFeed(query, count, start, sourceApi, sourceParams, Collections.EMPTY_MAP); + } + + /** + * Creates a new feed from the result of an HQL query, using the default strategy for converting + * documents in feed entries. + * + * @param query the HQL query used for retrieving the documents + * @param count the maximum number of documents to retrieve + * @param start the start index + * @return a new feed + * @see Document + * @see #getFeed(String, int, int, SyndEntrySourceApi, Map) + * @see SyndEntryDocumentSource + */ + public SyndFeed getDocumentFeed(String query, int count, int start) + { + return getDocumentFeed(query, count, start, Collections.EMPTY_MAP); + } + + /** + * Creates a new feed from the result of an HQL query, using the default strategy for converting + * articles in feed entries. By articles we mean any document containing a + * XWiki.ArticleClass object. + * + * @param query the HQL query used for retrieving the articles + * @param count the maximum number of articles to retrieve + * @param start the start index + * @return a new feed + * @see Document + * @see #getFeed(String, int, int, SyndEntrySourceApi, Map) + * @see SyndEntryArticleSource + */ + public SyndFeed getArticleFeed(String query, int count, int start) + { + return getArticleFeed(query, count, start, Collections.EMPTY_MAP); + } + + /** + * Instantiates the default document feed. + * + * @param query the HQL query used for retrieving the documents + * @param count the maximum number of documents to retrieve + * @param start the start index + * @return a new feed + * @see #getDocumentFeed(String, int, int) + */ + public SyndFeed getWebFeed(String query, int count, int start) + { + return getWebFeed(query, count, start, Collections.EMPTY_MAP); + } + + /** + * Instantiates the default article feed. + * + * @param query the HQL query used for retrieving the articles + * @param count the maximum number of articles to retrieve + * @param start the start index + * @return a new feed + * @see #getArticleFeed(String, int, int) + */ + public SyndFeed getBlogFeed(String query, int count, int start) + { + return getBlogFeed(query, count, start, Collections.EMPTY_MAP); + } + + /** + * Computes a new feed from a list of source items and a corresponding strategy for converting + * them in feed entries, filling in the feed meta data. + * + * @param list the list of source items + * @param sourceApi the strategy to use for computing feed entries from source items + * @param sourceParams strategy parameters + * @param metadata feed meta data (includes the author, description, copyright, encoding, url, + * title) + * @return a new feed + */ + public SyndFeed getFeed(List list, SyndEntrySourceApi sourceApi, Map sourceParams, + Map metadata) + { + getXWikiContext().remove(FEED_PLUGIN_EXCEPTION); + try { + return plugin.getFeed(list, sourceApi.getSyndEntrySource(), sourceParams, metadata, + getXWikiContext()); + } catch (XWikiException e) { + getXWikiContext().put(FEED_PLUGIN_EXCEPTION, e); + return null; + } + } + + /** + * Creates a new feed from a list of documents, using the default strategy for converting + * documents in feed entries, filling in the feed meta data. + * + * @param list a list of {@link Document} objects, {@link XWikiDocument} objects or document + * names + * @param metadata feed meta data (includes the author, description, copyright, encoding, url, + * title) + * @return a new feed + * @see Document + * @see #getFeed(List, SyndEntrySourceApi, Map, Map) + * @see SyndEntryDocumentSource + */ + public SyndFeed getDocumentFeed(List list, Map metadata) + { + return getFeed(list, getSyndEntryDocumentSource(), Collections.EMPTY_MAP, metadata); + } + + /** + * Creates a new feed from a list of articles, using the default strategy for converting + * articles in feed entries, filling in the feed meta data. By articles we mean any document + * containing an XWiki.ArticleClass object. + * + * @param list a list of articles + * @param metadata feed meta data (includes the author, description, copyright, encoding, url, + * title) + * @return a new feed + * @see Document + * @see #getFeed(List, SyndEntrySourceApi, Map, Map) + * @see SyndEntryArticleSource + */ + public SyndFeed getArticleFeed(List list, Map metadata) + { + return getFeed(list, getSyndEntryArticleSource(), Collections.EMPTY_MAP, metadata); + } + + private static boolean keyHasValue(Map map, Object key, Object defaultValue) + { + Object value = map.get(key); + return value != null && !value.equals(defaultValue); + } + + /** + * Fills the missing feed meta data fields with default values. + */ + private Map fillDefaultFeedMetadata(Map metadata) + { + XWiki xwiki = getXWikiContext().getWiki(); + XWikiDocument doc = getXWikiContext().getDoc(); + if (metadata.get("author") == null) { + metadata.put("author", xwiki.getUserName(doc.getAuthor(), null, false, + getXWikiContext())); + } + if (!keyHasValue(metadata, "copyright", "")) { + metadata.put("copyright", xwiki.getWebCopyright(getXWikiContext())); + } + if (!keyHasValue(metadata, "encoding", "")) { + metadata.put("encoding", xwiki.getEncoding()); + } + if (!keyHasValue(metadata, "url", "")) { + metadata.put("url", "http://" + getXWikiContext().getRequest().getServerName()); + } + if (!keyHasValue(metadata, "language", "")) { + metadata.put("language", doc.getDefaultLanguage()); + } + return metadata; + } + + private Map fillWebFeedMetadata(Map metadata) + { + fillDefaultFeedMetadata(metadata); + // these strings should be taken from a resource bundle + String title = "Feed for document changes"; + String description = title; + if (!keyHasValue(metadata, "title", "")) { + metadata.put("title", title); + } + if (!keyHasValue(metadata, "description", "")) { + metadata.put("description", description); + } + return metadata; + } + + private Map fillBlogFeedMetadata(Map metadata) + { + fillDefaultFeedMetadata(metadata); + // these strings should be taken from a resource bundle + String title = "Personal Wiki Blog"; + String description = title; + if (!keyHasValue(metadata, "title", "")) { + metadata.put("title", title); + } + if (!keyHasValue(metadata, "description", "")) { + metadata.put("description", description); + } + return metadata; + } + + /** + * Instantiates the default document feed. + * + * @param list a list of {@link Document} objects, {@link XWikiDocument} objects or document + * names + * @param metadata feed meta data (includes the author, description, copyright, encoding, url, + * title) + * @return a new feed + * @see #getDocumentFeed(List, Map) + */ + public SyndFeed getWebFeed(List list, Map metadata) + { + SyndFeed webFeed = getDocumentFeed(list, fillWebFeedMetadata(metadata)); + if (webFeed != null) { + webFeed.setImage(getDefaultFeedImage()); + } + return webFeed; + } + + /** + * Instantiates the default article feed. + * + * @param list a list of articles (as document instances or document names) + * @param metadata feed meta data (includes the author, description, copyright, encoding, url, + * title) + * @return a new feed + * @see #getArticleFeed(List, Map) + */ + public SyndFeed getBlogFeed(List list, Map metadata) + { + SyndFeed blogFeed = getArticleFeed(list, fillBlogFeedMetadata(metadata)); + if (blogFeed != null) { + blogFeed.setImage(getDefaultFeedImage()); + } + return blogFeed; + } + + /** + * Creates a new feed from the result of an HQL query and a corresponding strategy for + * converting the retrieved documents in feed entries, filling in the feed meta data. + * + * @param query the HQL query used for retrieving the documents + * @param count the maximum number of documents to retrieve + * @param start the start index + * @param sourceApi the strategy to use for computing feed entries from source items + * @param sourceParams strategy parameters + * @param metadata feed meta data (includes the author, description, copyright, encoding, url, + * title) + * @return a new feed + */ + public SyndFeed getFeed(String query, int count, int start, SyndEntrySourceApi sourceApi, + Map sourceParams, Map metadata) + { + getXWikiContext().remove(FEED_PLUGIN_EXCEPTION); + try { + return plugin.getFeed(query, count, start, sourceApi.getSyndEntrySource(), + sourceParams, metadata, getXWikiContext()); + } catch (XWikiException e) { + getXWikiContext().put(FEED_PLUGIN_EXCEPTION, e); + return null; + } + } + + /** + * Creates a new feed from the result of an HQL query, using the default strategy for converting + * documents in feed entries, filling in the feed meta data. + * + * @param query the HQL query used for retrieving the documents + * @param count the maximum number of documents to retrieve + * @param start the start index + * @param metadata feed meta data (includes the author, description, copyright, encoding, url, + * title) + * @return a new feed + * @see Document + * @see #getFeed(String, int, int, SyndEntrySourceApi, Map, Map) + * @see SyndEntryDocumentSource + */ + public SyndFeed getDocumentFeed(String query, int count, int start, Map metadata) + { + return getFeed(query, count, start, getSyndEntryDocumentSource(), Collections.EMPTY_MAP, + metadata); + } + + /** + * Creates a new feed from the result of an HQL query, using the default strategy for converting + * articles in feed entries, filling in the feed meta data. By articles we mean any document + * containing a XWiki.ArticleClass object. + * + * @param query the HQL query used for retrieving the articles + * @param count the maximum number of articles to retrieve + * @param start the start index + * @param metadata feed meta data (includes the author, description, copyright, encoding, url, + * title) + * @return a new feed + * @see Document + * @see #getFeed(String, int, int, SyndEntrySourceApi, Map, Map) + * @see SyndEntryArticleSource + */ + public SyndFeed getArticleFeed(String query, int count, int start, Map metadata) + { + return getFeed(query, count, start, getSyndEntryArticleSource(), Collections.EMPTY_MAP, + metadata); + } + + /** + * Instantiates the default document feed. + * + * @param query the HQL query used for retrieving the documents + * @param count the maximum number of documents to retrieve + * @param start the start index + * @param metadata feed meta data (includes the author, description, copyright, encoding, url, + * title) + * @return a new feed + * @see #getDocumentFeed(String, int, int, Map) + */ + public SyndFeed getWebFeed(String query, int count, int start, Map metadata) + { + if (query == null) { + query = "where 1=1 order by doc.date desc"; + } + SyndFeed webFeed = getDocumentFeed(query, count, start, fillWebFeedMetadata(metadata)); + if (webFeed != null) { + webFeed.setImage(getDefaultFeedImage()); + } + return webFeed; + } + + /** + * Instantiates the default article feed. + * + * @param query the HQL query used for retrieving the articles + * @param count the maximum number of articles to retrieve + * @param start the start index + * @param metadata feed meta data (includes the author, description, copyright, encoding, url, + * title) + * @return a new feed + * @see #getArticleFeed(String, int, int, Map) + */ + public SyndFeed getBlogFeed(String query, int count, int start, Map metadata) + { + if (query == null) { + XWikiRequest request = getXWikiContext().getRequest(); + String category = request.getParameter("category"); + if (category == null || category.equals("")) { + query = + ", BaseObject as obj where obj.name=doc.fullName and obj.className='XWiki.ArticleClass' and obj.name<>'XWiki.ArticleClassTemplate' order by doc.creationDate desc"; + } else { + query = + ", BaseObject as obj, DBStringListProperty as prop join prop.list list where obj.name=doc.fullName and obj.className='XWiki.ArticleClass' and obj.name<>'XWiki.ArticleClassTemplate' and obj.id=prop.id.id and prop.id.name='category' and list = '" + + category + "' order by doc.creationDate desc"; + } + } + SyndFeed blogFeed = getArticleFeed(query, count, start, fillBlogFeedMetadata(metadata)); + if (blogFeed != null) { + blogFeed.setImage(getDefaultFeedImage()); + } + return blogFeed; + } + + /** + * Converts a feed into its string representation using the specified syntax + * + * @param feed any type of feed, implementing the {@link SyndFeed} interface + * @param type the feed type (syntax) to use, null if none. It can be any version of RSS + * or Atom. Some possible values are "rss_1.0", "rss_2.0" and "atom_1.0" + * @return the string representation of the given feed using the syntax associated with the + * specified feed type + */ + public String getFeedOutput(SyndFeed feed, String type) + { + return plugin.getFeedOutput(feed, type, getXWikiContext()); + } + + /** + * @see #getFeedOutput(SyndFeed, String) + * @see #getFeed(List, SyndEntrySourceApi, Map, Map) + */ + public String getFeedOutput(List list, SyndEntrySourceApi sourceApi, Map sourceParams, + Map metadata, String type) + { + getXWikiContext().remove(FEED_PLUGIN_EXCEPTION); + try { + return plugin.getFeedOutput(list, sourceApi.getSyndEntrySource(), sourceParams, + metadata, type, getXWikiContext()); + } catch (XWikiException e) { + getXWikiContext().put(FEED_PLUGIN_EXCEPTION, e); + return null; + } + } + + /** + * @see #getFeedOutput(List, SyndEntrySourceApi, Map, Map, String) + * @see SyndEntryDocumentSource + */ + public String getDocumentFeedOutput(List list, Map metadata, String type) + { + return getFeedOutput(list, getSyndEntryDocumentSource(), Collections.EMPTY_MAP, metadata, + type); + } + + /** + * @see #getFeedOutput(List, SyndEntrySourceApi, Map, Map, String) + * @see SyndEntryArticleSource + */ + public String getArticleFeedOutput(List list, Map metadata, String type) + { + return getFeedOutput(list, getSyndEntryArticleSource(), Collections.EMPTY_MAP, metadata, + type); + } + + /** + * @see #getWebFeed(List, Map) + * @see #getFeedOutput(SyndFeed, String) + */ + public String getWebFeedOutput(List list, Map metadata, String type) + { + return getFeedOutput(getWebFeed(list, metadata), type); + } + + /** + * @see #getBlogFeed(List, Map) + * @see #getFeedOutput(SyndFeed, String) + */ + public String getBlogFeedOutput(List list, Map metadata, String type) + { + return getFeedOutput(getBlogFeed(list, metadata), type); + } + + /** + * @see #getFeedOutput(SyndFeed, String) + * @see #getFeed(String, int, int, SyndEntrySourceApi, Map, Map) + */ + public String getFeedOutput(String query, int count, int start, SyndEntrySourceApi sourceApi, + Map sourceParams, Map metadata, String type) + { + getXWikiContext().remove(FEED_PLUGIN_EXCEPTION); + try { + return plugin.getFeedOutput(query, count, start, sourceApi.getSyndEntrySource(), + sourceParams, metadata, type, getXWikiContext()); + } catch (XWikiException e) { + getXWikiContext().put(FEED_PLUGIN_EXCEPTION, e); + return null; + } + } + + /** + * @see #getFeedOutput(String, int, int, SyndEntrySourceApi, Map, Map, String) + * @see SyndEntryDocumentSource + */ + public String getDocumentFeedOutput(String query, int count, int start, Map metadata, + String type) + { + return getFeedOutput(query, count, start, getSyndEntryDocumentSource(), + Collections.EMPTY_MAP, metadata, type); + } + + /** + * @see #getFeedOutput(String, int, int, SyndEntrySourceApi, Map, Map, String) + * @see SyndEntryArticleSource + */ + public String getArticleFeedOutput(String query, int count, int start, Map metadata, + String type) + { + return getFeedOutput(query, count, start, getSyndEntryArticleSource(), + Collections.EMPTY_MAP, metadata, type); + } + + /** + * @see #getWebFeed(String, int, int, Map) + * @see #getFeedOutput(SyndFeed, String) + */ + public String getWebFeedOutput(String query, int count, int start, Map metadata, String type) + { + return getFeedOutput(getWebFeed(query, count, start, metadata), type); + } + + /** + * @see #getBlogFeed(String, int, int, Map) + * @see #getFeedOutput(SyndFeed, String) + */ + public String getBlogFeedOutput(String query, int count, int start, Map metadata, String type) + { + return getFeedOutput(getBlogFeed(query, count, start, metadata), type); + } }