View Javadoc

1   /**
2    * This file Copyright (c) 2008-2013 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.rssaggregator;
35  
36  import info.magnolia.cms.util.ObservationUtil;
37  import info.magnolia.cms.util.QueryUtil;
38  import info.magnolia.context.MgnlContext;
39  import info.magnolia.context.MgnlContext.Op;
40  import info.magnolia.jcr.node2bean.Node2BeanProcessor;
41  import info.magnolia.jcr.util.NodeTypes;
42  import info.magnolia.module.ModuleRegistry;
43  import info.magnolia.module.rssaggregator.generator.FeedGenerator;
44  import info.magnolia.module.rssaggregator.importhandler.RSSFeedFetcher;
45  import info.magnolia.module.scheduler.JobDefinition;
46  import info.magnolia.module.scheduler.SchedulerModule;
47  import info.magnolia.objectfactory.Components;
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   * Configuration bean of the rss aggregator module.
72   *
73   * @author had
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      // default number of months to go back for statistics collection
85      private static final String DEFAULT_MONTHS_INCLUDED = "3";
86  
87      private Map<String, FeedGenerator> feedGenerators = new HashMap<String, FeedGenerator>();
88  
89      private Map<String, String> planetOptions = new HashMap<String, String>();
90  
91      private RSSFeedFetcher fetcher;
92  
93      private boolean automatedImport;
94  
95      private String cron;
96  
97      private Map<String, RSSJob> jobs = new Hashtable<String, RSSJob>();
98  
99      public RSSAggregator(){
100         // start observation for every rss on startup of module and add new one on save of new RSS
101         // check for node move - should re-register
102         try {
103             mapRSSJobs();
104         } catch (Exception e) {
105             throw new RuntimeException(e);
106         }
107         Iterator<String> it = jobs.keySet().iterator();
108         while(it.hasNext()){
109             RSSJob job = jobs.get(it.next());
110             registerObservation(job);
111         }
112     }
113 
114     public static RSSAggregator getInstance() {
115         return (RSSAggregator) ModuleRegistry.Factory.getInstance().getModuleInstance("rssaggregator");
116     }
117 
118     public Map<String, FeedGenerator> getFeedGenerators() {
119         return new HashMap<String, FeedGenerator>(feedGenerators);
120     }
121 
122     public void setFeedGenerators(HashMap<String, FeedGenerator> feedGenerators) {
123         this.feedGenerators.clear();
124         this.feedGenerators.putAll(feedGenerators);
125     }
126 
127     public void addFeedGenerator(String name, FeedGenerator feedGenerator) {
128         this.feedGenerators.put(name, feedGenerator);
129     }
130 
131     public int getMonthsIncluded() {
132         if (getPlanetOptions().containsKey(MONTHS_INCLUDED_OPTION)) {
133             String lastMonthOption = getPlanetOptions().get(MONTHS_INCLUDED_OPTION);
134             if (StringUtils.isNumeric(lastMonthOption)) {
135                 return Integer.parseInt(lastMonthOption);
136             }
137 
138         }
139         return Integer.parseInt(DEFAULT_MONTHS_INCLUDED);
140     }
141 
142 
143     public Map<String, String> getPlanetOptions() {
144         return planetOptions;
145     }
146 
147     public void setPlanetOptions(Map<String, String> planetOptions) {
148         this.planetOptions = planetOptions;
149     }
150 
151     public void setFetcher(RSSFeedFetcher fetcher){
152         this.fetcher = fetcher;
153     }
154 
155     public RSSFeedFetcher getFetcher(){
156         return this.fetcher;
157     }
158 
159     public void setCron(String cron) {
160         this.cron = cron;
161     }
162 
163     public String getCron() {
164         return this.cron;
165     }
166 
167     public void setAutomatedImport(boolean automatedImport){
168         this.automatedImport = automatedImport;
169     }
170 
171     public boolean getAutomatedImport(){
172         return this.automatedImport;
173     }
174 
175     private void mapRSSJobs() throws Exception{
176         jobs.clear();
177         MgnlContext.doInSystemContext(new Op<Void, RepositoryException>() {
178             @Override
179             public Void exec() throws RepositoryException {
180                 try {
181                     Session session = MgnlContext.getInstance().getJCRSession(RSSAggregatorConstants.WORKSPACE);
182                     List<Node> rss = new ArrayList<Node>();
183                     Node root = session.getRootNode();
184                     findAllRSSInFolder(root, rss);
185                     Iterator<Node> nodes = rss.iterator();
186 
187                     Node2BeanProcessor n2b = Components.getComponent(Node2BeanProcessor.class);
188                     while(nodes.hasNext()){
189                         Node node = nodes.next();
190                         RSSJob job = (RSSJob) n2b.toBean(node, RSSJob.class);
191                         jobs.put(job.getName(), job);
192                         initJob(job);
193                     }
194                 }catch(Exception e){
195                     log.error("RSS jobs initial mapping error: " + e.getMessage());
196                 }
197                 return null;
198                 }
199             });
200     }
201 
202     private void findAllRSSInFolder(Node node, Collection<Node> rssCol) throws RepositoryException {
203         NodeIterator nodesIter = node.getNodes();
204         Collection<Node> nodes = new HashSet<Node>();
205         Collection<Node> folders = new HashSet<Node>();
206         while (nodesIter.hasNext()) {
207             Node newNode = (Node) nodesIter.next();
208             if (newNode.isNodeType(RSSAggregatorConstants.NODE_TYPE)) {
209                 nodes.add(newNode);
210             } else if (newNode.isNodeType(NodeTypes.Folder.NAME)) {
211                 folders.add(newNode);
212             }
213         }
214 
215         if (!nodes.isEmpty()) {
216             for (Node rssNode : nodes) {
217                 rssCol.add(rssNode);
218             }
219         }
220         if (!folders.isEmpty()) {
221             for (Node folder : folders) {
222                 findAllRSSInFolder(folder, rssCol);
223             }
224         }
225     }
226 
227     public void mapRSSJob(final String name) throws RepositoryException{
228         if(jobs.containsKey(name)){
229             jobs.remove(name);
230         }
231         MgnlContext.doInSystemContext(new Op<Void, RepositoryException>() {
232             @Override
233             public Void exec() throws RepositoryException {
234                 try {
235                     String query = "select * from [mgnl:content] as t where contains(t.name, '"+ name + "')";
236                     NodeIterator it = QueryUtil.search(RSSAggregatorConstants.WORKSPACE, query, javax.jcr.query.Query.JCR_SQL2, RSSAggregatorConstants.NODE_TYPE);
237                     Node node = it.nextNode();
238                     Node2BeanProcessor n2b = Components.getComponent(Node2BeanProcessor.class);
239                     RSSJob job = (RSSJob) n2b.toBean(node, RSSJob.class);
240                     jobs.put(job.getName(), job);
241                     initJob(job);
242                 }catch(Exception e){
243                     throw new RuntimeException(e);
244                 }
245                 return null;
246                 }
247             });
248     }
249 
250     private void initJob(RSSJob job) {
251         SchedulerModule scheduler = SchedulerModule.getInstance();
252         if ((job.getAutomatedImport() && job.getOverrideDefault()) || (!job.getOverrideDefault() && this.automatedImport)) {
253             Map<String, Object> params = new HashMap<String, Object>();
254             String cron = this.cron;
255             if(job.getOverrideDefault()){
256                 cron = job.getCron();
257             }
258             params.put("name", job.getName());
259             try {
260                 scheduler.stopJob(job.getName());
261                 scheduler.addJob(new JobDefinition(
262                         job.getName(),
263                         "rss",
264                         "importRss",//empty command has to be set in config
265                         cron,
266                         params));
267                 log.info("Added RSS job " + job.getName() + " with cron " + cron);
268             }
269             catch (SchedulerException e) {
270                 log.error("can't start scheduled job for rss import[" + job.getName() + "]", e);
271             }
272         }
273         else {
274             try {
275                 scheduler.stopJob(job.getName());
276             }
277             catch (SchedulerException e) {
278                 log.error("can't delete scheduled job for rss import[" + job.getName() + "]", e);
279             }
280         }
281     }
282 
283     public void registerObservation(RSSJob job){
284         String name = job.getName();
285         ObservationUtil.registerChangeListener(RSSAggregatorConstants.WORKSPACE, "/" + name, false, RSSAggregatorConstants.NODE_TYPE, new RSSEventListener(name));
286     }
287 
288     public boolean jobExists(String name) {
289         return jobs.containsKey(name);
290     }
291 
292     public void removeJob(String key){
293         jobs.remove(key);
294     }
295 
296     public RSSJob getJobByName (String name) {
297         return jobs.get(name);
298     }
299 
300     public void registerJob (String name, RSSJob job) {
301         jobs.put(name, job);
302     }
303 
304     public Map<String, RSSJob> getJobs(){
305         return this.jobs;
306     }
307 
308     private class RSSEventListener implements EventListener{
309 
310         private String name;
311 
312         public RSSEventListener(String name){
313             this.name = name;
314         }
315 
316         @Override
317         public void onEvent(EventIterator events) {
318             try{
319                 mapRSSJob(name);
320             }catch(Exception e){
321                 throw new RuntimeException(e);
322             }
323         }
324     }
325 }