Java tutorial
// Informa -- RSS Library for Java //Copyright (c) 2002 by Niko Schmuck // //Niko Schmuck //http://sourceforge.net/projects/informa //mailto:niko_schmuck@users.sourceforge.net // //This library is free software. // //You may redistribute it and/or modify it under the terms of the GNU //Lesser General Public License as published by the Free Software Foundation. // //Version 2.1 of the license should be included with this distribution in //the file LICENSE. If the license is not included with this distribution, //you may find a copy at the FSF web site at 'www.gnu.org' or 'www.fsf.org', //or you may write to the Free Software Foundation, 675 Mass Ave, Cambridge, //MA 02139 USA. // //This library is distributed in the hope that it will be useful, //but WITHOUT ANY WARRANTY; without even the implied waranty of //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //Lesser General Public License for more details. // //$Id: PersistChanGrpMgrTask.java,v 1.19 2006/12/04 23:43:27 italobb Exp $ package de.nava.informa.utils; import java.net.URL; import java.net.UnknownHostException; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.nava.informa.core.ChannelBuilderIF; import de.nava.informa.core.ChannelFormat; import de.nava.informa.core.ChannelIF; import de.nava.informa.core.ItemIF; import de.nava.informa.impl.hibernate.Channel; import de.nava.informa.impl.hibernate.ChannelBuilder; import de.nava.informa.impl.hibernate.Item; import de.nava.informa.parsers.FeedParser; /** * PersistChanGrpMgrTask - description... */ public class PersistChanGrpMgrTask extends Thread { private static final Log LOG = LogFactory.getLog(PersistChanGrpMgrTask.class); private PersistChanGrpMgr mgr; private ChannelBuilder builder; private ChannelBuilderIF tempBuilder; private Map<URL, UpdateChannelInfo> channelInfos; private long minChannelUpdateDelay; private volatile boolean running = false; /** * Construct and setup context of the PersistChanGrpMgr * * @param mgr * @param minChannelUpdateDelay minimum number of millis between channel updates. */ public PersistChanGrpMgrTask(PersistChanGrpMgr mgr, long minChannelUpdateDelay) { super("PCGrp: " + mgr.getChannelGroup().getTitle()); this.minChannelUpdateDelay = minChannelUpdateDelay; this.mgr = mgr; builder = mgr.getBuilder(); channelInfos = new HashMap<URL, UpdateChannelInfo>(); tempBuilder = new de.nava.informa.impl.basic.ChannelBuilder(); } /** * Minimum number of milliseconds between updates of channel. * * @param minChannelUpdateDelay minimum pause between updates in milliseconds. */ public void setMinChannelUpdateDelay(long minChannelUpdateDelay) { this.minChannelUpdateDelay = minChannelUpdateDelay; } /** * run - Called each iteration to process all the Channels in this Group. This will skip inactive * channels. - */ public void run() { running = true; try { // We do job and sleep until someone interupts us. while (!isInterrupted()) { long startedLoop = System.currentTimeMillis(); performUpdates(); // Calculate time left to sleep beween updates long leftToSleep = minChannelUpdateDelay - (startedLoop - System.currentTimeMillis()); LOG.debug("Going to sleep for " + leftToSleep + " millis"); if (leftToSleep > 0) Thread.sleep(leftToSleep); } } catch (InterruptedException e) { // Note that the catch looks like it just continues, but at the same time isInterrupted() goes // to true and ends the } catch (Exception ignoredException) // Ignore all Exceptions (assuming that they did their own // cleanup and we want to keep on polling. { // and continue } finally { running = false; synchronized (this) { notifyAll(); } } } /** * Returns TRUE if current thread is running. * * @return TRUE if running. */ public boolean isRunning() { return running; } /** * Interrupt the thread and return. * * @see java.lang.Thread#interrupt() */ public void interrupt() { interrupt(false); } /** * Interrupts execution of task. * * @param wait TRUE to wait for finish of task. */ public void interrupt(boolean wait) { super.interrupt(); if (wait && isRunning()) { while (isRunning()) { try { synchronized (this) { wait(1000); } } catch (InterruptedException ignored) { } } } } /** * Perform single update cycle for current group. */ public void performUpdates() { LOG.debug("Starting channel updates loop for " + mgr.getChannelGroup().getTitle()); mgr.notifyPolling(true); Iterator iter = mgr.channelIterator(); Channel nextChan; while (iter.hasNext()) { nextChan = (Channel) iter.next(); LOG.debug("processing: " + nextChan); // Catch all Exceptions coming out of handleChannel and continue iterating to the next one. try { handleChannel(nextChan, getUpdChanInfo(nextChan)); } catch (RuntimeException e) { LOG.error("Error during processing: " + nextChan, e); } catch (NoSuchMethodError ignoreNoSuchMethod) // Ignore and continue { LOG.error("NoSuchMethodError exception within Run method. Ignoring." + nextChan, ignoreNoSuchMethod); } } // Notify everyone that polling of group finished. mgr.notifyPolling(false); mgr.incrPollingCounter(); } /** * Return (and create if necessary) an UpdateChannelInfo object, which is a parallel object which * we use here to keep track of information about a channel. * * @param chan - Corresponding Channel. */ private UpdateChannelInfo getUpdChanInfo(Channel chan) { UpdateChannelInfo info = channelInfos.get(chan.getLocation()); if (info == null) // Create a new UpdateChannelInfo object and add it to the Map. { info = new UpdateChannelInfo(mgr.getAcceptNrErrors()); channelInfos.put(chan.getLocation(), info); } return info; } /** * Process the Channel information. * * @param chan - Channel to process * @param info - UpdateChannelInfo - additional Channel Info object */ private void handleChannel(Channel chan, UpdateChannelInfo info) { if (!info.shouldDeactivate()) { if (shouldUpdate(info)) { synchronized (builder) { if (!info.getFormatDetected()) handleChannelHeader(chan, info); handleChannelItems(chan, info); } info.setLastUpdatedTimestamp(System.currentTimeMillis()); } } else { // Returns true if more errors happened than threshold. LOG.debug("Not processing channel: " + chan + " because exceeded error threshold."); } } /** * Returns TRUE if the cannel represented by the <code>info</code> should be updated. Decision * is basing on the fact of last update. If there's not enough time passed since then we don't * need to update this channel. * * @param info info object of the channel. * @return result of the check. */ private boolean shouldUpdate(UpdateChannelInfo info) { return System.currentTimeMillis() - info.getLastUpdatedTimestamp() > minChannelUpdateDelay; } /** * handleChannelHeader - * * @param chan * @param info - */ private void handleChannelHeader(Channel chan, UpdateChannelInfo info) { if (!info.getFormatDetected()) { // If format has been detected then we've seen this Channel // already LOG.debug("Handling Channel Header. Format not yet detected."); try { builder.beginTransaction(); builder.reload(chan); ChannelFormat format = FormatDetector.getFormat(chan.getLocation()); chan.setFormat(format); info.setFormatDetected(true); chan.setLastUpdated(new Date()); builder.endTransaction(); } catch (UnknownHostException e) { // Normal situation when user is offline LOG.debug("Host not found: " + e.getMessage()); } catch (Exception e) { info.increaseProblemsOccurred(e); String msg = "Exception in handleChannelHeader for : " + chan; LOG.error(msg + "\n Continue...."); } finally { // If there was an exception we still will be in transaction. if (builder.inTransaction()) builder.resetTransaction(); } } } /** * Process items in the newly parsed Channel. If they are new (i.e. not yet persisted) then add * them to the Channel. Note the logXXX variables were put in to do better error reporting in the * event of an Exception. * * @param chan * @param info - */ private void handleChannelItems(Channel chan, UpdateChannelInfo info) { ChannelIF tempChannel = null; int logHowManySearched = 0; int logHowManyAdded = 0; // TODO: [Aleksey Gureev] I don't see locking of builder here. Locking of the whole peice will // be very // great resource consumption. It's necessary to rework whole method to lock builder for a // minimal time. try { builder.beginTransaction(); builder.reload(chan); /* * We will now parse the new channel's information into a *memory based* temporary channel. We * will then see which items that we received from the feed are already present and add the * new ones. */ tempChannel = FeedParser.parse(tempBuilder, chan.getLocation()); InformaUtils.copyChannelProperties(tempChannel, chan); /* * Tricky: this channel might have been loaded into memory by Hibernate in a preceding * Hibernate Session. We need to make it available in this session so it will be written back * to disk when the transaction is committed. */ chan.setLastUpdated(new Date()); mgr.notifyChannelRetrieved(chan); /* * Compare with the existing items, and only add new ones. In the future this is where we * would put code to diff an item to see how blog author has edited a certain item over time. */ if (!tempChannel.getItems().isEmpty()) { for (ItemIF itemIF : tempChannel.getItems()) { logHowManySearched++; de.nava.informa.impl.basic.Item transientItem = (de.nava.informa.impl.basic.Item) itemIF; if (!chan.getItems().contains(transientItem)) { LOG.info("Found new item: " + transientItem); logHowManyAdded++; /* * A persistent item is created, using all the state from the memory based item produced * by parser. */ ItemIF newItem = builder.createItem(chan, transientItem); mgr.notifyItemAdded((Item) newItem); } } // while it.hasNext() } builder.endTransaction(); } catch (UnknownHostException e) { // Normal situation when user is offline LOG.debug("Host not found: " + e.getMessage()); } catch (Exception e) { info.increaseProblemsOccurred(e); String msg = "Exception in handleChannelItems. # Potential new items = " + logHowManySearched + ", # Items actually added to channel: " + logHowManyAdded + "\n Stored Chan=" + chan + "\n ParsedChan=" + tempChannel; LOG.error(msg + "\n Continue...."); } finally { // If there was an exception we still will be in transaction. if (builder.inTransaction()) builder.resetTransaction(); } } }