head	1.14;
access;
symbols;
locks; strict;
comment	@# @;


1.14
date	2001.08.17.21.29.51;	author leonardr;	state Exp;
branches;
next	1.13;

1.13
date	2001.08.11.09.08.09;	author dlr;	state Exp;
branches;
next	1.12;

1.12
date	2001.08.03.19.07.13;	author leonardr;	state Exp;
branches;
next	1.11;

1.11
date	2001.08.03.02.41.13;	author dlr;	state Exp;
branches;
next	1.10;

1.10
date	2001.07.20.22.46.07;	author dlr;	state Exp;
branches;
next	1.9;

1.9
date	2001.06.08.22.15.08;	author leonardr;	state Exp;
branches;
next	1.8;

1.8
date	2001.06.08.20.44.45;	author leonardr;	state Exp;
branches;
next	1.7;

1.7
date	2001.06.07.02.24.13;	author leonardr;	state Exp;
branches;
next	1.6;

1.6
date	2001.06.07.01.58.20;	author leonardr;	state Exp;
branches;
next	1.5;

1.5
date	2001.05.30.20.15.25;	author leonardr;	state Exp;
branches;
next	1.4;

1.4
date	2001.05.23.01.08.07;	author leonardr;	state Exp;
branches;
next	1.3;

1.3
date	2001.05.23.00.35.00;	author leonardr;	state Exp;
branches;
next	1.2;

1.2
date	2001.05.22.00.14.19;	author leonardr;	state Exp;
branches;
next	1.1;

1.1
date	2001.05.21.21.06.01;	author leonardr;	state Exp;
branches;
next	;


desc
@@


1.14
log
@Gated debug statement.
@
text
@/* ================================================================
 * Copyright (c) 2001 CollabNet.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 * any, must include the following acknowlegement: "This product includes
 * software developed by CollabNet <http://www.collab.net/>."
 * Alternately, this acknowlegement may appear in the software itself, if
 * and wherever such third-party acknowlegements normally appear.
 *
 * 4. The hosted project names must not be used to endorse or promote
 * products derived from this software without prior written
 * permission. For written permission, please contact info@@collab.net.
 *
 * 5. Products derived from this software may not use the "Tigris" or
 * "Helm" names nor may "Tigris" or "Helm" appear in their names without
 * prior written permission of CollabNet.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL COLLABNET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of CollabNet.
 */

package org.tigris.helm.tool.cache;

import java.util.*;
import javax.servlet.ServletConfig;
import java.sql.Timestamp;

import org.apache.turbine.util.Log;
import org.apache.commons.util.exception.NestableException;
import org.apache.fulcrum.*;
import org.apache.torque.util.Criteria;

import org.tigris.helm.event.ClusterEvent;
import org.tigris.helm.event.CacheInvalidationEvent;
import org.tigris.helm.tool.HelmService;
import org.tigris.helm.tool.TemplateTool;
import org.tigris.helm.tool.cluster.Clustered;
import org.tigris.helm.tool.cluster.ClusterService;

/**
 * The registry of all object caches in a JVM.  Listens for and sends
 * cluster-level cache invalidation events.
 *
 * @@author <a href="mailto:dlr@@collab.net">Daniel Rall</a>
 * @@author <a href="mailto:leonardr@@collab.net">Leonard Richardson</a>
 */
