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