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

Java tutorial

Introduction

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

Source

/*
 * @(#)HazelcastHelper.java 2016/10/13
 *
 * Copyright (c) 2016 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.jcconf2016.node;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
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.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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., 2016/10/13
 */
@Component
public class HazelcastHelper {
    /**
     * Data time formatter.
     */
    public static final DateTimeFormatter NAME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss-SSS");

    /**
     * The key to access big data map.
     */
    public static final String BIG_DATA = "BIG_DATA";

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

    /**
     * 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";

    /**
     * Waiting milliseconds for a producer.
     */
    private static int WAIT_PRODUCER = 15000;

    /**
     * Minimum waiting milliseconds for a consumer.
     */
    private static int MIN_WAIT_CONSUMER = 10000;

    /**
     * Maximum waiting milliseconds for a consumer.
     */
    private static int MAX_WAIT_CONSUMER = 20000;

    /**
     * Minimum task size indicated by a producer.
     */
    private static int MIN_SIZE = 10;

    /**
     * Maximum task size indicated by a producer.
     */
    private static int MAX_SIZE = 20;

    /**
     * Minimum value generated by a consumer.
     */
    private static int MIN_VALUE = 10;

    /**
     * Maximum value generated by a consumer.
     */
    private static int MAX_VALUE = 1000 * 1000;

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

    /**
     * Hazelcast instance holder.
     */
    private static Holder hazelcastHolder = Holder.INSTANCE;

    /**
     * The {@code HazelcastInstance} holder is a singleton enumeration for the
     * usage of Hazelcast node.
     *
     * @author Joseph S. Kuo
     * @since 0.0.1, 2016/10/13
     */
    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.", 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, hazelcastHolder.getMemberId());
        HazelcastHelper.getMap(ACTIVE_NODES).put(nodeName, record);
        HazelcastHelper.<CloudNodeMessage>getTopic(nodeName).addMessageListener(cloudNode);
        hazelcastHolder.getInstance().getCluster().addMembershipListener(cloudNode);
        logger.info("[{}] The given node registered: {}", nodeName, record);

        final Optional<NodeRecord> optional = this.getMasterNodeRecord();
        logger.info("[{}] Found the master node: {}", nodeName, optional.orElse(null));
        if (!optional.isPresent()) {
            this.setMaster(cloudNode);
            return;
        }

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

