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