org.cyberjos.jcconf2014.node.HazelcastHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.cyberjos.jcconf2014.node.HazelcastHelper.java

Source

/*
 * @(#)HazelcastHelper.java 2014/09/01
 *
 * Copyright (c) 2014 Joseph S. Kuo
 * All Rights Reserved.
 *
 * --LICENSE NOTICE--
 * 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.
 * --LICENSE NOTICE--
 */
package org.cyberjos.jcconf2014.node;

import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.locks.Lock;

import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.hazelcast.config.ClasspathXmlConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IAtomicLong;
import com.hazelcast.core.IAtomicReference;
import com.hazelcast.core.IMap;
import com.hazelcast.core.IQueue;
import com.hazelcast.core.ITopic;

/**
 * Hazelcast helper.
 *
 * @author Joseph S. Kuo
 * @since 0.0., 2014/09/01
 */
@Component
public class HazelcastHelper {
    /**
     * The key to access all active nodes.
     */
    private static final String ACTIVE_NODES = "ACTIVE_NODES";

    /**
     * The key to access master node.
     */
    private static final String MASTER_NODE = "MASTER_NODE";

    /**
     * The key to access task queue.
     */
    private static final String TASK_QUEUE = "TASK_QUEUE";

    /**
     * The key to access task serial number.
     */
    private static final String TASK_NUMBER = "TASK_NUMBER";

    /**
     * The logger.
     */
    static final Logger logger = LoggerFactory.getLogger(HazelcastHelper.class);

    /**
     * The working mode.
     */
    private static final boolean WORKING_MODE = true;

    /**
     * The {@code HazelcastInstance} holder is a singleton enumeration for the
     * usage of Hazelcast node.
     *
     * @author Joseph S. Kuo
     * @since 0.0., 2014/09/28
     */
    private enum Holder {
        /**
         * The enumeration for singleton instance.
         */
        INSTANCE;

        /**
         * The singleton instance
         */
        private final HazelcastInstance hazelcastInstance;

        /**
         * Default constructor.
         */
        private Holder() {
            this.hazelcastInstance = Hazelcast.newHazelcastInstance(new ClasspathXmlConfig("hazelcast.xml"));
            logger.info("Hazelcast instance has been launched, member ID: {}", this.getMemberId());
        }

        /**
         * Returns the singleton instance of {@code HazelcastInstance}.
         *
         * @return the singleton instance of {@code HazelcastInstance}
         */
        public HazelcastInstance getInstance() {
            return this.hazelcastInstance;
        }

        /**
         * Returns the member ID.
         *
         * @return the member ID
         */
        public String getMemberId() {
            return this.hazelcastInstance.getCluster().getLocalMember().getUuid();
        }
    }

    /**
     * Registers the given node to the active node set.
     *
     * @param cloudNode the node to be registered
     * @throws NullPointerException if the given node is {@code null}
     */
    public void registerNode(final CloudNode cloudNode) {
        Objects.requireNonNull(cloudNode, "The given cloud node must not be null.");

        final String nodeName = cloudNode.getName();
        final NodeRecord record = new NodeRecord(nodeName, Holder.INSTANCE.getMemberId());
        HazelcastHelper.getMap(ACTIVE_NODES).put(nodeName, record);
        HazelcastHelper.<NodeMessage>getTopic(nodeName).addMessageListener(cloudNode);
        Holder.INSTANCE.getInstance().getCluster().addMembershipListener(cloudNode);
        logger.info("The given node registered: {}", record);

        final Optional<NodeRecord> optional = this.getMasterNodeRecord();
        if (!optional.isPresent()) {
            this.setMaster(cloudNode);
            return;
        }

        logger.info("Found the master node: {}", optional.get());

        if (WORKING_MODE) {
            final Thread thread = new Thread(this.createConsumer(cloudNode));
            thread.start();
        }
    }

    /**
     * Removes the node with the given name from the active node set.
     *
     * @param nodeName the node to be removed
     * @throws NullPointerException if the given node name is {@code null}
     */
    public void unregisterNode(final String nodeName) {
        Objects.requireNonNull(nodeName, "The given node name must not be null.");

        final NodeRecord record = HazelcastHelper.<String, NodeRecord>getMap(ACTIVE_NODES).remove(nodeName);
        HazelcastHelper.getTopic(nodeName).destroy();
        //        Holder.INSTANCE.getInstance().getCluster().removeMembershipListener(nodeName);
        logger.info("The given node is un-registered: {}", record);
    }

    /**
     * Sets the given node to be the master node.
     *
     * @param cloudNode the node to become master
     * @return {@code true} if the given node becomes the master node
     *         successfully
     * @throws NullPointerException if the given node is {@code null}
     */
    public synchronized boolean setMaster(final CloudNode cloudNode) {
        Objects.requireNonNull(cloudNode, "The given cloud node must not be null.");

        final Lock lock = Holder.INSTANCE.getInstance().getLock("my-distributed-lock");
        lock.lock();

        try {
            final NodeRecord masterRecord = HazelcastHelper.<NodeRecord>getAtomicReference(MASTER_NODE).get();

            if (masterRecord != null) {
                final long count = Holder.INSTANCE.getInstance().getCluster().getMembers().stream()
                        .filter(member -> StringUtils.equals(masterRecord.getMemberId(), member.getUuid())).count();

                if (count != 0) {
                    logger.warn("The master node has already existed: {}", masterRecord);
                    return false;
                }

                this.unregisterNode(masterRecord.getNodeName());
            }

            final NodeRecord newMasterRecord = HazelcastHelper.<String, NodeRecord>getMap(ACTIVE_NODES)
                    .get(cloudNode.getName());
            HazelcastHelper.getAtomicReference(MASTER_NODE).set(newMasterRecord);
            logger.info("This node has already become the new master node: {}", newMasterRecord);

            if (WORKING_MODE) {
                final Thread thread = new Thread(this.createProducer(cloudNode));
                thread.start();
            }
        } finally {
            lock.unlock();
        }

        return true;
    }

