KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > de > nava > informa > utils > PersistChanGrpMgr


1 // Informa -- RSS Library for Java
2
// Copyright (c) 2002 by Niko Schmuck
3
//
4
// Niko Schmuck
5
// http://sourceforge.net/projects/informa
6
// mailto:niko_schmuck@users.sourceforge.net
7
//
8
// This library is free software.
9
//
10
// You may redistribute it and/or modify it under the terms of the GNU
11
// Lesser General Public License as published by the Free Software Foundation.
12
//
13
// Version 2.1 of the license should be included with this distribution in
14
// the file LICENSE. If the license is not included with this distribution,
15
// you may find a copy at the FSF web site at 'www.gnu.org' or 'www.fsf.org',
16
// or you may write to the Free Software Foundation, 675 Mass Ave, Cambridge,
17
// MA 02139 USA.
18
//
19
// This library is distributed in the hope that it will be useful,
20
// but WITHOUT ANY WARRANTY; without even the implied waranty of
21
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22
// Lesser General Public License for more details.
23
//
24

25 // $Id: PersistChanGrpMgr.java,v 1.24 2004/10/14 21:02:16 niko_schmuck Exp $
26

27 package de.nava.informa.utils;
28
29 import java.util.*;
30 import java.util.List JavaDoc;
31
32 import net.sf.hibernate.*;
33 import net.sf.hibernate.Hibernate;
34
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37
38 import de.nava.informa.core.ChannelBuilderException;
39 import de.nava.informa.impl.hibernate.*;
40 import de.nava.informa.impl.hibernate.ChannelBuilder;
41
42 /**
43  * PersistChanGrpMgr - Controls and Manages a single Hibernate based Informa ChannelGroup. Provides
44  * for threaded Updating of the Channel Group, persistence management, session management etc.
45  *
46  * N O T T H R E A D S A F E
47  *
48  */

