Package org.apache.avalon.cornerstone.blocks.scheduler

Source Code of org.apache.avalon.cornerstone.blocks.scheduler.DefaultTimeScheduler

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed  under the  License is distributed on an "AS IS" BASIS,
* WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
* implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.avalon.cornerstone.blocks.scheduler;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Vector;
import java.util.List;
import java.util.Map;

import org.apache.avalon.cornerstone.services.scheduler.Target;
import org.apache.avalon.cornerstone.services.scheduler.TimeScheduler;
import org.apache.avalon.cornerstone.services.scheduler.TimeTrigger;
import org.apache.avalon.cornerstone.services.scheduler.TriggerFailureListener;
import org.apache.avalon.cornerstone.services.threads.ThreadManager;

import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.activity.Startable;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;

/**
* Default implementation of TimeScheduler service.
*
* @author <a href="mailto:dev@avalon.apache.org">Avalon Development Team</a>
* @avalon.component name="time-scheduler" lifestyle="singleton"
* @avalon.service type="org.apache.avalon.cornerstone.services.scheduler.TimeScheduler"
*/
public class DefaultTimeScheduler
    extends AbstractLogEnabled
    implements TimeScheduler, Serviceable, Startable, Disposable, Runnable, MonitorableTimeSchedulerMBean
{
    // ----------------------------------------------------------------------
    //  Properties
    // ----------------------------------------------------------------------
    private final Hashtable m_entries = new Hashtable();
    private final PriorityQueue m_priorityQueue =
        new SynchronizedPriorityQueue( new BinaryHeap() );
    private ThreadManager m_threadManager;
    private boolean m_running;
    private ArrayList m_triggerFailureListeners = new ArrayList();

    // ----------------------------------------------------------------------
    //  Getter/Setter methods
    // ----------------------------------------------------------------------
    //
    // LSD: these have been added in to allow subclasses of the
    // DefaultScheduler to override implementation behaviour.
    // You should *not* make these public in subclasses (hence
    // they are final); they're here for convenience implementation
    // only.

    protected final ThreadManager getThreadManager()
    {
        return m_threadManager;
    }

    protected final boolean isRunning()
    {
        return m_running;
    }

    protected final void setRunning( boolean running )
    {
        m_running = running;
    }

    protected final List getTriggerFailureListeners()
    {
        return m_triggerFailureListeners;
    }

    protected final Map getEntryMap()
    {
        return m_entries;
    }

    protected final PriorityQueue getPriorityQueue()
    {
        return m_priorityQueue;
    }

    // ----------------------------------------------------------------------
    //  Avalon Lifecycle
    // ----------------------------------------------------------------------

   /**
    * Supply of the service manager to the component.
    * @param serviceManager the service manager
    * @avalon.dependency type="org.apache.avalon.cornerstone.services.threads.ThreadManager"
    */
    public void service( final ServiceManager serviceManager )
        throws ServiceException
    {
        m_threadManager = (ThreadManager)serviceManager.lookup( ThreadManager.ROLE );
    }

    public void dispose()
    {
        if( getLogger().isDebugEnabled() )
        {
            getLogger().debug( "disposal" );
        }
        m_entries.clear();
        m_priorityQueue.clear();
    }

    public void start()
        throws Exception
    {
        //this should suck threads from a named pool
        getThreadManager().getDefaultThreadPool().execute( this );
    }

    public void stop()
    {
        m_running = false;
        synchronized( this )
        {
            notifyAll();
        }
    }

    // ----------------------------------------------------------------------
    //  Work Interface: Runnable
    // ----------------------------------------------------------------------
    /**
     * Entry point for thread that monitors entrys and triggers
     * entrys when necessary.
     */
    public void run()
    {
        m_running = true;

        while( m_running )
        {
            long duration = 0;

            if( !getPriorityQueue().isEmpty() )
            {
                TimeScheduledEntry entry = null;
                synchronized( this )
                {
                    entry = getNextEntry();
                    if( null == entry ) continue;

                    duration = entry.getNextTime() - System.currentTimeMillis();

                    if( duration < 0 )
                    {
                        //time to run job so remove it from priority queue
                        //and run it
                        getPriorityQueue().pop();

                        //Note that we need the pop to occur in a
                        //synchronized section while the runEntry
                        //does not need to be synchronized
                        //hence why there is to if statements
                        //structured in this ugly way
                    }
                }

                if( duration < 0 )
                {
                    // runs and reschedules the entry
                    runEntry( entry );                   
                    continue;
                }
                else if( 0 == duration )
                {
                    //give a short duration that will sleep
                    // so that next loop will definetly be below 0.
                    //Can not act on zero else multiple runs could go through
                    //at once
                    duration = 1;
                }
            }

            //wait/sleep until monitor is signalled which occurs when
            //next jobs is likely to occur or when a new job gets added to
            //top of heap
            try
            {
                synchronized( this )
                {
                    wait( duration );
                }
            }
            catch( final InterruptedException ie )
            {
            }
        }
    }

    // ----------------------------------------------------------------------
    //  Work Interface: Time Scheduler
    // ----------------------------------------------------------------------
    /**
     * Add a trigger failure listener
     * @param listener The listener
     */
    public void addTriggerFailureListener( TriggerFailureListener listener )
    {
        getTriggerFailureListeners().add( listener );
    }

    /**
     * Remove a trigger failure listener
     * @param listener The listener
     */
    public void removeTriggerFailureListener( TriggerFailureListener listener )
    {
        getTriggerFailureListeners().remove( listener );
    }

    /**
     * Schedule a time based trigger.
     * Note that if a TimeTrigger already has same name then it is removed.
     *
     * @param name the name of the trigger
     * @param trigger the trigger
     * @param target the target
     */
    public synchronized void addTrigger( final String name,
                                         final TimeTrigger trigger,
                                         final Target target )
    {
        try
        {
            removeTrigger( name );
        }
        catch( final NoSuchElementException nse )
        {
        }

        final TimeScheduledEntry entry = new TimeScheduledEntry( name, trigger, target );
        getEntryMap().put( name, entry );
        final boolean added = rescheduleEntry( entry, false );

        if( !added ) return;

        try
        {
            if( entry == getPriorityQueue().peek() )
            {
                notifyAll();
            }
        }
        catch( final NoSuchElementException nse )
        {
            final String message =
                "Unexpected exception when peek() on priority queue for " +
                entry.getName();
            getLogger().warn( message, nse );
        }
    }

    /**
     * Remove a scheduled trigger by name.
     *
     * @param name the name of the trigger
     * @exception NoSuchElementException if no trigger exists with that name
     */
    public synchronized void removeTrigger( String name )
        throws NoSuchElementException
    {
        //use the kill-o-matic against any entry with same name
        final TimeScheduledEntry entry = getEntry( name );
        entry.invalidate();
        getEntryMap().remove( name );
    }

    /**
     * Force a trigger time to be recalculated.
     *
     * @param name the name of the trigger
     * @exception NoSuchElementException if no trigger exists with that name
     */
    public synchronized void resetTrigger( final String name )
        throws NoSuchElementException
    {
        final TimeScheduledEntry entry = getEntry( name );
        entry.getTimeTrigger().reset();
        rescheduleEntry( entry, true );
    }

    // ----------------------------------------------------------------------
    //  Work Interface: MonitorableTimeSchedulerMBean
    // ----------------------------------------------------------------------

    /**
     * Return a collection of the triggerable names.
     * @return the collection
     */
    public synchronized Collection getEntries()
    {
        Collection coll = getEntryMap().keySet();
        Vector retval = new Vector();
        for( Iterator iterator = coll.iterator(); iterator.hasNext(); )
        {
            TimeScheduledEntry tse = (TimeScheduledEntry)getEntryMap().get( iterator.next() );
            retval.add( tse.toString() );
        }
        return retval;
    }

    // ----------------------------------------------------------------------
    //  Helper methods
    // ----------------------------------------------------------------------

    /**
     * Reschedule an entry.
     * if clone is true then invalidate old version and create a new entry to
     * insert into queue.
     *
     * @param timeEntry the entry
     * @param clone true if new entry is to be created
     * @return true if added to queue, false if not added
     */
    protected synchronized boolean rescheduleEntry( final TimeScheduledEntry timeEntry,
                                                  final boolean clone )
    {
        TimeScheduledEntry entry = timeEntry;

        if( clone )
        {
            entry = new TimeScheduledEntry( timeEntry.getName(),
                                            timeEntry.getTimeTrigger(),
                                            timeEntry.getTarget() );
            timeEntry.invalidate();

            // remove old refernce to the entry..so that next time
            // somebody calls getEntry( name ), we will get the new valid entry.
            getEntryMap().remove( timeEntry.getName() );
            getEntryMap().put( timeEntry.getName(), entry );
        }

        //reschedule if appropriate
        final long next = entry.getTimeTrigger().getTimeAfter( System.currentTimeMillis() );

        if( 0 < next )
        {
            entry.setNextTime( next );
            getPriorityQueue().insert( entry );

            if( entry == getPriorityQueue().peek() )
            {
                notify();
            }

            return true;
        }
        else
        {
            return false;
        }
    }

    /**
     * Retrieve entry from set.
     *
     * @param name the name of entry
     * @return the entry
     * @exception NoSuchElementException if no entry is found with that name
     */
    protected TimeScheduledEntry getEntry( final String name )
        throws NoSuchElementException
    {
        //use the kill-o-matic against any entry with same name
        final TimeScheduledEntry entry = (TimeScheduledEntry)getEntryMap().get( name );
        if( null != entry )
        {
            return entry;
        }
        else
        {
            throw new NoSuchElementException();
        }
    }

    /**
     * Run entry in a separate thread and reschedule it.
     *
     * @param entry the entry to run
     */
    protected void runEntry( final TimeScheduledEntry entry )
    {
        final Runnable runnable = new Runnable()
        {
            public void run()
            {
                doRunEntry( entry );
                // Stefan Seifert:
                // rescheduleEntry( entry, false );
                //
                // and then don't reschedule at the end of runEntry
                // this will ensure long-running events are
                // queued
                //
                // LSD:
                // that might break other apps. No-can-do.
            }
        };

        //this should suck threads from a named pool
        try
        {
            getThreadManager().getDefaultThreadPool().execute( runnable );
        }
        catch( final Exception e )
        {
            final String message = "Error executing trigger " + entry.getName();
            getLogger().warn( message, e );
        }
       
        // reschedule entry
        rescheduleEntry( entry, false );
    }

    /**
     * Helper method delegated to to run in a separate thread.
     *
     * @param entry the entry to run
     */
    protected void doRunEntry( final TimeScheduledEntry entry )
    {
        try
        {
            entry.getTarget().targetTriggered( entry.getName() );
        }
        catch( final Error e )
        {
            final String message = "Error occured executing trigger " + entry.getName();
            getLogger().error( message, e );
            notifyFailedTriggers( e );

        }
        catch( final Exception e )
        {
            final String message = "Exception occured executing trigger " + entry.getName();
            getLogger().warn( message, e );
            notifyFailedTriggers( e );
        }
    }

    /**
     * Retrieve next valid entry. It will pop off any
     * invalid entrys until the heap is empty or a valid entry
     * is found.
     *
     * @return the next valid entry or null if none
     */
    protected synchronized TimeScheduledEntry getNextEntry()
    {
        TimeScheduledEntry entry =
            (TimeScheduledEntry)getPriorityQueue().peek();

        //if job has been invalidated then remove it and continue
        while( !entry.isValid() )
        {
            getPriorityQueue().pop();

            if( getPriorityQueue().isEmpty() )
            {
                return null;
            }

            entry = (TimeScheduledEntry)getPriorityQueue().peek();
        }

        return entry;
    }

    protected void notifyFailedTriggers( Throwable t )
    {
        for( int i = 0; i < getTriggerFailureListeners().size(); i++ )
        {
            TriggerFailureListener triggerFailureListener = (TriggerFailureListener)m_triggerFailureListeners.get( i );
            triggerFailureListener.triggerFailure( t );
        }

    }
}
TOP

Related Classes of org.apache.avalon.cornerstone.blocks.scheduler.DefaultTimeScheduler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.