de.nava.informa.utils.PersistChanGrpMgrTask.java Source code

Java tutorial

Introduction

Here is the source code for de.nava.informa.utils.PersistChanGrpMgrTask.java

Source

// 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();
        }
    }
}