49 public class PersistChanGrpMgr
50 {
51
52   private static final int DEFAULT_STARTDELAY = 1 * 1000; // ms
53
private static final int DEFAULT_PERIOD = 10 * 60 * 1000; // ms
54
private static final int DEFAULT_ACCEPTERRORS = 10;
55
56   private static final int DBG_DEFAULT_STARTDELAY = 100; // ms
57
private static final int DBG_DEFAULT_PERIOD = 20000; // ms
58
private static final int DBG_DEFAULT_ACCEPTERRORS = 10;
59
60   private static Log logger = LogFactory.getLog(PersistChanGrpMgr.class);
61   private ChannelBuilder builder;
62   private ChannelGroup group;
63   private SessionHandler handler;
64   private PersistChanGrpMgrObserverIF globalChannelObserver;
65   private boolean activated = false;
66   private PersistChanGrpMgrTask task;
67   private int pollingCounter;
68
69   int taskStartDelay;
70   int taskPeriod;
71   int acceptNrErrors;
72
73   /**
74    * Constructor.
75    *
76    * @param handler - SessionHandler to use. This needs to have been built by caller.
77    *
78    * @param debug - - true will run this in debug mode, which basically means that threads are run
79    * with no delays thereby revealing threading bugs.
80    */

81   public PersistChanGrpMgr(SessionHandler handler, boolean debug)
82   {
83     if (handler == null) throw new IllegalStateException JavaDoc("Invalid handler");
84     this.handler = handler;
85     builder = new ChannelBuilder(handler);
86     pollingCounter = 0;
87
88     if (debug)
89     {
90       taskStartDelay = DBG_DEFAULT_STARTDELAY;
91       taskPeriod = DBG_DEFAULT_PERIOD;
92       acceptNrErrors = DBG_DEFAULT_ACCEPTERRORS;
93     } else
94     {
95       taskStartDelay = DEFAULT_STARTDELAY;
96       taskPeriod = DEFAULT_PERIOD;
97       acceptNrErrors = DEFAULT_ACCEPTERRORS;
98     }
99   }
100
101   /**
102    * Called to create a Group.
103    *
104    * @param name - Text name of the group
105    * @return - Channel Group being managed by this PersistChanGrpMgr
106    */

107   public ChannelGroup createGroup(String JavaDoc name)
108   {
109     logger.debug("Creating Persistent Group: " + name);
110     if (group != null) throw new IllegalStateException JavaDoc("Can't call createGroup twice in a row.");
111     if (activated) throw new IllegalStateException JavaDoc("Can't create groups while activated.");
112
113     ChannelGroup result = null;
114     synchronized (builder)
115     {
116       result = findChannelGroup(name);
117       if (result == null)
118       {
119         try
120         {
121           builder.beginTransaction();
122           result = (ChannelGroup) builder.createChannelGroup(name);
123           builder.endTransaction();
124         } catch (ChannelBuilderException e)
125         {
126           try
127           {
128             builder.endTransaction();
129           } catch (ChannelBuilderException e1)
130           {
131             e1.printStackTrace();
132           }
133           e.printStackTrace();
134         }
135       }
136       group = result;
137     }
138     logger.info("createGroup(\"" + name + "\" yielded: " + result);
139     return result;
140   }
141
142   /**
143    * Deletes persistent group.
144    */

145   public void deleteGroup()
146   {
147     if (group == null) return;
148     logger.debug("Deleting Persistent Group: " + group.getTitle());
149
150     synchronized (builder)
151     {
152       try
153       {
154         builder.beginTransaction();
155         builder.reload(group);
156
157         // Remove group from links with channels
158
Channel[] chans = (Channel[])group.getChannels().toArray(new Channel[0]);
159         for (int i = 0; i < chans.length; i++)
160         {
161           Channel chan = chans[i];
162
163           final Set grps = chan.getGroups();
164           grps.remove(group);
165           group.getChannels().remove(chan);
166
167           // Delete channel if it was the last group it was assigned to
168
if (grps.size() == 0) builder.delete(chan);
169         }
170
171         builder.delete(group);
172
173         builder.endTransaction();
174         group = null;
175       } catch (ChannelBuilderException e)
176       {
177         logger.error("Unable to delete Persistent Group: " + e.getMessage());
178         builder.resetTransaction();
179       }
180     }
181   }
182
183   /**
184    * Check if this PersistChanGrp has specified CHannel as a member already
185    *
186    * @param achannel - candidate channel to check
187    * @return TRUE = yes
188    */

189   public boolean hasChannel(final Channel achannel)
190   {
191     return group.getChannels().contains(achannel);
192   }
193
194   /**
195    * Add a channel to this Persisten Channel Group. If Channel already exists then just add it,
196    * if it doesn't then create it and add it.
197    *
198    * @param url the url of the rss feed
199    * @return Channel so created or located
200    */

201   public Channel addChannel(String JavaDoc url)
202   {
203     if (activated) throw new IllegalStateException JavaDoc("can't add Channels while activated.");
204     Channel achannel = null;
205     synchronized (builder)
206     {
207       try
208       {
209         builder.beginTransaction();
210         builder.reload(group);
211
212         achannel = findChannel(url);
213         if (achannel == null)
214         { // Channel is not in the database
215
achannel = newChannel(url);
216           logger.debug("Added New Channel: " + url);
217         } else
218         { // Channel is in the database, but it may not already be in this group
219
if (!hasChannel(achannel))
220           {
221             logger.debug("Loaded existing channel" + url);
222             group.add(achannel);
223             achannel.getGroups().add(group);
224           }
225         }
226         builder.endTransaction();
227       } catch (Exception JavaDoc e)
228       {
229         e.printStackTrace();
230         builder.resetTransaction();
231       }
232       return achannel;
233     }
234   }
235
236   /**
237    * Move a Channel from this PersistentChannelGroup to a different one
238    *
239    * @param channel channel in this PersistentChannelGroup that is being moved.
240    * @param destGrp destination where the Channel is going to
241    */

242   public void moveChannelTo(Channel channel, PersistChanGrpMgr destGrp)
243   {
244     if (activated || destGrp.isActivated())
245         throw new IllegalStateException JavaDoc("can't move Channels while activated.");
246     synchronized (builder)
247     {
248       try
249       {
250         builder.beginTransaction();
251         builder.reload(group);
252         builder.reload(channel);
253         ChannelGroup dstGroup = builder.reload(destGrp.getChannelGroup());
254
255         group.remove(channel);
256         channel.getGroups().remove(group);
257
258         dstGroup.add(channel);
259         channel.getGroups().add(dstGroup);
260
261         builder.endTransaction();
262       } catch (Exception JavaDoc e)
263       {
264         e.printStackTrace();
265         builder.resetTransaction();
266       }
267     }
268   }
269
270   /**
271    * Delete specified channel from this PersistChanGrpMgr. Status indicates whether Channel was
272    * previously part of this group.
273    *
274    * @param channel - Channel being removed from the Group.
275    * @return true if channel was deleted, false if channel was not a member to begin with
276    */

277   public boolean deleteChannel(Channel channel)
278   {
279     boolean result = false;
280     if (activated) throw new IllegalStateException JavaDoc("can't delete Channels while activated.");
281     synchronized (builder)
282     {
283       try
284       {
285         builder.beginTransaction();
286         builder.reload(group);
287         builder.reload(channel);
288
289         if (hasChannel(channel))
290         {
291           group.remove(channel);
292           channel.getGroups().remove(group);
293           builder.delete(channel);
294
295           result = true;
296         }
297
298         builder.endTransaction();
299       } catch (Exception JavaDoc e)
300       {
301         e.printStackTrace();
302         builder.resetTransaction();
303       }
304     }
305
306     return result;
307   }
308
309   /**
310    * Delete specified item from specified Channel
311    *
312    * @param channel - Channel to delete from
313    * @param item - Item to delete from that channel
314    *
315    * @return number of items left in the channel AFTER the deletion.
316    */

317   public int deleteItemFromChannel(Channel channel, Item item)
318   {
319     if (activated) throw new IllegalStateException JavaDoc("can't delete Items while activated");
320     int result = 0;
321     synchronized (builder)
322     {
323       try
324       {
325         builder.beginTransaction();
326         builder.reload(channel);
327         builder.reload(item);
328
329         channel.removeItem(item);
330         builder.delete(item);
331
332         result = channel.getItems().size();
333
334         builder.endTransaction();
335       } catch (ChannelBuilderException e)
336       {
337         e.printStackTrace();
338         builder.resetTransaction();
339       }
340     }
341     return result;
342   }
343
344   /**
345    * Return number of Items currently in specified Channel
346    *
347    * @param channel Channel to query
348    * @return number of Items
349    */

350   public int getItemCount(Channel channel) {
351     if (activated) throw new IllegalStateException JavaDoc("can't count Items while activated");
352     int result = 0;
353     synchronized (builder)
354     {
355       try
356       {
357         builder.beginTransaction();
358         builder.reload(channel);
359
360         result = channel.getItems().size();
361
362         builder.endTransaction();
363       } catch (ChannelBuilderException e)
364       {
365         e.printStackTrace();
366         builder.resetTransaction();
367       }
368     }
369     return result;
370   }
371   /*
372    * Notification handlers.
373    *
374    * With persistent Channels we have an alternate notification mechanism because keeping the
375    * observer in the ChannelIF doesn't work because that doesn't get persisted so the setting is
376    * lost between sessions. We might want to consider rearchitecting and not storing the observers
377    * in the ChannelIFs at all. Same goes for the Items.
378    */

379
380   /**
381    * notifyChannelsAndItems - Notify both item and channel listeners for a channel and all its
382    * items. This is useful if the client wants to treat a Channel that was recently read in by
383    * hibernate in a consistent way with listeners.
384    *
385    * @param channel - Relevant channel.
386    */

387   public void notifyChannelsAndItems(Channel channel)
388   {
389     synchronized (builder)
390     {
391       try
392       {
393         builder.beginTransaction();
394         builder.reload(channel);
395
396         notifyChannelRetrieved(channel);
397         notifyItems(channel);
398
399         builder.endTransaction();
400       } catch (ChannelBuilderException e)
401       {
402         e.printStackTrace();
403         builder.resetTransaction();
404       }
405     }
406   }
407
408   /**
409    * Send notifications for all the items of this channel that they have been added.
410    *
411    * @param channelHandle -
412    */

413   public void notifyItems(Channel channelHandle)
414   {
415     if (globalChannelObserver != null)
416     {
417       Iterator iterChan = channelHandle.getItems().iterator();
418       while (iterChan.hasNext())
419       {
420         notifyItemAdded((Item) iterChan.next());
421       }
422     }
423   }
424
425   /**
426    * notifyChannelsAndItems - Call notifyChannelAndItems(channels) across all channels in this
427    * PersistentChanGrpMgr.
428    */

429   public void notifyChannelsAndItems()
430   {
431     Iterator chanIter = group.getChannels().iterator();
432     while (chanIter.hasNext())
433     {
434       notifyChannelsAndItems((Channel) chanIter.next());
435     }
436   }
437
438   /**
439    * Send notifications about all Channels in this group (but not their items) -
440    */

441   public void notifyChannels()
442   {
443     // Iterator chanIter = group.getChannels().iterator();
444
Iterator chanIter = channelIterator();
445     while (chanIter.hasNext())
446     {
447       notifyChannelRetrieved((Channel) chanIter.next());
448     }
449   }
450
451   /**
452    * Send notification that specified channel was retrieved.
453    *
454    * @param chan -
455    */

456   public void notifyChannelRetrieved(Channel chan)
457   {
458     if (globalChannelObserver != null)
459     {
460       try
461       {
462         globalChannelObserver.channelRetrieved(chan);
463       } catch (Exception JavaDoc e)
464       {
465         // We don't need any troubles with observer exceptions.
466
logger.error(e.getMessage(), e);
467       }
468     }
469   }
470
471   /**
472    * Send notification that specified item was retrieved.
473    *
474    * @param newItem -
475    */

476   public void notifyItemAdded(Item newItem)
477   {
478     if (globalChannelObserver != null)
479     {
480       try
481       {
482         globalChannelObserver.itemAdded(newItem);
483       } catch (Exception JavaDoc e)
484       {
485         // We don't need any troubles with observer exceptions.
486
logger.error(e.getMessage(), e);
487       }
488     }
489   }
490
491   /**
492    * Notify that the PersistChanGrpMgrTask is currently in the middle of its 'run()' method.
493    *
494    * @param isPolling true - start polling, false- end
495    */

496   public void notifyPolling(boolean isPolling)
497   {
498     if (globalChannelObserver != null)
499     {
500       try
501       {
502         globalChannelObserver.pollingNow(group.getTitle(), pollingCounter, isPolling);
503       } catch (Exception JavaDoc e)
504       {
505         // We don't need any troubles with observer interrupt our polling.
506
logger.error(e.getMessage(), e);
507       }
508     }
509   }
510
511   /**
512    * Setup the one and only Global observer. Note this is not an observer chain, but just a single
513    * one.
514    *
515    * @param obser Observer to register
516    */

517   public void setGlobalObserver(PersistChanGrpMgrObserverIF obser)
518   {
519     globalChannelObserver = obser;
520   }
521
522   /**
523    * activate -
524    * -
525    */

526   public synchronized void activate()
527   {
528     if (activated) return;
529
530     task = new PersistChanGrpMgrTask(this, taskPeriod);
531     task.start();
532
533     activated = true;
534   }
535
536   /**
537    * Simply return whether we are currently activated (that is, running the tasks that download and
538    * process RSS. Will also return true if the PersistChanGrpMgrTask is still in the middle of
539    * finishing.
540    *
541    * @return true = activated
542    */

543   public boolean isActivated()
544   {
545     return activated || (task != null && task.isRunning());
546   }
547
548   /**
549    * Bump up polling counter by one.
550    *
551    */

552   public void incrPollingCounter()
553   {
554     pollingCounter++;
555   }
556
557   /**
558    * Return how many times the task has polled the feed since this PersistChanGrp was built
559    *
560    * @return polling count so far
561    */

562   public int getPollingCounter()
563   {
564     return pollingCounter;
565   }
566
567   /**
568    * Interrupts the update task and return immediately. Do not waits for task to stop.
569    */

570   public synchronized void deActivate()
571   {
572     deActivate(false);
573   }
574
575   /**
576    * Interrupts the update task and return immediately. Waits for task to finish
577    * if <code>waitForFinish</code> argument set.
578    *
579    * @param waitForFinish TRUE to wait until task actually finishes.
580    */

581   public synchronized void deActivate(final boolean waitForFinish)
582   {
583     if (task != null)
584         logger.debug("deActivate(" + task.getName() + ") " + activated);
585     else
586         logger.debug("deActivate task = null");
587
588     if (!activated) return;
589
590     task.interrupt(waitForFinish);
591
592     activated = false;
593   }
594
595   /**
596    * Change parameters of how this PersistChanGrpMgr works. Only allowed when this PersistChanGrp is
597    * inactive.
598    *
599    * @param startDel ms before starting (-1 means don't change.)
600    * @param period ms between iterations (-1 means don't change.)
601    * @param acceptErr number of errors before putting a channel offline (-1 means don't change)
602    */

603   public void setParams(final int startDel, final int period, final int acceptErr)
604   {
605     if (activated) throw new IllegalStateException JavaDoc("can't setParams while activated");
606     if (startDel != -1) taskStartDelay = startDel;
607     if (period != -1) taskPeriod = period;
608     if (acceptErr != -1) acceptNrErrors = acceptErr;
609   }
610
611   /**
612    * Create an iterator to iterate across all the channels in this group.
613    * @return the iterator
614    */

615   public Iterator channelIterator()
616   {
617     Iterator ret = null;
618     synchronized (builder)
619     {
620       try
621       {
622         builder.beginTransaction();
623         builder.reload(group);
624
625         ret = group.getAll().iterator();
626
627         builder.endTransaction();
628       } catch (Exception JavaDoc e)
629       {
630         e.printStackTrace();
631       }
632     }
633     return ret;
634   }
635
636   /**
637    * Get currently associated ChannelBuilder
638    *
639    * @return the current cb
640    */

641   public ChannelBuilder getBuilder()
642   {
643     return builder;
644   }
645
646   /**
647    * Get currently assocaited ChannelGrouo
648    *
649    * @return the cg
650    */

651   public ChannelGroup getChannelGroup()
652   {
653     return group;
654   }
655
656   /**
657    * Get currently assocaited SessionHandler
658    *
659    * @return the sh
660    */

661   public SessionHandler getHandler()
662   {
663     return handler;
664   }
665
666   /**
667    * @return acceptable number of errors
668    */

669   public int getAcceptNrErrors()
670   {
671     return acceptNrErrors;
672   }
673
674   /**
675    * Return nicely formatted string for this object
676    *
677    * @return - the string
678    */

679   public String JavaDoc toString()
680   {
681     String JavaDoc result = "";
682     synchronized (builder)
683     {
684       try
685       {
686         builder.beginTransaction();
687         builder.reload(group);
688
689         result = group.getTitle() + "[" + group.getChannels().size() + "]";
690
691         builder.endTransaction();
692       } catch (Exception JavaDoc e)
693       {
694         e.printStackTrace();
695       }
696     }
697     return result;
698   }
699
700   /**
701    * newChannel -
702    *
703    * @param url
704    * @return -
705    */

706   private Channel newChannel(String JavaDoc url)
707   {
708     Channel channel;
709     synchronized (builder)
710     {
711       channel = (Channel) builder.createChannel("[uninitialized channel]");
712     }
713     channel.setLocationString(url);
714
715     group.add(channel);
716     channel.getGroups().add(group);
717
718     return channel;
719   }
720
721   /**
722    * Search for a Channel for the indicated url (i.e. the site url or xml feed url)
723    *
724    * N.B: This method assumes that a builder.beginTransaction() has been performed.
725    *
726    * @param url
727    * @return - Channel or null if none found
728    */

729   private Channel findChannel(String JavaDoc url)
730   {
731     Channel achan = null;
732     synchronized (builder)
733     {
734       Session sess = builder.getSession();
735       try
736       {
737         final List JavaDoc channels = sess.find("from Channel chan where chan.locationString = ? order by chan.id desc",
738                 url, Hibernate.STRING);
739
740         // List channels will contain 0 or more Channels in the database for said url.
741

742         final int size = channels.size();
743         if (size > 0)
744         {
745           if (size > 1)
746           {
747             logger.error("Multiple Channels for " + url + " found.");
748           }
749           achan = (Channel) channels.get(0);
750         }
751       } catch (HibernateException e)
752       {
753         achan = null;
754         e.printStackTrace();
755       }
756     }
757
758     logger.info("findChannel: " + url + "->" + achan);
759     return achan;
760   }
761
762   /**
763    * Search for the indicated Channel Group.
764    *
765    * @param name - Name of ChannelGroup to locate in database
766    * @return - ChannelGroup or null if none found by that name
767    */

768   private ChannelGroup findChannelGroup(String JavaDoc name)
769   {
770     ChannelGroup result = null;
771     synchronized (builder)
772     {
773       try
774       {
775         builder.beginTransaction();
776
777         final Session sess = builder.getSession();
778         final List JavaDoc results = sess.find("from ChannelGroup as grp where grp.title = ?",
779                 name, Hibernate.STRING);
780
781         final int size = results.size();
782         if (size > 0)
783         {
784           if (size > 1)
785           {
786             logger.error("Multiple Channel Groups called " + name + " found.");
787           }
788           result = (ChannelGroup) results.get(0);
789         }
790
791         builder.endTransaction();
792       } catch (Exception JavaDoc e)
793       {
794         e.printStackTrace();
795         builder.resetTransaction();
796       }
797     }
798
799     logger.info("findChannelGroup: " + name + "->" + result);
800     return result;
801   }
802 }
803
Popular Tags