org.yes.cart.cluster.node.impl.JGroupsNodeServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.yes.cart.cluster.node.impl.JGroupsNodeServiceImpl.java

Source

/*
 * Copyright 2009 Denys Pavlov, Igor Azarnyi
 *
 *    Licensed 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.yes.cart.cluster.node.impl;

import org.apache.commons.collections.CollectionUtils;
import org.jgroups.Address;
import org.jgroups.JChannel;
import org.jgroups.ReceiverAdapter;
import org.jgroups.View;
import org.jgroups.blocks.MessageDispatcher;
import org.jgroups.blocks.RequestHandler;
import org.jgroups.blocks.RequestOptions;
import org.jgroups.util.Rsp;
import org.jgroups.util.RspList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.web.context.ServletContextAware;
import org.yes.cart.cluster.node.*;
import org.yes.cart.constants.AttributeNamesKeys;
import org.yes.cart.service.domain.SystemService;

import javax.servlet.ServletContext;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;

/**
 * User: denispavlov
 * Date: 16/06/2015
 * Time: 16:34
 */
public class JGroupsNodeServiceImpl implements NodeService, ServletContextAware, DisposableBean {

    private Logger LOG;

    private final Map<String, String> configuration = new HashMap<String, String>();
    private Node node = new NodeImpl(true, "-", null, "DEFAULT", "YCCLUSTER", true);
    private final List<Node> cluster = new CopyOnWriteArrayList<Node>();
    private final Map<Address, String> clusterAddresses = new HashMap<Address, String>();

    private JChannel jChannel;
    private MessageDispatcher jChannelMessageDispatcher;
    private Map<String, List<MessageListener>> listeners = new HashMap<String, List<MessageListener>>();

    private final SystemService systemService;

    private String jgroupsConfiguration = "yc-jgroups-udp.xml";

    private final Executor executor;

    public JGroupsNodeServiceImpl(final SystemService systemService, final Executor executor) {
        this.systemService = systemService;
        this.executor = executor;
    }

    /** {@inheritDoc} */
    public String getCurrentNodeId() {
        return node.getNodeId();
    }

    /** {@inheritDoc} */
    public Map<String, String> getConfiguration() {
        final Map<String, String> all = new HashMap<String, String>();
        all.putAll(configuration);
        all.put(AttributeNamesKeys.System.IMPORT_JOB_LOG_SIZE,
                systemService.getAttributeValueOrDefault(AttributeNamesKeys.System.IMPORT_JOB_LOG_SIZE, "10000"));
        all.put(AttributeNamesKeys.System.IMPORT_JOB_TIMEOUT_MS,
                systemService.getAttributeValueOrDefault(AttributeNamesKeys.System.IMPORT_JOB_TIMEOUT_MS, "60000"));
        all.put(AttributeNamesKeys.System.SYSTEM_BACKDOOR_TIMEOUT_MS, systemService
                .getAttributeValueOrDefault(AttributeNamesKeys.System.SYSTEM_BACKDOOR_TIMEOUT_MS, "60000"));
        all.put(AttributeNamesKeys.System.SYSTEM_BACKDOOR_PRODUCT_BULK_INDEX_TIMEOUT_MS,
                systemService.getAttributeValueOrDefault(
                        AttributeNamesKeys.System.SYSTEM_BACKDOOR_PRODUCT_BULK_INDEX_TIMEOUT_MS, "60000"));
        all.put(AttributeNamesKeys.System.SYSTEM_BACKDOOR_PRODUCT_SINGLE_INDEX_TIMEOUT_MS,
                systemService.getAttributeValueOrDefault(
                        AttributeNamesKeys.System.SYSTEM_BACKDOOR_PRODUCT_SINGLE_INDEX_TIMEOUT_MS, "60000"));
        all.put(AttributeNamesKeys.System.SYSTEM_BACKDOOR_SQL_TIMEOUT_MS, systemService
                .getAttributeValueOrDefault(AttributeNamesKeys.System.SYSTEM_BACKDOOR_SQL_TIMEOUT_MS, "60000"));
        all.put(AttributeNamesKeys.System.SYSTEM_BACKDOOR_CACHE_TIMEOUT_MS, systemService
                .getAttributeValueOrDefault(AttributeNamesKeys.System.SYSTEM_BACKDOOR_CACHE_TIMEOUT_MS, "60000"));
        all.put(AttributeNamesKeys.System.SYSTEM_BACKDOOR_IMAGE_TIMEOUT_MS, systemService
                .getAttributeValueOrDefault(AttributeNamesKeys.System.SYSTEM_BACKDOOR_IMAGE_TIMEOUT_MS, "60000"));
        return all;
    }

    /** {@inheritDoc} */
    public List<Node> getCluster() {

        return Collections.unmodifiableList(this.cluster);

    }

    /** {@inheritDoc} */
    public Node getCurrentNode() {
        return node;
    }

    /** {@inheritDoc} */
    public Node getYumNode() {

        final List<Node> cluster = getCluster();
        for (final Node node : cluster) {
            if (node.isYum()) {
                return node;
            }
        }
        return null;
    }