        if (WORKING_MODE) {
            this.runConsumer(cloudNode);
        }
    }

    /**
     * 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();
        hazelcastHolder.getInstance().getCluster().removeMembershipListener(nodeName);
        logger.info("[{}] The node is un-registered: {}", nodeName, 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 = hazelcastHolder.getInstance().getLock("my-distributed-lock");
        lock.lock();

        try {
            final String nodeName = cloudNode.getName();
            final Optional<NodeRecord> optional = this.getMasterNodeRecord();
            logger.info("[{}] Ensure the master node: {}", nodeName, optional.orElse(null));

            if (optional.isPresent()) {
                final NodeRecord masterRecord = optional.get();
                final long count = hazelcastHolder.getInstance().getCluster().getMembers().stream()
                        .filter(member -> StringUtils.equals(masterRecord.getMemberId(), member.getUuid())).count();

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

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

            final NodeRecord newMasterRecord = HazelcastHelper.<String, NodeRecord>getMap(ACTIVE_NODES)
                    .get(nodeName);
            HazelcastHelper.getAtomicReference(MASTER_NODE).set(newMasterRecord);
            logger.info("[{}] This node becomes the new master node: {}", nodeName, newMasterRecord);

            if (WORKING_MODE) {
                this.runProducer(cloudNode);
            }
        } 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 node record of the master node.
     *
     * @return the node record 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 CloudNodeMessage 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<Integer> getTaskQueue() {
        return HazelcastHelper.getQueue(TASK_QUEUE);
    }

    /**
     * Returns the distributed map related with the given name.
     *
     * @param <K> the type of key
     * @param <V> the type of value
     * @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 hazelcastHolder.getInstance().getMap(mapName);
    }

    /**
     * Returns the distributed blocking queue related with the given name.
     *
     * @param <T> the type of objects stored in the queue
     * @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 hazelcastHolder.getInstance().getQueue(queueName);
    }

    /**
     * Returns the topic related with the given name.
     *
     * @param <T> the type of objects transmitted by the topic
     * @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 hazelcastHolder.getInstance().getTopic(topicName);
    }

    /**
     * Returns the atomic reference related with the given name.
     *
     * @param <T> the type of objects handled by {@code AtomicReference}
     * @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 hazelcastHolder.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}
     */
    public static IAtomicLong getAtomicLong(final String numberName) {
        Objects.requireNonNull(numberName, "The given atomic integer name must not be null.");
        return hazelcastHolder.getInstance().getAtomicLong(numberName);
    }

    /**
     * Creates and runs a new consumer.
     *
     * @param cloudNode the cloud node which runs this new consumer
     */
    private void runConsumer(final CloudNode cloudNode) {
        final Thread thread = new Thread(() -> {
            final long serialNumber = HazelcastHelper.getAtomicLong("WORKER_SN").incrementAndGet();
            final String time = LocalDateTime.now().format(NAME_FORMATTER);
            final String workerName = String.format("Worker-%d-%s", serialNumber, time);
            final String nodeName = cloudNode.getName();
            logger.info("[{}] Starting a new consumer thread: {}.", nodeName, workerName);

            while (!this.isMaster(cloudNode)) {
                try {
                    logger.debug("[{}] Waiting for incoming task...", workerName);
                    final int taskSize = HazelcastHelper.this.getTaskQueue().take();
                    logger.debug("[{}] Received task size: {}", workerName, taskSize);
                    final IMap<Long, Integer> map = HazelcastHelper.getMap(BIG_DATA);
                    final IAtomicLong serial = HazelcastHelper.getAtomicLong(BIG_DATA);
                    logger.debug("[{}] Before map size : {}, SN: {}", workerName, map.size(), serial.get());
                    for (int i = 0; i < taskSize; i++) {
                        map.put(serial.incrementAndGet(), RandomUtils.nextInt(MIN_VALUE, MAX_VALUE));
                    }
                    logger.debug("[{}] After map size: {}, SN: {}", workerName, map.size(), serial.get());
                    logger.info("[{}] Finished task: {}", workerName, taskSize);
                    Thread.sleep(RandomUtils.nextInt(MIN_WAIT_CONSUMER, MAX_WAIT_CONSUMER));
                } catch (final Exception ex) {
                    logger.error(String.format("[%s] Exception occurred", workerName), ex);
                }
            }
        });
        thread.start();
    }

    /**
     * Creates and runs a new producer.
     *
     * @param cloudNode the cloud node which runs this new producer
     */
    private void runProducer(final CloudNode cloudNode) {
        final Thread thread = new Thread(() -> {
            final String nodeName = cloudNode.getName();
            logger.info("[{}] Producer thread started.", nodeName);

            while (this.isMaster(cloudNode)) {
                try {
                    final Set<String> nodes = new HashSet<>(HazelcastHelper.this.getActiveNodes());
                    nodes.remove(cloudNode.getName());
                    for (int i = 0; i < nodes.size(); i++) {
                        final int taskSize = RandomUtils.nextInt(MIN_SIZE, MAX_SIZE);
                        HazelcastHelper.this.getTaskQueue().put(taskSize);
                        logger.info("[{}] Added task size: {}", nodeName, taskSize);
                    }
                    Thread.sleep(WAIT_PRODUCER);
                } catch (final Exception ex) {
                    logger.error(String.format("[%s] Exception occurred", nodeName), ex);
                }
            }
        });
        thread.start();
    }
}