public class CacheRegistryService extends BaseService
    implements Runnable, HelmService, Clustered
{
    /**
     * Flag indicating whether in debug mode.
     */
    private static final boolean DEBUG = true;

    /**
     * The name of this service.  Used to retrive it from the broker.
     */
    public static final String SERVICE_NAME = "CacheRegistryService";

    /**
     * The special descriptor indicating that all caches should be
     * invalidated. 
     */
    public static final String ALL_CACHES_DESCRIPTOR = "*";

    /**
     * The event listener thread for this service instance.
     */
    protected Thread meAsThread = null;

    /**
     * Whether to stop the event listener.
     */
    protected boolean stop = false;

    /**
     * The cache registry maps registered RegisterableCache
     * implementations to their human-readable names.  
     */
    private Map cacheRegistry;

    /**
     * interestedCachesByDescriptor keeps track of which registered caches
     * are interested in which types of invalidation descriptor.
     */
    private Map interestedCachesByDescriptor;

    /**
     * Initializing constructor.
     */
    public CacheRegistryService()
    {
        cacheRegistry = new Hashtable();
        interestedCachesByDescriptor = new Hashtable();
    }

    /**
     * Called during <code>Turbine.init()</code> (i.e. early initialization).
     *
     * @@param config The configuration we're operating under.
     */
    public synchronized void init()
        throws InitializationException
    {
        try
        {
            ClusterService.getService().register(this);
        }
        catch (NestableException e)
        {
            throw new InitializationException(e.getMessage());
        }
        meAsThread = new Thread(this);
        setInit(true);

        meAsThread.start();
    }

    /**
     * Gets the descriptor used to key information for this type of service
     * (currently used in the database).
     */
    public String getDescriptor()
    {
        return SERVICE_NAME;
    }

    /**
     * Shuts down this service, stopping the thread.
     */
    public final void shutdown()   
    {        
        setInit(false);
        stop = true;
        try
        {
            ClusterService.getService().unregister(this);
        }
        catch (NestableException e)
        {
            Log.warn(SERVICE_NAME + ": shutdown()", e);
        }
    }

    /**
     * A wrapper method for getting the single instance of this
     * service from the Turbine services list.
     *
     * @@return Turbine's single instance of this service.
     */
    public static final CacheRegistryService getService()
    {
        return (CacheRegistryService)TurbineServices
            .getInstance().getService(CacheRegistryService.SERVICE_NAME);
    }

    /**
     * Returns all invalidation event types in which some cache is currently
     * interested.
     */
    public List getCurrentDescriptors()
    {        
        Object[] keys = interestedCachesByDescriptor.keySet().toArray();
        Arrays.sort(keys);
        return Arrays.asList(keys);
    }

    /**
     * Registers a cache as being interested in specified descriptors.
     * All caches are interested in the "*" descriptor.
     *
     * @@param cache The cache to register.
     * @@param descriptors An array of String descriptors describing which
     *                    cache cluster events to invalidate on.
     */
    public void register(RegisterableCache cache)
    {
        String name = cache.getName();        
        String[] descriptors = cache.getInterestingDescriptors();

        //Register the cache.
        cacheRegistry.put(cache, name);

        //Register the cache with all the descriptor types it finds
        //interesting.
        for (int i = 0; i <= descriptors.length; i++)
        {            
            String descriptor = (i == descriptors.length
                                 ? ALL_CACHES_DESCRIPTOR : descriptors[i]);
            List interestedCaches = (List)interestedCachesByDescriptor
                .get(descriptor);
            if (interestedCaches == null)
            {
                interestedCaches = new ArrayList(1);
            }
            interestedCaches.add(cache);
            interestedCachesByDescriptor.put(descriptor, interestedCaches);
        }
    }

    /**
     * Removes a cache from the registry.
     *
     * @@param cache The cache to register.
     */
    public void unregister(RegisterableCache cache)
    {
        Iterator i = interestedCachesByDescriptor.keySet().iterator(); 
        while (i.hasNext())
        {
            String key = (String)i.next();
            List l = (List)interestedCachesByDescriptor.get(key);
            if (l.size() == 1)
            {
                interestedCachesByDescriptor.remove(key);
            }
            else
            {
                l.remove(cache);
                interestedCachesByDescriptor.put(key,l);
            }
        }
    }

    /**
     * Every iteration, check the database for recent cache invalidations.
     * If any are found, invalidate the appropriate caches.
     */
    public void run()
    {
        stop = false;
        while (!stop)
        {
            try
            {
                ClusterService.getService().receiveEvent(this);
            }
            catch (Exception e)
            {
                Log.error(SERVICE_NAME + ": Error receiving cluster event", e);
            }
        }
    }

    /**
     * Callback for reception of event by
     * {@@link org.tigris.helm.tool.cluster.ClusterService}.
     *
     * @@see org.tigris.helm.tool.cluster.Clustered
     */
    public final void eventReceived(ClusterEvent event)
        throws NestableException
    {
        if (event instanceof CacheInvalidationEvent)
        {
            try
            {
                processInvalidation((CacheInvalidationEvent) event);
            }
            catch (Exception e)
            {
                throw new NestableException(e);
            }
        }
        else
        {
            Log.warn(SERVICE_NAME + ": Inavalid event sent to " +
                     "eventReceived(): " + event);
        }
    }

    /**
     * Invalidates all the RegisterableCaches which are interested in the
     * given event.
     */
    private void processInvalidation(CacheInvalidationEvent invalidation)
        throws Exception
    {
        if (DEBUG)
        {
            Log.debug(SERVICE_NAME + ": Processing cache invalidation " +
                      invalidation + " for descriptor " +
                      invalidation.getDescriptor());
        }
        List interestedCaches = (List) interestedCachesByDescriptor.get
            (invalidation.getDescriptor());
        if (interestedCaches != null)
        {
            for (Iterator j = interestedCaches.iterator(); j.hasNext(); )
            {
                RegisterableCache cache = (RegisterableCache)j.next();
                cache.invalidate();
            }
        }
    }

    /**
     * Invalidates local caches immediately.  Creates and saves an
     * invalidation event with the given descriptor so that other JVMs
     * will invalidate their caches.  
     *
     * There's a slight inefficiency here in that the invalidated
     * cache will shortly be invalidated again.
     *
     * @@param descriptor Text used to identify the type of cache to be
     *                   invalidated.
     */
    public void invalidate(String descriptor)
        throws Exception
    {
        // Send out invalidation event.  Our listener callback will
        // force invalidation of our caches.
        ClusterEvent event = new CacheInvalidationEvent(descriptor);
        if (DEBUG)
        {
            Log.debug(SERVICE_NAME + ": Sending invalidation for '" +
                      descriptor + "' objects");
        }
        ClusterService.getService().sendEvent(this, event);
    }

    /**
     * Describes the registered caches.
     */
    public String describeCaches()
    {
        StringBuffer buf = new StringBuffer();
        buf.append("Currently registered caches for this JVM: ");
        for (Iterator i = cacheRegistry.keySet().iterator(); i.hasNext();)
        {
            buf.append("\n\n");
            RegisterableCache r = (RegisterableCache)i.next();
            buf.append(r.describe());
        }
        return buf.toString();
    }

    /**
     * Returns the status of this service. 
     */
    public final String getStatus()
    {
        int caches = cacheRegistry.keySet().size();
        StringBuffer b = new StringBuffer()
            .append(caches).append(" registered cache")
            .append(caches==1 ? "" : "s").append(".\n");
        return b.toString();
    }

    /**
     * Returns the relative path to the template which contains controls
     * for controlling this service.
     */
    public String getAdminTemplateName() 
    {
        return TemplateTool.getPathToServiceAdminTemplate(getName() + ".vm");
    }
}
@