    /** {@inheritDoc} */
    public List<Node> getYesNodes() {

        final List<Node> cluster = getCluster();
        final List<Node> yes = new ArrayList<Node>();
        for (final Node node : cluster) {
            if (!node.isYum()) {
                yes.add(node);
            }
        }
        return Collections.unmodifiableList(yes);
    }

    /** {@inheritDoc} */
    public List<Node> getOtherYesNodes() {

        final List<Node> cluster = getCluster();
        final List<Node> yes = new ArrayList<Node>();
        for (final Node node : cluster) {
            if (!node.isYum() && !node.isCurrent()) {
                yes.add(node);
            }
        }
        return Collections.unmodifiableList(yes);
    }

    /** {@inheritDoc} */
    public void broadcast(final Message message) {

        LOG.debug("Sending message: {}", message);

        if (message instanceof RspMessage) {

            RspMessage rsp = (RspMessage) message;

            try {

                RspList<Message> result = jChannelMessageDispatcher.castMessage(null,
                        new org.jgroups.Message(null, null, rsp), RequestOptions.SYNC());

                for (Rsp<Message> single : result.values()) {
                    if (single.wasReceived() && single.getException() == null) {
                        rsp.addResponse(single.getValue());
                    } else {
                        rsp.addResponse(new BasicMessageImpl(null, message.getSubject(), Message.LOST));
                    }
                }

            } catch (Exception e) {

                LOG.error("Error sending message: " + message, e);

            }

        } else {
            try {

                jChannel.send(null, message);

            } catch (Exception e) {

                LOG.error("Error sending message: " + message, e);

            }
        }
    }

    /** {@inheritDoc} */
    public void subscribe(final String subject, final MessageListener listener) {
        synchronized (this) {
            List<MessageListener> subjectListeners = listeners.get(subject);
            if (subjectListeners == null) {
                subjectListeners = new ArrayList<MessageListener>();
                listeners.put(subject, subjectListeners);
            }
            subjectListeners.add(listener);
            LOG.debug("Registering listener for topic {}", subject);
        }
    }

    /** {@inheritDoc} */
    public void setServletContext(final ServletContext servletContext) {

        initNodeFromServletContext(servletContext);
        startJGroupsChannel();

    }

    void startJGroupsChannel() {

        try {
            this.jChannel = new JChannel(this.jgroupsConfiguration);

            LOG.info("Initialised JChannel for {}", node.getId());

        } catch (Exception e) {
            throw new RuntimeException("Unable to create JChannel: " + e.getMessage(), e);
        }

        final Runnable onViewAccepted = new Runnable() {
            @Override
            public void run() {

                synchronized (cluster) {
                    try {

                        // Refresh view and add self first
                        cluster.clear();
                        clusterAddresses.clear();
                        cluster.add(node);
                        clusterAddresses.put(jChannel.getAddress(), node.getNodeId());
                        ((NodeImpl) node).setChannel(jChannel.getAddress().toString());

                        // Send HELLO
                        final Message intro = new BasicMessageImpl(node.getNodeId(), "HELLO", node);

                        RspList<Message> result = jChannelMessageDispatcher.castMessage(null,
                                new org.jgroups.Message(null, null, intro), RequestOptions.SYNC());

                        for (Rsp<Message> single : result.values()) {
                            if (!single.getSender().equals(jChannel.getAddress())) {
                                if (single.wasReceived() && single.getException() == null) {

                                    final Message rsp = single.getValue();
                                    final NodeImpl node = new NodeImpl(false, (Node) rsp.getPayload());
                                    node.setChannel(single.getSender().toString());
                                    cluster.add(node);
                                    clusterAddresses.put(single.getSender(), rsp.getSource());

                                    LOG.info("Found member {} at {}", rsp.getSource(), single.getSender());

                                }
                            }
                        }

                    } catch (Exception e) {
                        LOG.error("Sending HELLO to all members from: {}", node.getNodeId());
                        LOG.error(e.getMessage(), e);
                    }
                }
            }
        };

        final ReceiverAdapter receiverAdapter = new ReceiverAdapter() {
            public void receive(final org.jgroups.Message msg) {

                if (isMessageValid(msg)) {

                    final Message notification = (Message) msg.getObject();
                    final List<MessageListener> subjectListeners = listeners.get(notification.getSubject());
                    if (subjectListeners != null) {

                        for (final MessageListener listener : subjectListeners) {

                            listener.onMessageReceived(notification);

                        }

                    } else {
                        LOG.warn("No listeners for message: {}", msg.getObject());
                    }

                }

            }

            @Override
            public void viewAccepted(final View view) {

                LOG.info("View accepted: {}", view.getViewId());
                executor.execute(onViewAccepted);

            }
        };

        final RequestHandler requestHandler = new RequestHandler() {

            @Override
            public Object handle(final org.jgroups.Message msg) throws Exception {

                if (isMessageValid(msg)) {

                    final Message notification = (Message) msg.getObject();
                    final List<MessageListener> subjectListeners = listeners.get(notification.getSubject());
                    if (CollectionUtils.isNotEmpty(subjectListeners)) {

                        final List rsp = new ArrayList();
                        for (final MessageListener listener : subjectListeners) {

                            rsp.add(listener.onMessageReceived(notification));

                        }

                        if (rsp.size() > 1) {
                            return rsp; // if many listeners return list
                        } else if (rsp.size() == 1) {
                            return rsp.get(0); // otherwise return first
                        }

                    } else {
                        LOG.warn("No listeners for message: {}", msg.getObject());
                    }

                }

                return null;

            }

        };

        jChannelMessageDispatcher = new MessageDispatcher(jChannel, receiverAdapter, receiverAdapter,
                requestHandler);
        addDefaultListeners();

        try {

            jChannel.connect(node.getClusterId());

            LOG.info("Initialised JChannel");

        } catch (Exception e) {
            throw new RuntimeException(
                    "Unable to connect to JChannel '" + node.getClusterId() + "': " + e.getMessage(), e);
        }

    }

