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

com.addc.commons.slp.AbstractSlpEnumeration Maven / Gradle / Ivy

Go to download

The addc-slp library supplies client classes for registering objects with a Service Location Protocol Daemon and for looking theses objects up later.

There is a newer version: 2.6
Show newest version
package com.addc.commons.slp;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.addc.commons.Mutex;
import com.addc.commons.slp.configuration.SLPConfig;
import com.addc.commons.slp.messages.SLPMessageHeader;
import com.addc.commons.slp.messages.UAMessage;

/**
 * The AbstractSlpEnumeration supplies methods common to all implementations of
 * {@link SlpEnumeration}.
 */
public abstract class AbstractSlpEnumeration implements SlpEnumeration {
    private static final Logger LOGGER= LoggerFactory.getLogger(AbstractSlpEnumeration.class);
    protected final SLPConfig config;
    protected final List received= new ArrayList<>();
    private final NetworkManager netManager;
    private final UAMessage uaMessage;
    private final DataOutputStream uaDos;
    private final int maxWait;
    private final List delivered= new ArrayList<>();
    private final Mutex mutex= new Mutex();
    private ByteArrayOutputStream uaBaos;
    private DatagramSocket uaSock;
    private boolean destroyed;

    /**
     * Used to remember if we've asked for responses yet. If this is a unicast
     * request, don't wait for more than one response.
     */
    private boolean requested;

    /** Holds the errorCode from the last response */
    private int errorCode;

    /**
     * Create a new ServiceLocationEnumerationImpl
     * 
     * @param config
     *            The configuration to use
     * @param netManager
     *            The network manager to use.
     * @param message
     *            The UA message.
     * @throws ServiceLocationException
     *             If the service cannot be located.
     */
    protected AbstractSlpEnumeration(SLPConfig config, NetworkManager netManager, UAMessage message)
            throws ServiceLocationException {
        this.config= config;
        maxWait= config.getMcastMaxWait();
        this.netManager= netManager;
        uaMessage= message;
        uaBaos= new ByteArrayOutputStream(config.getMTU());
        uaDos= new DataOutputStream(uaBaos);
        uaMessage.setXid(netManager.nextXid());
        try {
            uaSock= new DatagramSocket();
            transmitDatagram();
        } catch (IOException e) {
            LOGGER.error("Could not send message.", e);
            throw new ServiceLocationException("Could not send message.", e, SLPConstants.NETWORK_ERROR);
        }
    }

    @Override
    @SuppressWarnings("PMD.CyclomaticComplexity")
    public T next() throws ServiceLocationException, NoSuchElementException {
        if (isDestroyed()) {
            throw new IllegalStateException("The enumeration has been destroyed");
        }
        try {
            if (received.isEmpty() && (uaMessage.isMulticast() || !requested) && errorCode == 0) {
                errorCode= readResponse();
                requested= true;
            }

            if (errorCode != 0) {
                int error= errorCode;
                errorCode= 0;
                LOGGER.error("Error from SA {}", error);
                throw new ServiceLocationException("Error from SA", error);
            }

            if (!received.isEmpty()) {
                T result= received.remove(0);
                delivered.add(result);
                return result;
            }
            throw new NoSuchElementException();
        } catch (IOException e) {
            throw new ServiceLocationException(e.toString(), e, SLPConstants.NETWORK_ERROR);
        }
    }

    @Override
    public boolean hasMoreElements() {
        if (isDestroyed()) {
            return false;
        }
        if (!received.isEmpty()) {
            return true;
        }
        if (uaMessage.isMulticast() || !requested) {
            try {
                errorCode= readResponse();
                requested= true;
            } catch (IOException | ServiceLocationException e) {
                LOGGER.error("Failed to read response", e);
            }
        }

        return (!received.isEmpty());
    }

    @Override
    @SuppressWarnings("PMD.PreserveStackTrace")
    public T nextElement() {
        try {
            return next();
        } catch (ServiceLocationException e) {
            throw new NoSuchElementException(e.getMessage());
        }
    }

    @Override
    public void destroy() {
        synchronized (mutex) {
            if (!destroyed) {
                destroyed= true;
                if (uaBaos != null) {
                    try {
                        uaBaos.close();
                    } catch (IOException e) {
                        LOGGER.debug(e.getMessage());
                    }
                    uaBaos= null;
                }
                if (uaSock != null) {
                    uaSock.close();
                    uaSock= null;
                }
                LOGGER.info("Destroyed the enumeration");
            }
        }
    }

