All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hcjf.io.net.broadcast.BroadcastService Maven / Gradle / Ivy

package org.hcjf.io.net.broadcast;

import org.hcjf.bson.BsonDecoder;
import org.hcjf.bson.BsonDocument;
import org.hcjf.bson.BsonEncoder;
import org.hcjf.io.net.InetPortProvider;
import org.hcjf.log.Log;
import org.hcjf.properties.SystemProperties;
import org.hcjf.service.Service;
import org.hcjf.utils.Introspection;

import java.io.Closeable;
import java.io.IOException;
import java.net.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.LocalTime;
import java.time.temporal.ChronoField;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * This service provides capabilities to register broadcast task in order to notify
 * for all the net this task.
 * @author javaito
 */
public class BroadcastService extends Service {

    private static final String IMPLEMENTATION_FIELD_NAME = "implementation";
    private static final String IP_PROTOCOL_VERSION_4 = "4";
    private static final String IP_PROTOCOL_VERSION_6 = "6";

    private static final BroadcastService instance;
    private static final UUID instanceId;

    private Map interfaces;
    private BroadcastSender sender;
    private Map receivers;
    private Map consumers;
    private boolean shuttingDown;
    private MessageDigest messageDigest;

    static {
        instance = new BroadcastService(SystemProperties.get(SystemProperties.Net.Broadcast.SERVICE_NAME));
        instanceId = UUID.randomUUID();
    }

    private BroadcastService(String serviceName) {
        super(serviceName, 2);
    }

    /**
     * Returns the singleton instance of the service.
     * @return Service instance.
     */
    public static BroadcastService getInstance() {
        return instance;
    }

    /**
     * This method initialize all the necessary components of the service.
     */
    @Override
    protected void init() {
        this.interfaces = new HashMap<>();
        this.consumers = new HashMap<>();
        this.sender = new BroadcastSender();
        this.receivers = new HashMap<>();
        try {
            this.messageDigest = MessageDigest.getInstance(SystemProperties.get(SystemProperties.Net.Broadcast.SIGNATURE_ALGORITHM));
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("Broadcast signature algorithm not found", e);
        }
        fork(sender);
    }

    /**
     * Verify if the service is in the shutting down process.
     * @return True if the service in the shutting down process or not in the otherwise.
     */
    public boolean isShuttingDown() {
        return shuttingDown;
    }

    @Override
    protected void shutdown(ShutdownStage stage) {
        this.shuttingDown = true;
        synchronized (this.sender) {
            this.sender.notify();
        }
    }

    /**
     * Register a consumer into the the service.
     * @param consumer Object with the logic to consume the service.
     */
    @Override
    public void registerConsumer(BroadcastConsumer consumer) {
        this.consumers.put(consumer.getTaskName(), consumer);
        synchronized (this.sender) {
            this.sender.notify();
        }
    }

    /**
     * Unregister a consumer of the service.
     * @param consumer Consumer to unregister.
     */
    @Override
    public void unregisterConsumer(BroadcastConsumer consumer) {
        this.consumers.remove(consumer);
    }

    /**
     * Returns the broadcast interface, this interface contains the datagram socket to send a receive the
     * task between all the host of the net.
     * @param consumer Consumer with the information to create the interface.
     * @return Broadcast interface instance.
     * @throws SocketException
     */
    private synchronized BroadcastInterface getBroadcastInterface(BroadcastConsumer consumer) throws SocketException {
        Integer port = consumer.getPort();
        String interfaceId = BroadcastInterface.createInterfaceId(consumer.getNetInterfaceName(), port);
        BroadcastInterface result = interfaces.get(interfaceId);
        if(result == null) {

            //Obtain all the net interfaces of the operating system.
            Enumeration interfacesEnumeration = NetworkInterface.getNetworkInterfaces();
            boolean done = false;

            //Check for each interfaces if the interface is the same that the interface into the consumer.
            while(interfacesEnumeration.hasMoreElements()){
                NetworkInterface networkInterface = (NetworkInterface) interfacesEnumeration.nextElement();

                if(networkInterface.getName().equals(consumer.getNetInterfaceName())){

                    //Find the first interface address with the same protocol version that the consumer.
                    for(InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()){

                        switch(consumer.getIpVersion()) {
                            case IP_PROTOCOL_VERSION_4: {
                                done = interfaceAddress.getAddress() instanceof Inet4Address;
                                break;
                            }
                            case IP_PROTOCOL_VERSION_6: {
                                done = interfaceAddress.getAddress() instanceof Inet6Address;
                                break;
                            }
                            default: {
                                throw new IllegalArgumentException("Invalid ip version: " + consumer.getIpVersion());
                            }
                        }

                        if(done) {
                            //Creates a broadcast interface using the interface address
                            result = new BroadcastInterface(
                                    consumer.getNetInterfaceName(), port,
                                    interfaceAddress.getAddress(), interfaceAddress.getBroadcast());
                            interfaces.put(result.getId(), result);

                            //Creates a new instance of the receiver.
                            BroadcastReceiver broadcastReceiver = new BroadcastReceiver(result);
                            receivers.put(result.getId(), broadcastReceiver);
                            //Create a new thread to the interface receiver.
                            fork(broadcastReceiver);
                            break;
                        }
                    }
                }

                if(done) {
                    break;
                }
            }
        }

        return result;
    }

