1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.rendering.internal.macro.rss

File RssMacro.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart9.png
38% of files have more coverage

Code metrics

16
51
7
1
260
138
17
0.33
7.29
7
2.43

Classes

Class Line # Actions
RssMacro 64 51 0% 17 11
0.851351485.1%
 

Contributing tests

This file is covered by 8 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.rendering.internal.macro.rss;
21   
22    import java.io.StringReader;
23    import java.util.Arrays;
24    import java.util.Collections;
25    import java.util.List;
26   
27    import javax.inject.Inject;
28    import javax.inject.Named;
29    import javax.inject.Singleton;
30   
31    import org.apache.commons.lang3.StringUtils;
32    import org.xwiki.bridge.SkinAccessBridge;
33    import org.xwiki.component.annotation.Component;
34    import org.xwiki.rendering.block.Block;
35    import org.xwiki.rendering.block.GroupBlock;
36    import org.xwiki.rendering.block.ImageBlock;
37    import org.xwiki.rendering.block.LinkBlock;
38    import org.xwiki.rendering.block.ParagraphBlock;
39    import org.xwiki.rendering.block.RawBlock;
40    import org.xwiki.rendering.listener.reference.ResourceReference;
41    import org.xwiki.rendering.listener.reference.ResourceType;
42    import org.xwiki.rendering.macro.AbstractMacro;
43    import org.xwiki.rendering.macro.Macro;
44    import org.xwiki.rendering.macro.MacroExecutionException;
45    import org.xwiki.rendering.macro.box.BoxMacroParameters;
46    import org.xwiki.rendering.macro.rss.RssMacroParameters;
47    import org.xwiki.rendering.parser.ParseException;
48    import org.xwiki.rendering.parser.Parser;
49    import org.xwiki.rendering.syntax.Syntax;
50    import org.xwiki.rendering.transformation.MacroTransformationContext;
51   
52    import com.sun.syndication.feed.synd.SyndEntry;
53    import com.sun.syndication.feed.synd.SyndFeed;
54   
55    /**
56    * Macro that output latest feed entries from a RSS feed.
57    *
58    * @version $Id: 7b031c0e0567db9cde9ec691064bd1719d9eb432 $
59    * @since 1.8RC1
60    */
61    @Component
62    @Named("rss")
63    @Singleton
 
