/*
* ============================================================================
* 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.npdu.ip;
import java.net.InetSocketAddress;
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.ComplexACK;
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;
public class WaitingRoom {
private final HashMap<Key, Member> waitHere = new HashMap<Key, Member>();
private byte nextInvokeId;
synchronized public Key enterClient(InetSocketAddress peer, Address linkService) {
Member member = new Member();
Key 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 Key(peer, 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 Key enterServer(InetSocketAddress peer, Address linkService, byte id) {
// We set the server value in the key to false so that it matches with the message from the client.
Key key = new Key(peer, 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(Key key, long timeout, boolean throwTimeout) throws BACnetException {
return (AckAPDU) getAPDU(key, timeout, throwTimeout);
}
public ConfirmedRequest getRequest(Key key, long timeout, boolean throwTimeout) throws BACnetException {
return (ConfirmedRequest) getAPDU(key, timeout, throwTimeout);
}
public Segmentable getSegmentable(Key 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(Key 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(Key key) {
synchronized (waitHere) {
waitHere.remove(key);
}
}
public void notifyMember(InetSocketAddress peer, Address linkService, byte id, boolean isFromServer, APDU apdu)
throws BACnetException {
System.out.println("Received APDU (1): " + apdu);
if (apdu != null && apdu instanceof ComplexACK)
((ComplexACK) apdu).parseServiceData();
System.out.println("Received APDU: " + apdu);
Key key = new Key(peer, 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: peer=" + peer + ", id=" + (id & 0xff)
+ ", isFromServer=" + isFromServer + ", message=" + apdu);
}
private Member getMember(Key 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
*/
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
}
}
}
public class Key {
private final InetSocketAddress peer;
private final Address linkService;
private final byte invokeId;
private final boolean fromServer;
public Key(InetSocketAddress peer, Address linkService, byte invokeId, boolean fromServer) {
this.peer = peer;
this.linkService = linkService;
this.invokeId = invokeId;
this.fromServer = fromServer;
}
public InetSocketAddress getPeer() {
return peer;
}
public Address getLinkService() {
return linkService;
}
public byte getInvokeId() {
return invokeId;
}
public boolean isFromServer() {
return fromServer;
}
@Override
public String toString() {
return "Key(peer=" + peer + ", linkService=" + linkService + ", invokeId=" + invokeId + ", fromServer="
+ fromServer + ")";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (fromServer ? 1231 : 1237);
result = prime * result + ((linkService == null) ? 0 : linkService.hashCode());
result = prime * result + ((peer == null) ? 0 : peer.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Key other = (Key) obj;
if (fromServer != other.fromServer)
return false;
if (invokeId != other.invokeId)
return false;
if (linkService == null) {
if (other.linkService != null)
return false;
}
else if (!linkService.equals(other.linkService))
return false;
if (peer == null) {
if (other.peer != null)
return false;
}
else if (!peer.equals(other.peer))
return false;
return true;
}
}
}