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


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

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

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


desc
@@


1.3
log
@Servlet configuration is now part of component configuration. ComponentTools and ComponentTool provide an interface to component configuration and will be used in templates to process component-specific template fragments.
@
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.component;

import java.util.*;
import java.lang.*;
import java.lang.reflect.*;

import org.apache.torque.om.BaseObject;
import org.apache.torque.om.ComboKey;
import org.apache.torque.om.ObjectKey;
import org.apache.torque.om.NumberKey;
import org.apache.torque.TorqueException;

import org.apache.fulcrum.TurbineServices;
import org.apache.fulcrum.InitializationException;
import org.apache.fulcrum.ServiceException;
import org.apache.fulcrum.ServiceManager;
import org.apache.fulcrum.velocity.TurbineVelocity;

import org.apache.turbine.util.Log;

import org.apache.commons.util.exception.NestableException;
import org.apache.commons.collections.ExtendedProperties;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.context.Context;

import org.tigris.helm.component.ComponentTools;
import org.tigris.helm.component.ComponentTool;

import org.tigris.helm.event.ClusterEvent;
import org.tigris.helm.event.CompletionEvent;
import org.tigris.helm.event.ToolEvent;
import org.tigris.helm.event.PropagationFailedEvent;

import org.tigris.helm.tool.HelmEmailer;
import org.tigris.helm.tool.TemplateTool;
import org.tigris.helm.tool.cluster.ClusteredSingletonService;
import org.tigris.helm.tool.cluster.ClusterService;

import org.tigris.helm.util.MethodSignature;

/**
 * Service for propagating Helm state changes to tools.
 *
 * @@author <a href="mailto:leonardr@@collab.net">Leonard Richardson</a>
 * @@version $Id: ToolEventPropagationService.java,v 1.2 2001/08/30 16:02:35 leonardr Exp $
 */
public class ToolEventPropagationService extends ClusteredSingletonService
{
    /**
     * Name of this service.
     */
    private static final String SERVICE_NAME = "ToolEventPropagationService";

    /**
     * The subject to use for the propagation failure report email.
     * The tool and command will be appended to the subject.
     */
    private static final String PROPAGATION_FAILURE_SUBJECT = 
        "Propagation failure report: ";

    /**
     * The name of the template used to create the body of the propagation
     * failure report email.
     */
    private static final String PROPAGATION_FAILURE_TEMPLATE = 
        TemplateTool.getPathToMessageTemplate("PropagationFailed.vm");

    /**
     * Queue of events which the singleton has yet to process.
     */
    List queue = new ArrayList();

    /**
     * The thread which actually propagates events.
     */
    EventPropagator eventPropagator = new EventPropagator();

    /**
     * A map of task IDs to failure descriptors.
     */
    Map failures = new HashMap();

    /**
     * A map keying Strings to {MethodSignature, Method} arrays.
     */
    private Map methodsByListener = new HashMap();

    /**
     * Prefixes of mappings in TurbineResources.properties of
     * names to listener classes.
     */
    private static final String LISTENER_PREFIX = "component.listener.";
    
    /**
     * Method to use to transform a key into a business object.
     */
    private static final String PEER_KEY_METHOD = "retrieveByPK";

    /**
     * Parameters of method used to transform a key into a business
     * object.
     */
    private static final Class[] PEER_KEY_PARAMETERS = new Class[1];

    /**
     * Initialize the parameters of the method used to transform
     * a key into a business object.
     */
    static
    {
        Class arg;
        try
        {
            arg = Class.forName("org.apache.torque.om.ObjectKey");
        }
        catch (Exception e)
        {
            throw new Error("Unable to initialize " + SERVICE_NAME + '!');
        }
        PEER_KEY_PARAMETERS[0] = arg;
    }

    /**
     * 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 ToolEventPropagationService getService()
    {
        return (ToolEventPropagationService)TurbineServices
            .getInstance().getService(SERVICE_NAME);
    }

    /**
     * Constructor; initialize to not being the singleton.
     */
    public ToolEventPropagationService()
    {
        isSingleton = false;
    }

