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
55 import com.sun.syndication.feed.synd.SyndCategory;
56 import com.sun.syndication.feed.synd.SyndContent;
57 import com.sun.syndication.feed.synd.SyndEntry;
58 import com.sun.syndication.feed.synd.SyndFeed;
59
60 import info.magnolia.cms.util.QueryUtil;
61 import info.magnolia.commands.MgnlCommand;
62 import info.magnolia.context.Context;
63 import info.magnolia.jcr.util.NodeTypes;
64 import info.magnolia.jcr.util.NodeUtil;
65 import info.magnolia.jcr.util.PropertyUtil;
66 import info.magnolia.jcr.util.VersionUtil;
67 import info.magnolia.module.rssaggregator.RSSAggregator;
68 import info.magnolia.module.rssaggregator.RSSAggregatorConstants;
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 String query = "select * from [mgnl:content] as t where contains(t.name, '"+ name + "')";
119 NodeIterator it = QueryUtil.search(RSSAggregatorConstants.WORKSPACE, query, javax.jcr.query.Query.JCR_SQL2, RSSAggregatorConstants.NODE_TYPE);
120 return it.nextNode();
121 }
122
123 public Set<AggregateFeed> getFeeds(Node node) throws RepositoryException{
124 AggregateFeedContentMapper aggregateFeedMapper = new AggregateFeedContentMapper();
125 Set<AggregateFeed> aggregateFeeds = new HashSet<AggregateFeed>();
126 AggregateFeed aggregateFeed = aggregateFeedMapper.map(node);
127 aggregateFeeds.add(aggregateFeed);
128 return aggregateFeeds;
129 }
130
131 protected Set<String> saveAggregates(Set<AggregateFeed> aggregateFeeds, Node parentNode) throws RepositoryException {
132 Set<String> newAggregateContentUUIDs = new HashSet<String>();
133 for (AggregateFeed aggregateFeed : aggregateFeeds) {
134 Node aggregateNode = parentNode;
135 Node dataNode = getOrCreateNode(aggregateNode, "data", NodeTypes.Content.NAME);
136 newAggregateContentUUIDs.add(aggregateNode.getIdentifier());
137 AggregateFilter aggregateFilter = loadAggregateFilter(aggregateNode);
138 for (FeedChannel channel : aggregateFeed.getChannels()) {
139 if (channel.hasFeed()) {
140 saveFeedChannel(channel, aggregateFilter, dataNode);
141 }
142 }
143 }
144 return newAggregateContentUUIDs;
145 }
146
147 protected Node loadSingleAggregateNode(Node parentNode, String aggregateNodeName) throws RepositoryException {
148 NodeIterator nodeIterator = parentNode.getNodes(aggregateNodeName);
149 Collection<Node> aggregateNodes = new ArrayList<Node>();
150 while (nodeIterator.hasNext()) {
151 Node currentNode = nodeIterator.nextNode();
152 if (NodeUtil.isNodeType(currentNode, RSSAggregatorNodeTypes.RSSAggregator.NAME)) {
153 aggregateNodes.add(currentNode);
154 }
155 }
156 int size = aggregateNodes.size();
157 if (size > 1) {
158 throw new IllegalStateException(format(
159 "Expected content node '%s' to have at most 1 child named '%s' of item type '%s', but found %s",
160 parentNode, aggregateNodeName, RSSAggregatorNodeTypes.RSSAggregator.NAME, size));
161 }
162 if (aggregateNodes.isEmpty()) {
163 return null;
164 }
165 return aggregateNodes.iterator().next();
166 }
167
168 protected Node getOrCreateNode(Node contentNode, String name, String itemType) throws RepositoryException {
169 return NodeUtil.createPath(contentNode, name, itemType, true);
170 }
171
172 public AggregateFilter loadAggregateFilter(Node aggregateNode) throws RepositoryException {
173 Node filtersNode = aggregateNode.hasNode("filters") ? aggregateNode.getNode("filters") : null;
174 if (filtersNode == null) {
175 return new AggregateFilter(Collections.<FilterPredicate>emptySet());
176 }
177 Set<FilterPredicate> filters = new HashSet<FilterPredicate>();
178 List<Node> filterNodes = NodeUtil.asList(NodeUtil.getNodes(filtersNode, VersionUtil.getNodeTypeName(filtersNode)));
179
180 for (Node n : filterNodes) {
181 FilterPredicate filterPredicate = filterPredicateMapper.map(n);
182 if (filterPredicate == null) {
183 continue;
184 }
185 filters.add(filterPredicate);
186 }
187 return new AggregateFilter(filters);
188 }
189
190 @SuppressWarnings("unchecked")
191 protected Node saveFeedChannel(FeedChannel feedChannel, AggregateFilter aggregateFilter, Node dataNode) throws RepositoryException {
192 Node channelNode = recreateFeedChannelNode(feedChannel, dataNode);
193 List<SyndEntry> entries = feedChannel.getFeed().getEntries();
194 int size = entries.size();
195 for (int i = 0; i < size; i++) {
196 SyndEntry entry = entries.get(i);
197 String entryName = format("entry-%s", i);
198 if (aggregateFilter.include(entry)) {
199 createFeedChannelEntryNode(entry, entryName, channelNode);
200 }
201 }
202 return channelNode;
203 }
204
205 protected Node recreateFeedChannelNode(FeedChannel feedChannel, Node dataNode) throws RepositoryException {
206 String channelName = feedChannel.getName();
207 if (dataNode.hasNode(channelName)) {
208 String absPath = dataNode.getNode(channelName).getPath();
209 dataNode.getSession().removeItem(absPath);
210 }
211 Node channelNode = NodeUtil.createPath(dataNode, channelName, NodeTypes.Content.NAME, true);
212
213 SyndFeed feed = feedChannel.getFeed();
214 channelNode.setProperty("description", feed.getDescription());
215 channelNode.setProperty("link", feed.getLink());
216 channelNode.setProperty("rss", feedChannel.getUrl());
217 channelNode.setProperty("title", !isEmpty(feedChannel.getTitle()) ? feedChannel.getTitle() : feed.getTitle());
218 channelNode.setProperty("type", feed.getFeedType());
219 return channelNode;
220 }
221
222 protected Node createFeedChannelEntryNode(SyndEntry entry, String nodeName, Node channelNode) throws RepositoryException {
223 Node entryNode = NodeUtil.createPath(channelNode, nodeName, NodeTypes.Content.NAME, true);
224 entryNode.setProperty("author", entry.getAuthor() == null ? "" : entry.getAuthor());
225 entryNode.setProperty("channelTitle", PropertyUtil.getString(channelNode, "title"));
226 final SyndContent description = entry.getDescription();
227
228 String descriptionString;
229 if (description != null && StringUtils.isNotBlank(description.getValue())) {
230 descriptionString = description.getValue();
231 } else {
232 descriptionString = getEntryContent(entry);
233 }
234
235 entryNode.setProperty("description", descriptionString);
236 entryNode.setProperty("content", getEntryContent(entry));
237 entryNode.setProperty("link", entry.getLink());
238 Date publishedDate = entry.getPublishedDate();
239 if (publishedDate == null) {
240 publishedDate = new Date();
241 }
242 entryNode.setProperty("pubDate", publishedDate.getTime());
243 entryNode.setProperty("title", entry.getTitle());
244
245 createCategoriesNode(entry, entryNode);
246 return entryNode;
247 }
248
249 protected String getEntryContent(SyndEntry entry) {
250 String entryContent = "";
251
252 if (entry != null && entry.getContents().size() > 0) {
253 @SuppressWarnings("unchecked")
254 final List<SyndContent> contents = entry.getContents();
255 for (SyndContent content : contents) {
256 if (StringUtils.equalsIgnoreCase("html", content.getType()) && StringUtils.isNotBlank(content.getType())) {
257 entryContent = content.getValue();
258 break;
259 }
260 }
261 }
262 return entryContent;
263 }
264
265 protected Node createCategoriesNode(SyndEntry entry, Node entryNode) throws RepositoryException {
266 Node categoriesNode = NodeUtil.createPath(entryNode, "categories", NodeTypes.Content.NAME, true);
267 List<SyndCategory> categories = entry.getCategories();
268 for (int i = 0; i < categories.size(); i++) {
269 SyndCategory category = categories.get(i);
270 String categoryIndex = valueOf(i);
271 String categoryName = category.getName();
272 categoriesNode.setProperty(categoryIndex, categoryName);
273 }
274 return categoriesNode;
275 }
276
277 protected FilterPredicateContentMapper setFilterPredicateContentMapper(FilterPredicateContentMapper filterPredicateMapper) {
278 Assert.notNull(filterPredicateMapper, "'filterPredicateContentMapper' must not be null");
279 this.filterPredicateMapper = filterPredicateMapper;
280 return this.filterPredicateMapper;
281 }
282
283 public void setFetcher(RSSFeedFetcher fetcher){
284 this.fetcher = fetcher;
285 }
286
287 public RSSFeedFetcher getFetcher(){
288 return this.fetcher;
289 }
290
291 public void setName(String name) {
292 this.name = name;
293 }
294
295 public String getName() {
296 return this.name;
297 }
298 }