    boolean isMessageValid(final org.jgroups.Message msg) {

        if (msg.getObject() instanceof Message) {

            final Message notification = (Message) msg.getObject();

            if ((notification.getTargets() == null || notification.getTargets().contains(node.getNodeId()))
                    && (!notification.getSource().equals(node.getNodeId()))) {

                LOG.debug("Message received: {}", msg.getObject());

                return true;

            } else {

                LOG.debug("Message received but was not intended for this node: {}", msg.getObject());

            }

        } else {

            LOG.error("Message received was not of type org.yes.cart.cluster.node.Message: {}", msg.getObject());

        }

        return false;

    }

    void initNodeFromServletContext(final ServletContext servletContext) {

        final Enumeration parameters = servletContext.getInitParameterNames();
        while (parameters.hasMoreElements()) {

            final String key = String.valueOf(parameters.nextElement());
            final String value = servletContext.getInitParameter(key);

            configuration.put(key, value);

        }

        final String luceneDisabled = configuration.get(LUCENE_INDEX_DISABLED);
        final String luceneDisabledValue = luceneDisabled != null ? Boolean.valueOf(luceneDisabled).toString()
                : Boolean.FALSE.toString();
        configuration.put(LUCENE_INDEX_DISABLED, luceneDisabledValue);

        node = new NodeImpl(true, configuration.get(NODE_ID), configuration.get(NODE_TYPE),
                configuration.get(NODE_CONFIG), configuration.get(CLUSTER_ID),
                Boolean.valueOf(luceneDisabledValue));
        this.cluster.add(node);

        LOG = LoggerFactory.getLogger(node.getClusterId() + "." + node.getNodeId());

        if (LOG.isInfoEnabled()) {

            final StringBuilder stats = new StringBuilder();

            stats.append("\nLoading configuration for node...");
            stats.append("\nNode: ").append(node.getId());

            for (final Map.Entry<String, String> entry : configuration.entrySet()) {
                stats.append('\n').append(entry.getKey()).append(": ").append(entry.getValue());
            }

            LOG.info(stats.toString());
        }

    }

    void addDefaultListeners() {

        // Receive request for identification
        subscribe("HELLO", new MessageListener() {
            @Override
            public Serializable onMessageReceived(final Message message) {

                LOG.debug("Received HELLO from: {}", message.getSource());

                return new BasicMessageImpl(node.getNodeId(), Arrays.asList(message.getSource()), "HELLO", node);

            }
        });

        // Process bye request
        subscribe("BYE", new MessageListener() {
            @Override
            public Serializable onMessageReceived(final Message message) {

                LOG.debug("Received BYE from: {}", message.getSource());

                synchronized (cluster) {
                    final Node node = new NodeImpl(false, (Node) message.getPayload());
                    final Iterator<Node> clusterIt = cluster.iterator();
                    while (clusterIt.hasNext()) {
                        if (clusterIt.next().getNodeId().equals(node.getNodeId())) {
                            clusterIt.remove();
                            final Iterator<Map.Entry<Address, String>> addressesIt = clusterAddresses.entrySet()
                                    .iterator();
                            while (addressesIt.hasNext()) {
                                if (addressesIt.next().getValue().equals(node.getNodeId())) {
                                    addressesIt.remove();
                                    break;
                                }
                            }
                            break;
                        }
                    }
                }
                return null;
            }
        });

    }

    /**
     * Spring IoC setter.
     *
     * @param jgroupsConfiguration path to configuration file
     */
    public void setJgroupsConfiguration(final String jgroupsConfiguration) {
        this.jgroupsConfiguration = jgroupsConfiguration;
    }

    /**
     * {@inheritDoc}
     */
    public void destroy() throws Exception {

        LOG.info("Closing JGroups channel for node {}", node.getId());
        try {
            jChannel.send(null, new BasicMessageImpl(node.getNodeId(), "BYE", node));
            Thread.sleep(100L); // give some time for message to be sent
        } catch (Exception exp) {
            LOG.error("Could not send BYE message from node: {}", node.getId());
            LOG.error(exp.getMessage(), exp);
        }
        jChannel.close();

    }

}