64    public class RssMacro extends AbstractMacro<RssMacroParameters>
65    {
66    /**
67    * The name of the CSS class attribute.
68    */
69    private static final String CLASS_ATTRIBUTE = "class";
70   
71    private static final String FEED_CLASS_VALUE = "rssfeed";
72   
73    /**
74    * The description of the macro.
75    */
76    private static final String DESCRIPTION = "Output latest feed entries from a RSS feed.";
77   
78    /**
79    * The relative skin path of the feed icon to be displayed in the channel title.
80    */
81    private static final String FEED_ICON_RESOURCE_PATH = "icons/silk/feed.png";
82   
83    /**
84    * The Box macro is used to draw boxes around RSS feed items and for the main around the RSS feed list.
85    */
86    @Inject
87    @Named("box")
88    protected Macro<BoxMacroParameters> boxMacro;
89   
90    /**
91    * Used to get the RSS icon.
92    */
93    @Inject
94    private SkinAccessBridge skinAccessBridge;
95   
96    /**
97    * Needed to parse the ordinary text.
98    */
99    @Inject
100    @Named("plain/1.0")
101    private Parser plainTextParser;
102   
103    /**
104    * Create a Feed object from a feed specified as a URL.
105    */
106    private RomeFeedFactory romeFeedFactory = new DefaultRomeFeedFactory();
107   
108    /**
109    * Create and initialize the descriptor of the macro.
110    */
 
111  8 toggle public RssMacro()
112    {
113  8 super("RSS", DESCRIPTION, RssMacroParameters.class);
114  8 setDefaultCategory(DEFAULT_CATEGORY_CONTENT);
115    }
116   
 
117  0 toggle @Override
118    public boolean supportsInlineMode()
119    {
120  0 return false;
121    }
122   
 
123  8 toggle @Override
124    public List<Block> execute(RssMacroParameters parameters, String content, MacroTransformationContext context)
125    throws MacroExecutionException
126    {
127  8 List<Block> result;
128  8 SyndFeed feed = this.romeFeedFactory.createFeed(parameters);
129   
130  6 if (parameters.isDecoration()) {
131  4 BoxMacroParameters boxParameters = new BoxMacroParameters();
132  4 boolean hasImage = parameters.isImage() && (feed.getImage() != null);
133  4 boxParameters.setCssClass(FEED_CLASS_VALUE);
134   
135  4 if (StringUtils.isNotEmpty(parameters.getWidth())) {
136  0 boxParameters.setWidth(parameters.getWidth());
137    }
138   
139  4 boxParameters.setBlockTitle(generateBoxTitle("rsschanneltitle", feed));
140   
141  4 if (hasImage) {
142  2 boxParameters.setImage(new ResourceReference(feed.getImage().getUrl(), ResourceType.URL));
143    }
144   
145  4 result = this.boxMacro.execute(boxParameters, content == null ? StringUtils.EMPTY : content, context);
146    } else {
147  2 result = Arrays.<Block>asList(new GroupBlock(Collections.singletonMap(CLASS_ATTRIBUTE, FEED_CLASS_VALUE)));
148    }
149   
150  6 generateEntries(result.get(0), feed, parameters);
151   
152  6 return result;
153    }
154   
155    /**
156    * Renders the RSS's title.
157    *
158    * @param cssClass the CSS sheet
159    * @param feed the RSS feed data
160    * @return the list of blocks making the RSS Box title
161    */
 
162  4 toggle private List< ? extends Block> generateBoxTitle(String cssClass, SyndFeed feed)
163    {
164  4 List<Block> titleBlocks;
165   
166  4 if (feed.getLink() == null) {
167  0 titleBlocks = parsePlainText(feed.getTitle());
168    } else {
169    // Title link.
170  4 ResourceReference titleResourceReference = new ResourceReference(feed.getLink(), ResourceType.URL);
171   
172    // Title text link.
173  4 Block titleTextLinkBlock = new LinkBlock(parsePlainText(feed.getTitle()), titleResourceReference, true);
174   
175    // Rss icon.
176  4 String imagePath = this.skinAccessBridge.getSkinFile(FEED_ICON_RESOURCE_PATH);
177  4 ImageBlock imageBlock = new ImageBlock(new ResourceReference(imagePath, ResourceType.URL), false);
178   
179    // Title rss icon link.
180  4 Block titleImageLinkBlock = new LinkBlock(Arrays.<Block> asList(imageBlock), titleResourceReference, true);
181   
182  4 titleBlocks = Arrays.<Block> asList(titleTextLinkBlock, titleImageLinkBlock);
183    }
184  4 ParagraphBlock titleBlock = new ParagraphBlock(titleBlocks);
185  4 titleBlock.setParameter(CLASS_ATTRIBUTE, cssClass);
186   
187  4 return Collections.singletonList(titleBlock);
188    }
189   
190    /**
191    * Renders the given RSS's entries.
192    *
193    * @param parentBlock the parent Block to which the output is going to be added
194    * @param feed the RSS Channel we retrieved via the Feed URL
195    * @param parameters our parameter helper object
196    * @throws MacroExecutionException if the content cannot be rendered
197    */
 
198  6 toggle private void generateEntries(Block parentBlock, SyndFeed feed, RssMacroParameters parameters)
199    throws MacroExecutionException
200    {
201  6 int maxElements = parameters.getCount();
202  6 int count = 0;
203   
204  6 for (Object item : feed.getEntries()) {
205  14 ++count;
206  14 if (count > maxElements) {
207  4 break;
208    }
209  10 SyndEntry entry = (SyndEntry) item;
210   
211  10 ResourceReference titleResourceReference = new ResourceReference(entry.getLink(), ResourceType.URL);
212  10 Block titleBlock = new LinkBlock(parsePlainText(entry.getTitle()), titleResourceReference, true);
213  10 ParagraphBlock paragraphTitleBlock = new ParagraphBlock(Collections.singletonList(titleBlock));
214  10 paragraphTitleBlock.setParameter(CLASS_ATTRIBUTE, "rssitemtitle");
215  10 parentBlock.addChild(paragraphTitleBlock);
216   
217  10 if (parameters.isContent() && entry.getDescription() != null) {
218    // We are wrapping the feed entry content in a HTML macro, not considering what the declared content
219    // is, because some feed will declare text while they actually contain HTML.
220    // See http://stuffthathappens.com/blog/2007/10/29/i-hate-rss/
221    // A case where doing this might hurt is if a feed declares "text" and has any XML inside it does
222    // not want to be interpreted as such, but displayed as is instead. But this certainly is too rare
223    // compared to mis-formed feeds that say text while they want to say HTML.
224  10 Block html = new RawBlock(entry.getDescription().getValue(), Syntax.XHTML_1_0);
225  10 parentBlock.addChild(new GroupBlock(Arrays.asList(html), Collections.singletonMap(CLASS_ATTRIBUTE,
226    "rssitemdescription")));
227    }
228    }
229    }
230   
231    /**
232    * @param romeFeedFactory a custom implementation to use instead of the default, useful for tests
233    */
 
234  1 toggle protected void setFeedFactory(RomeFeedFactory romeFeedFactory)
235    {
236  1 this.romeFeedFactory = romeFeedFactory;
237    }
238   
239    /**
240    * Convenience method to not have to handle exceptions in several places.
241    *
242    * @param content the content to parse as plain text
243    * @return the parsed Blocks
244    * @since 2.0M3
245    */
 
246  14 toggle private List<Block> parsePlainText(String content)
247    {
248  14 if (StringUtils.isEmpty(content)) {
249  0 return Collections.emptyList();
250    }
251   
252  14 try {
253  14 return this.plainTextParser.parse(new StringReader(content)).getChildren().get(0).getChildren();
254    } catch (ParseException e) {
255    // This shouldn't happen since the parser cannot throw an exception since the source is a memory
256    // String.
257  0 throw new RuntimeException("Failed to parse [" + content + "] as plain text", e);
258    }
259    }
260    }