    /**
     * This method creates a signature using the name of the task, private key
     * of the consumer and the minute and seconds of the instant.
     * @param name Name of the task.
     * @param privateKey Private key of the consumer.
     * @param time Minute and second of the instant.
     * @return Signature created.
     */
    private String sign(String name, String privateKey, String time) {
        String signature = name + privateKey + time;
        return new String(messageDigest.digest(signature.getBytes()));
    }

    /**
     * Serialize the broadcast message and transform it in a byte array with bson format.
     * @param message Message to serialize.
     * @return Serialized message.
     */
    private byte[] encode(BroadcastMessage message) {
        BsonDocument document = new BsonDocument();
        Map getters = Introspection.getGetters(message.getClass());
        getters.forEach((K, V) -> {
            try {
                document.put(K, V.get(message));
            } catch (Exception e) {}
        });
        return BsonEncoder.encode(document);
    }

    /**
     * Creates a broadcast message instance from a bson stored into the byte array.
     * @param body Byte array with bson format.
     * @return Decoded message instance.
     */
    private BroadcastMessage decode(byte[] body) {
        BroadcastMessage message;
        try {
            BsonDocument document = BsonDecoder.decode(body);
            message = (BroadcastMessage)
                    Class.forName(document.get(IMPLEMENTATION_FIELD_NAME).getAsString()).getConstructor().newInstance();
            Map setters = Introspection.getSetters(message.getClass());
            setters.forEach((K, V) -> {
                try {
                    V.set(message, document.get(K).getValue());
                } catch (Exception e) {}
            });
        } catch (Exception e) {
            throw new RuntimeException();
        }
        return message;
    }

    /**
     * This runnable contains the logic to send a ping message periodically,
     * and send the shutdown message when the kill signal start the shutdown process
     * into the instance of system.
     */
    private class BroadcastSender implements Runnable {

        @Override
        public void run() {

            BsonDocument document;

            Instant instant;
            LocalTime localTime;
            String hour;
            String minute;

            PingMessage pingMessage;
            BroadcastInterface broadcastInterface;

            //Main loop
            while(!instance.isShuttingDown()) {
                //Consumers loop
                for(BroadcastConsumer consumer : consumers.values()) {
                    try {
                        instant = Instant.now();
                        localTime = LocalTime.now();

                        hour = Integer.toString(localTime.get(ChronoField.HOUR_OF_DAY));
                        minute = Integer.toString(localTime.get(ChronoField.MINUTE_OF_HOUR));
                        broadcastInterface = getBroadcastInterface(consumer);
                        pingMessage = new PingMessage();
                        pingMessage.setTaskName(consumer.getTaskName());
                        pingMessage.setHost(broadcastInterface.getLocalAddress().getHostName());
                        pingMessage.setPort(broadcastInterface.getPort());
                        pingMessage.setInstanceId(instanceId);
                        pingMessage.setTimestamp(instant.toEpochMilli());
                        pingMessage.setSignature(instance.sign(consumer.getTaskName(),consumer.getPrivateKey(), hour + minute));
                        pingMessage.setCustomParameters(consumer.getPingParameters());
                        byte[] body =  instance.encode(pingMessage);
                        DatagramPacket packet;
                        for (int i = broadcastInterface.getPort(); i < broadcastInterface.getPort() + 10; i++) {
                            packet = new DatagramPacket(body, body.length,
                                    broadcastInterface.getBroadcastAddress(), i);
                            broadcastInterface.getBroadcastSocket().send(packet);
                        }
                    } catch (Exception ex) {
                        Log.w(SystemProperties.get(SystemProperties.Net.Broadcast.LOG_TAG), ex.getMessage());
                    }
                }

                synchronized (this) {
                    try {
                        this.wait(SystemProperties.getLong(SystemProperties.Net.Broadcast.SENDER_DELAY));
                    } catch (InterruptedException ex) {
                    }
                }
            }

            ShutdownMessage shutdownMessage;
            for(BroadcastConsumer consumer : consumers.values()) {
                try {
                    instant = Instant.now();
                    localTime = LocalTime.now();

                    hour = Integer.toString(localTime.get(ChronoField.HOUR_OF_DAY));
                    minute = Integer.toString(localTime.get(ChronoField.MINUTE_OF_HOUR));
                    broadcastInterface = getBroadcastInterface(consumer);
                    shutdownMessage = new ShutdownMessage();
                    shutdownMessage.setTaskName(consumer.getTaskName());
                    shutdownMessage.setHost(broadcastInterface.getLocalAddress().getHostName());
                    shutdownMessage.setPort(broadcastInterface.getPort());
                    shutdownMessage.setInstanceId(instanceId);
                    shutdownMessage.setTimestamp(instant.toEpochMilli());
                    shutdownMessage.setSignature(instance.sign(consumer.getTaskName(),consumer.getPrivateKey(), hour + minute));
                    shutdownMessage.setCustomParameters(consumer.getPingParameters());
                    byte[] body =  instance.encode(shutdownMessage);
                    DatagramPacket packet = new DatagramPacket(body, body.length,
                            broadcastInterface.getBroadcastAddress(), broadcastInterface.getPort());
                    broadcastInterface.getBroadcastSocket().send(packet);
                    broadcastInterface.close();
                } catch (Exception ex) {
                    Log.w(SystemProperties.get(SystemProperties.Net.Broadcast.LOG_TAG),
                            "", ex);
                }
            }
        }

    }