    /**
     * Returns {@code true} if the given cloud node is the master node.
     *
     * @param cloudNode the cloud node
     * @return {@code true} if the given cloud node is the master node
     * @throws NullPointerException if the given node is {@code null}
     */
    public boolean isMaster(final CloudNode cloudNode) {
        Objects.requireNonNull(cloudNode, "The given node must not be null.");
        final Optional<NodeRecord> optional = this.getMasterNodeRecord();
        return optional.isPresent() && StringUtils.equals(cloudNode.getName(), optional.get().getNodeName());
    }

    /**
     * Returns the name of the master node.
     *
     * @return the name of the master node
     */
    public Optional<NodeRecord> getMasterNodeRecord() {
        return Optional.ofNullable(HazelcastHelper.<NodeRecord>getAtomicReference(MASTER_NODE).get());
    }

    /**
     * Sends the given message to the specified node.
     *
     * @param nodeMessage the node message
     * @throws NullPointerException if the given node is {@code null}
     */
    public void send(final NodeMessage nodeMessage) {
        Objects.requireNonNull(nodeMessage, "The given cloud message must not be null.");

        HazelcastHelper.getTopic(nodeMessage.getTo()).publish(nodeMessage);
    }

    /**
     * Returns the active node set.
     *
     * @return the active node set
     */
    public Set<String> getActiveNodes() {
        return HazelcastHelper.<String, NodeRecord>getMap(ACTIVE_NODES).keySet();
    }

    /**
     * Returns the task queue.
     *
     * @return the task queue
     */
    public BlockingQueue<String> getTaskQueue() {
        return HazelcastHelper.getQueue(TASK_QUEUE);
    }

    /**
     * Returns the distributed map related with the given name.
     *
     * @param mapName the distributed map
     * @return the name of map
     * @throws NullPointerException if the given name is {@code null}
     */
    private static <K, V> IMap<K, V> getMap(final String mapName) {
        Objects.requireNonNull(mapName, "The given map name must not be null.");
        return Holder.INSTANCE.getInstance().getMap(mapName);
    }

    /**
     * Returns the distributed blocking queue related with the given name.
     *
     * @param queueName the distributed blocking queue
     * @return the name of queue
     * @throws NullPointerException if the given name is {@code null}
     */
    private static <T> IQueue<T> getQueue(final String queueName) {
        Objects.requireNonNull(queueName, "The given queue name must not be null.");
        return Holder.INSTANCE.getInstance().getQueue(queueName);
    }

    /**
     * Returns the topic related with the given name.
     *
     * @param topicName the topic
     * @return the name of topic
     * @throws NullPointerException if the given name is {@code null}
     */
    private static <T> ITopic<T> getTopic(final String topicName) {
        Objects.requireNonNull(topicName, "The given topic name must not be null.");
        return Holder.INSTANCE.getInstance().getTopic(topicName);
    }

    /**
     * Returns the atomic reference related with the given name.
     *
     * @param referenceName the atomic reference
     * @return the name of atomic reference
     * @throws NullPointerException if the given name is {@code null}
     */
    private static <T> IAtomicReference<T> getAtomicReference(final String referenceName) {
        Objects.requireNonNull(referenceName, "The given atomic reference name must not be null.");
        return Holder.INSTANCE.getInstance().getAtomicReference(referenceName);
    }

    /**
     * Returns the atomic long integer related with the given name.
     *
     * @param numberName the atomic long integer
     * @return the name of atomic long integer
     * @throws NullPointerException if the given name is {@code null}
     */
    private static IAtomicLong getAtomicLong(final String numberName) {
        Objects.requireNonNull(numberName, "The given atomic integer name must not be null.");
        return Holder.INSTANCE.getInstance().getAtomicLong(numberName);
    }

    /**
     * Creates and returns a new consumer.
     *
     * @param cloudNode the cloud node which runs this new consumer
     * @return a new consumer
     */
    private Runnable createConsumer(final CloudNode cloudNode) {
        return () -> {
            logger.info("Consumer thread started.");
            while (!this.isMaster(cloudNode)) {
                try {
                    final String task = HazelcastHelper.this.getTaskQueue().take();
                    logger.info("Retrieved task: {}", task);
                    Thread.sleep(RandomUtils.nextInt(2500, 7000));
                    logger.info("Finished task: {}", task);
                } catch (final Exception ex) {
                    logger.error("Exception occurred!", ex);
                }
            }
        };
    }

    /**
     * Creates and returns a new producer.
     *
     * @param cloudNode the cloud node which runs this new producer
     * @return a new producer
     */
    private Runnable createProducer(final CloudNode cloudNode) {
        return () -> {
            logger.info("Producer thread started.");
            while (this.isMaster(cloudNode)) {
                try {
                    final Set<String> nodes = new HashSet<>(HazelcastHelper.this.getActiveNodes());
                    nodes.remove(cloudNode.getName());
                    if (nodes.size() > 0) {
                        final IAtomicLong serialNumber = getAtomicLong(TASK_NUMBER);
                        final String taskName = String.format("TASK-%d-%d", serialNumber.incrementAndGet(),
                                System.currentTimeMillis());
                        HazelcastHelper.this.getTaskQueue().put(taskName);
                        logger.info("Added task {}", taskName);
                    }
                    Thread.sleep(RandomUtils.nextInt(1500, 4000));
                } catch (final Exception ex) {
                    logger.error("Exception occurred!", ex);
                }
            }
        };
    }
}