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;
35
36 import info.magnolia.cms.util.ObservationUtil;
37 import info.magnolia.context.MgnlContext;
38 import info.magnolia.context.MgnlContext.Op;
39 import info.magnolia.jcr.node2bean.Node2BeanProcessor;
40 import info.magnolia.jcr.util.NodeTypes;
41 import info.magnolia.module.ModuleRegistry;
42 import info.magnolia.module.rssaggregator.generator.FeedGenerator;
43 import info.magnolia.module.rssaggregator.importhandler.RSSFeedFetcher;
44 import info.magnolia.module.scheduler.JobDefinition;
45 import info.magnolia.module.scheduler.SchedulerModule;
46 import info.magnolia.objectfactory.Components;
47 import info.magnolia.repository.RepositoryConstants;
48
49 import java.util.ArrayList;
50 import java.util.Collection;
51 import java.util.HashMap;
52 import java.util.HashSet;
53 import java.util.Hashtable;
54 import java.util.Iterator;
55 import java.util.List;
56 import java.util.Map;
57
58 import javax.jcr.Node;
59 import javax.jcr.NodeIterator;
60 import javax.jcr.RepositoryException;
61 import javax.jcr.Session;
62 import javax.jcr.observation.EventIterator;
63 import javax.jcr.observation.EventListener;
64
65 import org.apache.commons.lang.StringUtils;
66 import org.quartz.SchedulerException;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
69
70
71
72
73
74
75 public class RSSAggregator {
76
77 private static final Logger log = LoggerFactory.getLogger(RSSAggregator.class);
78
79 public static final String DEFAULT_FEEDTYPE = "rss_2.0";
80 public static final String DEFAULT_ENCODING = "UTF-8";
81 public static final String DEFAULT_CONTENT_TYPE = "text/xml";
82
83 private static final String MONTHS_INCLUDED_OPTION = "lastMonthsIncluded";
84
85 private static final String DEFAULT_MONTHS_INCLUDED = "3";
86 public static final String RSS_CONFIG_PATH = "/modules/rssaggregator/config";
87
88 private Map<String, FeedGenerator> feedGenerators = new HashMap<String, FeedGenerator>();
89
90 private Map<String, String> planetOptions = new HashMap<String, String>();
91
92 private RSSFeedFetcher fetcher;
93
94 private Map<String, RSSJob> jobs = new Hashtable<String, RSSJob>();
95
96 public RSSAggregator(){
97 init();
98 ObservationUtil.registerChangeListener(RepositoryConstants.CONFIG, RSS_CONFIG_PATH, false, NodeTypes.Content.NAME, new RSSConfigEventListener());
99 }
100
101 private void init(){
102 try {
103 mapRSSJobs();
104 } catch (Exception e) {
105 throw new RuntimeException(e);
106 }
107 }
108
109
110
111
112 public static RSSAggregator getInstance() {
113 return (RSSAggregator) Components.getComponent(ModuleRegistry.class).getModuleInstance("rssaggregator");
114 }
115
116 public Map<String, FeedGenerator> getFeedGenerators() {
117 return new HashMap<String, FeedGenerator>(feedGenerators);
118 }
119
120 public void setFeedGenerators(HashMap<String, FeedGenerator> feedGenerators) {
121 this.feedGenerators.clear();
122 this.feedGenerators.putAll(feedGenerators);
123 }
124
125 public void addFeedGenerator(String name, FeedGenerator feedGenerator) {
126 this.feedGenerators.put(name, feedGenerator);
127 }
128
129 public int getMonthsIncluded() {
130 if (getPlanetOptions().containsKey(MONTHS_INCLUDED_OPTION)) {
131 String lastMonthOption = getPlanetOptions().get(MONTHS_INCLUDED_OPTION);
132 if (StringUtils.isNumeric(lastMonthOption)) {
133 return Integer.parseInt(lastMonthOption);
134 }
135
136 }
137 return Integer.parseInt(DEFAULT_MONTHS_INCLUDED);
138 }
139
140 public Map<String, String> getPlanetOptions() {
141 return planetOptions;
142 }
143
144 public void setPlanetOptions(Map<String, String> planetOptions) {
145 this.planetOptions = planetOptions;
146 }
147
148 public void setFetcher(RSSFeedFetcher fetcher){
149 this.fetcher = fetcher;
150 }
151
152 public RSSFeedFetcher getFetcher(){
153 return this.fetcher;
154 }
155
156 private String retrieveCron() {
157 try {
158 return MgnlContext.getJCRSession(RepositoryConstants.CONFIG).getNode(RSS_CONFIG_PATH).getProperty("cron").getString();
159 } catch (RepositoryException e) {
160 log.debug("Property cron could not be retrieved from rss module configuration, hourly configuration will be used.");
161 }
162
163 return "0 0 0/1 1/1 * ? *";
164 }
165
166 private boolean retrieveAutomatedImport() {
167 try {
168 return MgnlContext.getJCRSession(RepositoryConstants.CONFIG).getNode(RSS_CONFIG_PATH).getProperty("automatedImport").getBoolean();
169 } catch (RepositoryException e) {
170 log.debug("Property automatedImport could not be retrieved from rss module configuration, it will be disabled by default.");
171 }
172
173 return false;
174 }
175
176 private void mapRSSJobs() throws Exception{
177 jobs.clear();
178 MgnlContext.doInSystemContext(new Op<Void, RepositoryException>() {
179 @Override
180 public Void exec() throws RepositoryException {
181 try {
182 Session session = MgnlContext.getInstance().getJCRSession(RSSAggregatorConstants.WORKSPACE);
183 List<Node> rss = new ArrayList<Node>();
184 Node root = session.getRootNode();
185 findAllRSSInFolder(root, rss);
186 Iterator<Node> nodes = rss.iterator();
187
188 Node2BeanProcessor n2b = Components.getComponent(Node2BeanProcessor.class);
189 while(nodes.hasNext()){
190 Node node = nodes.next();
191 RSSJob job = (RSSJob) n2b.toBean(node, RSSJob.class);
192 job.setName(node.getIdentifier());
193 jobs.put(job.getName(), job);
194 initJob(job);
195 }
196 }catch(Exception e){
197 log.error("RSS jobs initial mapping error: " + e.getMessage());
198 }
199 return null;
200 }
201 });
202 }
203
204 private void findAllRSSInFolder(Node node, Collection<Node> rssCol) throws RepositoryException {
205 NodeIterator nodesIter = node.getNodes();
206 Collection<Node> nodes = new HashSet<Node>();
207 Collection<Node> folders = new HashSet<Node>();
208 while (nodesIter.hasNext()) {
209 Node newNode = (Node) nodesIter.next();
210 if (newNode.isNodeType(RSSAggregatorConstants.NODE_TYPE)) {
211 nodes.add(newNode);
212 } else if (newNode.isNodeType(NodeTypes.Folder.NAME)) {
213 folders.add(newNode);
214 }
215 }
216
217 if (!nodes.isEmpty()) {
218 for (Node rssNode : nodes) {
219 rssCol.add(rssNode);
220 }
221 }
222 if (!folders.isEmpty()) {
223 for (Node folder : folders) {
224 findAllRSSInFolder(folder, rssCol);
225 }
226 }
227 }
228
229 public void mapRSSJob(final String name) throws RepositoryException{
230 if(jobs.containsKey(name)){
231 jobs.remove(name);
232 }
233 MgnlContext.doInSystemContext(new Op<Void, RepositoryException>() {
234 @Override
235 public Void exec() throws RepositoryException {
236 try {
237 Node node = MgnlContext.getJCRSession(RSSAggregatorConstants.WORKSPACE).getNodeByIdentifier(name);
238 Node2BeanProcessor n2b = Components.getComponent(Node2BeanProcessor.class);
239 RSSJob job = (RSSJob) n2b.toBean(node, RSSJob.class);
240 job.setName(node.getIdentifier());
241 jobs.put(job.getName(), job);
242 initJob(job);
243 } catch(Exception e){
244 throw new RuntimeException(e);
245 }
246 return null;
247 }
248 });
249 }
250
251 private void initJob(RSSJob job) {
252 SchedulerModule scheduler = (SchedulerModule) Components.getComponent(ModuleRegistry.class).getModuleInstance("scheduler");
253 if ((job.getAutomatedImport() && job.getOverrideDefault()) || (!job.getOverrideDefault() && retrieveAutomatedImport())) {
254 Map<String, Object> params = new HashMap<String, Object>();
255 String cron = retrieveCron();
256 if(job.getOverrideDefault()){
257 cron = job.getCron();
258 }
259 params.put("name", job.getName());
260 try {
261 scheduler.stopJob(job.getName());
262 scheduler.removeJob(job.getName());
263 scheduler.addJob(new JobDefinition(
264 job.getName(),
265 "rss",
266 "importRss",
267 cron,
268 params));
269 log.info("Added RSS job " + job.getName() + " with cron " + cron);
270 }
271 catch (SchedulerException e) {
272 log.error("can't start scheduled job for rss import[" + job.getName() + "]", e);
273 }
274 }
275 else {
276 try {
277 scheduler.stopJob(job.getName());
278 scheduler.removeJob(job.getName());
279 }
280 catch (SchedulerException e) {
281 log.error("can't delete scheduled job for rss import[" + job.getName() + "]", e);
282 }
283 }
284 }
285
286 public boolean jobExists(String name) {
287 return jobs.containsKey(name);
288 }
289
290 public void removeJob(String key){
291 jobs.remove(key);
292 }
293
294 public RSSJob getJobByName (String name) {
295 return jobs.get(name);
296 }
297
298 public void registerJob (String name, RSSJob job) {
299 jobs.put(name, job);
300 }
301
302 public Map<String, RSSJob> getJobs(){
303 return this.jobs;
304 }
305
306 private class RSSConfigEventListener implements EventListener{
307
308 @Override
309 public void onEvent(EventIterator events) {
310 init();
311 }
312 }
313 }