/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.test.mx.remoting.pingpong;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import javax.management.AttributeChangeNotification;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;

import org.jboss.logging.Logger;
import org.jboss.mx.remoting.MBeanLocator;
import org.jboss.mx.remoting.tracker.MBeanTracker;
import org.jboss.mx.remoting.tracker.MBeanTrackerAction;
import org.jboss.mx.util.JBossNotificationBroadcasterSupport;
import org.jboss.remoting.ConnectionFailedException;
import org.jboss.remoting.ident.Identity;
import org.jboss.util.id.GUID;

import EDU.oswego.cs.dl.util.concurrent.SynchronizedInt;

/**
 * PingPong is a simple test mbean that will call ping on other peers
 * in the JBoss remoting network and will fire a notification and
 * an attribute change notification when ping is invoked back to listeners.
 *
 * @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
 * @version $Revision: 1.1.2.4 $
 */
public class PingPong implements PingPongMBean, MBeanTrackerAction, MBeanRegistration
{
    private static final transient Logger log = Logger.getLogger(PingPong.class.getName());
    private MBeanTracker tracker;
    private Timer timer=new Timer(false);
    private String guid=GUID.asString();
    private Map friends=new HashMap();
    private MBeanServer server;
    private ObjectName objectName;
    private SynchronizedInt counter=new SynchronizedInt(0);
    private Map counters=new HashMap();
    private JBossNotificationBroadcasterSupport broadcaster=new JBossNotificationBroadcasterSupport();

    public synchronized Object ping (Object pong)
    {
        Object values[]=(Object[])pong;
        String uid=(String)values[0];
        Integer count=(Integer)values[1];
        Integer last = (Integer)counters.get(uid);
        counters.put(uid,count);
        if (last!=null && last.intValue()+1!=count.intValue())
        {
            log.error("Received a counter mismatch from: "+uid+", I expected: "+last.intValue()+1+", I received: "+count);
        }
        Notification notification = new Notification("pong",objectName,System.currentTimeMillis());
        broadcaster.sendNotification(notification);
        if (log.isDebugEnabled())
        {
            log.debug("ping called: "+pong+", sending notification: "+notification+" for objectName: "+objectName);
        }
        Notification stateChange = new AttributeChangeNotification(objectName,System.currentTimeMillis(),System.currentTimeMillis(),"State Changed","State",Integer.class.getName(),new Integer(1),new Integer(2));
        broadcaster.sendNotification(stateChange);
        return count;
    }

    /**
     * Add a listener to an MBean.
     *
     * @param   listener    implementation of the listener object
     * @param   filter      implementation of the filter object or <tt>null</tt>
     *                      if no filtering is required
     * @param   handback    A handback object associated with each notification
     *                      sent by this notification broadcaster.
     *
     * @throws  IllegalArgumentException if listener is <tt>null</tt>
     */
    public void addNotificationListener ( NotificationListener listener,
                                          NotificationFilter filter,
                                          Object handback )
            throws IllegalArgumentException
    {
        if (log.isDebugEnabled())
        {
            log.debug("addNotificationListener - listener: "+listener);
        }
        broadcaster.addNotificationListener(listener,filter,handback);
    }

    /**
     * Removes a listener from an MBean.
     *
     * @param   listener the listener object to remove
     *
     * @throws ListenerNotFoundException if the listener was not found
     */
    public void removeNotificationListener ( NotificationListener listener )
            throws ListenerNotFoundException
    {
        if (log.isDebugEnabled())
        {
            log.debug("removeNotificationListener - listener: "+listener);
        }
        broadcaster.removeNotificationListener(listener);
    }

    /**
     * Returns the notification metadata associated with the MBean.
     *
     * @see  MBeanNotificationInfo
     *
     * @return  MBean's notification metadata
     */
    public MBeanNotificationInfo[] getNotificationInfo ()
    {
        return new MBeanNotificationInfo[0];
    }

    /**
     * This method is called by the MBeanServer after deregistration takes
     * place.
     */
    public void postDeregister ()
    {
    }

    /**
     * This method is called by the MBeanServer after registration takes
     * place or when registration fails.
     *
     * @param registrationDone the MBeanServer passes true when the
     * MBean was registered, false otherwise.
     */
    public void postRegister (Boolean registrationDone)
    {
    }

    /**
     * This method is called by the MBeanServer before deregistration takes
     * place.<p>
     *
     * The MBean can throw an exception, this will stop the deregistration.
     * The exception is forwarded to the invoker wrapped in
     * an MBeanRegistrationException.
     */
    public void preDeregister ()
            throws Exception
    {
        stop();
    }