    @Override
    public boolean isDestroyed() {
        synchronized (mutex) {
            return destroyed;
        }
    }

    /**
     * Performs the actual transmission of a UA message.
     */
    private void transmitDatagram() throws IOException {
        InetAddress addr= netManager.getDaAddress();
        LOGGER.debug("Send request to {}", addr.getHostAddress());
        uaBaos.reset();

        if (addr.isMulticastAddress()) {
            LOGGER.debug("Using multicast");
            uaMessage.setMulticast(true);
            uaMessage.setTransmitSchedule(config.getMcastTimeouts());
        } else {
            LOGGER.debug("Not using multicast");
            uaMessage.setMulticast(false);
            uaMessage.setTransmitSchedule(config.getDatagramTimeouts());
        }

        if (!uaMessage.writeMessage(uaDos, false)) {
            LOGGER.warn("Message was too long, probably too many previous responders");
            return;
        }
        byte[] buf= uaBaos.toByteArray();

        DatagramPacket packet= new DatagramPacket(buf, buf.length);

        packet.setAddress(addr);
        packet.setPort(config.getPort());
        uaSock.send(packet);
        uaMessage.sent();
        LOGGER.info("Sent {}", uaMessage);
    }

    /**
     * Listens for a response to a UA message. The message may be retransmitted
     * if necessary. This method implements the multicast convergence algorithm
     * described in RFC 2608.
     * 
     * @return the response.
     * @throws IOException
     *             If there is an IO error
     */
    private int readResponse() throws IOException, ServiceLocationException {
        byte[] buf= new byte[config.getMTU()];
        DatagramPacket packet= new DatagramPacket(buf, buf.length);
        long started= System.currentTimeMillis();
        int failCount= 0;
        int error= 0;
        boolean gotResponse= false;

        // Received something and message is multicast or no response and
        // timed out and less than 2 failures
        while (received.isEmpty() && (uaMessage.isMulticast() || !gotResponse)
                && ((started + maxWait) > System.currentTimeMillis()) && failCount < 2) {
            DataInputStream dis= null;
            try {
                int tout= uaMessage.getNextTimeout();
                LOGGER.debug("Set SOTIMEOUT to {}", tout);
                uaSock.setSoTimeout(tout);
                uaSock.receive(packet);
                ByteArrayInputStream bais= new ByteArrayInputStream(packet.getData());
                dis= new DataInputStream(bais);

                SLPMessageHeader header= new SLPMessageHeader(config.getMTU());
                header.read(dis);

                if (header.getXid() == uaMessage.getXid()) {
                    failCount= 0;
                    gotResponse= true;
                    uaMessage.addResponder(packet.getAddress().getHostAddress());
                    error= readResponse(dis, header);
                    removeDuplicates();
                } else {
                    LOGGER.error("WARNING: Dropping message with XID {}", header.getXid());
                }
            } catch (InterruptedIOException e) {
                failCount++;
                LOGGER.warn("Read interrupted fail count={}", failCount, e);
                transmitDatagram();
            } finally {
                if (dis != null) {
                    dis.close();
                }
            }
        }

        if (!gotResponse && !uaMessage.isMulticast()) {
            // If no response from DA, it may be down.
            netManager.findDA();
            return readResponse();
        }

        return error;
    }

    /**
     * Removes duplicate entries from the list of responses.
     */
    private void removeDuplicates() {
        for (T object : delivered) {
            if (received.contains(object)) {
                received.remove(object);
            }
        }
    }

    /**
     * Parses a datagram packet's buffer, adding responses to the list of
     * received values.
     *
     * @param dis
     *            The DataInputStream to read from
     * @param header
     *            the header that has already been read
     * @throws IOException
     *             If there is read error
     * @throws ServiceLocationException
     *             If the function id does not match the request type
     */
    protected abstract void parseResponse(DataInputStream dis, SLPMessageHeader header)
            throws IOException, ServiceLocationException;

    private int readResponse(DataInputStream dis, SLPMessageHeader header)
            throws IOException, ServiceLocationException {
        int error= dis.readShort();
        parseResponse(dis, header);
        return error;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy