/*
* ============================================================================
* GNU General Public License
* ============================================================================
*
* Copyright (C) 2006-2011 Serotonin Software Technologies Inc. http://serotoninsoftware.com
* @author Matthew Lohbihler
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* When signing a commercial license with Serotonin Software Technologies Inc.,
* the following extension to GPL is made. A special exception to the GPL is
* included to allow you to distribute a combined work that includes BAcnet4J
* without being obliged to provide the source code for any proprietary components.
*/
package com.serotonin.bacnet4j.transport;
import java.util.HashMap;
import java.util.LinkedList;
import com.serotonin.bacnet4j.apdu.APDU;
import com.serotonin.bacnet4j.apdu.Abort;
import com.serotonin.bacnet4j.apdu.AckAPDU;
import com.serotonin.bacnet4j.apdu.ConfirmedRequest;
import com.serotonin.bacnet4j.apdu.Segmentable;
import com.serotonin.bacnet4j.exception.BACnetException;
import com.serotonin.bacnet4j.exception.BACnetRuntimeException;
import com.serotonin.bacnet4j.exception.BACnetTimeoutException;
import com.serotonin.bacnet4j.exception.SegmentedMessageAbortedException;
import com.serotonin.bacnet4j.type.constructed.Address;
import com.serotonin.bacnet4j.type.primitive.OctetString;
public class WaitingRoom {
private final HashMap<WaitingRoomKey, Member> waitHere = new HashMap<WaitingRoomKey, Member>();
private byte nextInvokeId;
synchronized public WaitingRoomKey enterClient(Address address, OctetString linkService) {
Member member = new Member();
WaitingRoomKey key;
// Loop until we find a key that is available.
int attempts = 256;
while (true) {
// We set the server value in the key to true so that it matches with the message from the server.
key = new WaitingRoomKey(address, linkService, nextInvokeId++, true);
synchronized (waitHere) {
if (waitHere.get(key) != null) {
// Key collision. Try again unless we've tried too many times.
if (--attempts > 0)
continue;
throw new BACnetRuntimeException("Cannot enter a client into the waiting room. key=" + key);
}
// Found a good id. Use it and exit.
waitHere.put(key, member);
break;
}
}
return key;
}
public WaitingRoomKey enterServer(Address address, OctetString linkService, byte id) {
// We set the server value in the key to false so that it matches with the message from the client.
WaitingRoomKey key = new WaitingRoomKey(address, linkService, id, false);
Member member = new Member();
synchronized (waitHere) {
if (waitHere.get(key) != null)
throw new BACnetRuntimeException("Cannot enter a server into the waiting room. key=" + key);
waitHere.put(key, member);
}
return key;
}
public AckAPDU getAck(WaitingRoomKey key, long timeout, boolean throwTimeout) throws BACnetException {
return (AckAPDU) getAPDU(key, timeout, throwTimeout);
}
public ConfirmedRequest getRequest(WaitingRoomKey key, long timeout, boolean throwTimeout) throws BACnetException {
return (ConfirmedRequest) getAPDU(key, timeout, throwTimeout);
}
public Segmentable getSegmentable(WaitingRoomKey key, long timeout, boolean throwTimeout) throws BACnetException {
APDU apdu = getAPDU(key, timeout, throwTimeout);
if (apdu instanceof Abort)
throw new SegmentedMessageAbortedException((Abort) apdu);
try {
return (Segmentable) apdu;
}
catch (ClassCastException e) {
throw new BACnetException("Receiving an APDU of type " + apdu.getClass()
+ " when expecting a Segmentable for key " + key);
}
}
public APDU getAPDU(WaitingRoomKey key, long timeout, boolean throwTimeout) throws BACnetException {
Member member = getMember(key);
APDU apdu = member.getAPDU(timeout);
if (apdu == null && throwTimeout)
throw new BACnetTimeoutException("Timeout while waiting for APDU id " + key.getInvokeId());
return apdu;
}
public void leave(WaitingRoomKey key) {
synchronized (waitHere) {
waitHere.remove(key);
}
}
public void notifyMember(Address address, OctetString linkService, byte id, boolean isFromServer, APDU apdu)
throws BACnetException {
WaitingRoomKey key = new WaitingRoomKey(address, linkService, id, isFromServer);
Member member = getMember(key);
if (member != null) {
member.setAPDU(apdu);
return;
}
// The member may not have gotten around to listening for a message yet, so enter a retry loop to
// make sure that this message gets to where it's supposed to go if there is somewhere to go.
int attempts = 5;
long sleep = 50;
while (attempts > 0) {
member = getMember(key);
if (member != null) {
member.setAPDU(apdu);
return;
}
attempts--;
try {
Thread.sleep(sleep);
}
catch (InterruptedException e) {
// no op
}
}
throw new BACnetException("No waiting recipient for message: address=" + address + ", id=" + (id & 0xff)
+ ", isFromServer=" + isFromServer + ", message=" + apdu);
}
private Member getMember(WaitingRoomKey key) {
synchronized (waitHere) {
return waitHere.get(key);
}
}
/**
* This class is used by network message controllers to manage the blocking of threads sending confirmed messages.
* The instance itself serves as a monitor upon which the sending thread can wait (with a timeout). When a response
* is received, the message controller can set it in here, automatically notifying the sending thread that the
* response is available.
*
* @author mlohbihler
*/
static class Member {
private final LinkedList<APDU> apdus = new LinkedList<APDU>();
synchronized void setAPDU(APDU apdu) {
apdus.add(apdu);
notify();
}
synchronized APDU getAPDU(long timeout) {
// Check if there is an APDU available now.
APDU result = apdus.poll();
if (result != null)
return result;
// If not, wait the timeout and then check again.
waitNoThrow(timeout);
return apdus.poll();
}
private void waitNoThrow(long timeout) {
try {
super.wait(timeout);
}
catch (InterruptedException e) {
// Ignore
}
}
}
}