    /**
     * This runnable is all the time listening the broadcast interface in order to
     * receive the broadcast messages sending for other host into the net.
     */
    private class BroadcastReceiver implements Runnable {

        private final BroadcastInterface broadcastInterface;

        public BroadcastReceiver(BroadcastInterface broadcastInterface) {
            this.broadcastInterface = broadcastInterface;
        }

        @Override
        public void run() {

            Instant instant;
            LocalTime localTime;
            String hour;
            String minute;

            byte[] buffer;
            DatagramPacket inputPacket;
            BroadcastMessage broadcastMessage;

            //Main loop
            while(!instance.isShuttingDown()) {
                try {
                    buffer = new byte[SystemProperties.getInteger(SystemProperties.Net.Broadcast.RECEIVER_BUFFER_SIZE)];
                    inputPacket = new DatagramPacket(buffer, buffer.length);
                    try {
                        broadcastInterface.getBroadcastSocket().receive(inputPacket);
                    } catch (IOException ex) {
                        continue;
                    }
                    broadcastMessage = instance.decode(inputPacket.getData());

                    BroadcastConsumer consumer = consumers.get(broadcastMessage.getTaskName());

                    if(broadcastMessage instanceof PingMessage) {
                        if(!broadcastMessage.getInstanceId().equals(instanceId)) {
                            consumer.onPing((PingMessage) broadcastMessage);

                            instant = Instant.now();
                            localTime = LocalTime.now();

                            hour = Integer.toString(localTime.get(ChronoField.HOUR_OF_DAY));
                            minute = Integer.toString(localTime.get(ChronoField.MINUTE_OF_HOUR));

                            PongMessage pongMessage = new PongMessage();
                            pongMessage.setTaskName(consumer.getTaskName());
                            pongMessage.setHost(broadcastInterface.getLocalAddress().getHostName());
                            pongMessage.setPort(broadcastInterface.getPort());
                            pongMessage.setInstanceId(instanceId);
                            pongMessage.setTimestamp(instant.toEpochMilli());
                            pongMessage.setSignature(instance.sign(consumer.getTaskName(),consumer.getPrivateKey(), hour + minute));
                            pongMessage.setCustomParameters(consumer.getPingParameters());
                            byte[] body =  instance.encode(pongMessage);
                            DatagramPacket packet = new DatagramPacket(body, body.length,
                                    InetAddress.getByName(broadcastMessage.getHost()), broadcastInterface.getPort());
                            broadcastInterface.getBroadcastSocket().send(packet);
                        }
                    } else if(broadcastMessage instanceof PongMessage) {
                        consumer.onPong((PongMessage) broadcastMessage);
                    } else if(broadcastMessage instanceof ShutdownMessage) {
                        if(!broadcastMessage.getHost().equals(broadcastInterface.getLocalAddress().getHostName())) {
                            consumer.onShutdown((ShutdownMessage) broadcastMessage);
                        }
                    }
                } catch (Exception ex) {
                    Log.w(SystemProperties.get(SystemProperties.Net.Broadcast.LOG_TAG),
                            "Broadcast receiver error", broadcastInterface.getName(), ex);
                }
            }
        }

    }

    /**
     * This broadcast interface is created for each combination of net interface name and port,
     * and contains all the information to create the socket to send a receive the broadcast messages.
     */
    private static class BroadcastInterface implements Closeable {

        private final String id;
        private final String name;
        private final Integer port;
        private final InetAddress localAddress;
        private final InetAddress broadcastAddress;
        private final DatagramSocket broadcastSocket;

        public BroadcastInterface(String name, Integer port,
                                  InetAddress localAddress, InetAddress broadcastAddress) throws SocketException {
            this.id = createInterfaceId(name, port);
            this.name = name;
            this.port = port;
            this.localAddress = localAddress;
            this.broadcastAddress = broadcastAddress;
            this.broadcastSocket = new DatagramSocket(port);
        }

        /**
         * Closes the broadcast socket.
         * @throws IOException
         */
        @Override
        public void close() throws IOException {
            broadcastSocket.close();
        }

        /**
         * Returns the id of the broadcast interface.
         * @return Interface id.
         */
        public String getId() {
            return id;
        }

        /**
         * Returns the name of the interface.
         * @return Name of the interface.
         */
        public String getName() {
            return name;
        }

        /**
         * Returns the port of the interface.
         * @return Port of the interface.
         */
        public Integer getPort() {
            return port;
        }

        /**
         * Returns the local address of the interface.
         * @return Local address.
         */
        public InetAddress getLocalAddress() {
            return localAddress;
        }

        /**
         * Returns the broadcast address of the interface.
         * @return Broadcast address.
         */
        public InetAddress getBroadcastAddress() {
            return broadcastAddress;
        }

        /**
         * Return the datagram socket of the broadcast interface.
         * @return Datagram socket.
         */
        public DatagramSocket getBroadcastSocket() {
            return broadcastSocket;
        }

        /**
         * Utility method to create the interface id based on the name of the interface and the port.
         * @param name Name of the interface.
         * @param port Port of the interface.
         * @return Generated id.
         */
        public static String createInterfaceId(String name, Integer port) {
            return name + port.toString();
        }

    }

    /**
     * Base class for all the broadcast messages.
     */
    private static abstract class BroadcastMessage {

        private String taskName;
        private String host;
        private Integer port;
        private Long timestamp;
        private String signature;
        private UUID instanceId;
        private Map customParameters;

        /**
         * Returns the task name.
         * @return Task name.
         */
        public String getTaskName() {
            return taskName;
        }

        /**
         * Sets the task name.
         * @param taskName Task name.
         */
        public void setTaskName(String taskName) {
            this.taskName = taskName;
        }

        /**
         * Returns the host name.
         * @return Host name.
         */
        public String getHost() {
            return host;
        }

        /**
         * Sets the host name.
         * @param host Host name.
         */
        public void setHost(String host) {
            this.host = host;
        }

        /**
         * Returns the port number.
         * @return Port number.
         */
        public Integer getPort() {
            return port;
        }

        /**
         * Set the port number.
         * @param port Port number.
         */
        public void setPort(Integer port) {
            this.port = port;
        }

        /**
         * Returns the creation timestamp.
         * @return Creation timestamp.
         */
        public Long getTimestamp() {
            return timestamp;
        }

        /**
         * Sets create timestamp.
         * @param timestamp Creation timestamp.
         */
        public void setTimestamp(Long timestamp) {
            this.timestamp = timestamp;
        }

        /**
         * Returns the package signature.
         * @return Package signature.
         */
        public String getSignature() {
            return signature;
        }

        /**
         * Sets the package signature.
         * @param signature Package signature.
         */
        public void setSignature(String signature) {
            this.signature = signature;
        }

        public UUID getInstanceId() {
            return instanceId;
        }

        public void setInstanceId(UUID instanceId) {
            this.instanceId = instanceId;
        }

        public Map getCustomParameters() {
            return customParameters;
        }

        public void setCustomParameters(Map customParameters) {
            this.customParameters = customParameters;
        }

        public String getImplementation() {
            return getClass().getName();
        }
    }

    /**
     * Message that is sending in order to publish the instance for all the net
     */
    public static class PingMessage extends BroadcastMessage {}

    /**
     * Message that is sending as response for the Ping Message.
     */
    public static class PongMessage extends BroadcastMessage {}

    /**
     * Message that is sending to notify for all the net that the instance is shutting down
     */
    public static class ShutdownMessage extends BroadcastMessage {}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy