1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.module.rssaggregator.command;
35
36 import static java.lang.String.*;
37 import static org.apache.commons.lang.StringUtils.isEmpty;
38
39 import info.magnolia.commands.MgnlCommand;
40 import info.magnolia.context.Context;
41 import info.magnolia.context.MgnlContext;
42 import info.magnolia.jcr.util.NodeTypes;
43 import info.magnolia.jcr.util.NodeUtil;
44 import info.magnolia.jcr.util.PropertyUtil;
45 import info.magnolia.module.ModuleRegistry;
46 import info.magnolia.module.rssaggregator.RSSAggregator;
47 import info.magnolia.module.rssaggregator.RSSAggregatorConstants;
48 import info.magnolia.module.rssaggregator.RSSAggregatorNodeTypes;
49 import info.magnolia.module.rssaggregator.RSSJob;
50 import info.magnolia.module.rssaggregator.importhandler.AggregateFeed;
51 import info.magnolia.module.rssaggregator.importhandler.AggregateFeedContentMapper;
52 import info.magnolia.module.rssaggregator.importhandler.AggregateFilter;
53 import info.magnolia.module.rssaggregator.importhandler.FeedChannel;
54 import info.magnolia.module.rssaggregator.importhandler.FilterPredicate;
55 import info.magnolia.module.rssaggregator.importhandler.FilterPredicateContentMapper;
56 import info.magnolia.module.rssaggregator.importhandler.RSSFeedFetcher;
57 import info.magnolia.module.rssaggregator.util.Assert;
58 import info.magnolia.module.scheduler.SchedulerModule;
59 import info.magnolia.objectfactory.Components;
60
61 import java.util.ArrayList;
62 import java.util.Collection;
63 import java.util.Collections;
64 import java.util.Date;
65 import java.util.HashSet;
66 import java.util.LinkedHashSet;
67 import java.util.List;
68 import java.util.Set;
69
70 import javax.jcr.Node;
71 import javax.jcr.NodeIterator;
72 import javax.jcr.RepositoryException;
73
74 import org.apache.commons.lang.StringUtils;
75 import org.quartz.SchedulerException;
76
77 import com.sun.syndication.feed.synd.SyndCategory;
78 import com.sun.syndication.feed.synd.SyndContent;
79 import com.sun.syndication.feed.synd.SyndEntry;
80 import com.sun.syndication.feed.synd.SyndFeed;
81
82
83
84
85 public class LaunchSingleRSSCommand extends MgnlCommand {
86
87 private RSSFeedFetcher fetcher;
88
89 private FilterPredicateContentMapper filterPredicateMapper;
90
91 private String name;
92
93 public LaunchSingleRSSCommand() {
94 }
95
96 public LaunchSingleRSSCommand(RSSJob job) {
97 this.name = job.getName();
98 this.fetcher = job.getFetcher();
99 }
100
101 private void init() {
102 if (this.fetcher == null) {
103 this.fetcher = ((RSSAggregator) Components.getComponent(ModuleRegistry.class).getModuleInstance("rssaggregator")).getFetcher();
104 }
105 setFilterPredicateContentMapper(new FilterPredicateContentMapper());
106 }
107
108 @Override
109 public boolean execute(Context context) throws Exception {
110 init();
111 Set<String> newContentUUIDs = new LinkedHashSet<String>();
112 Node rss = getRSSNodeByFeedName(name);
113 if (rss != null) {
114 Set<AggregateFeed> fetchedAggregateFeeds = fetcher.fetchAggregateFeeds(getFeeds(rss));
115 Set<String> newAggregateContentUUIDs = saveAggregates(fetchedAggregateFeeds, rss);
116 newContentUUIDs.addAll(newAggregateContentUUIDs);
117 rss.getSession().save();
118 }
119 return false;
120 }
121
122 private Node getRSSNodeByFeedName(String name) throws RepositoryException {
123 try {
124 return MgnlContext.getJCRSession(RSSAggregatorConstants.WORKSPACE).getNodeByIdentifier(name);
125 } catch (RepositoryException e) {
126 try {
127 log.debug("RSS with name " + name + "doesn't exist anymore");
128 SchedulerModule scheduler = (SchedulerModule) Components.getComponent(ModuleRegistry.class).getModuleInstance("scheduler");
129 scheduler.stopJob(name);
130 scheduler.removeJob(name);
131 } catch (SchedulerException ex) {
132
133 }
134 }
135 return null;
136 }
137
138 public Set<AggregateFeed> getFeeds(Node node) throws RepositoryException {
139 AggregateFeedContentMapper aggregateFeedMapper = new AggregateFeedContentMapper();
140 Set<AggregateFeed> aggregateFeeds = new HashSet<AggregateFeed>();
141 AggregateFeed aggregateFeed = aggregateFeedMapper.map(node);
142 aggregateFeeds.add(aggregateFeed);
143 return aggregateFeeds;
144 }
145
146 protected Set<String> saveAggregates(Set<AggregateFeed> aggregateFeeds, Node parentNode) throws RepositoryException {
147 Set<String> newAggregateContentUUIDs = new HashSet<String>();
148 for (AggregateFeed aggregateFeed : aggregateFeeds) {
149 Node aggregateNode = parentNode;
150 Node dataNode = NodeUtil.createPath(aggregateNode, "data", NodeTypes.Content.NAME);
151 newAggregateContentUUIDs.add(aggregateNode.getIdentifier());
152 AggregateFilter aggregateFilter = loadAggregateFilter(aggregateNode);
153 for (FeedChannel channel : aggregateFeed.getChannels()) {
154 if (channel.hasFeed()) {
155 saveFeedChannel(channel, aggregateFilter, dataNode);
156 }
157 }
158 }
159 return newAggregateContentUUIDs;
160 }
161
162 protected Node loadSingleAggregateNode(Node parentNode, String aggregateNodeName) throws RepositoryException {
163 NodeIterator nodeIterator = parentNode.getNodes(aggregateNodeName);
164 Collection<Node> aggregateNodes = new ArrayList<Node>();
165 while (nodeIterator.hasNext()) {
166 Node currentNode = nodeIterator.nextNode();
167 if (NodeUtil.isNodeType(currentNode, RSSAggregatorNodeTypes.RSSAggregator.NAME)) {
168 aggregateNodes.add(currentNode);
169 }
170 }
171 int size = aggregateNodes.size();
172 if (size > 1) {
173 throw new IllegalStateException(format(
174 "Expected content node '%s' to have at most 1 child named '%s' of item type '%s', but found %s",
175 parentNode, aggregateNodeName, RSSAggregatorNodeTypes.RSSAggregator.NAME, size));
176 }
177 if (aggregateNodes.isEmpty()) {
178 return null;
179 }
180 return aggregateNodes.iterator().next();
181 }
182
183 public AggregateFilter loadAggregateFilter(Node aggregateNode) throws RepositoryException {
184 Node filtersNode = aggregateNode.hasNode("filters") ? aggregateNode.getNode("filters") : null;
185 if (filtersNode == null) {
186 return new AggregateFilter(Collections.<FilterPredicate> emptySet());
187 }
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202 Set<FilterPredicate> filters = new LinkedHashSet<FilterPredicate>();
203
204 List<Node> filterNodes = NodeUtil.asList(NodeUtil.getNodes(filtersNode, NodeTypes.ContentNode.NAME));
205
206 for (Node n : filterNodes) {
207 FilterPredicate filterPredicate = filterPredicateMapper.map(n);
208 if (filterPredicate == null) {
209 continue;
210 }
211 filters.add(filterPredicate);
212 }
213 return new AggregateFilter(filters);
214 }
215
216 @SuppressWarnings("unchecked")
217 protected Node saveFeedChannel(FeedChannel feedChannel, AggregateFilter aggregateFilter, Node dataNode) throws RepositoryException {
218 Node channelNode = recreateFeedChannelNode(feedChannel, dataNode);
219 List<SyndEntry> entries = feedChannel.getFeed().getEntries();
220 int size = entries.size();
221 for (int i = 0; i < size; i++) {
222 SyndEntry entry = entries.get(i);
223 String entryName = format("entry-%s", i);
224 if (aggregateFilter.include(entry)) {
225 createFeedChannelEntryNode(entry, entryName, channelNode);
226 }
227 }
228 return channelNode;
229 }
230
231 protected Node recreateFeedChannelNode(FeedChannel feedChannel, Node dataNode) throws RepositoryException {
232 String channelName = feedChannel.getName();
233 if (dataNode.hasNode(channelName)) {
234 String absPath = dataNode.getNode(channelName).getPath();
235 dataNode.getSession().removeItem(absPath);
236 }
237 Node channelNode = NodeUtil.createPath(dataNode, channelName, NodeTypes.Content.NAME);
238
239 SyndFeed feed = feedChannel.getFeed();
240 channelNode.setProperty("description", feed.getDescription());
241 channelNode.setProperty("link", feed.getLink());
242 channelNode.setProperty("rss", feedChannel.getUrl());
243 channelNode.setProperty("title", !isEmpty(feedChannel.getTitle()) ? feedChannel.getTitle() : feed.getTitle());
244 channelNode.setProperty("type", feed.getFeedType());
245 return channelNode;
246 }
247
248 protected Node createFeedChannelEntryNode(SyndEntry entry, String nodeName, Node channelNode) throws RepositoryException {
249 Node entryNode = NodeUtil.createPath(channelNode, nodeName, NodeTypes.Content.NAME);
250 entryNode.setProperty("author", entry.getAuthor() == null ? "" : entry.getAuthor());
251 entryNode.setProperty("channelTitle", PropertyUtil.getString(channelNode, "title"));
252 final SyndContent description = entry.getDescription();
253
254 String descriptionString;
255 if (description != null && StringUtils.isNotBlank(description.getValue())) {
256 descriptionString = description.getValue();
257 } else {
258 descriptionString = getEntryContent(entry);
259 }
260
261 entryNode.setProperty("description", descriptionString);
262 entryNode.setProperty("content", getEntryContent(entry));
263 entryNode.setProperty("link", entry.getLink());
264 Date publishedDate = entry.getPublishedDate();
265 if (publishedDate == null) {
266 publishedDate = new Date();
267 }
268 entryNode.setProperty("pubDate", publishedDate.getTime());
269 entryNode.setProperty("title", entry.getTitle());
270
271 createCategoriesNode(entry, entryNode);
272 return entryNode;
273 }
274
275 protected String getEntryContent(SyndEntry entry) {
276 String entryContent = "";
277
278 if (entry != null && entry.getContents().size() > 0) {
279 @SuppressWarnings("unchecked")
280 final List<SyndContent> contents = entry.getContents();
281 for (SyndContent content : contents) {
282 if (StringUtils.equalsIgnoreCase("html", content.getType()) && StringUtils.isNotBlank(content.getType())) {
283 entryContent = content.getValue();
284 break;
285 }
286 }
287 }
288 return entryContent;
289 }
290
291 protected Node createCategoriesNode(SyndEntry entry, Node entryNode) throws RepositoryException {
292 Node categoriesNode = NodeUtil.createPath(entryNode, "categories", NodeTypes.Content.NAME);
293 List<SyndCategory> categories = entry.getCategories();
294 for (int i = 0; i < categories.size(); i++) {
295 SyndCategory category = categories.get(i);
296 String categoryIndex = valueOf(i);
297 String categoryName = category.getName();
298 categoriesNode.setProperty(categoryIndex, categoryName);
299 }
300 return categoriesNode;
301 }
302
303 protected FilterPredicateContentMapper setFilterPredicateContentMapper(FilterPredicateContentMapper filterPredicateMapper) {
304 Assert.notNull(filterPredicateMapper, "'filterPredicateContentMapper' must not be null");
305 this.filterPredicateMapper = filterPredicateMapper;
306 return this.filterPredicateMapper;
307 }
308
309 public void setFetcher(RSSFeedFetcher fetcher) {
310 this.fetcher = fetcher;
311 }
312
313 public RSSFeedFetcher getFetcher() {
314 return this.fetcher;
315 }
316
317 public void setName(String name) {
318 this.name = name;
319 }
320
321 public String getName() {
322 return this.name;
323 }
324 }