    /**
     * This method is called by the MBeanServer before registration takes
     * place. The MBean is passed a reference of the MBeanServer it is
     * about to be registered with. The MBean must return the ObjectName it
     * will be registered with. The MBeanServer can pass a suggested object
     * depending upon how the MBean is registered.<p>
     *
     * The MBean can stop the registration by throwing an exception.The
     * exception is forwarded to the invoker wrapped in an
     * MBeanRegistrationException.
     *
     * @param MBeanServer the MBeanServer the MBean is about to be
     * registered with.
     * @param ObjectName the suggested ObjectName supplied by the
     * MBeanServer.
     * @return the actual ObjectName to register this MBean with.
     * @exception Exception for any error, the MBean is not registered.
     */
    public ObjectName preRegister (MBeanServer server, ObjectName name)
            throws Exception
    {
        this.server = server;
        this.objectName = name;
        start();
        return name;
    }


    /**
     * Describe <code>start</code> method here.
     *
     * @jmx.managed-operation description="Second lifecycle method called after mbeans attributes are set.  During this method declared mbean dependencies are available and may be used.  After completion the mbean should be completely usable."
     *                        impact="ACTION"
     */
    public void start () throws Exception
    {
        tracker = new MBeanTracker(server,new Class[]{PingPongMBean.class},null,false,null,true,this);
        timer.scheduleAtFixedRate(new Pinger(),5000L,5000L);
    }

    /**
     * Describe <code>stop</code> method here.
     *
     * @jmx.managed-operation description="First shutdown lifecycle method.  This method should undo the effects of start"
     *                        impact="ACTION"
     */
    public void stop ()
    {
        if (tracker!=null)
        {
            tracker.destroy();
            tracker=null;
        }
    }

    /**
     * called when a mbean notification is fired
     *
     * @param locator
     * @param notification
     * @param handback
     */
    public void mbeanNotification (MBeanLocator locator, Notification notification, Object handback)
    {
        log.info("received notification from: "+locator+", notification: "+notification);
    }

    /**
     * called when an MBean is registered with the MBeanServer
     *
     * @param locator
     */
    public void mbeanRegistered (MBeanLocator locator)
    {
        if (Identity.get(server).equals(locator.getIdentity()))
        {
            log.info("I'm going to just ignore myself: "+locator);
            // ignore myself
            return;
        }
        log.info("found a new friend to play ping pong with: "+locator);
        PingPongMBean friend=null;
        try
        {
            friend=(PingPongMBean)locator.narrow(PingPongMBean.class);
        }
        catch (Exception e)
        {
            log.error("error casting my friend to PingPongMBean - his locator is: "+locator,e);
            return;
        }
        synchronized(friends)
        {
            friends.put(locator,friend);
        }
    }

    /**
     * called when the mbean state changes.  Note: this method will only be called on MBeans that have a
     * <tt>State</tt> attribute and where state change attribute notifications are fired
     *
     * @param locator
     * @param oldState
     * @param newState
     */
    public void mbeanStateChanged (MBeanLocator locator, int oldState, int newState)
    {
        log.info("one of my partners ("+locator+") changed its state from: "+oldState+" to: "+newState);
    }

    /**
     * called when an MBean is unregistered with the MBeanServer
     *
     * @param locator
     */
    public void mbeanUnregistered (MBeanLocator locator)
    {
        log.info("I lost a friend, "+locator);
        friends.remove(locator);
    }

    final class Pinger extends TimerTask
    {
        /**
         * The action to be performed by this timer task.
         */
        public void run ()
        {
            Map copy = null;
            synchronized (friends)
            {
                copy = new HashMap(friends);
            }
            if (copy.isEmpty())
            {
                log.info("I don't have any friends on the network, how boring...");
                return;
            }
            Iterator iter = copy.keySet().iterator();

            Integer value = new Integer(counter.increment());
            while(iter.hasNext())
            {
                MBeanLocator l=(MBeanLocator)iter.next();
                try
                {
                    Object v=new Object[]{guid,value};
                    Object obj = l.getServerLocator().getMBeanServer().invoke(l.getObjectName(),"ping",new Object[]{v},new String[]{Object.class.getName()});
                    //PingPongMBean friend=(PingPongMBean)copy.get(l);
                    log.info("pinging my friend at: "+l+", with: "+value+" and he returned me back: "+obj);
                    // send my friend a ping
                    //Object obj = friend.ping(myhash);
                    if (!obj.equals(value))
                    {
                        log.warn("My friend passed me back something I don't understand?! I passed him: "+value+", I received: "+obj);
                    }
                }
                catch (Throwable ex)
                {
                    if (ex instanceof MBeanException)
                    {
                        MBeanException mbe=(MBeanException)ex;
                        if (mbe.getTargetException() instanceof ConnectionFailedException)
                        {
                            log.info("my friend died during a ping, "+l);
                            return;
                        }
                    }
                    log.warn("My friend doesn't like me, he gave me an exception back",ex);
                }
            }
            copy=null;
        }
    }
}

