com.addc.commons.slp.AbstractSlpEnumeration Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of addc-slp Show documentation
Show all versions of addc-slp Show documentation
The addc-slp library supplies client classes for registering objects with a Service Location Protocol Daemon and
for looking theses objects up later.
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