1.13
log
@Upgrading to Turbine 3.0-dev, Fulcrum 1.0, and Torque 1.0.
@
text
@d304 6
a309 3
        Log.debug(SERVICE_NAME + ": Processing cache invalidation " +
                  invalidation + " for descriptor " +
                  invalidation.getDescriptor());
@


1.12
log
@Sort the descriptors.
@
text
@d54 3
a56 3
import org.apache.turbine.util.TurbineException;
import org.apache.turbine.services.*;
import org.apache.turbine.util.db.Criteria;
d72 1
a72 1
public class CacheRegistryService extends TurbineBaseService
d127 1
a127 1
    public synchronized void init(ServletConfig config)
d134 1
a134 1
        catch (TurbineException e)
d164 1
a164 1
        catch (TurbineException e)
d277 1
a277 1
        throws TurbineException
d287 1
a287 1
                throw new TurbineException(e);
@


1.11
log
@Now with delicious Spread!
@
text
@d189 2
a190 6
        List descriptors = new ArrayList(keys.length);
        for (int i = 0; i < keys.length; i++)
        {
            descriptors.add((String)keys[i]);
        }
        return descriptors;
@


1.10
log
@Reduced amount of code.
@
text
@d54 1
d58 2
a59 2
import org.tigris.helm.om.ClusterEvent;
import org.tigris.helm.om.ClusterEventPeer;
d62 2
d66 2
a67 3
 * The registry of all object caches in a JVM.  Communicates cache
 * invalidation events to the database, and periodically scans the
 * database for cache invalidations.
