package org.opennaas.extensions.protocols.tl1;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.opennaas.core.resources.protocol.IProtocolMessageFilter;
import org.opennaas.core.resources.protocol.IProtocolSession;
import org.opennaas.core.resources.protocol.IProtocolSessionListener;
import org.opennaas.core.resources.protocol.ProtocolException;
import org.opennaas.core.resources.protocol.ProtocolSessionContext;
import org.opennaas.core.resources.transport.IStreamTransport;
import org.opennaas.core.resources.transport.ITransport;
import org.opennaas.core.resources.transport.TransportException;
import org.opennaas.extensions.protocols.tl1.message.TL1AckMsg;
import org.opennaas.extensions.protocols.tl1.message.TL1InputMsg;
import org.opennaas.extensions.protocols.tl1.message.TL1OutputMsg;
import org.opennaas.extensions.protocols.tl1.message.TL1ResponseMsg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is used to open a TL1 session and send and receive message to and from the switch.
*
* @author Mathieu Lemay
* @author Eduard Grasa
* @version 1.0
*/
public class TL1ProtocolSession implements IProtocolSession {
/** TL1 Session Log */
static private Logger logger = LoggerFactory.getLogger(TL1ProtocolSession.class);
/**
* The receive worker, gets all the chars from the communications channel, assembles and parses TL1 messages and either sends a notification
* (autonomous messages) or adds them to the syncResponseQueue
*/
private TL1StreamReader tl1StreamReader;
/** Number of retries when receiving the Bad Acknowledgments */
public static final int NUMRETRIES = 1;
/** The queue where parsed response message are sent **/
private BlockingQueue<TL1OutputMsg> syncResponseMessageQueue;
/** Contains information about the protocol session configuration: transport, host, port, ... **/
private ProtocolSessionContext protocolSessionContext = null;
private String sessionID = null;
private IStreamTransport transport = null;
private Status status = null;
private Map<String, IProtocolSessionListener> protocolListeners = null;
private Map<String, IProtocolMessageFilter> protocolMessageFilters = null;
/**
* Creates a new instance of a TL1 Session
*
* @param capabilityDescriptor
* @throws ProtocolException
*/
public TL1ProtocolSession(ProtocolSessionContext protocolSessionContext, String sessionID) throws ProtocolException {
this.protocolSessionContext = protocolSessionContext;
this.sessionID = sessionID;
this.protocolListeners = new HashMap<String, IProtocolSessionListener>();
this.protocolMessageFilters = new HashMap<String, IProtocolMessageFilter>();
this.status = Status.DISCONNECTED_BY_USER;
// Initialize the received message queue
syncResponseMessageQueue = new LinkedBlockingQueue<TL1OutputMsg>();
}
public void wireTransport(ITransport transport) throws ProtocolException {
if (!(transport instanceof IStreamTransport)) {
throw new ProtocolException("TL1 transports must be stream transports");
}
this.transport = (IStreamTransport) transport;
}
public BlockingQueue<TL1OutputMsg> getSyncResponseMessageQueue() {
return syncResponseMessageQueue;
}
@Override
public void asyncSend(Object requestMessage) throws ProtocolException {
this.sendCmdNoWait((String) requestMessage);
}
@Override
public Object sendReceive(Object requestMessage) throws ProtocolException {
return this.sendCmdWait((String) requestMessage);
}
@Override
public void connect() throws ProtocolException {
startSession();
}
@Override
public void disconnect() throws ProtocolException {
stopSession();
}
/**
* If the TL1 Stream reader detects that the session has been interrupted, it will invoke this operation to restart to the session with the
* managed device
*
* @throws EngineException
*/
public void restartSession() {
try {
stopTL1StreamReader();
disconnectTransport();
startSession();
} catch (ProtocolException ex) {
ex.printStackTrace();
}
}
public void notify(Object message) {
Iterator<IProtocolSessionListener> protocolSessionListeners = this.protocolListeners.values().iterator();
while (protocolSessionListeners.hasNext()) {
protocolSessionListeners.next().messageReceived(message);
}
}
/**
* Opens a connection to the specific host/port
*
* @throws FailedCmdException
* Exception thrown if command failed
*/
private void startSession() throws ProtocolException {
logger.info("Starting protocol session");
// Start the receiver thread it will continuously get and parse the messages from the communication channel
try {
transport.connect();
createAndStartTL1StreamReader();
loginToDevice();
} catch (TransportException ex) {
throw new ProtocolException("Could not connect to the managed device", ex);
} catch (ProtocolException ex) {
// Stop the tl1 stream reader
tl1StreamReader.die = true;
throw ex;
}
}
private void createAndStartTL1StreamReader() throws ProtocolException {
try {
tl1StreamReader = new TL1StreamReader(transport.getInputStream(), new char[] { ';', '>', '<' },
syncResponseMessageQueue, this);
tl1StreamReader.start();
} catch (TransportException ex) {
throw new ProtocolException("Problems getting the transport input stream", ex);
}
}
private void loginToDevice() throws ProtocolException {
String username = (String) this.protocolSessionContext.getSessionParameters().get(ProtocolSessionContext.USERNAME);
String password = (String) this.protocolSessionContext.getSessionParameters().get(ProtocolSessionContext.PASSWORD);
sendWaitResponse("ACT-USER::" + username + ":123::" + password + ";");
}
/**
* Closes the connection
*/
private void stopSession() throws ProtocolException {
logoutFromDevice();
stopTL1StreamReader();
disconnectTransport();
logger.info("Protocol session stopped");
}
private void logoutFromDevice() throws ProtocolException {
String username = (String) this.protocolSessionContext.getSessionParameters().get(ProtocolSessionContext.USERNAME);
sendWaitResponse("CANC-USER::" + username + ":178;");
}
private void stopTL1StreamReader() {
tl1StreamReader.interrupt();
tl1StreamReader.die = true;
}
private void disconnectTransport() throws ProtocolException {
try {
transport.disconnect();
} catch (TransportException ex) {
throw new ProtocolException("Problems disconnecting the transport ", ex);
}
}
/**
* Sends out TL1 command to the agent.
*
* @param message
* Command to send in TL1InputMsg Format
* @return TL1ResponseMsg
* @throws FailedCmdException
* Exception thrown if command failed
*/
public Object sendWaitResponse(Object message) throws ProtocolException {
Object msg;
if (message instanceof TL1InputMsg) {
TL1InputMsg request = (TL1InputMsg) message;
msg = (Object) this.sendCmdWait(request.toString());
}
else {
msg = (Object) this.sendCmdWait((String) message);
}
return msg;
}
/**
* Sends an array of TL1 commands to the device
*/
public Object[] sendWaitResponse(Object[] messageList) throws ProtocolException {
Object[] msg = new Object[messageList.length];
for (int i = 0; i < messageList.length; i++) {
msg[i] = sendWaitResponse(messageList[i]);
}
return msg;
}
/**
* Sends out TL1 command to the agent.
*
* If you are using the Virtual Transport for testing, you will have to disable the CTAG Checking. This is because the CTAG is generated before
* sending the command, but the CTAG returning from the switch-simulation xml files is fixed and will not match. The affected code below is marked
* with comments. The SCS will have to be re-compiled and restarted after the ctag checking is disabled. Don't forget to enable it again when
* you're finished with your testing.
*
* @param request
* TL1 raw command
* @return TL1ResponseMsg
* @throws FailedCmdException
* Exception thrown if command failed
*/
private TL1ResponseMsg sendCmdWait(String request) throws ProtocolException {
TL1OutputMsg tl1ResponseMessage = null;
TL1ResponseMsg finalTL1ResponseMessage = null;
boolean retry = true;
String ctag = getCTAGFromMessage(request);
// Send the TL1 message
sendCmdNoWait(request);
// wait for the response in the synchronous message queue
boolean exit = false;
while (!exit) {
try {
logger.debug("Waiting for the message response");
tl1ResponseMessage = syncResponseMessageQueue.take();
logger.debug("I've got a possible response, let's check if it's for me");
// Here we got an ACK message that matches the CTAG of the message we sent
if (gotACKMessage(tl1ResponseMessage, ctag)) {
logger.debug("I have an ACK message for me");
// If we got a busy status, we'll try to retransmit the message
if (gotBusyStatus(tl1ResponseMessage)) {
// If we're allowed to retransmit the message, do it, but just once
if (retry) {
retry = false;
waitAndSendMessageAgain(request);
} else {
break;
}
}
} else if (gotResponseMessage(tl1ResponseMessage, ctag)) {
logger.debug("I've got a response message for me");
if (tl1ResponseMessage.getTermCode() == '>') {
finalTL1ResponseMessage = createOrAppendTL1ReponseMessage(tl1ResponseMessage, finalTL1ResponseMessage);
} else if (tl1ResponseMessage.getTermCode() == ';') {
finalTL1ResponseMessage = createOrAppendTL1ReponseMessage(tl1ResponseMessage, finalTL1ResponseMessage);
exit = true;
}
}
} catch (InterruptedException e) {
}
}
if (finalTL1ResponseMessage == null) {
throw (new ProtocolException("Could not get response from switch to command:\n" + request));
}
return finalTL1ResponseMessage;
}
private String getCTAGFromMessage(String message) throws ProtocolException {
// Get the correlation tag (always in the fourth section of the command)
String ctag = null;
try {
ctag = message.split(":")[3].split(";")[0];
} catch (Exception ex) {
throw new ProtocolException("The TL1 message did not contain a valid CTAG. The CTAG is required.");
}
logger.debug("CTAG for request is " + ctag);
if (ctag == null || ctag.equals("")) {
throw new ProtocolException("The TL1 message did not contain a CTAG. The CTAG is required.");
}
return ctag;
}
private boolean gotACKMessage(TL1OutputMsg tl1ResponseMessage, String ctag) {
return tl1ResponseMessage.getType() == TL1OutputMsg.ACK_TYPE
&& ctag.equals(((TL1AckMsg) tl1ResponseMessage).getCTAG());
}
private boolean gotBusyStatus(TL1OutputMsg tl1ResponseMessage) {
return ((TL1AckMsg) tl1ResponseMessage).getAckCode().equals(TL1AckMsg.RETRY_LATER)
|| ((TL1AckMsg) tl1ResponseMessage).getAckCode().equals(TL1AckMsg.NO_ACKNOWLEDGEMENT)
|| ((TL1AckMsg) tl1ResponseMessage).getAckCode().equals(TL1AckMsg.NO_GOOD);
}
private void waitAndSendMessageAgain(String message) throws ProtocolException {
logger.debug("Got a busy Status. Will try again in 5 seconds");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
logger.info(e.getMessage(), e);
}
sendCmdNoWait(message);
}
private boolean gotResponseMessage(TL1OutputMsg tl1ResponseMessage, String ctag) {
return tl1ResponseMessage.getType() == TL1OutputMsg.RESP_TYPE
&& ctag.equals(((TL1ResponseMsg) tl1ResponseMessage).getCTAG());
}
private TL1ResponseMsg createOrAppendTL1ReponseMessage(TL1OutputMsg tl1ResponseMessage, TL1ResponseMsg finalTL1ResponseMessage) {
if (finalTL1ResponseMessage == null) {
finalTL1ResponseMessage = (TL1ResponseMsg) tl1ResponseMessage;
} else {
finalTL1ResponseMessage.append((TL1ResponseMsg) tl1ResponseMessage);
}
return finalTL1ResponseMessage;
}
/**
* Sends a command to the stream transport, without waiting for the request
*
* @param request
* @throws ProtocolException
*/
private void sendCmdNoWait(String request) throws ProtocolException {
logger.info("Sending message: " + request);
try {
transport.send(request.toCharArray());
} catch (Exception ex) {
ex.printStackTrace();
throw new ProtocolException("Problems sending this message to the managed device: " + request, ex);
}
}
/**
* Send a message without waiting for the response
*/
public void sendDontWaitResponse(Object message) throws ProtocolException {
if (message instanceof TL1InputMsg) {
TL1InputMsg request = (TL1InputMsg) message;
this.sendCmdNoWait(request.toString());
}
else {
this.sendCmdNoWait((String) message);
}
}
/**
* Sends an array of messages without waiting for the response
*/
public void sendDontWaitResponse(Object[] messageList) throws ProtocolException {
for (int i = 0; i < messageList.length; i++) {
sendDontWaitResponse(messageList[i]);
}
}
@Override
public ProtocolSessionContext getSessionContext() {
return protocolSessionContext;
}
@Override
public String getSessionId() {
return sessionID;
}
@Override
public Status getStatus() {
return status;
}
@Override
public void registerProtocolSessionListener(
IProtocolSessionListener protocolSessionListener,
IProtocolMessageFilter protocolMessageFilter, String idListener) {
protocolMessageFilters.put(idListener, protocolMessageFilter);
protocolListeners.put(idListener, protocolSessionListener);
}
@Override
public void unregisterProtocolSessionListener(
IProtocolSessionListener protocolSessionListener, String idListener) {
protocolMessageFilters.remove(idListener);
protocolListeners.remove(idListener);
}
@Override
public void setSessionContext(ProtocolSessionContext context) {
this.protocolSessionContext = context;
}
@Override
public void setSessionId(String sessionId) {
this.sessionID = sessionId;
}
}