    /**
     * Descriptor for this service.
     */
    public final String getDescriptor()
    {
        return SERVICE_NAME;
    }

    /**
     * Returns the status of this service. 
     */
    public final String getStatus()
        throws ServiceException
    {
        return describeRegisteredMethods();
    }
    
    /**
     * Queue of unpropagated events.
     */
    public final List getQueue()
    {
        return queue;
    }

    /**
     * Initializes this service.  Called during Fulcrum
     * initialization.  
     */
    public synchronized void init()
        throws InitializationException
    {
        super.init();
        
        methodsByListener.clear();

        ExtendedProperties conf =
            ((ServiceManager)TurbineServices.getInstance()).getConfiguration();
        try
        {            
            for (Iterator i = ComponentTools.singleton.getTools()
                     .values().iterator(); i.hasNext();)
            {
                ComponentTool tool = (ComponentTool)i.next();
                Class listener = tool.getListener();
                if (listener != null)
                {
                    registerClass(tool.getName(), listener);
                }
            }
        }
        catch (Exception e)
        {
            Log.error(e);
            throw new InitializationException
                ("Unable to initialize " + SERVICE_NAME + '!');
        }

        Thread propagatorThread = new Thread(eventPropagator);
        propagatorThread.start();
    }

    /**
     * Registers the methods of the named class with the event propagator.
     */
    private void registerClass(String listener, Class classInstance)
        throws Exception
    {
        Method[] methods = classInstance.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++)
        {
            Method m = methods[i];
            MethodSignature sig = new MethodSignature(m);
            Object[] array = {sig, m}; 
            Set mappedMethods = (Set)methodsByListener.get(listener);
            if (mappedMethods == null)
            {
                mappedMethods = new HashSet();
            }
            mappedMethods.add(array);
            methodsByListener.put(listener, mappedMethods);
        }
        if (!listener.equals(ToolEvent.ALL_TOOLS))
        {
            registerClass(ToolEvent.ALL_TOOLS, classInstance);
        }
    }

    /**
     * Sends an event across the wire to be propagated to tools.
     */
    public void propagate(ToolEvent e)
        throws NestableException
    {
        ClusterService.getService().sendEvent(this, e);
    }

    /**
     * Tool event: Adds the event to the queue.<br>
     * Completion event: Removes the specified event from the queue.<br>
     * Propagation failed event: Updates the failure information for the
     *                           specified event.
     */
    public void eventReceived(ClusterEvent e)
        throws NestableException
    {
        if (e instanceof ToolEvent)
        {
            ToolEvent te = (ToolEvent)e;
            queue.add(te);
        }
        else if (e instanceof CompletionEvent)
        {
            CompletionEvent ce = (CompletionEvent)e;
            String toolEventID = ce.getToolEventID();
            failures.remove(toolEventID);
            for (Iterator i = queue.iterator(); i.hasNext();)
            {
                ToolEvent te = (ToolEvent)i.next();
                if (toolEventID.equals(te.getID()))
                {
                    i.remove();
                    break;
                }
            }
        }
        else if (e instanceof PropagationFailedEvent)
        {
            logFailure((PropagationFailedEvent)e);
        }
    }

    /**
     * Returns the current map of failures.
     */
    public Map getFailures()
    {
        return failures;
    }

    /**
     * Logs the failure of an event to propagate. Failures are logged
     * by each member of a cluster.
     */
    private void logFailure(PropagationFailedEvent e)
    {
        String id = e.getEventID();
        Date date = e.getFailureDate();
        String message = e.getFailureMessage();
        List failure = (List)failures.get(id);
        if (failure == null)
        {
            failure = new ArrayList(4);
        }

        if (failure.size() >= 4)
        {
            //The event has failed at least twice before and we're
            //overwriting the most recent failure message with the new
            //one.
            int offset = 2;
            failure.set(offset++, date);
            failure.set(offset++, message);
        }
        else
        {
            failure.add(date);
            failure.add(message);
        }

        failures.put(id, failure);
    }
    
    /**
     * Describes all the methods currently registered with the
     * propagator.
     */
    private String describeRegisteredMethods()
    {
        StringBuffer s = new StringBuffer();
        for (Iterator i = methodsByListener.keySet().iterator();
             i.hasNext();)
        {            
            String listener = (String)i.next();
            s.append(listener + ":\n");
            Set methods = (Set)methodsByListener.get(listener);
            for (Iterator j = methods.iterator(); j.hasNext();)
            {
                Object[] a = (Object[])j.next();
                Method m = (Method)a[1];
                s.append("    " + m.getDeclaringClass().getName() 
                         + '.' + m.getName() + '\n');
            }
            s.append('\n');
        }
        return s.toString();
    }

    /**
     * Returns all the methods with which the given signature would
     * work.  
     */
    List getMethodsMatching(String key, MethodSignature sig)
    {
        Set methods = (Set)methodsByListener.get(key);
        List matchingMethods = new ArrayList();
        for (Iterator i = methods.iterator(); i.hasNext();)
        {
            Object[] a = (Object[])i.next();
            MethodSignature s = (MethodSignature)a[0];
            Method m = (Method)a[1];
            if (sig.subclasses(s))
            {
                matchingMethods.add(m);
            }
        }
        return matchingMethods;
    }

    /**
     * The class which actually propagates the events.
     */
    private class EventPropagator implements Runnable
    {      
        private static final int MAX_FAILURES = 10;
        
        /**
         * Event propagator loop.
         */
        public void run()
        {
            while (true)
            {
                if (isSingleton)
                {
                    try
                    {                    
                        doService();
                    }
                    catch (Exception e)
                    {
                        Log.error(e);
                    }
                }
                try
                {
                    Thread.sleep(1000);
                }
                catch (InterruptedException ignore)
                {
                    //Back to work.
                }
            }
        }

        /**
         * Propagates all events in the queue.
         */
        public void doService()
            throws NestableException
        {
            for (Iterator i = queue.iterator(); i.hasNext();)
            {
                ToolEvent e = (ToolEvent)i.next();
                if (processToolEvent(e))
                {
                    i.remove();
                }
            }
        }

        /**
         * Calls previously registered methods corresponding to this
         * event and its arguments.
         *
         * @@return true iff the event should be removed from the
         * queue.
         */
        private boolean processToolEvent(ToolEvent event)
            throws NestableException
        {
            boolean clearEvent = true;
            String listener = event.getTool();
            String[] peerClassNames = event.getPeers();
            String[] keys = event.getKeys();        
            
            Class[] peerClasses = new Class[peerClassNames.length];
            Object[] objects = new Object[keys.length];
            Class[] objectClasses = new Class[keys.length];
            try
            {
                for (int i = 0; i < keys.length; i++)
                {
                    String keyString = keys[i];
                    ObjectKey key = new NumberKey(keyString);
                    
                    //Find the class of this object.
                    Class peerClass = Class.forName(peerClassNames[i]);
                    peerClasses[i] = peerClass;                    
                    
                    //Find the actual object.
                    try 
                    {
                        Method m = peerClass.getMethod
                            (PEER_KEY_METHOD, PEER_KEY_PARAMETERS);
                        ObjectKey[] arguments = {key};
                        objects[i] = (BaseObject)m.invoke(null, arguments);
                    }
                    catch (Exception ex)
                    {
                        //We were passed something that's not a Peer. 
                        //Just store the object itself.
                        objects[i] = key;
                    }
                    objectClasses[i] = objects[i].getClass();
                }
                
                MethodSignature sig = new MethodSignature(event.getName(), 
                                                          objectClasses);
                List methods = getMethodsMatching(listener, sig);
                for (Iterator i = methods.iterator(); i.hasNext();)
                {
                    Method m = (Method)i.next();
                    try
                    {
                        m.invoke(null, objects);
                    }
                    catch (InvocationTargetException e)
                    {                            
                        Throwable t = e.getTargetException();
                        if (t instanceof PropagationFailed)
                        {
                            //Only the singleton calls handleFailure,
                            //but every cluster member logs the
                            //failure after receiving the
                            //PropagationFailedEvent generated by
                            //handleFailure.
                            PropagationFailed pf = (PropagationFailed)t;
                            clearEvent = handleFailure(event, pf);
                        }
                        else
                        {
                            throw e;
                        }
                    }
                }
                if (clearEvent)
                {
                    CompletionEvent ce = new CompletionEvent(event);
                    ClusterService.getService().sendEvent
                        (ToolEventPropagationService.getService(), ce);
                }
            }
            catch (Exception ex)
            {
                throw new NestableException(ex);
            }
            return clearEvent;
        }           

        /**
         * Handles an event propagation failure. 
         *
         * @@return true iff the event that failed should be removed from
         * the queue.
         */
        private boolean handleFailure(ToolEvent event, PropagationFailed e)
            throws NestableException
        {
            boolean removeEvent = false;
            String newID = event.getID();            

            //If a broadcast event failed, we need to resend it to
            //only the tool that failed.
            String newListener = e.getListener();
            if (event.getTool().equals(ToolEvent.ALL_TOOLS))
            {
                ToolEvent newEvent = new ToolEvent
                    (event.getName(), newListener, event.getPeers(), 
                     event.getKeys());
                ToolEventPropagationService.getService().propagate(newEvent);
                removeEvent = true;
                newID = newEvent.getID();
            }

            //If no prior failure exists for this event, send out email
            //about it.
            List failure = (List)failures.get(event.getID());
            if (failure == null)
            {
                Log.error("PROPAGATION ERROR: " + newListener + '/' 
                          + event.getName() + ": " + e.getMessage());

                String subject = PROPAGATION_FAILURE_SUBJECT 
                    + newListener + '/' + event.getName();

                Context ctx = new VelocityContext();
                ctx.put("event", event);
                ctx.put("failure", e);
                try
                {
                    String body = TurbineVelocity.handleRequest
                        (ctx, PROPAGATION_FAILURE_TEMPLATE);
                    new HelmEmailer().messageUsersWithHostOwnership
                        (subject, body);
                }
                catch (Exception ex)
                {
                    Log.error("Unable to notify admins of error!", ex);
                }

            }

            //We need to broadcast news that the propagation failed.
            PropagationFailedEvent pfe = new PropagationFailedEvent(newID, e);
            ClusterService.getService().sendEvent
                (ToolEventPropagationService.getService(), pfe);

            return removeEvent;
        }
    }    
}
@


1.2
log
@Sorted out my and dlr's code.
@
text
@d73 3
d92 1
a92 1
 * @@version $Id: ToolEventPropagationService.java,v 1.1 2001/08/29 19:42:49 leonardr Exp $
d229 3
a231 2
        {
            for (Iterator i = conf.getKeys(LISTENER_PREFIX); i.hasNext();)
d233 6
a238 6
                String listenerProp = (String)i.next();
                String listenerName = listenerProp
                    .substring(LISTENER_PREFIX.length());
                String listenerClassName = conf.getString(listenerProp);
                Log.debug("Listener: " + listenerProp  + "/" + listenerClassName);
                registerClass(listenerName, Class.forName(listenerClassName));
d372 3
a374 3
        {
            Object z = i.next();
            String listener = (String)z;
@


1.1
log
@Moved integration-oriented code into new component package.
@
text
@d89 1
a89 1
 * @@version $Id: ToolEventPropagationService.java,v 1.10 2001/08/28 23:02:04 leonardr Exp $
d233 1
@

