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