d70 1
a70 1
 * @@author <a href="mailto:leonardr@@collab.net">Leonard Richardson</a> 
d73 1
a73 1
    implements Runnable, HelmService
d92 1
a92 2
     * Gets the descriptor used to key information for this type of service
     * (currently used in the database).
d94 1
a94 4
    public String getDescriptor()
    {
        return SERVICE_NAME;
    }
d97 1
a97 2
     * Returns the time, in milliseconds, separating the singleton's
     * assertions of singletonhood.  
d99 1
a99 4
    public long getPollTime()
    {
        return 1000 * 2;
    }
a112 4

    protected boolean stop = false;
    protected Thread meAsThread = null;

d114 1
a114 1
     * Initializing constructor
d127 1
a127 1
    public void init(ServletConfig config)
d130 9
d140 1
a140 1
        meAsThread = new Thread(this);
d145 9
d159 9
a167 1
        sendStopSignal();
a229 1

d237 2
a238 2
        for (Iterator i = interestedCachesByDescriptor.keySet().iterator(); 
             i.hasNext();)
a254 8
     * Tells the thread that it should stop running.
     */
    protected void sendStopSignal()
    {
        stop = true;
    }

    /**
a259 1
        Date lastCacheInvalidation = null;
d265 1
a265 20
                Date thisCheck = new Date();
                List invalidations =
                    getCacheInvalidations(lastCacheInvalidation);
                lastCacheInvalidation = thisCheck;

                for (Iterator i = invalidations.iterator(); i.hasNext();)
                {
                    ClusterEvent invalidation = (ClusterEvent)i.next();
                    processInvalidation(invalidation);
                }

                try                             
                {
                    Thread.sleep(getPollTime());
                } 
                catch (InterruptedException resumeRunning)
                {
                    // The JVM wants us to get back to work.
                }

d269 1
a269 2
                Log.error("Exception thrown in " + getDescriptor() 
                          + " singleton loop " , e);
d275 4
a278 1
     * Returns cache invalidations that happened after the given date.
d280 2
a281 2
    public List getCacheInvalidations(Date after)
        throws Exception
d283 1
a283 3
        Criteria c = new Criteria(2)
            .add(ClusterEventPeer.TYPE, SERVICE_NAME);
        if (after != null)
d285 13
a297 2
            c.add(ClusterEventPeer.STAMP, new Timestamp(after.getTime()),
                  Criteria.GREATER_THAN);
a298 1
        return ClusterEventPeer.doSelect(c);
d305 1
a305 1
    public void processInvalidation(ClusterEvent invalidation)
d308 5
a312 2
        List interestedCaches = (List)interestedCachesByDescriptor.get
            (invalidation.getDescriptor().toString());
d315 1
a315 2
            for (Iterator j = interestedCaches.iterator();
                 j.hasNext();)
d330 3
d337 9
a345 5
        ClusterEvent e = ClusterEventPeer.retrieveInstance(SERVICE_NAME, 
                                                           descriptor, null);
        e.setStamp(new Date());
        e.save();
        processInvalidation(e);
d348 3
d370 2
a371 2
        StringBuffer b = new StringBuffer("").append(caches)
            .append(" registered cache")
a383 1

@


1.9
log
@Use the correct java.sql data type and avoid invalidating caches
you've already invalidated. Now that's innovative!
@
text
@d51 1
d298 2
a299 3
            java.sql.Timestamp afterTime 
                = new java.sql.Timestamp(after.getTime());
            c.add(ClusterEventPeer.STAMP, afterTime, Criteria.GREATER_THAN);
@


1.8
log
@Bugfixes and debugging cleanup.
@
text
@d297 2
a298 1
            java.sql.Time afterTime = new java.sql.Time(after.getTime());
@


1.7
log
@Controls for services now go right next to their statuses. Also added status for TaskRunnerService
@
text
@d294 6
a299 2
            .add(ClusterEventPeer.TYPE, SERVICE_NAME)
            .add(ClusterEventPeer.STAMP, new Date(), Criteria.GREATER_THAN);
d308 1
d310 2
a311 2
        List interestedCaches = 
            (List)interestedCachesByDescriptor.get(invalidation);
a333 1
        Log.debug("Invalidating " + descriptor);
@


1.6
log
@Added placeholder HelmService interface.
@
text
@d59 1
d360 9
@


1.5
log
@Big cleanup of cache self-descriptions. Also added an admin tool to do manual selective cache invalidation.
@
text
@d58 1
d69 1
a69 1
    implements Runnable
d347 12
@


1.4
log
@Added method to display the contents of every currently registered cache.
@
text
@d164 15
d224 9
a232 2
            l.remove(cache);
            interestedCachesByDescriptor.put(key,l);
d264 1
a264 12
                    List interestedCaches = 
                        (List)interestedCachesByDescriptor.get(invalidation);
                    if (interestedCaches != null)
                    {
                        for (Iterator j = interestedCaches.iterator();
                             j.hasNext();)
                        {
                            RegisterableCache cache = 
                                (RegisterableCache)j.next();
                            cache.invalidate();
                        }
                    }
d297 27
d332 1
d338 1
a338 1
        buf.append("Currently registered caches: ");
@


1.3
log
@Cleanup of caching and cache invalidation.
@
text
@d296 13
@


1.2
log
@Bug fixes for clustering.
@
text
@d80 5
a84 1
    public static final long POLL_TIME = 1000 * 3;
d96 11
a106 3
     * The CacheRegistry for a JVM maps ClusterEvent object descriptors
     * to lists of RegisterableCaches which are interested in objects of
     * that type.
d110 7
d126 1
d165 1
d171 1
a171 1
    public void register(RegisterableCache cache, String[] descriptors)
d173 14
a186 4
        for (int i = 0; i < descriptors.length; i++)
        {
            String descriptor = descriptors[i];
            List interestedCaches = (List)cacheRegistry.get(descriptor);
d192 1
a192 1
            cacheRegistry.put(descriptor, interestedCaches);
d204 2
a205 1
        for (Iterator i = cacheRegistry.keySet().iterator(); i.hasNext();)
d208 1
a208 1
            List l = (List)cacheRegistry.get(key);
d210 1
a210 1
            cacheRegistry.put(key,l);
a236 1

d243 1
a243 1
                        (List)cacheRegistry.get(invalidation);
d249 2
a250 1
                            RegisterableCache cache = (RegisterableCache)j.next();
d258 1
a258 1
                    Thread.sleep(POLL_TIME);
@


1.1
log
@Moved clustering features out of RMI and into the database.
@
text
@d161 1
a161 1
            cacheRegistry.put(descriptor, descriptors);
d213 1
a213 2
                    for (Iterator j = interestedCaches.iterator();
                         j.hasNext();)
d215 6
a220 2
                        RegisterableCache cache = (RegisterableCache)j.next();
                        cache.invalidate();
d257 3
a259 3
        ClusterEvent e = new ClusterEvent();
        e.setType(SERVICE_NAME);
        e.setDescriptor(descriptor);
@

