Package org.jboss.mx.remote.discovery

Source Code of org.jboss.mx.remote.discovery.AbstractDetector$Publisher

/**
* @(#)$Id: AbstractDetector.java,v 1.18 2003/01/14 13:40:52 juhalindfors Exp $
*/
package org.jboss.mx.remote.discovery;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.io.InvalidClassException;

import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanServer;
import javax.management.ObjectInstance;
import javax.management.ObjectName;

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

import org.jboss.mx.logging.Logger;
import org.jboss.mx.logging.LoggerManager;
import org.jboss.mx.remote.*;
import org.jboss.mx.remote.connector.ConnectorFactory;
import org.jboss.mx.remote.connector.ConnectorMBean;
import org.jboss.mx.util.MBeanTyper;
import org.jboss.mx.util.ThreadPool;

/**
* AbstractDetector is an abstract DetectorMBean class for building Connector implementations
*
* @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
* @author <a href="mailto:telrod@e2technologies.net">Tom Elrod</a>
* */
public abstract class AbstractDetector extends RemoteServiceMBeanSupport
        implements DetectorMBean, ConnectorFactory.Listener
{
    protected final Logger log = LoggerManager.getLoggerManager().getLogger(getClass().getName());
    protected final SynchronizedLong attributeSequence = new SynchronizedLong(0);
    protected static final boolean logMethod = Boolean.getBoolean("jboss.mx.detector.debug");

    protected String type;
    protected long interval = 1500;
    protected String serverId;
    protected String instanceid;
    protected final Map connectors = Collections.synchronizedMap(new HashMap());
    protected long timeout = (interval + (interval / 2));
    protected long timeoutInterval = 2000;
    protected Timer timer;
    protected Pinger pinger;
    protected Publisher publisher;
    protected Detector detector;
    protected NotificationChannel notificationChannel;
    protected String detectionDomain = System.getProperty("jboss.mx.detector.domain","JBOSS");
    protected ThreadPool threadPool = new ThreadPool(false);

    //TODO -TME This is a hack so can only detect locally for testing.
    private boolean localOnly = Boolean.getBoolean("jboss.mx.detector.local");


    final class NotificationChannel extends Thread
    {
        boolean stopped = false;
        Channel channel = new LinkedQueue();

        private NotificationChannel()
        {
            super("Detector-NotificationChannel");
            threadPool.setMaximumSize(5);
            threadPool.setActive(true);
        }

        public void run()
        {
            while(stopped == false)
            {
                try
                {
                    Runnable r = (Runnable) channel.take();
                    if(r != null)
                    {
                        threadPool.run(r);
                    }
                }
                catch(InterruptedException e)
                {
                    break;
                }
            }
        }
    }

    /**
     * set the detection interval in milliseconds
     *
     * @param interval
     */
    public void setInterval(long interval)
    {
        long old = this.interval;
        this.interval = interval;
        fireAttributeChange("detector.interval.changed", "Interval", Long.TYPE, new Long(old), new Long(this.interval));
    }

    /**
     * get the detection interval in milliseconds
     *
     * @return interval
     */
    public long getInterval()
    {
        return interval;
    }

    /**
     * get the type of detector
     *
     * @return detector type
     */
    public String getDetectorType()
    {
        return type;
    }

    /**
     * return the domain that this detector is part
     *
     * @return
     */
    public String getDetectionDomain ()
    {
        return detectionDomain;
    }

    /**
     * set the domain for detection - detectors will only process notifications
     * from other detectors within the same domain
     *
     * @param domain
     */
    public void setDetectionDomain (String domain)
    {
        String oldVal = this.detectionDomain;
        this.detectionDomain = domain;
        fireAttributeChange("detector.domain.change","DetectionDomain",String.class,oldVal,detectionDomain);
    }

    /**
     * return the notification info
     *
     * @return notification metadata
     */
    public MBeanNotificationInfo[] getNotificationInfo()
    {
        MBeanNotificationInfo ni[] = new MBeanNotificationInfo[2];
        ni[0] = new MBeanNotificationInfo(new String[]{"detector.interval.changed", "detector.instanceid.changed", "detector.partition.changed"}, AttributeChangeNotification.class.getName(), "attribute changed notification");
        ni[1] = new MBeanNotificationInfo(new String[]{DetectionNotification.STARTUP, DetectionNotification.FAILURE}, DetectionNotification.class.getName(), "detection notification");
        return ni;
    }

    /**
     * fire a connector found notification to any listeners
     *
     * @param local
     * @param remote
     * @param serverId
     * @param transport
     * @param properties
     * @return notification instance
     */
    protected DetectionNotification fireConnectorFound(String detectionDomain, ObjectName local, ObjectName remote, String serverId, String instanceid, String transport, Map properties)
    {
        DetectionNotification msg = createDetectionStartupNotification(detectionDomain, local, remote, serverId, instanceid, transport, properties);
        putNotificationOnChannel(msg);
        return msg;
    }

    /**
     * create a detection startup notification object - allows subclasses to override or change values from the base
     * class
     *
     * @param local
     * @param remote
     * @param serverId
     * @param transport
     * @param properties
     * @return detection startup notification
     */
    protected DetectionNotification createDetectionStartupNotification(String detectionDomain, ObjectName local, ObjectName remote, String serverId, String instanceid, String transport, Map properties)
    {
        DetectionNotification msg = new DetectionNotification(detectionDomain, remote, serverId, instanceid, DetectionNotification.STARTUP, "Detected a new Connector with transport: " + transport + " at: " + serverId);
        msg.setRemoteName(remote.getCanonicalName());
        msg.setLocalName(local.getCanonicalName());
        msg.setTransport(transport);
        msg.setProperties(properties);
        return msg;
    }

    /**
     * create a detection failure notification object - allows subclasses to override or change values from the base
     * class
     *
     * @param local
     * @param remote
     * @param serverId
     * @param transport
     * @param properties
     * @return detection failure notification
     */
    protected DetectionNotification createDetectionFailureNotification(String detectionDomain, ObjectName local, ObjectName remote, String serverId, String instanceid, String transport, Map properties)
    {
        DetectionNotification msg = new DetectionNotification(detectionDomain, remote, serverId, instanceid, DetectionNotification.FAILURE, "Detected a lost Connector with transport: " + transport + " at: " + serverId);
        msg.setRemoteName(remote.getCanonicalName());
        msg.setLocalName(local.getCanonicalName());
        msg.setTransport(transport);
        msg.setProperties(properties);
        return msg;
    }

    /**
     * fire a connector lost notification to any listeners
     *
     * @param local
     * @param remote
     * @param serverId
     * @param transport
     * @param properties
     * @return notification instance
     */
    protected DetectionNotification fireConnectorLost(String detectionDomain, ObjectName local, ObjectName remote, String serverId, String instanceid, String transport, Map properties)
    {
        final DetectionNotification msg = createDetectionFailureNotification(detectionDomain, local, remote, serverId, instanceid, transport, properties);
        putNotificationOnChannel(msg);
        return msg;
    }

    protected void putNotificationOnChannel(final Notification n)
    {
        if(notificationChannel != null)
        {
            try
            {
                notificationChannel.channel.put(new Runnable()
                {
                    public void run()
                    {
                        sendNotification(n);
                    }
                });
            }
            catch(InterruptedException e)
            {
                log.warn("Error sending notification: " + n, e);
            }
        }
    }


    /**
     * helper method for firing an attribute change event
     *
     * @param event
     * @param attribute
     * @param type
     * @param oldVal
     * @param newVal
     */
    protected void fireAttributeChange(String event, String attribute, Class type, Object oldVal, Object newVal)
    {
        if((oldVal == null && newVal != null) ||
                (oldVal != null && newVal == null) ||
                (oldVal.equals(newVal) == false))
        {
            sendNotification(
                    new AttributeChangeNotification(
                            this,
                            attributeSequence.increment(),
                            System.currentTimeMillis(),
                            event,
                            attribute,
                            type.getName(),
                            oldVal,
                            newVal));
        }
    }

    /**
     * Sub-classes should override this method to provide
     * custum 'create' logic.
     *
     * <p>This method is empty, and is provided for convenience
     *    when concrete service classes do not need to perform
     *    anything specific for this state change.
     */
    protected void createService() throws Exception
    {
        super.createService();
        // get the instance id
        this.instanceid = InstanceID.getID(getServer());
    }

    /**
     * return the instance ID of the Detector MBeanServer
     *
     * @return instance id
     */
    public String getInstanceID()
    {
        return instanceid;
    }

    protected MBeanServer getServer()
    {
        return super.getServer();
    }

    /**
     * inner class for book-keeping of connectors
     */
    protected class Connector
    {
        private String key;
        public Map properties;
        public long lastDetection;
        public String serverId;
        public String instanceId;
        public String type;
        public ObjectName remotename;
        public ObjectName localname;
        public String transport;
        public MBeanServer server;
        public String domain;

        public String toString()
        {
            return "Connector [serverId=" + serverId + ",transport=" + transport + ",instanceId=" + instanceId + ",lastDetection=" + new Date(lastDetection) + "]";
        }
    }

    /**
     * remove the connector
     *
     * @param c
     */
    protected void remove(Connector c)
    {
        if(log.isDebugEnabled())
        {
            log.debug("removing connector = " + c);
        }
        fireConnectorLost(c.domain, c.localname, c.remotename, c.serverId, c.instanceId, c.transport, c.properties);

        try
        {
            // clean up the connector
            ConnectorFactory.destroyConnector(c.serverId);
        }
        catch(Exception ex)
        {
            // don't handle since the remote side may be dead -- and that's ok
        }
        connectors.remove(c.key);
        c = null;
    }

    /**
     * remove all
     */
    protected void removeAllConnectors()
    {
        // remove all connectors
        synchronized(connectors)
        {
            Iterator i = connectors.values().iterator();
            while(i.hasNext())
            {
                Connector c = (Connector) i.next();
                i.remove();
                remove(c);
                c = null;
            }
        }
    }

    /**
     * Sub-classes should override this method to provide
     * custum 'stop' logic.
     *
     * <p>This method is empty, and is provided for convenience
     *    when concrete service classes do not need to perform
     *    anything specific for this state change.
     */
    protected void stopService() throws Exception
    {
        super.stopService();
        ConnectorFactory.removeListener(this);
        stopTimer();
        stopPublisher();
        stopDetector();
        stopNotificationChannel();
        removeAllConnectors();
    }

    /**
     * Sub-classes should override this method to provide
     * custum 'start' logic.
     *
     * <p>This method is empty, and is provided for convenience
     *    when concrete service classes do not need to perform
     *    anything specific for this state change.
     */
    protected void startService() throws Exception
    {
        super.startService();
        serverId = JMXUtil.getServerId(getServer());
        if(log.isDebugEnabled())
        {
            log.debug(getName() + "- serverid = " + serverId);
        }
        ConnectorFactory.addListener(this);
        startNotificationChannel();
        startPublisher();
        startDetector();
        startTimer();
    }

    /**
     * start the notification channel
     */
    protected void startNotificationChannel()
    {
        notificationChannel = new NotificationChannel();
        notificationChannel.start();
    }

    /**
     * stop the notification channel
     */
    protected void stopNotificationChannel()
    {
        if(notificationChannel != null)
        {
            notificationChannel.stopped = true;
            try
            {
                notificationChannel.interrupt();
            }
            catch(Exception e)
            {

            }
            notificationChannel = null;
        }
    }

    /**
     * start the pinger timer
     *
     */
    protected void startTimer()
    {
        timer = new Timer(true);
        timer.scheduleAtFixedRate(new Pinger(), timeoutInterval * 2, timeoutInterval);
    }

    /**
     * stop the pinger timer
     *
     */
    protected void stopTimer()
    {
        if(timer != null)
        {
            timer.cancel();
            timer = null;
        }
    }

    protected class Pinger extends TimerTask
    {
        public void run()
        {
            List list = null;
            // make a copy so we don't block others...
            synchronized(connectors)
            {
                list = new ArrayList(connectors.values());
            }
            Iterator i = list.iterator();
            long now = System.currentTimeMillis();
            while(i.hasNext())
            {
                Connector c = (Connector) i.next();
                if(now - c.lastDetection >= timeout)
                {
                    if(logMethod && log.isDebugEnabled())
                    {
                        log.debug("Pinging connector: " + c);
                    }
                    // we haven't received a ping in more than timeout
                    // double check that we're still alive
                    boolean remove = true;
                    for(int x = 0; x < 3; x++)
                    {
                        try
                        {
                            Integer state = (Integer) c.server.getAttribute(c.remotename, "State");
                            if(logMethod && log.isDebugEnabled())
                            {
                                log.debug("State of Connector [" + c + "] is=" + state);
                            }
                            if(state != null && state.intValue() == STARTED)
                            {
                                c.lastDetection = System.currentTimeMillis();
                                remove = false;
                                break;
                            }
                            else
                            {
                                // pause just a split msec...
                                Thread.currentThread().sleep(50);
                            }
                        }
                        catch(Throwable ex)
                        {
                            break;
                        }
                    }
                    if(remove)
                    {
                        if(logMethod && log.isDebugEnabled())
                        {
                            log.debug("Ping failed - removing Connector: " + c);
                        }
                        remove(c);
                        connectors.remove(c.key);
                        i.remove();
                        c = null;
                    }
                }
            }
        }
    }

    /**
     * start the publisher thread
     *
     * @throws Exception
     */
    protected void startPublisher()
            throws Exception
    {
        publisher = new Publisher();
        publisher.start();
    }

    /**
     * stop the publisher thread
     *
     * @throws Exception
     */
    protected void stopPublisher()
            throws Exception
    {
        if(publisher != null)
        {
            publisher.running = false;
            publisher.interrupt();
            publisher = null;
        }
    }

    /**
     * subclass should override to publish in the protocol transport specify way
     *
     * @param notification
     * @throws Exception
     */
    protected abstract void publish(DetectionNotification notification)
            throws Exception;

    /**
     * inner class to publish our local transports
     */
    private final class Publisher extends Thread
    {
        final InetAddress addr;
        final String serverid;
        boolean running = true;

        Publisher()
                throws Exception
        {
            setName("Detector-Publisher");
            setPriority(Thread.NORM_PRIORITY - 1);
            setDaemon(false);
            addr = InetAddress.getLocalHost();
            MBeanServer server = getServer();
            serverid = JMXUtil.getServerId(server);

        }

        public void run()
        {
            while(running)
            {
                String transports[] = ConnectorFactory.getConnectorTransports();
                try
                {
                    for(int c = 0; c < transports.length; c++)
                    {
                        ObjectName query = new ObjectName("jmx.remoting:type=Connector,transport=" + transports[c] + ",*");
                        /*if (log.isDebugEnabled())
                        {
                            log.debug("query => "+query);
                        } */
                        Set beans = JMXUtil.queryLocalMBeans(getServer(), query, null);
                        Iterator i = beans.iterator();
                        while(i.hasNext())
                        {
                            ObjectInstance oi = (ObjectInstance) i.next();
                            try
                            {
                                Integer state = (Integer) getServer().getAttribute(oi.getObjectName(), "State");
                                if(state.intValue() != ConnectorMBean.STARTED)
                                {
                                    if(log.isDebugEnabled())
                                    {
                                        log.debug("Connector: " + oi.getObjectName() + " is not started - ignoring publish");
                                    }
                                    continue;
                                }
                            }
                            catch(Exception ex)
                            {
                                continue;
                            }
                            ConnectorMBean mbean = (ConnectorMBean) MBeanTyper.typeMBean(getServer(), oi.getObjectName(), ConnectorMBean.class);
                            String type = mbean.getTransportType();
                            Map props = mbean.getTransportProperties();
                            ObjectName co = oi.getObjectName();
                            // convert object name in to a suitable JMXRemoting objectname
                            ObjectName on = JMXRemotingObjectName.create(serverid, addr, co, co.getKeyProperty("name"), instanceid);
                            DetectionNotification msg = createDetectionStartupNotification(detectionDomain, co, on, serverId, instanceid, type, props);
                            if(logMethod && log.isDebugEnabled())
                            {
                                log.debug(">> publishing connector detection msg: " + msg);
                            }
                            publish(msg);
                        }
                    }
                }
                catch(Exception ex)
                {
                    log.warn("Error sending detection message", ex);
                }
                finally
                {
                    try
                    {
                        sleep(interval);
                    }
                    catch(InterruptedException ex)
                    {
                        break;
                    }
                }
            }
        }
    }

    /**
     * make a connector key for the map
     *
     * @param msg
     * @return key
     */
    protected String makeKey(DetectionNotification msg)
    {
        return msg.getServerId() + ":" + msg.getTransport();
    }

    /**
     * receive the detection message in some protocol or transport specific way
     *
     * @return detection notification
     */
    protected abstract DetectionNotification receiveDetection()
            throws Exception;

    /**
     * //TODO -TME This is actually a short term hack to be able to avoid detection of other remote
     * servers while testing.  Need to come back and add a real, more abstract, way to do this (such
     * as being able to add detection strategy).
     * @param localOnly
     */
    public void setDetectLocalOnly(boolean localOnly)
    {
        this.localOnly = localOnly;
    }
    /**
     * Determins if detection message should continue to be processed base on some
     * kind of preference or strategy (such as if running on local machine).
     * @param msg
     * @return
     */
    protected boolean shouldProcessStartup(DetectionNotification msg)
    {
        if(localOnly)
        {
            try
            {
                String localIp = InetAddress.getLocalHost().getHostAddress();
                String msgIp = msg.getIPAddress().getHostAddress();

                if(!localIp.equals(msgIp))
                {
                    return false;
                }
            }
            catch(UnknownHostException e)
            {
                log.warn("can not get local host ip");
            }
        }
        return detectionDomain.equals(msg.getDetectionDomain());
    }

    protected void register (String key, DetectionNotification msg, Connector connector)
    {
        try
        {

            // this are opposites since our local name used is actually the
            // remote name to register locally, and vise versa
            MBeanServerConnection conn = new MBeanServerConnection(msg);
            connector.server = ConnectorFactory.createConnector(conn);
            if(logMethod && log.isDebugEnabled())
            {
                log.debug("*** Registered Connector to remote server id: " + connector.serverId);
            }
        }
        catch(Throwable ex)
        {
            if(logMethod && log.isDebugEnabled())
            {
                ex.printStackTrace();
            }
            if(ex instanceof java.net.ConnectException)
            {
                // we failed to connect to remote connector, discard
            }
            else
            {
                log.error("Couldn't create a Connector to server: " + connector.serverId + ", transport: " + connector.transport + ", localname: " + connector.localname + ", remotename: " + connector.remotename, ex);
            }
            if(connector.server != null)
            {
                try
                {
                    ConnectorFactory.destroyConnector(connector.serverId);
                }
                catch(Exception ex1)
                {
                    //
                }
            }
            connectors.remove(key);
            return;
        }

        if(log.isDebugEnabled())
        {
            log.debug("<< FOUND! Detected new Connector ... " + msg);
        }
        // fire detection notification
        fireConnectorFound(connector.domain, connector.localname, connector.remotename, msg.getServerId(), msg.getInstanceId(), msg.getTransport(), msg.getProperties());

    }
    protected synchronized void process(final DetectionNotification msg)
    {
        if(msg == null)
        {
            return;
        }

        if(msg.getServerId().equals(serverId))
        {
            // ignore detections from ourself
            return;
        }
        // process the detection message
        String key = makeKey(msg);
        Connector connector = (Connector) connectors.get(key);
        if(connector != null && msg.getInstanceId() != null && connector.instanceId.equals(msg.getInstanceId()) == false)
        {
            // we have detected a notification that indicates the instance id has changed for the same serverid
            remove(connector);
            // null which will force it to reload
            connector = null;
        }
        if(connector != null && msg.getProperties() != null && msg.getProperties().equals(connector.properties) == false && msg.getType().equals(DetectionNotification.STARTUP))
        {
            // we have dedicated a connector
            remove(connector);
            connector = null;
        }
        //log.debug("key="+key+", found="+connector);
        if(connector == null && msg.getType().equals(DetectionNotification.STARTUP))
        {
            if(shouldProcessStartup(msg))
            {
                if(ConnectorFactory.hasMBeanServerForServerId(msg.getServerId()))
                {
                    // ignore if we have it already registered
                    return;
                }
                // found a new connector
                connector = new Connector();
                connector.properties = msg.getProperties();
                connector.serverId = msg.getServerId();
                connector.transport = msg.getTransport();
                connector.instanceId = msg.getInstanceId();
                connector.key = key;
                connector.lastDetection = System.currentTimeMillis();
                try
                {
                    connector.localname = new ObjectName(msg.getRemoteName());
                    connector.remotename = new ObjectName(msg.getLocalName());
                    connector.domain = msg.getDetectionDomain();
                }
                catch (Exception ex)
                {
                    // this should never happen
                    throw new RuntimeException(ex);
                }
                if(connector.instanceId == null)
                {
                    connector.instanceId = connector.serverId;
                }
                final Connector _conn = connector;
                final String _key = key;
                connectors.put(key, connector);
                threadPool.run(
                    new Runnable()
                    {
                        public void run ()
                        {
                            register(_key,msg,_conn);
                        }
                    }
                );
                return;
            }
        }
        if(connector != null)
        {
            connector.lastDetection = System.currentTimeMillis();
        }
        else
            if(msg.getType().equals(DetectionNotification.FAILURE) && connector != null)
            {
                // destroy
                try
                {
                    ConnectorFactory.destroyConnector(connector.serverId);
                }
                catch(Exception ex)
                {
                    if(log.isDebugEnabled())
                    {
                        log.debug("Error destroying connector on remote failure - " + connector.serverId, ex);
                    }
                }
                connectors.remove(key);
                if(connector.server != null)
                {
                    try
                    {
                        ConnectorFactory.destroyConnector(connector.serverId);
                    }
                    catch(Exception ex1)
                    {
                        //
                    }
                }
                fireConnectorLost(connector.domain,connector.localname, connector.remotename, msg.getServerId(), msg.getInstanceId(), msg.getTransport(), msg.getProperties());
            }
    }

    /**
     * start the detector thread
     *
     */
    protected void startDetector()
    {
        detector = new Detector();
        detector.start();
    }

    /**
     * stop the detector thread
     *
     */
    protected void stopDetector()
    {
        if(detector != null)
        {
            detector.running = false;
            detector.interrupt();
            detector = null;
        }
    }

    /**
     * event is fired when a connector is created
     *
     * @param server
     * @param conn
     */
    public void connectorCreated(MBeanServer server, MBeanServerConnection conn)
    {
        // NO OP - will catch in publish
    }

    /**
     * event is fired when a connector is destroyed
     *
     * @param server
     * @param conn
     */
    public void connectorDestroyed(MBeanServer server, MBeanServerConnection conn)
    {
        List list = null;
        // make a copy so we don't block others
        synchronized(connectors)
        {
            list = new ArrayList(connectors.values());
        }
        Iterator iter = list.iterator();
        while(iter.hasNext())
        {
            Connector con = (Connector) iter.next();
            // look for connector
            // ideally, we need to also include instanceid
            if(con.serverId.equals(serverId) && con.properties.equals(conn.getProperties()) && conn.getTransport().equals(con.transport))
            {
                // remove by key (this is a copy, so move from the source)
                Object obj = connectors.remove(con.key);
                if(obj != null)
                {
                    // someone removed connector before we detected it gone, fire lost notification
                    fireConnectorLost(con.domain, con.localname, con.remotename, con.serverId, con.instanceId, con.transport, con.properties);
                }
                obj = null;
                break;
            }
        }
        list = null;
        iter = null;
    }

    /**
     * class that will do the detection
     */
    protected class Detector extends Thread
    {
        boolean running = true;

        Detector()
        {
            setName("Detector");
            setPriority(Thread.NORM_PRIORITY - 1);
            setDaemon(false);
        }

        public void run()
        {
            while(running)
            {
                try
                {
                    DetectionNotification msg = receiveDetection();
                    process(msg);
                }
                catch (InvalidClassException ice)
                {
                    // our detection notification fails because a remote server
                    // has a different version of our detection class
                    log.warn("Received and out-of-date Detection Notification from remote detector");
                }
                catch(Exception ex)
                {
                    log.warn("Error receiving detection", ex);
                }
            }
        }
    }

}
TOP

Related Classes of org.jboss.mx.remote.discovery.AbstractDetector$Publisher

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.